joeylib2/examples/audio/audio.c

216 lines
6.1 KiB
C

// Audio demo: starts the baked chip-tracker song (demoSong.song ->
// demoSong.h via tools/songbake) on entry, triggers a short digital
// SFX every time SPACE is tapped, and exits on ESC. M switches to the
// .MOD loaded from the DATA folder on the disk image (see
// make/<plat>.mk for each platform's packaging step); T switches back
// to the tracker song; P pauses/resumes tracker playback in place.
// The two layers share output hardware on the ST/Amiga, so the demo
// always stops one before starting the other.
//
// On platforms where the audio HAL is still a stub, jlAudioInit
// returns false, every audio call is a quiet no-op, and the demo runs
// as a silent input loop -- you can confirm the build links and the
// input plumbing still works without hearing anything.
#include <stdio.h>
#include <stdlib.h>
#include <joey/joey.h>
#include "demoSong.h"
// Each platform encodes the MOD differently. The IIgs build converts
// test.mod -> test.ntp via joeymod at build time and ships the .NTP;
// every other platform ships the raw .MOD. test.sfx is the same raw
// PCM blob everywhere. The tracker song needs no per-platform form at
// all -- the baked JYM1 bytes are identical on every port.
#if defined(JOEYLIB_PLATFORM_IIGS)
# define TEST_MOD_PATH "DATA/TEST.NTP"
#else
# define TEST_MOD_PATH "DATA/test.mod"
#endif
#define TEST_SFX_PATH "DATA/test.sfx"
// Slot 3: the tracker's three voices ride SFX slots 0-2 on the IIgs,
// so the demo's one-shot SFX stays out of their way.
#define SFX_SLOT 3
#define SFX_RATE_HZ 8000
#define COLOR_BG 0
#define COLOR_HINT 1
#define COLOR_BAR 2
#define BAR_X 16
#define BAR_Y 88
#define BAR_W (SURFACE_WIDTH - 32)
#define BAR_H 16
// Read an entire file into a freshly allocated buffer. Caller must
// free(*outBytes) once the audio engine has consumed the data.
// Returns false if the file is missing, empty, or larger than maxLen.
static bool loadFile(const char *path, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen) {
FILE *fp;
long fileSize;
uint8_t *buf;
size_t readBytes;
fp = fopen(path, "rb");
if (fp == NULL) {
return false;
}
if (fseek(fp, 0L, SEEK_END) != 0) {
fclose(fp);
return false;
}
fileSize = ftell(fp);
if (fileSize <= 0 || (uint32_t)fileSize > maxLen) {
fclose(fp);
return false;
}
if (fseek(fp, 0L, SEEK_SET) != 0) {
fclose(fp);
return false;
}
buf = (uint8_t *)malloc((size_t)fileSize);
if (buf == NULL) {
fclose(fp);
return false;
}
readBytes = fread(buf, 1, (size_t)fileSize, fp);
fclose(fp);
if (readBytes != (size_t)fileSize) {
free(buf);
return false;
}
*outBytes = buf;
*outLen = (uint32_t)fileSize;
return true;
}
static void buildPalette(jlSurfaceT *screen) {
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
uint16_t i;
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
colors[i] = 0x0000;
}
colors[COLOR_BG] = 0x0000;
colors[COLOR_HINT] = 0x0444;
colors[COLOR_BAR] = 0x00F0;
jlPaletteSet(screen, 0, colors);
}
// Visual feedback for "audio is running, but you cannot tell on a
// stub HAL": pulse a horizontal bar between the hint color and the
// active color whenever SFX has fired this frame.
static void initialPaint(jlSurfaceT *screen, bool audioOk) {
jlSurfaceClear(screen, COLOR_BG);
jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
audioOk ? COLOR_HINT : COLOR_BG);
jlStagePresent();
}
int main(void) {
jlConfigT config;
jlSurfaceT *screen;
bool audioOk;
bool musicPaused;
int16_t flashFrames;
uint8_t *modBytes;
uint32_t modLen;
uint8_t *sfxBytes;
uint32_t sfxLen;
config.codegenBytes = 8 * 1024;
config.audioBytes = 64UL * 1024;
if (!jlInit(&config)) {
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
return 1;
}
screen = jlStageGet();
if (screen == NULL) {
fprintf(stderr, "jlStageGet returned NULL\n");
jlShutdown();
return 1;
}
modBytes = NULL;
sfxBytes = NULL;
modLen = 0;
sfxLen = 0;
audioOk = jlAudioInit();
if (audioOk) {
// Keep the MOD bytes resident so M can (re)start it later;
// jlAudioPlayMod copies them at play time.
(void)loadFile(TEST_MOD_PATH, 64UL * 1024UL, &modBytes, &modLen);
(void)loadFile(TEST_SFX_PATH, 64UL * 1024UL, &sfxBytes, &sfxLen);
}
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
buildPalette(screen);
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
initialPaint(screen, audioOk);
musicPaused = false;
flashFrames = 0;
for (;;) {
jlWaitVBL();
jlInputPoll();
jlAudioFrameTick();
if (jlKeyPressed(KEY_ESCAPE)) {
break;
}
if (jlKeyPressed(KEY_SPACE) && sfxBytes != NULL) {
jlAudioPlaySfx(SFX_SLOT, sfxBytes, sfxLen, SFX_RATE_HZ);
flashFrames = 8;
}
if (jlKeyPressed(KEY_M) && modBytes != NULL) {
jlMusicStop();
jlAudioPlayMod(modBytes, modLen, true);
}
if (jlKeyPressed(KEY_T)) {
jlAudioStopMod();
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
}
if (jlKeyPressed(KEY_P)) {
if (musicPaused) {
jlMusicResume();
} else {
jlMusicPause();
}
musicPaused = !musicPaused;
}
if (flashFrames > 0) {
jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_BAR);
jlStagePresent();
flashFrames--;
if (flashFrames == 0) {
jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
jlStagePresent();
}
}
}
jlMusicStop();
if (audioOk) {
jlAudioStopMod();
}
jlAudioShutdown();
if (modBytes != NULL) {
free(modBytes);
}
if (sfxBytes != NULL) {
free(sfxBytes);
}
jlShutdown();
return 0;
}