192 lines
5.3 KiB
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);
|
|
stagePresent();
|
|
}
|
|
|
|
|
|
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 = 64UL * 1024;
|
|
config.assetBytes = 128UL * 1024;
|
|
|
|
if (!joeyInit(&config)) {
|
|
fprintf(stderr, "joeyInit failed: %s\n", joeyLastError());
|
|
return 1;
|
|
}
|
|
|
|
screen = stageGet();
|
|
if (screen == NULL) {
|
|
fprintf(stderr, "stageGet 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);
|
|
stagePresentRect(BAR_X, BAR_Y, BAR_W, BAR_H);
|
|
flashFrames--;
|
|
if (flashFrames == 0) {
|
|
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
|
|
stagePresentRect(BAR_X, BAR_Y, BAR_W, BAR_H);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (audioOk) {
|
|
joeyAudioStopMod();
|
|
joeyAudioShutdown();
|
|
}
|
|
if (sfxBytes != NULL) {
|
|
free(sfxBytes);
|
|
}
|
|
joeyShutdown();
|
|
return 0;
|
|
}
|