Author Topic: Why is this skipping?  (Read 410 times)

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Why is this skipping?
« on: July 15, 2018, 09:10:08 am »
If anyone bothers to look at this, that's cool.

If you are someone who is particularly short on time / has better things to do (like XL2), best to move on, because I have been fumbling around with an interruptible PCM playback system for more than a month now (and there are already better solutions, but I'm stubborn).
I thought I had it good, and I do have working concepts, but I've come to the conclusion that I need a buffer to give the Saturn time to switch files in the case I want to load something while the music is playing. Otherwise, the music has to halt. I have no experience with this kind of thing...

Code: [Select]
#define PCMRAM	(631734272)
#define LWRAM (2097152)
#define HIMEM (100679680)
//HIMEM has 376 sectors (752 KB)
#define HIEND (101449728)
//Each buffer is 8 sectors / 16 KB, a total of 32 sectors / 64 KB
#define PCMBUF1 (101433344)
#define PCMBUF2 (101416960)
#define PCMBUF3 (101400576)
#define PCMWORK (101384192)

//THIS IS TESTED
#define M3072KHZ     (31118)
//


Code: [Select]
					/**MUSIC SYSTEM SETUP**/

Sint8* music = "SCARE.AIFF";
TESTLOOP:
    music_pcmfx.mode = JoSoundMono16Bit;
//This function is supposed to play a mono PCM sound effect constantly.
//Some HIMEM is consumed in the process (32 sectors / 64KB at the END OF HIMEM)
Sint32 fid_m = GFS_NameToId(music);
music_fs = GFS_Open(fid_m);
Sint32 msize;
Sint32 nsct_m;
Sint32 stat;
Sint32 rdsize;

Sint32 nsct_f;
Sint32 fsize;
//GFS Error Information
Sint32 a_stat;
//Get sectors
//HEY! SBL DOCUMENTATION IS WRONG! THIRD ITEM nzect IS GFS SECTOR COUNT. SECOND ITEM IS CD SECTOR SIZE.
//TIP: MEMORY MUST BE MANAGED IN SECTORS (2KB)
GFS_GetFileSize(music_fs, NULL, &nsct_m, NULL);
GFS_GetFileInfo(music_fs, NULL, NULL, &msize, NULL);
//This determines playback time?
music_pcmfx.data_length = (msize);
_pcm[(int)0].mode = (Uint8)music_pcmfx.mode;
_pcm[(int)0].pitch = M3072KHZ;
//How many frames are we reading?
if(rt_step < msize){
music_sectors = (msize + (rt_step - 1))/(rt_step);
}
jo_printf(0, 18, "(%i)", msize);

GFS_SetReadPara(music_fs, rt_step);
GFS_SetTransPara(music_fs, rt_sector);
//music_fs should be SCU type since it's going to high memory.
GFS_SetTmode(music_fs, GFS_TMODE_SCU);
//Seek music to the desired location
GFS_Seek(music_fs, play_ref, GFS_SEEK_SET);
//MUSIC loading follows
GFS_NwCdRead(music_fs, (20 * 2048));


//Let's make a pre-playback loop to read from the CD to fill a series of buffers using a work buffer.
for( ; buffers_filled < 3 ; ){
if(buffers_filled == 0){
rd_pcmbuf = PCMBUF1;
}
if(buffers_filled == 1){
rd_pcmbuf = PCMBUF2;
}
if(buffers_filled == 2){
rd_pcmbuf = PCMBUF3;
}
if(music_frames == 4){
slDMACopy(PCMWORK, rd_pcmbuf, (8 * 2048));
slDMAWait();
buffers_filled += 1;
music_frames = 0;
}
if(music_frames < 4){
music_frames++;
GFS_NwFread(music_fs, rt_sector, PCMWORK  + (music_frames * rt_step), rt_step);
play_ref += 2;
}
do{
// game_code();
slSynch();
if(music_frames < 4){
GFS_NwExecOne(music_fs);
GFS_NwGetStat(music_fs, &stat, &rdsize);
}
jo_printf(0, 20, "(27)");
jo_printf(0, 7, "(%i)", music_frames);
jo_printf(7, 7, "(%i)", play_ref);
jo_printf(0, 10, "(%i)", stat);
jo_printf(0, 11, "(%i)", fetch_timer);
jo_printf(10, 11, "(fetched filesize)");
}while(stat != GFS_SVR_COMPLETED && rdsize < rt_step);
jo_printf(0, 22, "(%i)", buffers_filled);
}
GFS_Close(music_fs);
//Let's then make a test loop to playback in sequence the three playback buffers that have been filled.
for( ; buffers_filled > 0 ; ){
// game_code();
slSynch();
jo_printf(0, 20, "(49)");
jo_printf(0, 11, "(%i)", fetch_timer);
jo_printf(0, 22, "(%i)", buffers_filled);

fetch_timer++;

if(fetch_timer == 8){
buffers_filled -= 1;
fetch_timer = 0;
}
if(buffers_filled == 0){
curpcmbuf = PCMBUF3;
}
if(buffers_filled == 1){
curpcmbuf = PCMBUF2;
}
if(buffers_filled == 2){
curpcmbuf = PCMBUF1;
}
slSndFlush();

music_pcmfx.data = curpcmbuf; //this line does not matter
music_pcmfx.current_playing_channel = 0;

slSoundRequest("bbbbwbb", SND_PCM_START, 0, 0, 631767039, (0), 0, 0, 0);

slPCMOn(&_pcm[(int)0], music_pcmfx.data, music_pcmfx.data_length);
slPCMParmChange(&_pcm[(int)0]);
}
//buffers_filled = 0;
goto TESTLOOP;

Actual transfer into PCMRAM to play is handled at Vblank with an interrupt:

Code: [Select]
void	my_vlank(void){
//A new music buffer system is needed.
//A filled-by-request buffer of 4x8 sectors, 32 sectors, 64 KB, at the end of high memory...
//A system managed .. how? The core issue is the file system slows down the entire Saturn, causing skipping.
//Tenets: GFS Seek has a variable completion time (but does not halt the system). GFS_Close does not complete immediately.
//Management of the buffer system must never close or re-open the music file, unless the buffer is completely full.
//Copy memory from work area buffer directly into the PCM stream buffer
slDMACopy(curpcmbuf, PCMRAM, (8 * 2048));
slDMAWait();
    slGouraudTblCopy();
}

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #1 on: July 15, 2018, 02:33:15 pm »
Yes, you can ignore this, I have other problems to solve but I am making progress.

SaturnTeam

  • Jr. Member
  • **
  • Posts: 68
  • Karma: +5/-0
    • View Profile
    • Saturn Team
Re: Why is this skipping?
« Reply #2 on: July 15, 2018, 10:43:58 pm »
I wish I could help, but I don't know enough about that.
Founder of Saturn Team

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #3 on: July 16, 2018, 01:54:27 am »
Don't worry, I appreciate you reading though.
Buffers are more complicated than I would have expected. Which is good, I'm in it for the challenge

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #4 on: July 17, 2018, 12:49:44 pm »
Maybe someone can give this a listen if they know a lot about audio and tell what's going on.
I'm not certain...
https://youtu.be/BdOBkLl8jFg

XL2

  • Sr. Member
  • ****
  • Posts: 341
  • Karma: +72/-1
    • View Profile
Re: Why is this skipping?
« Reply #5 on: July 17, 2018, 03:20:08 pm »
Yeah, I wish I could help, but sadly I haven't played much with PCM.
I don't hear anything that couldn't be done on the audio CPUs thought.

Now, that would require using this tool backward and creating a tool to convert from midi to sequence :
https://github.com/mistydemeo/seq2mid/find/master?q=

That's really the best way to go, even if it's not the easiest one!

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #6 on: July 17, 2018, 03:31:18 pm »
Actually, I think I have figured it out so far. Memory copies are slowing the program down enough to cause desync. From a different perspective, the music is playing too quickly.

In any case, I wonder if I could use the audio CPU to manage the memory copies. I am actually using audio RAM as the playback buffer. (It actually works faster that way, it seems!)
You just can't tell SGL your sound data is stored there, because it tries to use an SCU DMA access and that can't happen over the same bus, so it crashes. Command SGL to use anything in LWRAM and then overwrite the static with slSoundRequest, write directly to the PCM channel buffer you need, and it will play the sound. Sadly, that's two SCU DMAs going on just to make this possible the way I am doing it. But I will soldier on!
I've also found out that your simple memcpy functions seem to help force synchronization (by putting in just enough delay). Well, not actually enough delay. A closer amount of delay.
/e: memcpy_w seems to improve the synchronization drastically, even moreso than memcpy_l. Funny enough XL2, it seems your memcopy functions are actually faster than slDMA.
[actually, they just halt the program a little bit]
I think I can work with this my spacing out the copies. They don't all need to happen on the same frame.

I really wouldn't have expected this to be a performance issue. Now I am worried if this will NEVER work with a game running on top of it!

/e2: A brief look in SBL and there does not seem to be a generic sample for memory copy. Darn.
It might be high-time for me to learn assembly. (Yeah right, like I could ever do that, AND figure out how to address the sound CPU!) All I would need is to designate one area in sound RAM that the sound CPU copies down through 3 16KB buffers, finally into the main PCM playback buffer. (Obviously that's not gonna happen, but it's a thought..)
« Last Edit: July 17, 2018, 04:56:53 pm by ponut64 »

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #7 on: July 17, 2018, 05:27:00 pm »
Or maybe I should be a not-dofus, and don't copy things pointlessly.

XL2

  • Sr. Member
  • ****
  • Posts: 341
  • Karma: +72/-1
    • View Profile
Re: Why is this skipping?
« Reply #8 on: July 17, 2018, 07:47:35 pm »
Memcpy l uses long words (32 bits) while memcpy w uses word access (16 bits). You must use word access when writing to anything else than high ram since the only 32 bits things are related to the sh2 AFAIK.
Also, if you have a buffer in main ram, use the high ram as low ram is also 16 bits access and, if I'm not mistaken, you can't scu dma directly from low ram, so it will copy to a high buffer, then scu dma to sound ram, which is obviously slow (again, if I'm not mistaken).
The faster transfers are during vblank with indirect access (dma) since it's at that point where SGL will put the audio cpu in "listening mode".
I haven't looked at your code, but did you try to transfer during vblank?
Scu dma channel 0 should be free during that point, so just look at my model convert code for the "myvblank" function to transfer data.
While SGL documentation says otherwise, the slDMAcopy function seems to look at the destination and then switches to either cpu or scu dma depending on the destination.
Edit : Seems like you do it during vblank. Also, don't use dma wait as it will just cause useless delays. There is also a sbl function to transfer data to the audio ram, I had the code last time I shared with you, use that instead of slDMAcopy if you still have issues.
« Last Edit: July 17, 2018, 07:50:13 pm by XL2 »

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #9 on: July 17, 2018, 08:24:51 pm »
That's a lot of handy tips. Especially big thanks for memcpy_w and memcpy_l explanation.

Yes, main transfer is done during vblank.
Yes, I have learned that slDMAWait is unnecessary in this case and will drag the system to a crawl. (A real system!)
I've now got it to where I don't need that much more optimization using slDMA. I will look over SBL, maybe its transfers are faster.
I've got it to where it is clear when listening to the music that it is a cyclic desync. (Unfortunately, it starts out desync'd, but I will get to that later)
I could also restructure my buffer so it uses less copies/DMAs to begin with.

/e: memcpy_w is the proper way to transfer to audio RAM (if that is the method), however it does not seem to work at Vblank. Which is a shame, since the program works perfectly if memcpy_w is the function used for transfer to the playback region. I suspect it doesn't work at Vblank because SGL is trying to copy garbage into that region of memory all the time. Maybe if I understood slSoundRequest SND_PCM_ON better, I could get around that. I did actually find the assembly source code for SND_PCM_ON.

Code: [Select]
;************************************************************************
; HOST Command = $85 : PCM stream play start *
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
; P1 :   D7      = L ( mono ) / H ( stereo ) *
;        D6      = no care *
;        D5      = no care *
;        D4      = L ( 16bit PCM ) / H ( 8bit PCM ) *
;        D3      = no care *
;        D2~D0  = PCM Stream# *
; P2   : D7~D5  = Direct level [DISDL] *
;        D4~D0  = Direct Pan   [DIPAN] *
; P3   : D7~D0  = PCM start address bit19~12 *
; P4   : D7~D0  = //      bit11~4  ( bit3~0 = always "0" ) *
; P5,6 : D15~D0 = PCM data sample ( memory use ) size High byte per 1ch*
; P7,8 : D14~D11 = [OCT] *
; D9 ~D0  = [FNS] *
; P9   : D6~D3  = [ISEL] Rch ( stereo/mono ) *
; D2~D0  = [IMXL] Rch ( stereo/mono ) *
; P10  : D6~D3  = [ISEL] Lch ( stereo ) *
; D2~D0  = [IMXL] Lch ( stereo ) *
; P11  : D7~D0  = total level *
;************************************************************************
global drv_PCM_st
drv_PCM_st:
lea Pcm_Strm(a6),a3 ; PCM stream# stack area
lea slot_work(a6),a2 ;
move.b HOST_P1(a0),d7 ; P1 : bit7 H = stereo
bpl drv_85_mono
;===============================;
drv_85_stereo: andi.w #7,d7 ; = PCM Stream#
add.w d7,d7 ;
move.b 0(a3,d7.w),d4 ; R : play flag & slot#
bset.b #7,0(a3,d7.w) ;
andi.w #$1F,d4 ;
lsl.w #6,d4 ;
move.b 1(a3,d7.w),d5 ; L : play flag & slot#
bset.b #7,1(a3,d7.w) ;
andi.w #$1F,d5 ;
lsl.w #6,d5 ;
;-------------------------------;
; d4.w = ( R ) slot work offset
; d5.w = ( L ) slot work offset
drv_85_st1: move.b HOST_P1(a0),d0 ; P1 : D4 = 8bit / D210 = #
move.b d0,d7 ;
andi.w #7,d0 ; = PCM Stream#
ori.b #$80,d0 ;
move.b d0,PSPN(a2,d4.w) ; save PCM Stream# & play flag
move.b d0,PSPN(a2,d5.w) ; save PCM Stream# & play flag
lsr.w #1,d4 ; $00,$20,..,$3E0
lsr.w #1,d5 ;

move.b #$10,$00(a5,d4.w) ; <<< key off >>>
move.b #$10,$00(a5,d5.w) ; <<< key off >>>

andi.w #00010000B,d7 ; 16/8 bit PCM ?
ori.w #00100000B,d7 ; set Forward loop
swap d7 ;
clr.w d7 ;
moveq #0,d0 ;
move.w d0,SCSP_PCM_LSA(a5,d4.w) ; [LSA] R = 0000
move.w d0,SCSP_PCM_LSA(a5,d5.w) ; [LSA] L = 0000
move.w HOST_P3(a0),d0 ; P3,4 : [SA] 上位16bits
lsl.l #4,d0 ; *10H
or.l d7,d0 ;
move.l d0,(a5,d4.w) ; <<< [SA] R >>>
moveq #0,d1 ;
move.b d1,SCSP_SISD(a5,d4.w) ; <<< [TL/MDL] = 0 : R >>>

;@ move.b HOST_P7(a0),SCSP_TLVL(a5,d4.w) ;
move.b HOST_P11(a0),SCSP_TLVL(a5,d4.w) ;
move.w d1,SCSP_MDLSL(a5,d4.w) ; <<< [TL/MDL] = 0 : R >>>
move.b d1,SCSP_SISD(a5,d5.w) ; <<< [TL/MDL] = 0 : L >>>
;@ move.b HOST_P7(a0),SCSP_TLVL(a5,d5.w) ;
move.b HOST_P11(a0),SCSP_TLVL(a5,d5.w) ;
move.w d1,SCSP_MDLSL(a5,d5.w) ; <<< [TL/MDL] = 0 : L >>>
move.w d1,SCSP_RELFO(a5,d4.w) ; <<< [LFO] : R >>>
move.w d1,SCSP_RELFO(a5,d5.w) ; <<< [LFO] : L >>>
move.w HOST_P5(a0),d1 ; = 1,2,...,FFFF,0000
subq.w #1,d1 ; = 0,1,...,FFFE,FFFF
move.w d1,SCSP_PCM_LEA(a5,d4.w) ; <<< [LEA] R >>>
move.w d1,SCSP_PCM_LEA(a5,d5.w) ; <<< [LEA] L >>>
addq.l #1,d1 ; = 1,2,...,FFFF,10000
; d0.l = [LPCTL][8BIT][SA] R
; d1.l = PCM sample size / slot
add.l d1,d0 ; + data sample size
btst.l #20,d7 ; 8/16 bit ?
bne.s drv_85_1 ; jump if 8bit data
add.l d1,d0 ; + data sample size
drv_85_1: move.l d0,(a5,d5.w) ; <<< [SA] L >>>

move.b HOST_P2(a0),d0 ; P2   : [DISDL],[DIPAN]
andi.b #$E0,d0 ;
ori.b #$0F,d0 ; Pan = [R]
; == for monoral TV 対応 ==
btst.b #7,SND_OUT_ST(a6) ; MONO/STEREO status
beq.s drv_85_3ST ; jump if STEREO mode
andi.b #$F0,d0 ; set Center
drv_85_3ST: move.b d0,SCSP_DISDLPN(a5,d4.w) ; <<< [DISDL],[DIPAN] R >>>
add.w d4,d4
move.b d0,sl_DISDLPAN(a2,d4.w)
lsr.w #1,d4
ori.b #$10,d0 ; set Pan = [L]
move.b d0,SCSP_DISDLPN(a5,d5.w) ; <<< [DISDL],[DIPAN] L >>>
add.w d5,d5
move.b d0,sl_DISDLPAN(a2,d5.w)
lsr.w #1,d5

move.l #$001F001F,d0 ; AR,RR = 1FH
move.l d0,$08(a5,d4.w) ; <<< [D2R][D1R][EHLD][AR] >>>
move.l d0,$08(a5,d5.w) ; <<< [D2R][D1R][EHLD][AR] >>>
move.b HOST_P9(a0),d0
move.b d0,$15(a5,d4.w) ; <<< [ISEL],[IMXL] : R >>>
move.b HOST_P10(a0),d0
move.b d0,$15(a5,d5.w) ; <<< [ISEL],[IMXL] : L >>>
move.w HOST_P7(a0),d0
move.w d0,$10(a5,d4.w) ; <<< [OCT],[FNS] : R >>>
move.w d0,$10(a5,d5.w) ; <<< [OCT],[FNS] : L >>>

move.b #$08,$00(a5,d4.w) ; <<< key on 準備 >>>
move.b #$18,$00(a5,d5.w) ; <<< 同時 key on >>>

rts
;===============================;
; d7.b = P1:mode
drv_85_mono: andi.w #7,d7 ; = PCM Stream#
add.w d7,d7 ;
move.b 0(a3,d7.w),d4 ; R : play flag & slot#
bset.b #7,0(a3,d7.w) ;
andi.w #$1F,d4 ;
lsl.w #6,d4 ;
move.b 1(a3,d7.w),d5 ; L : play flag & slot#
bclr.b #7,1(a3,d7.w) ;
andi.w #$1F,d5 ;
lsl.w #6,d5 ;
;-------------------------------;
; d4.w = ( R ) slot work offset
; d5.w = ( L ) slot work offset
drv_85_mn1: move.b HOST_P1(a0),d0 ; P1 : D4 = 8bit / D210 = #
move.b d0,d7 ;
andi.w #7,d0 ; = PCM Stream#
ori.b #$80,d0 ;
move.b d0,PSPN(a2,d4.w) ; save PCM Stream# & play flag
move.b PSPN(a2,d5.w),d0 ; L 成分
move.b #0,PSPN(a2,d5.w) ; clear PCM Stream# & play flag
lsr.w #1,d4 ; $00,$20,..,$3E0
lsr.w #1,d5 ;

move.b #$10,$00(a5,d4.w) ; <<< key off >>>
move.b #$10,$00(a5,d5.w) ; <<< key off >>>

andi.w #00010000B,d7 ; 16/8 bit PCM ?
ori.w #00100000B,d7 ; set forward loop
swap d7 ;
clr.w d7 ;
moveq #0,d0 ;
move.w d0,SCSP_PCM_LSA(a5,d4.w) ; <<< [LSA] >>>
move.w HOST_P3(a0),d0 ; P3,4 : [SA] 上位16bits
lsl.l #4,d0 ;
or.l d7,d0 ;
move.l d0,(a5,d4.w) ; <<< [SA] >>>
;@ move.l d0,d3 ; stack d3.l = [SA] R
moveq #0,d1 ;

move.b d1,SCSP_SISD(a5,d4.w) ; <<< [SI],[SD] = 0 >>>
move.b HOST_P11(a0),SCSP_TLVL(a5,d4.w) ;
move.w d1,SCSP_MDLSL(a5,d4.w) ; <<< [MSL],[MDXYSL] = 0 >>>
move.w d1,SCSP_RELFO(a5,d4.w) ; <<< [LFO] >>>
move.w HOST_P5(a0),d1 ; = 1,2,...,FFFF,0000
subq.w #1,d1 ; = 0,1,...,FFFE,FFFF
move.w d1,SCSP_PCM_LEA(a5,d4.w) ; <<< [LEA] >>>

move.b HOST_P2(a0),d0 ; P2   : [DISDL],[DIPAN]
btst.b #7,SND_OUT_ST(a6) ; MONO/STEREO status
beq.s drv_85_6ST ; jump if STEREO mode
andi.b #$F0,d0 ; set Center
drv_85_6ST:
move.b d0,SCSP_DISDLPN(a5,d4.w) ; <<< [DISDL],[DIPAN] >>>
move.l #$001F001F,SCSP_D2R1R(a5,d4.w) ; <<< AR,RR = 1FH >>>
move.b HOST_P9(a0),SCSP_ISEL+1(a5,d4.w) ; <<< [ISEL],[IMXL] >>>
move.w HOST_P7(a0),SCSP_OCTFNS(a5,d4.w) ; <<< [OCT],[FNS] >>>
move.b #$18,SCSP_KXKB(a5,d4.w) ; <<< key on >>>
add.w d4,d4
move.b d0,sl_DISDLPAN(a2,d4.w)
rts

I'd like to have the copy run at Vblank, otherwise it will become desynchronized when interrupted.
Oh well, I'm going to burn a CD real quick.
« Last Edit: July 17, 2018, 08:53:42 pm by ponut64 »

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #10 on: July 17, 2018, 09:14:41 pm »
Just did a quick real hardware test.
Again, memcpy_w is the final transfer from the buffers (already in sound RAM from start to finish) to the PCM playback buffer of channel 0 (also in sound RAM).
GFS_NwFread is pushing it to sound RAM set to SCU transfer.
It works very well, with only one flaw, that being stutter. I imagine that stutter is simply caused by there being too many DMAs going on, but it can be overlooked for the time being.


I'm gonna burn another disc with memcpy_w at VBLANK and see what the Saturn does.
That's where the transfer should be, but SSF and Bizhawk immediately crash if it is there.

To literally nobodies surprise, it does crash on real hardware like that. I guess I had to try. Maybe I can find a workaround.
Thanks again XL2.

/e: I have of course not yet tested whether or not this whole extended buffer ordeal actually achieves my intentions of having interruptable file system of music playback, but I digress.
« Last Edit: July 17, 2018, 09:25:50 pm by ponut64 »

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #11 on: July 18, 2018, 07:33:28 pm »
I have come here once again to announce that this is driving me insane.

When you typo your work buffer where your first buffer should be, things might work more or less correctly on emulators, but utterly crash and burn on real hardware and you kinda lose all faith in everything.

Also, SDMA0 and SDMA1 as file system access DMAs aren't playing nice on real hardware right now.

I still have hope about finishing this... I've got the concept working on real hardware but there are major bugs and skippings and oh hey

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #12 on: July 22, 2018, 03:48:38 am »
So I have something cool to share. Not entirely finished yet, but, it is real.

I have finally managed to make an uninterrupted music playback system that can also load files while music is playing.
I've also found the Saturn's CD system can reliably read and fetch 8 sectors per frame at 30 FPS. It could do a little more, unsure.

This system has a lot of problems and maybe some things you can benefit from.
One, SGL has a command called "slSoundRequest" to send commands directly to the MC68EC000. I use that to play sound.
Two, something is injecting static during playback. I don't know what the cause is and thus have no clue about a solution. I had to expand the buffer from a potential 80 KB with more frequent sector switches to 180KB with less frequent buffer sector switches (less times the slSoundRequest command is sent), since this mitigates the static. It's still there and its really annoying.
Three, this buffer is in sound RAM, and as far as I figure, with directly addressed commands all but the first 4KB of sound RAM are usable (because your playback memory address is 16 high-order bits of 20-bit data, meaning it assumes 4 lower order bits are zero).
Four, this potentially uses less CPU grunt than SGL and SBL PCM playback methods, since there are zero DMAs (outside of the command) happening between the main CPUs and the SCSP during playback. The file system writes the new sound data into the ring buffer directly using SCU DMA.

Also, it's calibrated ONLY for 30.72KHz mono sound.

Code: [Select]
//Pseudo-code: Assemble if you want to explore or ask me to post the files
#define M3072KHZ (31122)

/**MUSIC BUFFER REGION 160KB / 80 SECTORS / 5 * 32768 **/
///THIS IS UNNECESSARILY LARGE
#define PCMBUF1 (631304192)
#define PCMBUF2 (631336960)
#define PCMBUF3 (631369728)
#define PCMBUF4 (631402496)
#define PCMBUF5 (631435264)

#define MAP_TO_SCSP(sh2map_snd_adr) ((sh2map_snd_adr - SNDRAM)>>4)

typedef struct{
void* rd_pcmbuf;
int play_pcmbuf;
} snd_ring;

//Rate of data reading
static Sint16 m_step = (8 * 2048);
static Sint16 m_sector = 8;
static Sint8 mcpy_factor = 2;
static Sint16 rt_step = (8 * 2048);
static Sint16 rt_sector = 8;

//Buffered space between buffer segments is important. This is why this takes up so much RAM. Lower buffer segment size = lesser space consumed by buffer segmentation
// = lower overall size possible.
music_buf[0].rd_pcmbuf = (void*)PCMBUF4;
music_buf[0].play_pcmbuf = MAP_TO_SCSP(PCMBUF1);
music_buf[1].rd_pcmbuf = (void*)PCMBUF5;
music_buf[1].play_pcmbuf = MAP_TO_SCSP(PCMBUF2);
music_buf[2].rd_pcmbuf = (void*)PCMBUF1;
music_buf[2].play_pcmbuf = MAP_TO_SCSP(PCMBUF3);
music_buf[3].rd_pcmbuf = (void*)PCMBUF2;
music_buf[3].play_pcmbuf = MAP_TO_SCSP(PCMBUF4);
music_buf[4].rd_pcmbuf = (void*)PCMBUF3;
music_buf[4].play_pcmbuf = MAP_TO_SCSP(PCMBUF5);

/**There are variables and functions left out for the file system**/
/**
MAIN MUSIC-GAME LOOP
Fault Tolerance needs study.
Sample clipping needs correction.
Data M_TRIG : Music trigger. Sets Vblank buffer playback toggle ON.
Data music_frames : Frames read from music file system under copy factor.
Data mcpy_factor : Frames to read from music file to complete an 16-sector buffer.
Data mrd_pos : Last or current read case in music buffer. It is the last position in the case of file system interrupting music.
Data buf_pos : The current playback case in music buffer. Synchronized with mrd_pos at file system interrupt (NOTE: not system IRQ, see break)
Data music_fs : GFS handle of music file. [Gets closed and re-opened. Music and file system can operate with max open files of 2]
Data m_sector : Number of sectors to fetch from CD block buffer.
Data music_buf : Music ring buffer struct array. Ring buffer is comprised of numerous 16-sector buffers. [At 30.72KHz mono, that is 8 frames of sound]
Data m_step : Bytes to fetch from CD block buffer. Also used as address offset (in the case of sub-8 sectors used as m_sector ; > 1 used as mcpy_factor)
Data play_ref : Seek reference when music file is re-opened.
Data rdsize : Fetch size of single read loop increment.
Data stat : Execution error/status information. If it is ever 2, you have a problem.
Function game_code : Your game.
Function slSynch : Frame limit synchronization.
buffers_filled : Unnecessary, but notifies system how many buffers have new data. It is incremented down at Vblank IRQ.

FILE SYSTEM INTERRUPT
Bool process_requested : Trigger in game code as to whether or not file process is requested. [TODO: sound_requested, etc]
Data fetch_timer : Buffer switch timer. See Vblank IRQ. This timer will increment buf_pos which triggers reads until mrd_pos equals buf_pos.
**/
for( ; ; ){
if(play_ref < nsct_m){
m_trig = true;
} else if(play_ref >= nsct_m){
m_trig = false;
if(m_trig){
slSoundRequest("b", SND_PCM_STOP, 0);
}
}
  if(music_frames < mcpy_factor && mrd_pos != buf_pos){
GFS_NwFread(music_fs, m_sector, (void*)music_buf[mrd_pos].rd_pcmbuf + (music_frames * m_step), m_step);
play_ref += m_sector;
music_frames++;
}
do{
game_code();
slSynch();
GFS_NwExecOne(music_fs);
GFS_NwGetStat(music_fs, &stat, &rdsize);
if(music_frames == mcpy_factor){
buffers_filled += 1;
if(mrd_pos != buf_pos){
mrd_pos++;
}
music_frames = 0;
}
jo_printf(0, 20, "(49) loop label");
jo_printf(0, 22, "(%i) cur rd case", mrd_pos);
jo_printf(0, 23, "(%i) cur play case", buf_pos);
jo_printf(0, 7, "(%i)", music_frames);
jo_printf(7, 7, "(%i) sct off", play_ref);
jo_printf(0, 10, "(%i) fs stat", stat);
jo_printf(0, 11, "(%i) fetch", fetch_timer);
}while(stat != GFS_SVR_COMPLETED && rdsize < m_step);
if(mrd_pos > 4){
mrd_pos = 0;
}

if(process_requested == true && fetch_timer >= (mcpy_factor * 1) && fetch_timer <= 32){
if(mrd_pos == buf_pos){
break;
}
}
}
/**END MUSIC SYSTEM SETUP**/
GFS_Close(music_fs);

//Ensure file handler loop is handled like this:
//for( ; fetch_timer >= (mcpy_factor * 1) && fetch_timer <= 32 && mrd_pos == buf_pos ; ){
game_code();
slSynch();
}

