Post by gameblabla on Jan 27, 2024 23:47:06 GMT
Hello guys,
it's been a while since i dealt with the PC-FX last time.
I thought i should post about what i did since last time.
I'll talk about ADPCM playback on the PC-FX.
ADPCM
Last time, i managed to play some basic ADPCM samples based on OldRover's code but could not make channel 1 work nor could i get looping or the KRAM pages.
I'm happy to say i mostly got it working ! The bad news is that it exposed a bug in cd_read_kram...
Let me show you the code first
void Initialize_ADPCM(uint32_t freq)
{
mainfreq = freq;
eris_low_adpcm_set_control(freq, 0, 0, 0, 0);
eris_low_adpcm_set_volume(0, 63, 63);
eris_low_adpcm_set_volume(1, 63, 63);
/*
When in ring buffer mode, the buffer contents are looped. Sequential mode does not loop
Bits Description
0 Buffer Mode
0 = Sequential
1 = Ring
1 End interrupt
0 = Disabled
1 = Allow
2 Intermediate interrupt
0 = Disabled
1 = Allow
*/
out16(0x600,0x51);
out16(0x604,0);
out16(0x600,0x52);
out16(0x604,0);
}
static unsigned char temp_sample_space[65536];
void LoadADPCMCD(u32 lba, u32 addr, uint32_t size_sample)
{
// Works but slower
// There's eris_cd_read_kram(BINARY_LBA_TITLEI_VOX, start_adress, sspace);
// but for whatever reason, it fails to work for PAGE1, so it only works for PAGE0... :/
eris_cd_read(lba, temp_sample_space, size_sample);
eris_king_set_kram_read(addr, 1);
eris_king_set_kram_write(addr, 1);
king_kram_write_buffer(temp_sample_space, size_sample);
}
void Play_ADPCM(uint32_t channel, uint32_t start_adress, uint32_t sizet, unsigned char loop)
{
//eris_king_set_kram_pages(0, BG_KRAM_PAGE, 0, ADPCM_KRAM_PAGE);
eris_king_set_kram_read(start_adress, 1);
/*
0x58 is the ADPCM channel 1 start address.
This is going to tell KING where to get the ADPCM data from.
ADPCM data is kept in normal KRAM. Using 0x5C instead of 0x58 does the same for channel 1.
*/
if (channel == 1)
{
out16(0x600,0x52);
out16(0x604,loop);
out16(0x600,0x5C);
}
else
{
out16(0x600,0x51);
out16(0x604,loop);
out16(0x600,0x58);
}
/*
The KRAM address has to be divided by 256. I wrote this out longhand to demonstrate.
*/
out16(0x604,(start_adress/256));
/*
Now I'm telling KING to set the end address. This is 0x59 for channel 0 and 0x5D for channel 1.
*/
if (channel == 1) out16(0x600,0x5D);
else out16(0x600,0x59);
/*
The end address is an absolute address, not a divided one.
Size should be in unsigned short
Now that you've told KING the range of the sample, it's time to actually play it.
OldRover made a mistake in his example and used out16 but in fact, it's a word.
*/
out32(0x604,((start_adress)+((sizet>>1))));
//Intermediate address, divided by 64. Used only if the intermediate interrupt bit in the channel's control is enabled.
/*if (channel == 1) out16(0x600,0x5E);
else out16(0x600,0x5A);
out16(0x606, ((start_adress)+ (sizet >> 16)));*/
/*
0x50 is the register that controls playback for both channels. That also means that you will need to be mindful of samples already playing.
*/
out16(0x600,0x50);
/*
Finally, play the sample. Bit 0 plays the sample configured for channel 0 and bit 1 plays the sample configured for channel 1.
Bits 2 and 3 control the sampling rate and use the same scheme as earlier.
Since I want 16kHz, I set bit 2 on and bit 3 off. So... bit 0 on, bit 1 off, bit 2 on, bit 3 off... value is 5. This plays the ADPCM sample in channel 0 at 16kHz.
If you need to stop a sample while it's playing, select register 0x50 and send a 0 to the bit of the channel you wish to stop.
Writing just 0 to 0x604 will stop both channels.
You can read 0x604 to see which channels are currently in use, and then just stop either of them if you want to.
If you set the buffer type to Ring, the sample will playback infinitely until you tell it to stop in this manner... or so the docs state.
So that pretty much sums up how to use ADPCM on the PC-FX.
*/
int playCommand = 0;
if (channel == 1) playCommand |= 2; // Set bit 1 for channel 1
else playCommand |= 1; // Set bit 0 for channel 0
// Set frequency bits
switch (mainfreq) {
case ADPCM_RATE_16000: playCommand |= 4; break;
case ADPCM_RATE_8000: playCommand |= 8; break;
case ADPCM_RATE_4000: playCommand |= 16; break;
// Add more cases here for other frequencies
}
out16(0x604, playCommand);
}
So you must have some way to either load the sample directly to KRAM or load the same from Main RAM to KRAM.
The issue is that if you use a high color mode like 64k/16M mode, you barely have any space left for audio samples.
Luckily, it's possible to use another KRAM page for audio samples as well as background... at your choosing.
So whenever you put something in KRAM (assuming you set the pages with eris_king_set_kram_pages(0, BG_KRAM_PAGE, 0, ADPCM_KRAM_PAGE))
eris_king_set_kram_read(addr, 1);
eris_king_set_kram_write(addr, 1);
king_kram_write_buffer_bytes(temp_sample_space, size_sample);
And you want your ADPCM samples to be stored in PAGE 1 as to avoid it overwriting your graphics, just set Bit 31. ( =| 0x80000000)
This would be faster with eris_cd_read_kram(BINARY_LBA_TITLEI_VOX, start_adress, sspace); rather than just using cd_read and then manually pushing to KRAM,
but the problem is that liberis's eris_cd_read_kram does not support KRAM pages, it always put it as PAGE 0 no matter if you set the bit or not.
It works if you do it manually as i was able to confirm, just not with eris_cd_read_kram. I'm suspecting it has to do with eris_low_scsi_begin_dma(u32 kram_addr, u32 size); as it does use it internally from assembly.
It does use out.h in there so i switched them to out.w just to see if it did push them as words but that did nothing to fix it.
Ideally someone would have to rewrite the damn thing in C as to make it easier to debug.
Not that it matters too much tho, as you will soon see...
Loading Data from CD
So i was under the impression that the CD code + pcfx-cdlink code was making sure that the embedded files through ADD_FILES were not put into code+data but rather just as sectors that you can then load into memory.
That was my impression but it turns out to be not correct... If you embed more than 2MB of data (aka the PC-FX total RAM amount), the tool will fail as it appears to use the leftover space for heap and stack.
The problem tho, is that adding files this way takes up an amount of RAM, even tho the whole point of using ADD_FILES was to avoid bundling it as part of code+data.
If i wanted to do that, i would just use objcopy/C headers...
This does become a problem for larger games (which is what i was trying to do)... What to do ?
I noticed there are SEEK and READ SCSI commands : github.com/libretro-mirrors/mednafen-git/blob/master/src/cdrom/scsicd.cpp#L2402
But i thought the low level SCSI were using those, not just loading stuff from main RAM. Or is it really loading from CD, just that there are two copies : one in RAM and one on CD ?
If so, that's very dumb... Perhaps pcfx-cdlink should get fixed to place the lba after the stack+heap.
CDPLAY unreliable
So two years ago i got CDDA working for the most part
_cd_playtrk:
mov lp, r19
movea 0x7FF, r0, r10
not r10, r13
mov r6, r11
mov r7, r18
mov r8, r16
mov r16, r17
add r10, r16 # for rounding up
and r13, r16
add -12,sp
movea -40,r0,r10
st.h r0,-8[fp]
st.b r6,-8[fp]
st.b r10,-10[fp]
mov -10,r6
mov 1,r10
st.h r0,-2[fp]
st.b r10,-9[fp]
add fp,r6
mov 10,r7
movea -128,r0,r10
st.h r0,-6[fp]
st.h r0,-4[fp]
st.b r10,-1[fp]
jal _eris_low_scsi_command
addi 32, sp, sp
movea 0x800, r0, r10
1: nop
nop
nop
nop
add -1, r10
bne 1b
jal _eris_low_scsi_status
mov r18, r10
mov r19, lp
jmp [lp]
_cd_endtrk:
mov lp, r19
movea 0x7FF, r0, r10
not r10, r13
mov r6, r11
mov r7, r18
mov r8, r16
mov r16, r17
add r10, r16 # for rounding up
and r13, r16
add -12,sp
movea -39,r0,r10
st.b r7,-9[fp]
st.b r6,-8[fp]
st.b r10,-10[fp]
addi -10,fp,r6
mov 10,r7
movea -128,r0,r10
st.b r10,-1[fp]
jal _eris_low_scsi_command
addi 32, sp, sp
movea 0x800, r0, r10
1: nop
nop
nop
nop
add -1, r10
bne 1b
jal _eris_low_scsi_status
mov r18, r10
mov r19, lp
jmp [lp]
To be more precise, it starts playing back CDDA once endtrck is called.
cd_playtrk(2, 0);
cd_endtrk(3, CDDA_NORMAL);
CDDA_NORMAL is 1, CDDA_LOOP is 0x4 and CDDA_SILENT is 0.
However whenever i use it at a later point in my code, it very often generates an exception in mednafen.
Disabling fixes that... But i am unsure if i'm running out of memory or not, probably not as it would still happen even after i disabled plenty of code.
The SCSI stuff is super finicky and annoying to deal with...
PSG PCM
This trick is well known already in the PC Engine world but i thought to mention it also applies on the PC-FX.
For 5-bit 15Khz playback, you can just set the timer with a period of 100 and then set the playback code in the timer function.
__attribute__ ((interrupt)) void samplepsg_timer_irq (void)
{
const int i = 0;
eris_timer_ack_irq();
//for(int i=0;i<SAMPLES_PSG_NUMBER;i++)
{
if (samplepsg_play[i])
{
eris_low_psg_waveform_data(samplepsg_buf[i][samplepsg_play[i]]);
samplepsg_play[i]++;
if (samplepsg_play[i] >= samplepsg_size[i])
{
samplepsg_play[i] = 0;
}
}
}
}
void initTimer(TimerIRQHandler timerIRQHandler, int period) {
// Disable all interrupts before changing handlers.
irq_set_mask(0x7F);
// Set the custom IRQ handler
irq_set_raw_handler(0x9, timerIRQHandler);
// Enable Timer interrupt.
irq_set_mask(0x3F);
// Reset and start the Timer with the specified period.
eris_timer_init();
eris_timer_set_period(period);
eris_timer_start(1);
// Clear any necessary variables or flags.
zda_timer_count = 0;
// Allow all IRQs.
irq_set_level(8);
// Enable V810 CPU's interrupt handling.
irq_enable();
}
It sounds decent enough and gives you more channels if you need.
The downside is that you used up the timer just to play PCM code and if you need better precision for calculating say the framerate, well you can't basically unless you reinit and stuff.
Also, it's somewhat a bit larger than ADPCM anyway so i don't think i'll end up using it that much.
There's still stuff like 7up sprites/background that you can use over your BG0 background so you can still use sprites and still have a main colorful display.
No a trick really but something i was reminded of while working on this.
Sadly the CD loading stuff is kind of a big blocker... I also seem to encounter random compiling issues but i'm unsure if it's because i'm running out of memory or SCSI-related issues.