// 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/.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 #include #include // 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; }