Last active
May 11, 2025 22:48
-
-
Save NAEL2XD/f62bc8c8415f51b62a01f1519b2c8ee9 to your computer and use it in GitHub Desktop.
3DS: Sounds Utils
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "sound.h" | |
| #include <tremor/ivorbisfile.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #define MAX_CHANNELS 24 // Maximum NDSP channels | |
| #define MAX_SOUNDS 24 // Maximum number of pre-cached sounds | |
| #define THREAD_STACK_SZ (32 * 1024) | |
| #define THREAD_AFFINITY -1 | |
| typedef struct { | |
| OggVorbis_File vorbisFile; | |
| ndspWaveBuf waveBufs[3]; | |
| int16_t *audioBuffer; | |
| LightEvent event; | |
| volatile bool quit; | |
| Thread threadId; | |
| int channel; | |
| bool playing; | |
| bool loaded; | |
| } AudioStream; | |
| static AudioStream streams[MAX_SOUNDS]; | |
| static bool soundSystemInitialized = false; | |
| // Internal function to fill a buffer with audio data. | |
| static bool fillBuffer(AudioStream *stream, ndspWaveBuf *waveBuf) { | |
| int totalBytes = 0; | |
| while (totalBytes < waveBuf->nsamples * sizeof(int16_t)) { | |
| int16_t *buffer = waveBuf->data_pcm16 + (totalBytes / sizeof(int16_t)); | |
| size_t bufferSize = waveBuf->nsamples * sizeof(int16_t) - totalBytes; | |
| int bytesRead = ov_read(&stream->vorbisFile, (char *)buffer, bufferSize, NULL); | |
| if (bytesRead <= 0) { | |
| if (bytesRead == 0) break; | |
| return false; | |
| } | |
| totalBytes += bytesRead; | |
| } | |
| if (totalBytes == 0) return false; | |
| waveBuf->nsamples = totalBytes / sizeof(int16_t); | |
| ndspChnWaveBufAdd(stream->channel, waveBuf); | |
| DSP_FlushDataCache(waveBuf->data_pcm16, totalBytes); | |
| return true; | |
| } | |
| // Internal function to pre-fill all buffers for a stream. | |
| static void prefillBuffers(AudioStream *stream) { | |
| for (size_t i = 0; i < 3; i++) { | |
| if (!fillBuffer(stream, &stream->waveBufs[i])) { | |
| stream->playing = false; | |
| break; | |
| } | |
| } | |
| } | |
| // Internal thread function for audio playback. | |
| static void audioThread(void *arg) { | |
| AudioStream *stream = (AudioStream *)arg; | |
| while (!stream->quit) { | |
| if (!stream->playing) { | |
| LightEvent_Wait(&stream->event); | |
| continue; | |
| } | |
| for (size_t i = 0; i < 3; i++) { | |
| if (stream->waveBufs[i].status == NDSP_WBUF_DONE) { | |
| if (!fillBuffer(stream, &stream->waveBufs[i])) { | |
| stream->playing = false; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Initializes the sound system. | |
| bool SOUND_init() { | |
| if (soundSystemInitialized) return true; | |
| romfsInit(); | |
| ndspInit(); | |
| memset(streams, 0, sizeof(streams)); | |
| soundSystemInitialized = true; | |
| return true; | |
| } | |
| // Preloads a sound file and assigns it an ID. | |
| int SOUND_precacheToID(const char *path) { | |
| for (int i = 0; i < MAX_SOUNDS; i++) { | |
| if (streams[i].loaded) continue; | |
| FILE *fh = fopen(path, "rb"); | |
| if (!fh) { | |
| printf("Failed to open %s\n", path); | |
| return -1; | |
| } | |
| if (ov_open(fh, &streams[i].vorbisFile, NULL, 0)) { | |
| fclose(fh); | |
| printf("Failed to load Ogg Vorbis file: %s\n", path); | |
| return -1; | |
| } | |
| vorbis_info *vi = ov_info(&streams[i].vorbisFile, -1); | |
| ndspChnReset(i); | |
| ndspChnSetInterp(i, NDSP_INTERP_POLYPHASE); | |
| ndspChnSetRate(i, vi->rate); | |
| ndspChnSetFormat(i, vi->channels == 1 ? NDSP_FORMAT_MONO_PCM16 : NDSP_FORMAT_STEREO_PCM16); | |
| size_t samplesPerBuf = vi->rate * 60 / 1000; // Smaller buffer for reduced delay | |
| size_t waveBufSize = samplesPerBuf * vi->channels * sizeof(int16_t); | |
| size_t bufferSize = waveBufSize * 3; | |
| streams[i].audioBuffer = (int16_t *)linearAlloc(bufferSize); | |
| if (!streams[i].audioBuffer) { | |
| ov_clear(&streams[i].vorbisFile); | |
| printf("Failed to allocate audio buffer\n"); | |
| return -1; | |
| } | |
| memset(streams[i].waveBufs, 0, sizeof(streams[i].waveBufs)); | |
| int16_t *buffer = streams[i].audioBuffer; | |
| for (size_t j = 0; j < 3; j++) { | |
| streams[i].waveBufs[j].data_vaddr = buffer; | |
| streams[i].waveBufs[j].nsamples = samplesPerBuf; | |
| streams[i].waveBufs[j].status = NDSP_WBUF_DONE; | |
| buffer += samplesPerBuf * vi->channels; | |
| } | |
| streams[i].channel = i; | |
| streams[i].playing = false; | |
| streams[i].loaded = true; | |
| LightEvent_Init(&streams[i].event, RESET_ONESHOT); | |
| streams[i].quit = false; | |
| streams[i].threadId = threadCreate(audioThread, &streams[i], THREAD_STACK_SZ, 0x30, THREAD_AFFINITY, false); | |
| // Pre-fill buffers to minimize delay | |
| prefillBuffers(&streams[i]); | |
| return i; | |
| } | |
| printf("No available channels for sound\n"); | |
| return -1; | |
| } | |
| // Plays a sound by ID. | |
| void SOUND_playByID(int id, bool restart) { | |
| if (id < 0 || id >= MAX_SOUNDS || !streams[id].loaded) { | |
| printf("Invalid sound ID: %d\n", id); | |
| return; | |
| } | |
| if (restart || !streams[id].playing) { | |
| ov_time_seek(&streams[id].vorbisFile, 0); // Restart the audio | |
| streams[id].playing = true; | |
| prefillBuffers(&streams[id]); // Ensure buffers are filled immediately | |
| LightEvent_Signal(&streams[id].event); | |
| } | |
| } | |
| // Stops a sound by ID. | |
| void SOUND_stopByID(int id) { | |
| if (id < 0 || id >= MAX_SOUNDS || !streams[id].loaded) { | |
| printf("Invalid sound ID: %d\n", id); | |
| return; | |
| } | |
| streams[id].playing = false; | |
| } | |
| // Cleans up and shuts down the sound system. | |
| void SOUND_exit() { | |
| if (!soundSystemInitialized) return; | |
| for (int i = 0; i < MAX_SOUNDS; i++) { | |
| if (streams[i].loaded) { | |
| streams[i].quit = true; | |
| LightEvent_Signal(&streams[i].event); | |
| threadJoin(streams[i].threadId, U64_MAX); | |
| threadFree(streams[i].threadId); | |
| ndspChnReset(i); | |
| linearFree(streams[i].audioBuffer); | |
| ov_clear(&streams[i].vorbisFile); | |
| } | |
| } | |
| ndspExit(); | |
| romfsExit(); | |
| soundSystemInitialized = false; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef SOUND_H | |
| #define SOUND_H | |
| #include <3ds.h> | |
| /** | |
| * @brief Initializes the sound system and prepares NDSP for audio playback. | |
| * | |
| * This function sets up the necessary resources for the sound system, including | |
| * initializing NDSP and preparing audio channels for playback. | |
| * | |
| * @return true if the sound system was successfully initialized, false otherwise. | |
| */ | |
| bool SOUND_init(); | |
| /** | |
| * @brief Preloads an Ogg Vorbis file into memory and assigns it a unique sound ID. | |
| * | |
| * This function loads the specified Ogg Vorbis file into memory and prepares it | |
| * for playback. Each preloaded sound is assigned a unique ID that can be used to | |
| * play or stop the sound. | |
| * | |
| * @param path The file path to the Ogg Vorbis file to preload. | |
| * @return The unique ID of the preloaded sound, or -1 if the file could not be loaded. | |
| */ | |
| int SOUND_precacheToID(const char *path); | |
| /** | |
| * @brief Plays a preloaded sound by its unique ID. | |
| * | |
| * This function starts playback of the sound associated with the given ID. If the | |
| * `restart` parameter is true, the sound will restart from the beginning. | |
| * | |
| * @param id The ID of the sound to play. | |
| * @param restart If true, the sound will restart from the beginning. | |
| */ | |
| void SOUND_playByID(int id, bool restart); | |
| /** | |
| * @brief Stops playback of a sound by its unique ID. | |
| * | |
| * This function stops the playback of the sound associated with the given ID. | |
| * | |
| * @param id The ID of the sound to stop. | |
| */ | |
| void SOUND_stopByID(int id); | |
| /** | |
| * @brief Shuts down the sound system and cleans up all resources. | |
| * | |
| * This function stops all audio playback, frees all allocated memory, and shuts down NDSP. | |
| * It should be called when the application is exiting or when sound is no longer needed. | |
| */ | |
| void SOUND_exit(); | |
| #endif // SOUND_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment