play vag in sequence

Audio and Music (Sound Processing Unit) based area of development, including VAB, XA, etc
Post Reply
hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

play vag in sequence

Post by hhaunt » September 6th, 2024, 12:13 am

Hi everyone,
I've been developing a Final Fantasy-style RPG for several months, with prerendered backgrounds. The project is open source. I’ve managed to get almost everything working, but now I’m encountering a problem with the audio. I’d like to play background music while moving between the prerendered backgrounds. Obviously, if I do it with direct play from an XA file, the audio cuts off because when I change the background, it loads data from the CD-ROM for the next background.

A solution that I think I've understood is to use .VAG files and load pieces of audio little by little into a buffer, playing them bit by bit. Currently, I’m trying with the following code, and I can correctly play a single VAG file. However, I can’t figure out how to proceed. Now I just wanted to try looping the VAG playback, I thought that by changing the data_addr in the transfer callback, it would work, but I’m missing something. Does anyone have an example?

Code: Select all

SpuStCallbackProc prepare_callback(unsigned long voice_bit, long c_status) {
	SpuVoiceAttr s_attr;
	s_attr.mask =
	(
		SPU_VOICE_VOLL |
		SPU_VOICE_VOLR |
		SPU_VOICE_PITCH |
		SPU_VOICE_WDSA |
		SPU_VOICE_ADSR_AMODE |
		SPU_VOICE_ADSR_SMODE |
		SPU_VOICE_ADSR_RMODE |
		SPU_VOICE_ADSR_AR |
		SPU_VOICE_ADSR_DR |
		SPU_VOICE_ADSR_SR |
		SPU_VOICE_ADSR_RR |
		SPU_VOICE_ADSR_SL
	);
	s_attr.voice = SPU_0CH;
	s_attr.addr = stenv->voice[0].buf_addr;
	s_attr.volume.left  = 0x3fff;
	s_attr.volume.right = 0x3fff;
	s_attr.pitch = 0x1000;
	s_attr.a_mode       = SPU_VOICE_LINEARIncN;
	s_attr.s_mode       = SPU_VOICE_LINEARIncN;
	s_attr.r_mode       = SPU_VOICE_LINEARDecN;
	s_attr.ar           = 0x0;
	s_attr.dr           = 0x0;
	s_attr.sr           = 0x0;
	s_attr.rr           = 0x0;
	s_attr.sl           = 0xf;
	SpuSetVoiceAttr(&s_attr);
	stenv->voice[0].data_addr += stenv->size / 2;
	SpuStTransfer(SPU_ST_PLAY, SPU_0CH);
	printf("\n\n prepare callback \n\n");
	return 0;
}

SpuStCallbackProc transfer_finished_callback(unsigned long voice_bit, long c_status) {
    // this does not work, it start to play in loop but just the first half of the song
	//stenv->voice[0].data_addr = (u_long)current_vag_data;
	//SpuStTransfer(SPU_ST_PREPARE, SPU_0CH);
    // -------------------------------------------------------------
	
	// this is working but just ends the song and play it once
	printf("\n\n transfer callback \n\n");
	stenv->voice[0].data_addr += stenv->size / 2;
	stenv->voice[0].status = SPU_ST_STOP;;
	stenv->voice[0].last_size = stenv->size / 2;
	return 0;
}

SpuStCallbackProc stream_finished_callback(unsigned long voice_bit, long c_status) {
	SpuStQuit();
	/*spu_init();
	spu_load_vag(current_vag_data, 343200, SPU_0CH);
	printf("\n\n STREAM finished callback \n\n");*/
	return 0;
}

// AUDIO PLAYER
void spu_init() {
	SpuInit();
	SpuInitMalloc(SOUND_MALLOC_MAX, (char*)(SPU_MALLOC_RECSIZ * (SOUND_MALLOC_MAX + 1)));
	l_c_attr.mask = (SPU_COMMON_MVOLL | SPU_COMMON_MVOLR);
	l_c_attr.mvol.left  = 0x3fff; // set master left volume
	l_c_attr.mvol.right = 0x3fff; // set master right volume
	SpuSetCommonAttr(&l_c_attr);
	stenv = SpuStInit(0);
	if(stenv == NULL){
		printf("spu st init error\n");
	}
}

void spu_load_vag(u_long *vag_data, u_long vag_size, int voice_channel){
	if ((stenv->voice[0].buf_addr = SpuMalloc(vag_size)) == -1) {
		printf("spu buf_addr malloc error\n");
	}
	stenv->size = vag_size;
	//stenv->low_priority = SPU_ON; 
	if(current_vag_data == NULL)
		current_vag_data = vag_data;
	stenv->voice[0].data_addr = (u_long)vag_data;

	SpuStSetPreparationFinishedCallback((SpuStCallbackProc)prepare_callback);
	SpuStSetTransferFinishedCallback((SpuStCallbackProc)transfer_finished_callback);
	SpuStSetStreamFinishedCallback((SpuStCallbackProc)stream_finished_callback);
	SpuStTransfer(SPU_ST_PREPARE, SPU_0CH);
}
I thought that by changing data_addr to a RAM address (where a new piece of music loaded from the CD-ROM is stored) in the transfer callback, or simply moving the address back and forth by its half, and recalling SpuStTransfer(SPU_ST_PREPARE, SPU_0CH), it would continuously play whatever music was at that address, but I’m missing something.
Maybe to do something like this I need to use more channels? maybe the approach is totally wrong? :D

Also, I know that there is a "simpler" way to play vag audio, like this:

Code: Select all

      SpuSetTransferMode(SpuTransByDMA); 
       l_vag1_spu_addr = SpuMalloc(vag_size);
       //SpuSetTransferCallback((SpuTransferCallbackProc)spuTransfer_callback);
       SpuSetTransferStartAddr(l_vag1_spu_addr);
       SpuWrite((u_char *)vag_data, vag_size);
       SpuIsTransferCompleted(SPU_TRANSFER_WAIT)
maybe this approach is simpler, but it seems that there is no control on what to play first and later, I can just play a vag file writing to the spu with SpuWrite, maybe I'm missing something here too :D

my project is open source and available on github
thanks in advance

hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

Post by hhaunt » September 6th, 2024, 2:56 am

Another doubt:
if in the stream finished callback I reinitialize the audio and re-execute the spu_load_vag function, I can loop the music. It follows that if inside the spu_load_vag function I change the data_addr to a new music file loaded in RAM, it should work, but I don't think it's the correct way to do it. From reading the documentation, it seems that I should handle this within the transfer finished callback.

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 643
Joined: November 12th, 2012, 2:36 pm
Contact:

Post by nocash » September 6th, 2024, 6:58 am

Is that pre-rendered background and music-streaming because you don't know how to draw tile/map graphics and play midi/instrument music? Or do you actually need it for hyper-realistic graphics and songs recorded from real-life bands?

I am not familar with the SDK functions. And I don't know which "transfers" and what "RAM" you are referring to. The overall music streaming flowchart should look like so:
1) Transfer data from CDROM to Main-RAM
2) Transfer data from Main-RAM to SPU-RAM
3) Let the SPU transfer the data from SPU-RAM to the audio output

For step 3, I would play a large block in a loop, and split it into two smaller blocks, then use the SPU's interrupt feature to get a notification when it starts to play one of the smaller blocks, and let step 2 transfer new data to the other block, and alongsides let step 1 load more data when needed.

The .VAG format looks a bit odd. It isn't black magic to remove the file header from the first sector, but a headerless .VB file with raw data might be a bit more suitable for streaming.

hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

Post by hhaunt » September 6th, 2024, 7:25 am

nocash wrote: September 6th, 2024, 6:58 am Is that pre-rendered background and music-streaming because you don't know how to draw tile/map graphics and play midi/instrument music? Or do you actually need it for hyper-realistic graphics and songs recorded from real-life bands?
I've made tons of tile engine games for other systems, I'm just trying to doing a game with pre-rendered backgrounds because Final Fantasy 7-8-9 were made in that way, and I like that kind of games too.
nocash wrote: September 6th, 2024, 6:58 am 1) Transfer data from CDROM to Main-RAM
2) Transfer data from Main-RAM to SPU-RAM
3) Let the SPU transfer the data from SPU-RAM to the audio output

For step 3, I would play a large block in a loop, and split it into two smaller blocks, then use the SPU's interrupt feature to get a notification when it starts to play one of the smaller blocks, and let step 2 transfer new data to the other block, and alongsides let step 1 load more data when needed.
ya that's the idea, but I'm encountering some problem to understand how to manage the thing with the callbacks, I will figure out.

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 643
Joined: November 12th, 2012, 2:36 pm
Contact:

Post by nocash » September 6th, 2024, 4:31 pm

The Port "1F801DA4h - Sound RAM IRQ Address (IRQ9)" feature can be used when the SPU is reading/playing which buffer half. Either by using a callback/interrupt handler, or by simply polling the IRQ flag. That's probably the only thing you need.

hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

Post by hhaunt » September 6th, 2024, 8:42 pm

thanks for the reply, reading the doc, I found how to set the interrupt:

Code: Select all

SpuIRQCallbackProc spu_handler(){
	printf("\n\ninterrupt hitted\n\n");
	return 0;
}

void spu_load_vag(u_long *vag_data){
	long size = 300000; // size in bytes
	current_vag_data = vag_data;
	spu_addr[0] = SpuMallocWithStartAddr(0x01010, size); 

	SpuSetTransferMode(SpuTransByDMA);
	SpuSetTransferStartAddr(spu_addr[0]);
	SpuWrite((u_char *)current_vag_data, size);

	SpuIsTransferCompleted(SPU_TRANSFER_WAIT);
	spu_set_voice_attr(SPU_0CH, spu_addr[0]);	
	spu_play(SPU_0CH);

	SpuSetIRQ(SPU_ON);
	SpuSetIRQAddr(spu_addr[0] + (size/2));
	SpuSetIRQCallback((SpuIRQCallbackProc)spu_handler);
	printf("\naddress %ld \n\n", spu_addr[0]);
}

SpuSetIRQ(SPU_ON);
SpuSetIRQAddr(spu_addr[0] + (size/2));
SpuSetIRQCallback((SpuIRQCallbackProc)spu_handler);

with these 3 functions I should be able to see when the address spu_addr[0] (which is 0x01010) + (size/2) has been read by spu (guessing).
But the callback is not triggered, if I change SputSetIRQAddr parameter to SpuSetIRQAddr(spu_addr[0])); it triggers correctly at the beginning of the music, I'm missing something of obvious ahah or maybe I'm doing it totally wrong.

EDIT:
modifying what was doing before trying directly with Spu IRQ, I manage to make a loop, still need to fix something but it's working, so maybe now I can do the memory swap trick from the main ram.
Anyway if someone knows what is the problem with the SpuSetIRQAddr let me know, that was a cleaner way maybe

Code: Select all

SpuStCallbackProc prepare_callback(unsigned long voice_bit, long c_status) {
	stenv->voice[voice_bit-1].data_addr += stenv->size / 2;
	SpuStTransfer(SPU_ST_PLAY, voice_bit);
	printf("\n\n prepare callback voice bit %ld \n\n", voice_bit);
	return 0;
}

