Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions samples/audio/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
22 changes: 16 additions & 6 deletions src/audio/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,33 @@ 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;

AudioStatus[channel].inProgress = 1;
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;

Expand Down Expand Up @@ -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
Expand Down
81 changes: 53 additions & 28 deletions src/audio/bgm.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,49 +59,65 @@ 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 {
byte = streaming ? *streambuffer++ : *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--;
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
47 changes: 43 additions & 4 deletions src/bgm.h
Original file line number Diff line number Diff line change
@@ -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_
Expand All @@ -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.
Expand All @@ -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.
*
Expand Down Expand Up @@ -62,6 +100,7 @@ typedef struct {
*/
unsigned char reserved[32];
} BGM_FORMAT_HEADER;
#pragma pack(pop)

#ifdef __cplusplus
}
Expand Down