void my_vlank(void){
    slGouraudTblCopy();
if(m_trig == true){
if(fetch_timer == 0){
slSoundRequest("bbwwwbb", SND_PCM_START, 0, 224, (music_buf[buf_pos].play_pcmbuf), (32768), (M3072KHZ), 0, 0);
}
fetch_timer++;
if(fetch_timer >= 32){
buffers_filled -= 1;
buf_pos++;
///Ring buffer wrap
if(buf_pos > 4){
buf_pos = 0;
}
fetch_timer = 0;
}
}
}

« Last Edit: July 22, 2018, 03:50:23 am by ponut64 »

XL2

  • Sr. Member
  • ****
  • Posts: 341
  • Karma: +72/-1
    • View Profile
Re: Why is this skipping?
« Reply #13 on: July 22, 2018, 03:44:56 pm »
Wow, nice!
Good to know you are getting it to work.
I'm mainly interested in streaming data, so to know that you can expect 4 sectors per frame at 60 fps is great.
You could do something a bit like Crash Bandicoot where you stream the visibility data to have 0 overdraw, which means you could fill the screen with polygons at a decent framerate.

ponut64

  • Full Member
  • ***
  • Posts: 175
  • Karma: +13/-0
    • View Profile
Re: Why is this skipping?
« Reply #14 on: July 22, 2018, 06:32:05 pm »
In that case, you need to be careful about the lead time of closing, seeking in a file (GFS_Seek), or changing directories.
All of these things can take up to a full second to complete, which is a circumstance where you would want to structure your visibility data in a way where you can buffer 60 frames of the nearest said visibility data in memory. In Crash Bandicoot's case, the linear structure of the game makes it obvious how they did that.
« Last Edit: July 22, 2018, 06:34:53 pm by ponut64 »

 

Sitemap 1 2 3 4 5 6 7 8 9 10 
SMF spam blocked by CleanTalk