SpuStCallbackProc transfer_finished_callback(unsigned long voice_bit, long c_status) {
	u_long *addr = &stenv->voice[voice_bit-1].data_addr;
	*addr += stenv->size / 2;
	printf("\n\n data_addr %lu \n\n", (u_long)addr);
	if(*addr >= (u_long)current_vag_data + stenv->size){
		*addr = (u_long)current_vag_data;
		SpuStTransfer(SPU_ST_PREPARE, SPU_0CH);
		printf("\n\n reset data_addr %lu \n\n", (u_long)addr);
	}
	printf("\n\n transfer callback \n\n");
	return 0;
}


User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 643
Joined: November 12th, 2012, 2:36 pm
Contact:

Post by nocash » September 7th, 2024, 9:40 am

Did you recurse whether the SDK wants the addr, size, and size/2 in units of bytes, or bytes/8 (or yet other units)?
On hardware, the 512Kbyte SPU RAM addresses are squeezed into 16bit addresss values (bytes/8).
(Or actually it's bytes/16 because the hardware does somewhat ignore the lower bit of the 16bit addresss.)

hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

Post by hhaunt » September 8th, 2024, 7:37 am

Well, I solved the puzzle I guess...

Code: Select all

SpuIRQCallbackProc spu_handler(){
	printf("\n\n interrupt hitted \n\n");
	return 0;
}

SpuTransferCallbackProc spu_transfer_callback(){
	printf("\n\n transfer callback hitted \n\n");
	SpuSetIRQ(SPU_ON);
	SpuSetIRQAddr(spu_addr[0] + 150000); // half size
	SpuSetIRQCallback((SpuIRQCallbackProc)spu_handler);
	return 0;
}

void spu_load_vag(u_long *vag_data, u_long vag_size, int voice_channel){
	long size = 300000; // size in bytes
	current_vag_data = vag_data;
	//spu_addr[0] = SpuMallocWithStartAddr(0x01010, size); 
	spu_addr[0] = SpuMalloc(size);

	SpuSetTransferMode(SpuTransByDMA);
	SpuSetTransferStartAddr(spu_addr[0]);
	SpuWrite((u_char *)current_vag_data, size);
	SpuSetTransferCallback((SpuTransferCallbackProc)spu_transfer_callback);

	spu_set_voice_attr(SPU_0CH, spu_addr[0]);	
	SpuSetKey(SpuOn, SPU_0CH);

	printf("\nspu_addr %ld \n\n", spu_addr[0]);
}

replacing

Code: Select all

SpuIsTransferCompleted(SPU_TRANSFER_WAIT);
with

Code: Select all

SpuSetTransferCallback((SpuTransferCallbackProc)spu_transfer_callback);
and set the Spu IRQ functions in the spu_transfer_callback, it will hit the interrupt correctly (spu_handler) at byte 150000 (half music size, 300000).

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 643
Joined: November 12th, 2012, 2:36 pm
Contact:

Post by nocash » September 8th, 2024, 10:12 am

Congrats.

The decimal number 150000 looks dangerously frightening, it's barely matching the 16-byte alignment requirement, it should work, but be careful if you change the value in future.

For the actual cdrom streaming, I would use two (or more) smaller blocks, which do better match up with the 800h-byte cdrom sector size, and which won't stall the CPU for too long when DMAing huge blocks to SPU.

Using the SpuTransferCallback looks a bit overcomplicated - it would work without that... but it might actually make some sense there (the IrqAddr must be set up *after* the transfer to SPU RAM, otherwise the transfer itself will trigger the IRQ when writing to that addr).

hhaunt
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: April 16th, 2024, 9:17 am

Post by hhaunt » September 9th, 2024, 11:17 pm

nocash wrote: September 8th, 2024, 10:12 am Using the SpuTransferCallback looks a bit overcomplicated - it would work without that... but it might actually make some sense there (the IrqAddr must be set up *after* the transfer to SPU RAM, otherwise the transfer itself will trigger the IRQ when writing to that addr).
yea, that's was the problem I guess, the transfer itself was triggering the IRQ when writing

Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests