joeylib2/examples/audio/audio.c

192 lines
5.3 KiB
C

// Audio demo: starts a .MOD on entry, triggers a short digital SFX
// every time SPACE is tapped, and exits on ESC. The MOD and SFX bytes
// are NOT embedded in the binary -- they're loaded at runtime from
// the DATA folder shipped on the disk image (see make/<plat>.mk for
// each platform's packaging step).
//
// On platforms where the audio HAL is still a stub, joeyAudioInit
// 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>
// 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.
#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"
#define SFX_SLOT 0
#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(SurfaceT *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;
paletteSet(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(SurfaceT *screen, bool audioOk) {
surfaceClear(screen, COLOR_BG);
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
audioOk ? COLOR_HINT : COLOR_BG);
surfacePresent(screen);
}
int main(void) {
JoeyConfigT config;
SurfaceT *screen;
bool audioOk;
int16_t flashFrames;
uint8_t *modBytes;
uint32_t modLen;
uint8_t *sfxBytes;
uint32_t sfxLen;
config.hostMode = HOST_MODE_TAKEOVER;
config.codegenBytes = 8 * 1024;
config.maxSurfaces = 4;
config.audioBytes = 64 * 1024;
config.assetBytes = 128 * 1024;
if (!joeyInit(&config)) {
fprintf(stderr, "joeyInit failed: %s\n", joeyLastError());
return 1;
}
screen = surfaceGetScreen();
if (screen == NULL) {
fprintf(stderr, "surfaceGetScreen returned NULL\n");
joeyShutdown();
return 1;
}
modBytes = NULL;
sfxBytes = NULL;
modLen = 0;
sfxLen = 0;
audioOk = joeyAudioInit();
if (audioOk) {
if (loadFile(TEST_MOD_PATH, 64UL * 1024UL, &modBytes, &modLen)) {
joeyAudioPlayMod(modBytes, modLen, true);
// joeyAudioPlayMod copies the bytes into the engine's own
// buffer; safe to release ours immediately.
free(modBytes);
modBytes = NULL;
}
(void)loadFile(TEST_SFX_PATH, 64UL * 1024UL, &sfxBytes, &sfxLen);
}
buildPalette(screen);
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
initialPaint(screen, audioOk);
flashFrames = 0;
for (;;) {
joeyWaitVBL();
joeyInputPoll();
joeyAudioFrameTick();
if (joeyKeyPressed(KEY_ESCAPE)) {
break;
}
if (joeyKeyPressed(KEY_SPACE) && sfxBytes != NULL) {
joeyAudioPlaySfx(SFX_SLOT, sfxBytes, sfxLen, SFX_RATE_HZ);
flashFrames = 8;
}
if (flashFrames > 0) {
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_BAR);
surfacePresentRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H);
flashFrames--;
if (flashFrames == 0) {
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
surfacePresentRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H);
}
}
}
if (audioOk) {
joeyAudioStopMod();
joeyAudioShutdown();
}
if (sfxBytes != NULL) {
free(sfxBytes);
}
joeyShutdown();
return 0;
}