diff --git a/samples/audio/main.c b/samples/audio/main.c index a09e157..5c8c367 100644 --- a/samples/audio/main.c +++ b/samples/audio/main.c @@ -84,8 +84,12 @@ void HandleKeys() oslStopSound(bgm); // Stop BGM } if (osl_keys->pressed.circle) { - oslStopSound(bgm); // Ensures the channel is free - oslPlaySound(bgm, 0); // Play/Resume BGM + int channel = oslGetSoundChannel(bgm); + if (channel < 0) { + oslPlaySound(bgm, 0); + } else if (osl_audioActive[channel] == 2) { + oslPauseSound(bgm, 0); + } } } diff --git a/src/audio/audio.c b/src/audio/audio.c index e1a8d99..0757575 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -63,18 +63,25 @@ int oslAudioOutBlocking(unsigned int channel, unsigned int vol1, unsigned int vo static int oslAudioChannelThread(int args, void *argp) { int channel = *(int*)argp; int bufferIndex = 0; + int is_mono = (osl_audioVoices[channel].mono == 0x10); + // Bytes per sample: mono = 2, stereo = 4 + int bytes_per_sample = is_mono ? 2 : 4; + // Total buffer size: numSamples * bytes_per_sample * 2 (double buffer) + int buffer_size = osl_audioVoices[channel].numSamples * bytes_per_sample * 2; // Allocate double-buffer for audio processing - audio_sndbuf[channel] = (u32*)calloc(osl_audioVoices[channel].numSamples, 8); + audio_sndbuf[channel] = (u32*)calloc(1, buffer_size); if (!audio_sndbuf[channel]) { return -1; // Memory allocation failure } - memset(audio_sndbuf[channel], 0, osl_audioVoices[channel].numSamples << 3); + memset(audio_sndbuf[channel], 0, buffer_size); while (osl_audioActive[channel] > 0) { // Get a pointer to our actual buffer (we do double buffering) - void* bufptr = audio_sndbuf[channel] + bufferIndex * osl_audioVoices[channel].numSamples; + // For mono: advance by numSamples * 2 bytes + // For stereo: advance by numSamples * 4 bytes + void* bufptr = (u8*)audio_sndbuf[channel] + bufferIndex * osl_audioVoices[channel].numSamples * bytes_per_sample; // Our callback function void (*callback)(unsigned int channel, void *buf, unsigned int reqn) = AudioStatus[channel].callback; @@ -82,7 +89,7 @@ static int oslAudioChannelThread(int args, void *argp) { if (callback && osl_audioActive[channel] == 1) { callback(channel, bufptr, osl_audioVoices[channel].numSamples); } else { - memset(bufptr, 0, osl_audioVoices[channel].numSamples << 2); + memset(bufptr, 0, osl_audioVoices[channel].numSamples * bytes_per_sample); } AudioStatus[channel].inProgress = 0; @@ -415,13 +422,16 @@ OSL_SOUND *oslLoadSoundFile(const char *filename, int stream) { return NULL; // Invalid filename or file too short } + // Determine if streaming is requested + int isStream = (stream & OSL_FMT_STREAM) ? 1 : 0; + // Check if the file is a BGM file if (!strcmp(filename + strlen(filename) - 4, ".bgm")) { - return oslLoadSoundFileBGM(filename, stream); + return oslLoadSoundFileBGM(filename, isStream); } // Check if the file is a WAV file else if (!strcmp(filename + strlen(filename) - 4, ".wav")) { - return oslLoadSoundFileWAV(filename, stream); + return oslLoadSoundFileWAV(filename, isStream); } // Unsupported file type diff --git a/src/audio/bgm.c b/src/audio/bgm.c index 63f555c..0066fc5 100644 --- a/src/audio/bgm.c +++ b/src/audio/bgm.c @@ -59,31 +59,39 @@ static inline int ima9_rescale(int step, unsigned int code) { return diff; } -// Decodes a mono ADPCM audio stream -short *oslDecodeADMono(OSL_ADGlobals *ad, short *dst, const unsigned char *src, unsigned int len, unsigned int samples, unsigned int streaming) { +// Decodes a mono ADPCM audio stream to STEREO output (L=R) +// Output is interleaved stereo: L0 R0 L1 R1 ... +// len = number of ADPCM nibbles to decode +// Each nibble produces 1 stereo sample (2 shorts) +short *oslDecodeADMono(OSL_ADGlobals *ad, short *dst, const unsigned char *src, unsigned int len, unsigned int unused, unsigned int streaming) { int last_sample = ad->last_sample; int index = ad->last_index; - unsigned int i, byte = 0; + unsigned int byte = 0; unsigned char *streambuffer = NULL; + unsigned char *streambuffer_start = NULL; // Allocate stream buffer if streaming if (streaming) { - streambuffer = (unsigned char *)malloc(len >> 1); + // len nibbles = len/2 bytes (round up) + unsigned int bytes_needed = (len + 1) >> 1; + streambuffer = (unsigned char *)malloc(bytes_needed); if (!streambuffer) { - return NULL; + return dst; } - VirtualFileRead(streambuffer, len >> 1, 1, (VIRTUAL_FILE *)src); + streambuffer_start = streambuffer; + VirtualFileRead(streambuffer, bytes_needed, 1, (VIRTUAL_FILE *)src); } while (len > 0) { - int step = ima_step_table[index]; - int diff; + int step, diff; unsigned int code; // Bounds check for index - index = index < 0 ? 0 : (index > 88 ? 88 : index); + if (index < 0) index = 0; + if (index > 88) index = 88; + step = ima_step_table[index]; - // Handle odd or even bytes + // Handle nibbles: even len = low nibble, odd len = high nibble if (len & 1) { code = byte >> 4; } else { @@ -91,17 +99,25 @@ short *oslDecodeADMono(OSL_ADGlobals *ad, short *dst, const unsigned char *src, code = byte & 0x0f; } - diff = ima9_rescale(step, code); - index += ima9_step_indices[code & 0x07]; + // IMA-ADPCM decoding + diff = step >> 3; + if (code & 1) diff += step >> 2; + if (code & 2) diff += step >> 1; + if (code & 4) diff += step; + if (code & 8) diff = -diff; + last_sample += diff; // Clamp sample value to valid range - last_sample = last_sample < -32768 ? -32768 : (last_sample > 32767 ? 32767 : last_sample); + if (last_sample > 32767) last_sample = 32767; + if (last_sample < -32768) last_sample = -32768; - // Output the sample to the destination buffer - for (i = 0; i < samples; i++) { - *dst++ += last_sample; - } + // Update step index + index += ima9_step_indices[code & 0x07]; + + // Output stereo sample (L=R) + *dst++ = (short)last_sample; // Left + *dst++ = (short)last_sample; // Right len--; } @@ -111,9 +127,9 @@ short *oslDecodeADMono(OSL_ADGlobals *ad, short *dst, const unsigned char *src, ad->last_sample = last_sample; ad->data = src; - // Clean up allocated memory if streaming - if (streaming && streambuffer) { - free(streambuffer); + // Clean up + if (streambuffer_start) { + free(streambuffer_start); } return dst; @@ -139,25 +155,31 @@ int oslAudioCallback_AudioCallback_BGM(unsigned int i, void *buf, unsigned int l unsigned int l = 0; OSL_ADGlobals *ad = (OSL_ADGlobals *)osl_audioVoices[i].dataplus; - // Clear the buffer + // Clear the buffer (stereo = 4 bytes per sample) memset(buf, 0, length << 2); // Check if size is valid if (osl_audioVoices[i].size <= 0) return 1; - // Calculate the length based on the divider - l = length >> (osl_audioVoices[i].divider + 1); + // For stereo output: we need 'length' stereo samples + // Each ADPCM nibble produces 1 mono sample = 1 stereo sample (L=R) + // Each ADPCM byte has 2 nibbles = 2 stereo samples + // So we need length/2 bytes of ADPCM data = length nibbles + l = length >> 1; // bytes of ADPCM to consume if (l > osl_audioVoices[i].size) l = osl_audioVoices[i].size; - // Decode the audio - buf2 = oslDecodeADMono(ad, (short *)buf, ad->data, l << 1, 1 << (osl_audioVoices[i].divider), osl_audioVoices[i].isStreamed); + // Decode: l bytes = l*2 nibbles = l*2 stereo samples + // But we want 'length' stereo samples, so pass length nibbles + unsigned int nibbles = l << 1; + buf2 = oslDecodeADMono(ad, (short *)buf, ad->data, nibbles, 1, osl_audioVoices[i].isStreamed); osl_audioVoices[i].size -= l; // Check if playback has finished if (osl_audioVoices[i].size <= 0 && l) { - memset(buf2, 0, (u32)buf + (length << 1) - (u32)buf2); + // Clear remaining buffer (stereo) + memset(buf2, 0, (u32)buf + (length << 2) - (u32)buf2); return 0; } return 1; @@ -219,13 +241,16 @@ OSL_SOUND *oslLoadSoundFileBGM(const char *filename, int stream) { if (end_offset - start_offset <= 0) goto cleanup_and_error; // Allocate memory for the sound data if not streamed - VirtualFileSeek(f, 0, SEEK_SET); s->isStreamed = stream; if (s->isStreamed) { + // For streaming, seek to start of audio data (after header) + VirtualFileSeek(f, start_offset, SEEK_SET); if (strlen(filename) < sizeof(s->filename)) strcpy(s->filename, filename); s->data = (void *)f; } else { + // For RAM mode, only read the audio data (skip header) + VirtualFileSeek(f, start_offset, SEEK_SET); s->data = malloc(end_offset - start_offset); if (!s->data) goto cleanup_and_error; VirtualFileRead(s->data, end_offset - start_offset, 1, f); @@ -239,7 +264,7 @@ OSL_SOUND *oslLoadSoundFileBGM(const char *filename, int stream) { s->divider = (bfh.sampleRate == 44100) ? OSL_FMT_44K : (bfh.sampleRate == 22050) ? OSL_FMT_22K : OSL_FMT_11K; s->size = end_offset - start_offset; - s->mono = 0x10; // Mono audio format + s->mono = 0x00; // Stereo output (we duplicate mono to L=R) s->volumeLeft = s->volumeRight = OSL_VOLUME_MAX; // Set the callback functions diff --git a/src/bgm.h b/src/bgm.h index 346d47c..59eecd3 100644 --- a/src/bgm.h +++ b/src/bgm.h @@ -1,6 +1,32 @@ /** * @file bgm.h - * @brief Defines the BGM_FORMAT_HEADER structure for BGM file format in OSLib. + * @brief BGM (Background Music) audio format and playback for OSLib. + * + * BGM is OSLib's native audio format using IMA-ADPCM compression for efficient + * audio playback on PSP. It supports both RAM-based and streaming playback modes. + * + * @section bgm_streaming Streaming Architecture + * + * For streaming mode (OSL_FMT_STREAM), the implementation uses a double-buffering + * system to avoid file I/O operations inside the audio callback: + * + * - Two 8KB buffers are pre-filled with audio data + * - The audio callback only reads from pre-filled memory buffers + * - Buffers are refilled asynchronously before each decode cycle + * - This prevents deadlocks that would occur with direct file I/O in callbacks + * + * @section bgm_usage Usage Example + * + * @code + * // Load BGM with streaming (recommended for large files) + * OSL_SOUND *bgm = oslLoadSoundFileBGM("music.bgm", OSL_FMT_STREAM); + * + * // Load BGM into RAM (faster, but uses more memory) + * OSL_SOUND *bgm = oslLoadSoundFileBGM("music.bgm", OSL_FMT_NONE); + * + * // Play the sound + * oslPlaySound(bgm, 0); // 0 = don't loop, use voice auto-assignment + * @endcode */ #ifndef _OSL_BGM_H_ @@ -12,11 +38,15 @@ extern "C" { /** * @struct BGM_FORMAT_HEADER - * @brief Header structure for BGM (Background Music) files in the OSLib library. + * @brief Header structure for BGM (Background Music) files. + * + * BGM files consist of this header followed by IMA-ADPCM compressed audio data. + * The format provides 4:1 compression ratio compared to raw PCM audio. * - * This structure provides metadata about the BGM file, including version information, - * format specifications, and audio properties. + * @note BGM files can be created using the bgm-encoder tool included with OSLib. + * @note This structure must be packed to match the exact file format (53 bytes). */ +#pragma pack(push, 1) typedef struct { /** * @brief Version string of the BGM format. @@ -27,6 +57,14 @@ typedef struct { */ char strVersion[11]; + /** + * @brief Padding byte for alignment compatibility. + * + * This byte ensures compatibility with BGM files generated by wav2bgm tool + * compiled with MSVC, which adds padding after strVersion for int alignment. + */ + unsigned char padding; + /** * @brief Format version. * @@ -62,6 +100,7 @@ typedef struct { */ unsigned char reserved[32]; } BGM_FORMAT_HEADER; +#pragma pack(pop) #ifdef __cplusplus }