Best way to play an on-the-fly generated PCM stream

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

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 :)
 
welcome to this board hkzlab :)


first you can check if you have loaded the sound driver :

Code:
#define	SDDRV_NAME	"SDDRVS.TSK"

#define	SDDRV_SIZE	26610 //0x7000

#define	SDDRV_ADDR	0x00202000//0x6080000

unsigned char *drv;

char sound_map[] =  {0xff,0xff};

tutu = (unsigned char *)malloc(SDDRV_SIZE);

GFS_Load(GFS_NameToId((Sint8*)SDDRV_NAME),0,(void *)drv, SDDRV_SIZE);

slInitSound(drv , SDDRV_SIZE , (Uint8 *)sound_map , sizeof(sound_map)) ;

free(drv);


to init pcm parameters, it looks like this :

Code:
PCM m_dat[4];

m_dat[i].mode = 0;


if(desired->format==AUDIO_U8 || desired->format==AUDIO_S8)  m_dat[i].mode|=_PCM8Bit;

if(desired->format==AUDIO_U16LSB || desired->format==AUDIO_S16LSB)  m_dat[i].mode|=_PCM16Bit;

if(desired->channels==1) m_dat[i].mode|=_Mono;

if(desired->channels==2) m_dat[i].mode|=_Stereo;

oct = PCM_CALC_OCT(desired->freq);

shift_freq = PCM_CALC_SHIFT_FREQ(oct);

fns = PCM_CALC_FNS(desired->freq, shift_freq);

m_dat[i].pitch =PCM_SET_PITCH_WORD(oct, fns);

m_dat[i].level = 127;

m_dat[i].channel = i*2;


if(!slPCMStat(&m_dat[i]))

    slPCMOn(&m_dat[i],chunk->abuf,chunk->alen);


about sound size, pcm has to be over 0x900 to be played with sl_pcm, the hint is to set chunk->alen to 0x900 when it under this value.


there is another way to play sound in loop with sbl, i used it on a sms emu for saturn. There is a sample in SBL 6


Code:
void UsrVblankIn( void )

{

   PER_GetPort(__port);	

   if(sound)

      PCM_MeVblIn();

   SCL_ScrollShow();

}


static PcmHn createHandle(PcmCreatePara *para)

{

	PcmHn pcm;


	pcm = PCM_CreateMemHandle(para);

	if (pcm == NULL) 

             {

		return NULL;

	}

	PCM_NotifyWriteSize(pcm, 7680*2);

	return pcm;

}


....


sndInit();

PCM_MeInit();

PcmCreatePara	para;

PcmInfo 		info;

PcmStatus	*st;

static PcmWork g_movie_work;


delta=0;

titi2=0;

PCM_PARA_WORK(&para) = (struct PcmWork *)&g_movie_work;

PCM_PARA_RING_ADDR(&para) = (Sint8 *)g_movie_buf;

PCM_PARA_RING_SIZE(&para) = RING_BUF_SIZE;

PCM_PARA_PCM_ADDR(&para) = PCM_ADDR;

PCM_PARA_PCM_SIZE(&para) = PCM_SIZE;

st = &g_movie_work.status;


memset((Sint8 *)g_movie_buf,0,7680L*4);

st->need_ci = PCM_ON;

PCM_INFO_FILE_TYPE(&info) = PCM_FILE_TYPE_NO_HEADER;PCM_INFO_DATA_TYPE(&info)=PCM_DATA_TYPE_RLRLRL;//PCM_DATA_TYPE_LRLRLR;

PCM_INFO_FILE_SIZE(&info) = 7680L*2;//0x4000;//214896;

PCM_INFO_CHANNEL(&info) = 0x01;

PCM_INFO_SAMPLING_BIT(&info) = 16;

PCM_INFO_SAMPLING_RATE(&info)	= 7680L;//30720L;//44100L;

PCM_INFO_SAMPLE_FILE(&info) = 7680L*2;//30720L;//214896;

//	pcm = PCM_CreateStmHandle(&para, stm);

pcm = createHandle(&para);

PCM_SetPcmStreamNo(pcm, 0);

PCM_SetInfo(pcm, &info);

PCM_ChangePcmPara(pcm);

//PCM_SetLoop(pcm, 30720L);

PCM_MeSetLoop(pcm, 7680L);


if (pcm == NULL) 

{

   return;

}

...


PCM_Start(pcm);


...


while(1)

{

   PSG_Update(&g_movie_buf[delta],  128)

   PCM_Task(pcm);

}


hope it helps :)
 
Thanks for the help!


Now i know why slPCMOn did not play the sound in the buffer filled by Sarien: it is a lot smaller than 0x900 bytes.

As for the SBL code, i'll have to read through it using my manuals to understand it. I'll let you know :)


I was thinking to release a "preview" version of my port, without sound, after i received my keyboard adapter and added support for it (playing Leisure Suit Larry without kb is a bit impossible)... but it seems that italian postal system lost my package and i'll have to reorder another one when i'll have the money >:-(


Thanks again!
 
I've been trying the SBL code in a test program, but i'm unable to get any audio output.

Can that
Code:
PCM_VblIn();
function be executed using slIntFunction() from SGL?

edit: ok, i can get some sound now. I forgot to set the data type :p
 
Thanks vbt!

I finally got near-perfect audio in sarien using SBL seamless branching!


Now i need to do some cleanup to the code, add keyboard support and then the Saturn will have its own AGI interpreter :D
 
  • Like
Reactions: vbt
Back
Top