Hi everyone!
As my first "porting experience", i'm trying to port Sarien (an AGI adventure game interpreter) on the Sega Saturn, using SBL + SGL libraries.
The port is already in fairly good shape, it can load games from cd, save and load in the internal backup memory and plays at decent speed.
What it's currently missing is keyboard support (i've mapped some keys on the pad until my adapter gets here) and proper audio support.
Normally, a sarien port should have a thread with a loop calling a function which fills an 820 byte buffer with PCM data (16 bit, mono, 22050hz) and then send this small buffer to the audio card.
I am facing a problem because of this: if i pass the 820 byte buffer to the slPCMOn() function, i hear absolutely nothing. To start hearing sound i need to concatenate an array of ~20 buffers, passing all of them to slPCMOn in one run.
My current approach is to keep two "big" buffers, each one made of around 64 buffer slots (each slot 820 bytes).
I then periodically run a function to fill the slots of one of the buffers, when the big buffer gets filled it is passed to slPCMOn(), and the "filling" function switches to work on the next big buffer (which will then be played when ready and when sound playing from previous buffer stopped).
To ensure that those functions are called periodically i use
The problem is that doing it this way makes the audio stutters: there is a very little moment of silence when the sound from one buffer stops and the next one starts playing.
Is there a better way to manage this "continuous" pcm stream using sgl/sbl?
Maybe a way to get the saturn loop on a buffer and keep adding data in there?
These are the functions i wrote:
Thanks for having read this far 🙂
As my first "porting experience", i'm trying to port Sarien (an AGI adventure game interpreter) on the Sega Saturn, using SBL + SGL libraries.
The port is already in fairly good shape, it can load games from cd, save and load in the internal backup memory and plays at decent speed.
What it's currently missing is keyboard support (i've mapped some keys on the pad until my adapter gets here) and proper audio support.
Normally, a sarien port should have a thread with a loop calling a function which fills an 820 byte buffer with PCM data (16 bit, mono, 22050hz) and then send this small buffer to the audio card.
I am facing a problem because of this: if i pass the 820 byte buffer to the slPCMOn() function, i hear absolutely nothing. To start hearing sound i need to concatenate an array of ~20 buffers, passing all of them to slPCMOn in one run.
My current approach is to keep two "big" buffers, each one made of around 64 buffer slots (each slot 820 bytes).
I then periodically run a function to fill the slots of one of the buffers, when the big buffer gets filled it is passed to slPCMOn(), and the "filling" function switches to work on the next big buffer (which will then be played when ready and when sound playing from previous buffer stopped).
To ensure that those functions are called periodically i use
Code:
slSynchFunction(manage_sound);
The problem is that doing it this way makes the audio stutters: there is a very little moment of silence when the sound from one buffer stops and the next one starts playing.
Is there a better way to manage this "continuous" pcm stream using sgl/sbl?
Maybe a way to get the saturn loop on a buffer and keep adding data in there?
These are the functions i wrote:
Code:
typedef struct {
char pcm_buffer[BUF_SLOTS][BUFFER_SIZE * 2];
int isFull;
int isUsed;
} audio_buffer;
extern SINT16 snd_buffer[BUFFER_SIZE];
audio_buffer **snd_bufs;
static int currentBuffer;
static int currentSlot;
static int lastPlayedBuffer;
....
void play_from_buffer(void) {
if(!slPCMStat(&chn_out_0)) { // If sound is still playing, nothing to do
snd_bufs[lastPlayedBuffer]->isFull = 0; // Last played buffer is no longer full.
snd_bufs[lastPlayedBuffer]->isUsed = 0; // Last played buffer is no longer accessed.
int nextBuf = (lastPlayedBuffer + 1) % MAX_BUFFERS; // This one would be the next played buffer
if(snd_bufs[nextBuf]->isFull && !snd_bufs[nextBuf]->isUsed) { // We can play this buffer.
lastPlayedBuffer = nextBuf; // This is the last played buffer now.
snd_bufs[lastPlayedBuffer]->isUsed = 1; // This buffer is accessed
slPCMOn(&chn_out_0, (char*)(snd_bufs[lastPlayedBuffer]->pcm_buffer), BUF_SLOTS * (BUFFER_SIZE * 2)); // Start the sound
}
}
return;
}
void fill_buffer_slot(void) {
int nextBuf = (currentBuffer + 1) % MAX_BUFFERS;
int nextSlot = (currentSlot + 1) % BUF_SLOTS;
int workingBuffer = currentBuffer;
if (!snd_bufs[workingBuffer]->isUsed) { // If the current buffer is not used...
snd_bufs[workingBuffer]->isUsed = 1; // Mark it as used
// Play & Mix the sound.
play_sound();
mix_sound();
char *pcm_buf = snd_bufs[workingBuffer]->pcm_buffer[currentSlot];
//memcpy(pcm_buf, snd_buffer, BUFFER_SIZE * 2); // Copy the sound sample in the buffer
slDMACopy(snd_buffer, pcm_buf, BUFFER_SIZE * 2);
if(nextSlot == 0) { // We have filled this buffer
snd_bufs[workingBuffer]->isFull = 1; // Mark it as full...
currentBuffer = nextBuf; // ...and use the next buffer
}
currentSlot = nextSlot; // This is the slot we'll use next.
slDMAWait();
snd_bufs[workingBuffer]->isUsed = 0; // We have finished using it.
}
return;
}
void manage_sound(void) {
play_from_buffer();
fill_buffer_slot();
//play_from_buffer();
}
Thanks for having read this far 🙂