# Sound Processing Unit (SPU) [SPU Overview](soundprocessingunitspu.md#spu-overview)
[SPU ADPCM Samples](soundprocessingunitspu.md#spu-adpcm-samples)
[SPU ADPCM Pitch](soundprocessingunitspu.md#spu-adpcm-pitch)
[SPU Volume and ADSR Generator](soundprocessingunitspu.md#spu-volume-and-adsr-generator)
[SPU Voice Flags](soundprocessingunitspu.md#spu-voice-flags)
[SPU Noise Generator](soundprocessingunitspu.md#spu-noise-generator)
[SPU Control and Status Register](soundprocessingunitspu.md#spu-control-and-status-register)
[SPU Memory Access](soundprocessingunitspu.md#spu-memory-access)
[SPU Interrupt](soundprocessingunitspu.md#spu-interrupt)
[SPU Reverb Registers](soundprocessingunitspu.md#spu-reverb-registers)
[SPU Reverb Formula](soundprocessingunitspu.md#spu-reverb-formula)
[SPU Reverb Examples](soundprocessingunitspu.md#spu-reverb-examples)
[SPU Unknown Registers](soundprocessingunitspu.md#spu-unknown-registers)
[SPU Internal Timing](soundprocessingunitspu.md#spu-internal-timing)
## SPU Overview #### SPU I/O Port Summary ``` 1F801C00h..1F801D7Fh - Voice 0..23 Registers (eight 16bit regs per voice) 1F801D80h..1F801D87h - SPU Control (volume) 1F801D88h..1F801D9Fh - Voice 0..23 Flags (six 1bit flags per voice) 1F801DA2h..1F801DBFh - SPU Control (memory, control, etc.) 1F801DC0h..1F801DFFh - Reverb configuration area 1F801E00h..1F801E5Fh - Voice 0..23 Internal Registers 1F801E60h..1F801E7Fh - Unknown? 1F801E80h..1F801FFFh - Unused? ``` #### SPU Memory layout (512Kbyte RAM) ``` 00000h-003FFh CD Audio left (1Kbyte) ;\CD Audio before Volume processing 00400h-007FFh CD Audio right (1Kbyte) ;/signed 16bit samples at 44.1kHz 00800h-00BFFh Voice 1 mono (1Kbyte) ;\Voice 1 and 3 after ADSR processing 00C00h-00FFFh Voice 3 mono (1Kbyte) ;/signed 16bit samples at 44.1kHz 01000h-xxxxxh ADPCM Samples (first 16bytes usually contain a Sine wave) xxxxxh-7FFFFh Reverb work area ``` As shown above, the first 4Kbytes are used as special capture buffers, and, if desired, one can also use the Reverb hardware to capture output from other voice(s).
The SPU memory is not mapped to the CPU bus, it can be accessed only via I/O, or via DMA transfers (DMA4).
#### Voices The SPU has 24 hardware voices. These voices can be used to reproduce sample data, noise or can be used as frequency modulator on the next voice. Each voice has it's own programmable ADSR envelope filter. The main volume can be programmed independently for left and right output.
#### Voice Capabilities All 24 voices are having exactly the same capabilities(?), with the exception that Voice 1 and 3 are having a special Capture feature (see SPU Memory map).
There seems to be no way to produce square waves (without storing a square wavefrom in memory... although, since SPU RAM isn't connected to the CPU bus, the "useless" DMA for square wave data wouldn't slowdown the CPU bus)?
#### Additional Sound Inputs External Audio can be input (from the Expansion Port?), and the CDROM drive can be commanded to playback normal Audio CDs (via Play command), or XA-ADPCM sectors (via Read command), and to pass that data to the SPU.
#### Unstable and Delayed I/O The SPU occassionally seems to "miss" I/O writes (not sure if that can be fixed by any Memory Control settings?), a stable workaround is too write all values twice (except of course, Fifo writes). The SPU seems to process written values at 44100Hz rate (so it may take 1/44100 seconds (300h clock cycles) until it has actually realized the new value).
#### Mono/Stereo Audio Output The standard PSX Audio cables have separate Left/Right signals, that is good for stereo TVs, but, when using a normal mono TV, only one of the two audio signals (Left or Right) can be connected. PSX programs should thus offer an option to disable stereo effects, and to output an equal volume to both cables.
#### SPU Bus-Width The SPU is connected to a 16bit databus. 8bit/16bit/32bit reads and 16bit/32bit writes are implemented. However, 8bit writes are NOT implemented: 8bit writes to ODD addresses are simply ignored (without causing any exceptions), 8bit writes to EVEN addresses are executed as 16bit writes (eg. "movp r1,12345678h, movb [spu\_port],r1" will write 5678h instead of 78h).
## SPU ADPCM Samples The SPU supports only ADPCM compressed samples (uncompressed samples seem to be totally unsupported; leaving apart that one can write uncompressed 16bit PCM samples to the Reverb Buffer, which can be then output at 22050Hz, as long as they aren't overwritten by the hardware).
#### 1F801C06h+N\*10h - Voice 0..23 ADPCM Start Address (R/W) This register holds the sample start address (not the current address, ie. the register doesn't increment during playback).
``` 15-0 Startaddress of sound in Sound buffer (in 8-byte units) ``` Writing to this register has no effect on the currently playing voice.
The start address is copied to the current address upon Key On.
#### 1F801C0Eh+N\*10h - Voice 0..23 ADPCM Repeat Address (R/W) If the hardware finds an ADPCM header with Loop-Start-Bit, then it copies the current address to the repeat addresss register.
If the hardware finds an ADPCM header with Loop-Stop-Bit, then it copies the repeat addresss register setting to the current address; that, \ playing the current ADPCM block.
``` 15-0 Address sample loops to at end (in 8-byte units) ``` Normally, repeat works automatically via the above start/stop bits, and software doesn't need to deal with the Repeat Address Register. However, reading from it may be useful to sense if the hardware has reached a start bit, and writing may be also useful in some cases, eg. to redirect a one-shot sample (with stop-bit, but without any start-bits) to a silent-loop located elsewhere in memory.
#### Sample Data (SPU-ADPCM) Samples consist of one or more 16-byte blocks:
``` 00h Shift/Filter (reportedly same as for CD-XA) (see there) 01h Flag Bits (see below) 02h Compressed Data (LSBs=1st Sample, MSBs=2nd Sample) 03h Compressed Data (LSBs=3rd Sample, MSBs=4th Sample) 04h Compressed Data (LSBs=5th Sample, MSBs=6th Sample) ... ... 0Fh Compressed Data (LSBs=27th Sample, MSBs=28th Sample) ``` #### Flag Bits (in 2nd byte of ADPCM Header) ``` 0 Loop End (0=No change, 1=Set ENDX flag and Jump to [1F801C0Eh+N*10h]) 1 Loop Repeat (0=Force Release and set ADSR Level to Zero; only if Bit0=1) 2 Loop Start (0=No change, 1=Copy current address to [1F801C0Eh+N*10h]) 3-7 Unknown (usually 0) ``` Possible combinations for Bit0-1 are:
``` Code 0 = Normal (continue at next 16-byte block) Code 1 = End+Mute (jump to Loop-address, set ENDX flag, Release, Env=0000h) Code 2 = Ignored (same as Code 0) Code 3 = End+Repeat (jump to Loop-address, set ENDX flag) ``` #### Looped and One-shot Samples The Loop Start/End flags in the ADPCM Header allow to play one or more sample block(s) in a loop, that can be either all block(s) endless repeated, or only the last some block(s) of the sample.
There's no way to stop the output, so a one-shot sample must be followed by dummy block (with Loop Start/End flags both set, and all data nibbles set to zero; so that the block gets endless repeated, but doesn't produce any sound).
#### SPU-ADPCM vs XA-ADPCM The PSX supports two ADPCM formats: SPU-ADPCM (as described above), and XA-ADPCM. XA-ADPCM is decompressed by the CDROM Controller, and sent directly to the sound mixer, without needing to store the data in SPU RAM, nor needing to use a Voice channel.
The actual decompression algorithm is the same for both formats. However, the XA nibbles are arranged in different order, and XA uses 2x28 nibbles per block (instead of 2x14), XA blocks can contain mono or stereo data, XA supports only two sample rates, and, XA doesn't support looping.
## SPU ADPCM Pitch #### 1F801C04h+N\*10h - Voice 0..23 ADPCM Sample Rate (R/W) (VxPitch) ``` 0-15 Sample rate (0=stop, 4000h=fastest, 4001h..FFFFh=usually same as 4000h) ``` Defines the ADPCM sample rate (1000h = 44100Hz). This register (and PMON) does affect only the ADPCM sample frequency (but not on the Noise frequency, which is defined - and shared for all voices - in the SPUCNT register).
#### 1F801D90h - Voice 0..23 Pitch Modulation Enable Flags (PMON) Pitch modulation allows to generate "Frequency Sweep" effects by mis-using the amplitude from channel (x-1) as pitch factor for channel (x).
``` 0 Unknown... Unused? 1-23 Flags for Voice 1..23 (0=Normal, 1=Modulate by Voice 0..22) 24-31 Not used ``` For example, output a very loud 1Hz sine-wave on channel 4 (with ADSR volume 4000h, and with Left/Right volume=0; unless you actually want to output it to the speaker). Then additionally output a 2kHz sine wave on channel 5 with PMON.Bit5 set. The "2kHz" sound should then repeatedly sweep within 1kHz..3kHz range (or, for a more decent sweep in 1.8kHz..2.2kHz range, drop the ADSR volume of channel 4).
#### Pitch Counter The pitch counter is adjusted at 44100Hz rate as follows:
``` Step = VxPitch ;range +0000h..+FFFFh (0...705.6 kHz) IF PMON.Bit(x)=1 AND (x>0) ;pitch modulation enable Factor = VxOUTX(x-1) ;range -8000h..+7FFFh (prev voice amplitude) Factor = Factor+8000h ;range +0000h..+FFFFh (factor = 0.00 .. 1.99) Step=SignExpand16to32(Step) ;hardware glitch on VxPitch>7FFFh, make sign Step = (Step * Factor) SAR 15 ;range 0..1FFFFh (glitchy if VxPitch>7FFFh) Step=Step AND 0000FFFFh ;hardware glitch on VxPitch>7FFFh, kill sign IF Step>3FFFh then Step=4000h ;range +0000h..+3FFFh (0.. 176.4kHz) Counter = Counter + Step ``` Counter.Bit12 and up indicates the current sample (within a ADPCM block).
Counter.Bit3..11 are used as 8bit gaussian interpolation index.
#### Maximum Sound Frequency The Mixer and DAC supports a 44.1kHz output rate (allowing to produce max 22.1kHz tones). The Reverb unit supports only half the frequency.
The pitch counter supports sample rates up to 176.4kHz. However, exceeding the 44.1kHz limit causes the hardware to skip samples (or actually: to apply incomplete interpolation on the 'skipped' samples).
VxPitch can be theoretically 0..FFFFh (max 705.6kHz), normally 4000h..FFFFh are simply clipped to max=4000h (176.4kHz). Except, 4000h..FFFFh could be used with pitch modulation (as they are multiplied by 0.00..1.99 before clipping; in practice this works only for 4000h..7FFFh; as values 8000h..FFFFh are mistaken as signed values).
#### 4-Point Gaussian Interpolation Interpolation is applied on the 4 most recent 16bit ADPCM samples (new,old,older,oldest), using bit4-11 of the pitch counter as 8bit interpolation index (i=00h..FFh):
``` out = ((gauss[0FFh-i] * oldest) SAR 15) out = out + ((gauss[1FFh-i] * older) SAR 15) out = out + ((gauss[100h+i] * old) SAR 15) out = out + ((gauss[000h+i] * new) SAR 15) ``` The Gauss table contains the following values (in hex):
``` -001h,-001h,-001h,-001h,-001h,-001h,-001h,-001h ;\ -001h,-001h,-001h,-001h,-001h,-001h,-001h,-001h ; 0000h,0000h,0000h,0000h,0000h,0000h,0000h,0001h ; 0001h,0001h,0001h,0002h,0002h,0002h,0003h,0003h ; 0003h,0004h,0004h,0005h,0005h,0006h,0007h,0007h ; 0008h,0009h,0009h,000Ah,000Bh,000Ch,000Dh,000Eh ; 000Fh,0010h,0011h,0012h,0013h,0015h,0016h,0018h ; entry 0019h,001Bh,001Ch,001Eh,0020h,0021h,0023h,0025h ; 000h..07Fh 0027h,0029h,002Ch,002Eh,0030h,0033h,0035h,0038h ; 003Ah,003Dh,0040h,0043h,0046h,0049h,004Dh,0050h ; 0054h,0057h,005Bh,005Fh,0063h,0067h,006Bh,006Fh ; 0074h,0078h,007Dh,0082h,0087h,008Ch,0091h,0096h ; 009Ch,00A1h,00A7h,00ADh,00B3h,00BAh,00C0h,00C7h ; 00CDh,00D4h,00DBh,00E3h,00EAh,00F2h,00FAh,0101h ; 010Ah,0112h,011Bh,0123h,012Ch,0135h,013Fh,0148h ; 0152h,015Ch,0166h,0171h,017Bh,0186h,0191h,019Ch ;/ 01A8h,01B4h,01C0h,01CCh,01D9h,01E5h,01F2h,0200h ;\ 020Dh,021Bh,0229h,0237h,0246h,0255h,0264h,0273h ; 0283h,0293h,02A3h,02B4h,02C4h,02D6h,02E7h,02F9h ; 030Bh,031Dh,0330h,0343h,0356h,036Ah,037Eh,0392h ; 03A7h,03BCh,03D1h,03E7h,03FCh,0413h,042Ah,0441h ; 0458h,0470h,0488h,04A0h,04B9h,04D2h,04ECh,0506h ; 0520h,053Bh,0556h,0572h,058Eh,05AAh,05C7h,05E4h ; entry 0601h,061Fh,063Eh,065Ch,067Ch,069Bh,06BBh,06DCh ; 080h..0FFh 06FDh,071Eh,0740h,0762h,0784h,07A7h,07CBh,07EFh ; 0813h,0838h,085Dh,0883h,08A9h,08D0h,08F7h,091Eh ; 0946h,096Fh,0998h,09C1h,09EBh,0A16h,0A40h,0A6Ch ; 0A98h,0AC4h,0AF1h,0B1Eh,0B4Ch,0B7Ah,0BA9h,0BD8h ; 0C07h,0C38h,0C68h,0C99h,0CCBh,0CFDh,0D30h,0D63h ; 0D97h,0DCBh,0E00h,0E35h,0E6Bh,0EA1h,0ED7h,0F0Fh ; 0F46h,0F7Fh,0FB7h,0FF1h,102Ah,1065h,109Fh,10DBh ; 1116h,1153h,118Fh,11CDh,120Bh,1249h,1288h,12C7h ;/ 1307h,1347h,1388h,13C9h,140Bh,144Dh,1490h,14D4h ;\ 1517h,155Ch,15A0h,15E6h,162Ch,1672h,16B9h,1700h ; 1747h,1790h,17D8h,1821h,186Bh,18B5h,1900h,194Bh ; 1996h,19E2h,1A2Eh,1A7Bh,1AC8h,1B16h,1B64h,1BB3h ; 1C02h,1C51h,1CA1h,1CF1h,1D42h,1D93h,1DE5h,1E37h ; 1E89h,1EDCh,1F2Fh,1F82h,1FD6h,202Ah,207Fh,20D4h ; 2129h,217Fh,21D5h,222Ch,2282h,22DAh,2331h,2389h ; entry 23E1h,2439h,2492h,24EBh,2545h,259Eh,25F8h,2653h ; 100h..17Fh 26ADh,2708h,2763h,27BEh,281Ah,2876h,28D2h,292Eh ; 298Bh,29E7h,2A44h,2AA1h,2AFFh,2B5Ch,2BBAh,2C18h ; 2C76h,2CD4h,2D33h,2D91h,2DF0h,2E4Fh,2EAEh,2F0Dh ; 2F6Ch,2FCCh,302Bh,308Bh,30EAh,314Ah,31AAh,3209h ; 3269h,32C9h,3329h,3389h,33E9h,3449h,34A9h,3509h ; 3569h,35C9h,3629h,3689h,36E8h,3748h,37A8h,3807h ; 3867h,38C6h,3926h,3985h,39E4h,3A43h,3AA2h,3B00h ; 3B5Fh,3BBDh,3C1Bh,3C79h,3CD7h,3D35h,3D92h,3DEFh ;/ 3E4Ch,3EA9h,3F05h,3F62h,3FBDh,4019h,4074h,40D0h ;\ 412Ah,4185h,41DFh,4239h,4292h,42EBh,4344h,439Ch ; 43F4h,444Ch,44A3h,44FAh,4550h,45A6h,45FCh,4651h ; 46A6h,46FAh,474Eh,47A1h,47F4h,4846h,4898h,48E9h ; 493Ah,498Ah,49D9h,4A29h,4A77h,4AC5h,4B13h,4B5Fh ; 4BACh,4BF7h,4C42h,4C8Dh,4CD7h,4D20h,4D68h,4DB0h ; 4DF7h,4E3Eh,4E84h,4EC9h,4F0Eh,4F52h,4F95h,4FD7h ; entry 5019h,505Ah,509Ah,50DAh,5118h,5156h,5194h,51D0h ; 180h..1FFh 520Ch,5247h,5281h,52BAh,52F3h,532Ah,5361h,5397h ; 53CCh,5401h,5434h,5467h,5499h,54CAh,54FAh,5529h ; 5558h,5585h,55B2h,55DEh,5609h,5632h,565Bh,5684h ; 56ABh,56D1h,56F6h,571Bh,573Eh,5761h,5782h,57A3h ; 57C3h,57E2h,57FFh,581Ch,5838h,5853h,586Dh,5886h ; 589Eh,58B5h,58CBh,58E0h,58F4h,5907h,5919h,592Ah ; 593Ah,5949h,5958h,5965h,5971h,597Ch,5986h,598Fh ; 5997h,599Eh,59A4h,59A9h,59ADh,59B0h,59B2h,59B3h ;/ ``` The PSX table is a bit different as the SNES table: Values up to 3569h are smaller as on SNES, the remaining values are bigger as on SNES, and the width of the PSX table entries is 4bit higher as on SNES.
The PSX table is slightly bugged: Theoretically, each four values (gauss[000h+i], gauss[0FFh-i], gauss[100h+i], gauss[1FFh-i]) should sum up to 8000h, but in practice they do sum up to 7F7Fh..7F81h (fortunately the PSX sum doesn't exceed the 8000h limit; meaning that the PSX interpolations won't overflow, which has been a hardware glitch on the SNES).
#### Waveform Examples ``` Incoming ADPCM Data ---> Interpolated Data _ _ _ _ | | | | | | | | . . . . Nibbles=79797979, Filter=0 | | | | | | | | ---> / \ / \ / \ / \ HALF-volume ZIGZAG-wave | |_| |_| |_| |_ ' ' ' ' ___ ___ | | | | .'. .'. Nibbles=77997799, Filter=0 | | | | ---> / \ / \ FULL-volume SINE-wave | |___| |___ ' '.' '. _______ ___ | | .' '. Nibbles=77779999, Filter=0 | | ---> / \ SQUARE wave (with rounded edges) | |_______ ' '.____ _____ _ __ | |_ _| .' ''. .' Nibbles=7777CC44, Filter=0 | |___| ---> / '..' CUSTOM wave-form | ' ___ __ | |___| | _ \ ! / . \ ! / Nibbles=77DE9HZK, Filter=V |_ ____| _| ---> - + - + - + - SOLAR STORM wave-form __| |______|___ / ! \ ' / ! \ ``` ## SPU Volume and ADSR Generator #### 1F801C08h+N\*10h - Voice 0..23 Attack/Decay/Sustain/Release (ADSR) (32bit) ``` ____lower 16bit (at 1F801C08h+N*10h)___________________________________ 15 Attack Mode (0=Linear, 1=Exponential) - Attack Direction (Fixed, always Increase) (until Level 7FFFh) 14-10 Attack Shift (0..1Fh = Fast..Slow) 9-8 Attack Step (0..3 = "+7,+6,+5,+4") - Decay Mode (Fixed, always Exponential) - Decay Direction (Fixed, always Decrease) (until Sustain Level) 7-4 Decay Shift (0..0Fh = Fast..Slow) - Decay Step (Fixed, always "-8") 3-0 Sustain Level (0..0Fh) ;Level=(N+1)*800h ____upper 16bit (at 1F801C0Ah+N*10h)___________________________________ 31 Sustain Mode (0=Linear, 1=Exponential) 30 Sustain Direction (0=Increase, 1=Decrease) (until Key OFF flag) 29 Not used? (should be zero) 28-24 Sustain Shift (0..1Fh = Fast..Slow) 23-22 Sustain Step (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec) 21 Release Mode (0=Linear, 1=Exponential) - Release Direction (Fixed, always Decrease) (until Level 0000h) 20-16 Release Shift (0..1Fh = Fast..Slow) - Release Step (Fixed, always "-8") ``` The Attack phase gets started when the software sets the voice ON flag (see below), the hardware does then automatically go through Attack/Decay/Sustain, and switches from Sustain to Release when the software sets the Key OFF flag.
#### 1F801D80h - Mainvolume left #### 1F801D82h - Mainvolume right #### 1F801C00h+N\*10h - Voice 0..23 Volume Left #### 1F801C02h+N\*10h - Voice 0..23 Volume Right Fixed Volume Mode (when Bit15=0):
``` 15 Must be zero (0=Volume Mode) 0-14 Voice volume/2 (-4000h..+3FFFh = Volume -8000h..+7FFEh) ``` Sweep Volume Mode (when Bit15=1):
``` 15 Must be set (1=Sweep Mode) 14 Sweep Mode (0=Linear, 1=Exponential) 13 Sweep Direction (0=Increase, 1=Decrease) 12 Sweep Phase (0=Positive, 1=Negative) 7-11 Not used? (should be zero) 6-2 Sweep Shift (0..1Fh = Fast..Slow) 1-0 Sweep Step (0..3 = "+7,+6,+5,+4" or "-8,-7,-6,-5") (inc/dec) ``` Sweep is another Volume envelope, additionally to the ADSR volume envelope (unlike ADSR, sweep can be used for stereo effects, such like blending from left to right).
Sweep starts at the current volume (which can be set via Bit15=0, however, caution - the Bit15=0 setting isn't applied until the next 44.1kHz cycle; so setting the initial level with Bit15=0, followed by the sweep parameter with Bit15=1 works only if there's a suitable delay between the two operations). Once when sweep is started, the current volume level increases to +7FFFh, or decreases to 0000h.
Sweep Phase should be equal to the sign of the current volume (not yet tested, in the negative mode it does probably "increase" to -7FFFh?). The Phase bit seems to have no effect in Exponential Decrease mode.
#### 1F801DB0h - CD Audio Input Volume (for normal CD-DA, and compressed XA-ADPCM) #### 1F801DB4h - External Audio Input Volume ``` 0-15 Volume Left (-8000h..+7FFFh) 16-31 Volume Right (-8000h..+7FFFh) ``` Note: The CDROM controller supports additional CD volume control (including ability to convert stereo CD output to mono, or to swap left/right channels).
#### Envelope Operation depending on Shift/Step/Mode/Direction ``` AdsrCycles = 1 SHL Max(0,ShiftValue-11) AdsrStep = StepValue SHL Max(0,11-ShiftValue) IF exponential AND increase AND AdsrLevel>6000h THEN AdsrCycles=AdsrCycles*4 IF exponential AND decrease THEN AdsrStep=AdsrStep*AdsrLevel/8000h Wait(AdsrCycles) ;cycles counted at 44.1kHz clock AdsrLevel=AdsrLevel+AdsrStep ;saturated to 0..+7FFFh ``` Exponential Increase is a fake (simply changes to a slower linear increase rate at higher volume levels).
#### 1F801C0Ch+N\*10h - Voice 0..23 Current ADSR volume (R/W) ``` 15-0 Current ADSR Volume (0..+7FFFh) (or -8000h..+7FFFh on manual write) ``` Reportedly Release can go down to -1 (FFFFh), but that isn't true; and release ends at 0... or does THAT depend on an END flag found in the sample-data?
The register is read/writeable, writing allows to let the ADSR generator to "jump" to a specific volume level. But, ACTUALLY, the ADSR generator does overwrite the setting (from another internal register) whenever applying a new Step?!
#### 1F801DB8h - Current Main Volume Left/Right #### 1F801E00h+voice\*04h - Voice 0..23 Current Volume Left/Right ``` 0-15 Current Volume Left (-8000h..+7FFFh) 16-31 Current Volume Right (-8000h..+7FFFh) ``` These are internal registers, normally not used by software (the Volume settings are usually set via Ports 1F801D80h and 1F801C00h+N\*10h).
#### Note Negative volumes are phase inverted, otherwise same as positive.
## SPU Voice Flags #### 1F801D88h - Voice 0..23 Key ON (Start Attack/Decay/Sustain) (KON) (W) ``` 0-23 Voice 0..23 On (0=No change, 1=Start Attack/Decay/Sustain) 24-31 Not used ``` Starts the ADSR Envelope, and automatically initializes ADSR Volume to zero, and copies Voice Start Address to Voice Repeat Address.
#### 1F801D8Ch - Voice 0..23 Key OFF (Start Release) (KOFF) (W) ``` 0-23 Voice 0..23 Off (0=No change, 1=Start Release) 24-31 Not used ``` For a full ADSR pattern, OFF would be usually issued in the Sustain period, however, it can be issued at any time (eg. to abort Attack, skip the Decay and Sustain periods, and switch immediately to Release).
#### 1F801D9Ch - Voice 0..23 ON/OFF (status) (ENDX) (R) ``` 0-23 Voice 0..23 Status (0=Newly Keyed On, 1=Reached LOOP-END) 24-31 Not used ``` The bits get CLEARED when setting the corresponding KEY ON bits.
The bits get SET when reaching an LOOP-END flag in ADPCM header.bit0.
#### R/W Key On and Key Off should be treated as write-only (although, reading returns the most recently 32bit value, this doesn't doesn't provide any status information about whether sound is on or off).
The on/off (status) (ENDX) register should be treated read-only (writing is possible in so far that the written value can be read-back for a short moment, however, thereafter the hardware is overwriting that value).
## SPU Noise Generator #### 1F801D94h - Voice 0..23 Noise mode enable (NON) ``` 0-23 Voice 0..23 Noise (0=ADPCM, 1=Noise) 24-31 Not used ``` #### SPU Noise Generator The signed 16bit output Level is calculated as so (repeated at 44.1kHz clock):
``` Wait(1 cycle) ;at 44.1kHz clock Timer=Timer-NoiseStep ;subtract Step (4..7) ParityBit = NoiseLevel.Bit15 xor Bit12 xor Bit11 xor Bit10 xor 1 IF Timer<0 then NoiseLevel = NoiseLevel*2 + ParityBit IF Timer<0 then Timer=Timer+(20000h SHR NoiseShift) ;reload timer once IF Timer<0 then Timer=Timer+(20000h SHR NoiseShift) ;reload again if needed ``` Note that the Noise frequency is solely controlled by the Shift/Step values in SPUCNT register (the ADPCM Sample Rate has absolutely no effect on noise), so when using noise for multiple voices, all of them are forcefully having the same frequency; the only workaround is to store a random ADPCM pattern in SPU RAM, which can be then used with any desired sample rate(s).
## SPU Control and Status Register #### 1F801DAAh - SPU Control Register (SPUCNT) ``` 15 SPU Enable (0=Off, 1=On) (Don't care for CD Audio) 14 Mute SPU (0=Mute, 1=Unmute) (Don't care for CD Audio) 13-10 Noise Frequency Shift (0..0Fh = Low .. High Frequency) 9-8 Noise Frequency Step (0..03h = Step "4,5,6,7") 7 Reverb Master Enable (0=Disabled, 1=Enabled) 6 IRQ9 Enable (0=Disabled/Acknowledge, 1=Enabled; only when Bit15=1) 5-4 Sound RAM Transfer Mode (0=Stop, 1=ManualWrite, 2=DMAwrite, 3=DMAread) 3 External Audio Reverb (0=Off, 1=On) 2 CD Audio Reverb (0=Off, 1=On) (for CD-DA and XA-ADPCM) 1 External Audio Enable (0=Off, 1=On) 0 CD Audio Enable (0=Off, 1=On) (for CD-DA and XA-ADPCM) ``` Changes to bit0-5 aren't applied immediately; after writing to SPUCNT, it'd be usually recommended to wait until the LSBs of SPUSTAT are updated accordingly. Before setting a new Transfer Mode, it'd be recommended first to set the "Stop" mode (and, again, wait until Stop is applied in SPUSTAT).
#### 1F801DAEh - SPU Status Register (SPUSTAT) (R) ``` 15-12 Unknown/Unused (seems to be usually zero) 11 Writing to First/Second half of Capture Buffers (0=First, 1=Second) 10 Data Transfer Busy Flag (0=Ready, 1=Busy) 9 Data Transfer DMA Read Request (0=No, 1=Yes) 8 Data Transfer DMA Write Request (0=No, 1=Yes) 7 Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 6 IRQ9 Flag (0=No, 1=Interrupt Request) 5-0 Current SPU Mode (same as SPUCNT.Bit5-0, but, applied a bit delayed) ``` When switching SPUCNT to DMA-read mode, status bit9 and bit7 aren't set immediately (apparently the SPU is first internally collecting the data in the Fifo, before transferring it).
Bit11 indicates if data is currently written to the first or second half of the four 1K-byte capture buffers (for CD Audio left/right, and voice 1/3). Note: Bit11 works only if Bit2 and/or Bit3 of Port 1F801DACh are set.
The SPUSTAT register should be treated read-only (writing is possible in so far that the written value can be read-back for a short moment, however, thereafter the hardware is overwriting that value).
## SPU Memory Access #### 1F801DA6h - Sound RAM Data Transfer Address ``` 15-0 Address in sound buffer divided by eight ``` Used for manual write and DMA read/write SPU memory. Writing to this registers stores the written value in 1F801DA6h, and does additional store the value (multiplied by 8) in another internal "current address" register (that internal register does increment during transfers, whilst the 1F801DA6h value DOESN'T increment).
#### 1F801DA8h - Sound RAM Data Transfer Fifo ``` 15-0 Data (max 32 halfwords) ``` Used for manual-write. Not sure if it can be also used for manual read?
#### 1F801DACh - Sound RAM Data Transfer Control (should be 0004h) ``` 15-4 Unknown/no effect? (should be zero) 3-1 Sound RAM Data Transfer Type (see below) (should be 2) 0 Unknown/no effect? (should be zero) ``` The Transfer Type selects how data is forwarded from Fifo to SPU RAM:
``` __Transfer Type___Halfwords in Fifo________Halfwords written to SPU RAM__ 0,1,6,7 Fill A,B,C,D,E,F,G,H,...,X X,X,X,X,X,X,X,X,... 2 Normal A,B,C,D,E,F,G,H,...,X A,B,C,D,E,F,G,H,... 3 Rep2 A,B,C,D,E,F,G,H,...,X A,A,C,C,E,E,G,G,... 4 Rep4 A,B,C,D,E,F,G,H,...,X A,A,A,A,E,E,E,E,... 5 Rep8 A,B,C,D,E,F,G,H,...,X H,H,H,H,H,H,H,H,... ``` Rep2 skips the 2nd halfword, Rep4 skips 2nd..4th, Rep8 skips 1st..7th.
Fill uses only the LAST halfword in Fifo, that might be useful for memfill purposes, although, the length is probably determined by the number of writes to the Fifo (?) so one must still issue writes for ALL halfwords...?
Note:
The above rather bizarre results apply to WRITE mode. In READ mode, the register causes the same halfword to be read 2/4/8 times (for rep2/4/8).
#### SPU RAM Manual Write - Be sure that [1F801DACh] is set to 0004h
- Set SPUCNT to "Stop" (and wait until it is applied in SPUSTAT)
- Set the transfer address
- Write 1..32 halfword(s) to the Fifo
- Set SPUCNT to "Manual Write" (and wait until it is applied in SPUSTAT)
- Wait until Transfer Busy in SPUSTAT goes off (that, AFTER above apply-wait)
For multi-block transfers: Repeat the above last three steps (that is rarely done by any games, but it is done by the BIOS intro; observe that waiting for SPUCNT writes being applied in SPUSTAT won't work in that case (since SPUCNT was already in manual write mode from previous block), so one must instead use some hardcoded delay of at least 300h cycles; the BIOS is using a much longer bizarre delay though).
#### SPU RAM DMA-Write - Be sure that [1F801DACh] is set to 0004h
- Set SPUCNT to "Stop" (and wait until it is applied in SPUSTAT)
- Set the transfer address
- Set SPUCNT to "DMA Write" (and wait until it is applied in SPUSTAT)
- Start DMA4 at CPU Side (blocksize=10h, control=01000201h)
- Wait until DMA4 finishes (at CPU side)
#### SPU RAM Manual-Read As by now, there's no known method for reading SPU RAM without using DMA.
#### SPU RAM DMA-Read (stable reading, with [1F801014h].bit24-27 = nonzero) - Be sure that [1F801014h] is set to 220931E1h (bit24-27 MUST be nonzero)
- Be sure that [1F801DACh] is set to 0004h
- Set SPUCNT to "Stop" (and wait until it is applied in SPUSTAT)
- Set the transfer address
- Set SPUCNT to "DMA Read" (and wait until it is applied in SPUSTAT)
- Start DMA4 at CPU Side (blocksize=10h, control=01000200h)
- Wait until DMA4 finishes (at CPU side)
#### SPU RAM DMA-Read (unstable reading, with [1F801014h].bit24-27 = zero) Below describes some dirt effects and some trickery to get around those dirt effects.
``` Below problems (and workarounds) apply ONLY if [1F801014h].bit24-27 = zero. Ie. below info describes what happens when [1F801014h] is mis-initialized. Normally one should set [1F801014h]=220931E1h (and can ignore below info). ``` With [1F801014h].bit24-27=zero, reading SPU RAM via DMA works glitchy:
The first received halfword within each block is FFFFh. So with a DMA blocksize of 10h words (=20h halfwords), the following is received:
``` 1st block: FFFFh, halfwords[00h..1Eh] 2nd block: FFFFh, halfwords[20h..3Eh] etc. ``` that'd theoretically match the SPU Fifo Size, but, because of the inserted FFFFh value, the last Fifo entry isn't received, ie. halfword[1Fh,3Fh] are lost. As a workaround, one can increase the DMA blocksize to 11h words, and then the following is received:
``` 1st block: FFFFh, halfwords[00h..1Eh], twice halfword[1Fh] 2nd block: FFFFh, halfwords[20h..3Eh], twice halfword[3Fh] etc. ``` this time, all data is received, but after the transfer one must still remove the FFFFh values, and the duplicated halfwords by software. Aside from the \ FFFFh values there are occassionaly some unstable halfwords ORed by FFFFh (or ORed by other garbage values), this can be fixed by using "rep2" mode, which does then receive:
``` 1st block: FFFFh, halfwords[00h,00h,..0Eh,0Eh], triple halfword[0Fh] 2nd block: FFFFh, halfwords[10h,10h,..1Eh,1Eh], triple halfword[1Fh] etc. ``` again, remove the first halfword (FFFFh) and the last halfword, and, take the duplicated halfwords ANDed together. Unstable values occur only every 32 halfwords or so (probably when the SPU is simultaneously reading ADPCM data), but do never occur on two continous halfwords, so, even if one halfword was ORed by garbage, the other halfword is always correct, and the result of the ANDed halfwords is 100% stable.
Note: The unstable reading does NOT occur always, when resetting the PSX a couple of times it does occassionally boot-up with totally stable reading, since there is no known way to activate the stable "mode" via I/O ports, the stable/unstable behaviour does eventually depend on internal clock dividers/multipliers, and whether they are starting in sync with the CPU or not.
Caution: The "rep2" trick cannot be used in combination with reverb (reverb seems to be using the Port 1F801DACh Sound RAM Data Transfer Control, too).
## SPU Interrupt #### 1F801DA4h - Sound RAM IRQ Address (IRQ9) ``` 15-0 Address in sound buffer divided by eight ``` See also: SPUCNT (IRQ enable/disable/acknowledge) and SPUSTAT (IRQ flag).
#### Voice Interrupt Triggers an IRQ when a voice reads ADPCM data from the IRQ address.
Mind that ADPCM cannot be stopped (uh, except, probably they CAN be stopped, by setting the sample rate to zero?), all voices are permanently reading data from SPU RAM - even in Noise mode, even if the Voice Volume is zero, and even if the ADSR pattern has finished the Release period - so even inaudible voices can trigger IRQs. To prevent unwanted IRQs, best set all unused voices to an endless looped dummy ADPCM block.
For stable IRQs, the IRQ address should be aligned to the 16-byte ADPCM blocks. If if the IRQ address is in the middle of a 16-byte ADPCM block, then the IRQ doesn't seem to trigger always (unknown why, but it seems to occassionally miss IRQs, even if the block gets repeated several times).
#### Capture Interrupt Setting the IRQ address to 0000h..01FFh (aka byte address 00000h..00FFFh) will trigger IRQs on writes to the four capture buffers. Each of the four buffers contains 400h bytes (=200h samples), so the IRQ rate will be around 86.13Hz (44100Hz/200h).
CD-Audio capture is always active (even CD-Audio output is disabld in SPUCNT, and even if the drive door is open). Voice capture is (probably) also always active (even if the corresponding voice is off).
Capture IRQs do NOT occur if 1F801DACh.bit3-2 are both zero.
#### Reverb Interrupt Reverb is also triggering interrupts if the IRQ address is located in the reverb buffer area. Unknown \ of the various reverb read(s) and/or reverb write(s) are triggering interrupts.
#### Data Transfers Data Transfers (usually via DMA4) to/from SPU-RAM do also trap SPU interrupts.
#### Note The IRQ Address is used in the following games (not exhaustive): Metal Gear Solid: Dialogue and Konami intro. Legend of Mana Hercules: the memory card loading screen's lip sync. Tokimeki Memorial 2 Crash Team Racing: Lip sync, requires capture buffers. The Misadventures of Tron Bonne: Dialogues. Need For Speed 3: (somewhat?).
## SPU Reverb Registers #### Reverb Volume and Address Registers (R/W) ``` Port Reg Name Type Expl. 1F801D84h spu vLOUT volume Reverb Output Volume Left 1F801D86h spu vROUT volume Reverb Output Volume Right 1F801DA2h spu mBASE base Reverb Work Area Start Address in Sound RAM 1F801DC0h rev00 dAPF1 disp Reverb APF Offset 1 1F801DC2h rev01 dAPF2 disp Reverb APF Offset 2 1F801DC4h rev02 vIIR volume Reverb Reflection Volume 1 1F801DC6h rev03 vCOMB1 volume Reverb Comb Volume 1 1F801DC8h rev04 vCOMB2 volume Reverb Comb Volume 2 1F801DCAh rev05 vCOMB3 volume Reverb Comb Volume 3 1F801DCCh rev06 vCOMB4 volume Reverb Comb Volume 4 1F801DCEh rev07 vWALL volume Reverb Reflection Volume 2 1F801DD0h rev08 vAPF1 volume Reverb APF Volume 1 1F801DD2h rev09 vAPF2 volume Reverb APF Volume 2 1F801DD4h rev0A mLSAME src/dst Reverb Same Side Reflection Address 1 Left 1F801DD6h rev0B mRSAME src/dst Reverb Same Side Reflection Address 1 Right 1F801DD8h rev0C mLCOMB1 src Reverb Comb Address 1 Left 1F801DDAh rev0D mRCOMB1 src Reverb Comb Address 1 Right 1F801DDCh rev0E mLCOMB2 src Reverb Comb Address 2 Left 1F801DDEh rev0F mRCOMB2 src Reverb Comb Address 2 Right 1F801DE0h rev10 dLSAME src Reverb Same Side Reflection Address 2 Left 1F801DE2h rev11 dRSAME src Reverb Same Side Reflection Address 2 Right 1F801DE4h rev12 mLDIFF src/dst Reverb Different Side Reflect Address 1 Left 1F801DE6h rev13 mRDIFF src/dst Reverb Different Side Reflect Address 1 Right 1F801DE8h rev14 mLCOMB3 src Reverb Comb Address 3 Left 1F801DEAh rev15 mRCOMB3 src Reverb Comb Address 3 Right 1F801DECh rev16 mLCOMB4 src Reverb Comb Address 4 Left 1F801DEEh rev17 mRCOMB4 src Reverb Comb Address 4 Right 1F801DF0h rev18 dLDIFF src Reverb Different Side Reflect Address 2 Left 1F801DF2h rev19 dRDIFF src Reverb Different Side Reflect Address 2 Right 1F801DF4h rev1A mLAPF1 src/dst Reverb APF Address 1 Left 1F801DF6h rev1B mRAPF1 src/dst Reverb APF Address 1 Right 1F801DF8h rev1C mLAPF2 src/dst Reverb APF Address 2 Left 1F801DFAh rev1D mRAPF2 src/dst Reverb APF Address 2 Right 1F801DFCh rev1E vLIN volume Reverb Input Volume Left 1F801DFEh rev1F vRIN volume Reverb Input Volume Right ``` All volume registers are signed 16bit (range -8000h..+7FFFh).
All src/dst/disp/base registers are addresses in SPU memory (divided by 8), src/dst are relative to the current buffer address, the disp registers are relative to src registers, the base register defines the start address of the reverb buffer (the end address is fixed, at 7FFFEh). Writing a value to mBASE does additionally set the current buffer address to that value.
#### 1F801D98h - Voice 0..23 Reverb mode aka Echo On (EON) (R/W) ``` 0-23 Voice 0..23 Destination (0=To Mixer, 1=To Mixer and to Reverb) 24-31 Not used ``` Sets reverb for the channel. As soon as the sample ends, the reverb for that channel is turned off... that's fine, but WHEN does it end?
In Reverb mode, the voice seems to output BOTH normal (immediately) AND via Reverb (delayed).
#### Reverb Bits in SPUCNT Register (R/W) The SPUCNT register contains a Reverb Master Enable flag, and Reverb Enable flags for External Audio input and CD Audio input.
When the Reverb Master Enable flag is cleared, the SPU stops to write any data to the Reverb buffer (that is useful when zero-filling the reverb buffer; ensuring that already-zero values aren't overwritten by still-nonzero values).
However, the Reverb Master Enable flag does not disable output from Reverb buffer to the speakers (that might be useful to output uncompressed 22050Hz samples) (otherwise, to disable the buffer output, set the Reverb Output volume to zero and/or zerofill the reverb buffer).
## SPU Reverb Formula #### Reverb Formula ``` ___Input from Mixer (Input volume multiplied with incoming data)_____________ Lin = vLIN * LeftInput ;from any channels that have Reverb enabled Rin = vRIN * RightInput ;from any channels that have Reverb enabled ____Same Side Reflection (left-to-left and right-to-right)___________________ [mLSAME] = (Lin + [dLSAME]*vWALL - [mLSAME-2])*vIIR + [mLSAME-2] ;L-to-L [mRSAME] = (Rin + [dRSAME]*vWALL - [mRSAME-2])*vIIR + [mRSAME-2] ;R-to-R ___Different Side Reflection (left-to-right and right-to-left)_______________ [mLDIFF] = (Lin + [dRDIFF]*vWALL - [mLDIFF-2])*vIIR + [mLDIFF-2] ;R-to-L [mRDIFF] = (Rin + [dLDIFF]*vWALL - [mRDIFF-2])*vIIR + [mRDIFF-2] ;L-to-R ___Early Echo (Comb Filter, with input from buffer)__________________________ Lout=vCOMB1*[mLCOMB1]+vCOMB2*[mLCOMB2]+vCOMB3*[mLCOMB3]+vCOMB4*[mLCOMB4] Rout=vCOMB1*[mRCOMB1]+vCOMB2*[mRCOMB2]+vCOMB3*[mRCOMB3]+vCOMB4*[mRCOMB4] ___Late Reverb APF1 (All Pass Filter 1, with input from COMB)________________ Lout=Lout-vAPF1*[mLAPF1-dAPF1], [mLAPF1]=Lout, Lout=Lout*vAPF1+[mLAPF1-dAPF1] Rout=Rout-vAPF1*[mRAPF1-dAPF1], [mRAPF1]=Rout, Rout=Rout*vAPF1+[mRAPF1-dAPF1] ___Late Reverb APF2 (All Pass Filter 2, with input from APF1)________________ Lout=Lout-vAPF2*[mLAPF2-dAPF2], [mLAPF2]=Lout, Lout=Lout*vAPF2+[mLAPF2-dAPF2] Rout=Rout-vAPF2*[mRAPF2-dAPF2], [mRAPF2]=Rout, Rout=Rout*vAPF2+[mRAPF2-dAPF2] ___Output to Mixer (Output volume multiplied with input from APF2)___________ LeftOutput = Lout*vLOUT RightOutput = Rout*vROUT ___Finally, before repeating the above steps_________________________________ BufferAddress = MAX(mBASE, (BufferAddress+2) AND 7FFFEh) Wait one 22050Hz cycle, then repeat the above stuff ``` #### Notes The values written to memory are saturated to -8000h..+7FFFh.
The multiplication results are divided by +8000h, to fit them to 16bit range.
All memory addresses are relative to the current BufferAddress, and wrapped within mBASE..7FFFEh when exceeding that region.
All data in the Reverb buffer consists of signed 16bit samples. The Left and Right Reverb Buffer addresses should be choosen so that one half of the buffer contains Left samples, and the other half Right samples (ie. the data is L,L,L,L,... R,R,R,R,...; it is NOT interlaced like L,R,L,R,...), during operation, when the buffer address increases, the Left half will overwrite the older samples of the Right half, and vice-versa.
The reverb hardware spends one 44100h cycle on left calculations, and the next 44100h cycle on right calculations (unlike as shown in the above formula, where left/right are shown simultaneously at 22050Hz).
#### Reverb Disable SPUCNT.bit7 disables writes to reverb buffer, but reads from reverb buffer do still occur. If vAPF2 is zero then it does simply read "Lout=[mLAPF2-dAPF2]" and "Rout=[mRAPF2-dAPF2]". If vAPF2 is nonzero then it does additionally use data from APF1, if vAPF1 and vAPF2 are both nonzero then it's also using data from COMB. However, the SAME/DIFF stages aren't used when reverb is disabled.
#### Bug vIIR works only in range -7FFFh..+7FFFh. When set to -8000h, the multiplication by -8000h is still done correctly, but, the final result (the value written to memory) gets negated (this is a pretty strange feature, it is NOT a simple overflow bug, it does affect the "+[mLSAME-2]" addition; although that part normally shouldn't be affected by the "\*vIIR" multiplication). Similar effects might (?) occur on some other volume registers when they are set to -8000h.
#### Speed of Sound The speed of sound is circa 340 meters per second (in dry air, at room temperature). For example, a voice that travels to a wall at 17 meters distance, and back to its origin, should have a delay of 0.1 seconds.
## SPU Reverb Examples #### Reverb Examples Below are some Reverb examples, showing the required memory size (ie. set Port 1F801DA2h to "(80000h-size)/8"), and the Reverb register settings for Port 1F801DC0h..1F801DFFh, ie. arranged like so:
``` dAPF1 dAPF2 vIIR vCOMB1 vCOMB2 vCOMB3 vCOMB4 vWALL ;1F801DC0h..CEh vAPF1 vAPF2 mLSAME mRSAME mLCOMB1 mRCOMB1 mLCOMB2 mRCOMB2 ;1F801DD0h..DEh dLSAME dRSAME mLDIFF mRDIFF mLCOMB3 mRCOMB3 mLCOMB4 mRCOMB4 ;1F801DE0h..EEh dLDIFF dRDIFF mLAPF1 mRAPF1 mLAPF2 mRAPF2 vLIN vRIN ;1F801DF0h..FEh ``` Also, don't forget to initialize Port 1F801D84h, 1F801D86h, 1F801D98h, and SPUCNT, and to zerofill the Reverb Buffer (so that no garbage values are output when activating reverb). For whatever reason, one MUST also initialize Port 1F801DACh (otherwise reverb stays off).
#### Room (size=26C0h bytes) ``` 007Dh,005Bh,6D80h,54B8h,BED0h,0000h,0000h,BA80h 5800h,5300h,04D6h,0333h,03F0h,0227h,0374h,01EFh 0334h,01B5h,0000h,0000h,0000h,0000h,0000h,0000h 0000h,0000h,01B4h,0136h,00B8h,005Ch,8000h,8000h ``` #### Studio Small (size=1F40h bytes) ``` 0033h,0025h,70F0h,4FA8h,BCE0h,4410h,C0F0h,9C00h 5280h,4EC0h,03E4h,031Bh,03A4h,02AFh,0372h,0266h 031Ch,025Dh,025Ch,018Eh,022Fh,0135h,01D2h,00B7h 018Fh,00B5h,00B4h,0080h,004Ch,0026h,8000h,8000h ``` #### Studio Medium (size=4840h bytes) ``` 00B1h,007Fh,70F0h,4FA8h,BCE0h,4510h,BEF0h,B4C0h 5280h,4EC0h,0904h,076Bh,0824h,065Fh,07A2h,0616h 076Ch,05EDh,05ECh,042Eh,050Fh,0305h,0462h,02B7h 042Fh,0265h,0264h,01B2h,0100h,0080h,8000h,8000h ``` #### Studio Large (size=6FE0h bytes) ``` 00E3h,00A9h,6F60h,4FA8h,BCE0h,4510h,BEF0h,A680h 5680h,52C0h,0DFBh,0B58h,0D09h,0A3Ch,0BD9h,0973h 0B59h,08DAh,08D9h,05E9h,07ECh,04B0h,06EFh,03D2h 05EAh,031Dh,031Ch,0238h,0154h,00AAh,8000h,8000h ``` #### Hall (size=ADE0h bytes) ``` 01A5h,0139h,6000h,5000h,4C00h,B800h,BC00h,C000h 6000h,5C00h,15BAh,11BBh,14C2h,10BDh,11BCh,0DC1h 11C0h,0DC3h,0DC0h,09C1h,0BC4h,07C1h,0A00h,06CDh 09C2h,05C1h,05C0h,041Ah,0274h,013Ah,8000h,8000h ``` #### Half Echo (size=3C00h bytes) ``` 0017h,0013h,70F0h,4FA8h,BCE0h,4510h,BEF0h,8500h 5F80h,54C0h,0371h,02AFh,02E5h,01DFh,02B0h,01D7h 0358h,026Ah,01D6h,011Eh,012Dh,00B1h,011Fh,0059h 01A0h,00E3h,0058h,0040h,0028h,0014h,8000h,8000h ``` #### Space Echo (size=F6C0h bytes) ``` 033Dh,0231h,7E00h,5000h,B400h,B000h,4C00h,B000h 6000h,5400h,1ED6h,1A31h,1D14h,183Bh,1BC2h,16B2h 1A32h,15EFh,15EEh,1055h,1334h,0F2Dh,11F6h,0C5Dh 1056h,0AE1h,0AE0h,07A2h,0464h,0232h,8000h,8000h ``` #### Chaos Echo (almost infinite) (size=18040h bytes) ``` 0001h,0001h,7FFFh,7FFFh,0000h,0000h,0000h,8100h 0000h,0000h,1FFFh,0FFFh,1005h,0005h,0000h,0000h 1005h,0005h,0000h,0000h,0000h,0000h,0000h,0000h 0000h,0000h,1004h,1002h,0004h,0002h,8000h,8000h ``` #### Delay (one-shot echo) (size=18040h bytes) ``` 0001h,0001h,7FFFh,7FFFh,0000h,0000h,0000h,0000h 0000h,0000h,1FFFh,0FFFh,1005h,0005h,0000h,0000h 1005h,0005h,0000h,0000h,0000h,0000h,0000h,0000h 0000h,0000h,1004h,1002h,0004h,0002h,8000h,8000h ``` #### Reverb off (size=10h dummy bytes) ``` 0000h,0000h,0000h,0000h,0000h,0000h,0000h,0000h 0000h,0000h,0001h,0001h,0001h,0001h,0001h,0001h 0000h,0000h,0001h,0001h,0001h,0001h,0001h,0001h 0000h,0000h,0001h,0001h,0001h,0001h,0000h,0000h ``` Note that the memory offsets should be 0001h here (not 0000h), otherwise zerofilling the reverb buffer seems to fail (maybe because zero memory offsets somehow cause the fill-value to mixed with the old value or so; that appears even when reverb master enable is zero). Also, when not using reverb, Port 1F801D84h, 1F801D86h, 1F801D98h, and the SPUCNT reverb bits should be set to zero.
## SPU Unknown Registers #### 1F801DA0h - Some kind of a read-only status register.. or just garbage..? ``` 0-15 Unknown? ``` Usually 9D78h, occassionaly changes to 17DAh or 108Eh for a short moment.
Other day: Usually 9CF8h, or occassionally 9CFAh.
Another day: Usually 0000h, or occassionally 4000h.
#### 1F801DBCh - 4 bytes - Unknown? (R/W) ``` 80 21 4B DF ``` Other day (dots = same as above):
``` .. 31 .. .. ``` #### 1F801E60h - 32 bytes - Unknown? (R/W) ``` 7E 61 A9 96 47 39 F9 1E E1 E1 80 DD E8 17 7F FB FB BF 1D 6C 8F EC F3 04 06 23 89 45 C1 6D 31 82 ``` Other day (dots = same as above):
``` .. .. .. .. .. .. .. .. .. .. .. .. .. .. 7B .. .. .. .. .. .. .. .. .. 04 .. .. .. .. .. .. 86 ``` The bytes at 1F801DBCh and 1F801E60h usually have the above values on cold-boot. The registers are read/write-able, although writing any values to them doesn't seem to have any effect on sound output. Also, the SPU doesn't seem to modify the registers at any time during sound output, nor reverb calculations, nor activated external audio input... the registers seem to be just some kind of general-purpose RAM.
## SPU Internal State Machine from SPU RAM Timing ### Introduction The 33.8 Mhz clock of the PSX is a well chosen value. It is exactly 768 x 44.1 Khz = For each audio sample in CD quality, there are 768 cycles of system clock. So, the state machine has to repeat its complete cycle every 768 system clock cycles. Now the full job to do within those 768 cycles: - 24 channels to process. - Reverb to compute and write back. - Write back to voice 1 / 3, audio CD L/R. - Do transfer from/to CPU bus of SPU RAM data if asked. ### First look at the data from logic analyzer. By looking at the signal of the SPU RAM chip, it is possible to figure out what it is reading and writing. - A read or a write to the SPU Ram is happening in 8 clock cycles. (Did not check in detail, but probably allow refresh and everything) - Each channel is using 24 cycles. (3 operations of 8 cycles) - Has TWO read for the current ADPCM block : one to the header of the currently played ADPCM block, one to the current 16 bit of the ADPCM. - A unrelated READ (see later) - 8 Cycle for each operation : WRITE CD Left, WRITE CD Right, Voice 1 WRITE, Voice 3 WRITE. - Reverb operations : 14 memory operations of 8 cycles. ### Sequence of work When doing the analysis from data, it is possible to figure out what are the operations, in what order they are done. But it is not possible to figure out what is the FIRST operation in the loop. So we arbitrarely decide to start the loop at 'Voice 1' (voice being from 1 to 24). - Voice 1 - Write CD Left - Write CD Right - Write Voice 1 - Write Voice 3 - Reverb - Voice 2 - Voice 3 - Voice 4 - ... - ... - Voice 23 - Voice 24 As written earlier, each Voice is 3x RAM access (one unrelated), reverb is 14x RAM access, then 4x RAM access for the all write. ### What we can guess from those information. - If system wants to keep reverb done in the end, and write in sync against Voice 1 and 3, then the loop would most likely start at Voice 2. - ADPCM decoder has to keep ADPCM decoder internal state about the samples. As the algorithm depends on the previous value inside a block, it can't do a direct access to a given sample in the block. - We also understand how reverb is using 22 Khz because of the lack of bandwidth to do everything in 768 cycles if done at 44.1 Khz. - Even when voices are not active, they always read something. It is possible to guess that the sample is simply ignored at some point in the data path (volume set to zero internally or mux not selecting the value). Interestingly, it may be possible if garbage is introduced in those read, to know how it is cancelled (enabling suddenly the channel and reading the sample out of the channel 1 or 3) -> DSP keeps history of sample for Gaussian Interpolation. ### Reverb Computation Order #### ``` [Left Side] [Right Side] READ REVERB dLSame dRSame READ REVERB mLSame-1 mRSame-1 READ REVERB dRDiff dLDiff XXXX REVERB mLSame mRSame <-- WRITE becomes READ if REVERB DISABLED. READ REVERB mLDiff-1 mRDiff-1 READ REVERB mLComb1 mRComb1 XXXX REVERB mLDiff mRDiff <-- WRITE becomes READ if REVERB DISABLED. READ REVERB mLComb2 mRComb2 READ REVERB mLComb3 mRComb3 READ REVERB mLComb4 mRComb4 READ REVERB mLAPF1 - dAPF1 mRAPF1 - dAPF1 READ REVERB mLAPF2 - dAPF2 mRAPF2 - dAPF2 XXXX REVERB mLAPF1 mRAPF1 <-- WRITE becomes READ if REVERB DISABLED. XXXX REVERB mLAPF2 mRAPF2 <-- WRITE becomes READ if REVERB DISABLED. ``` We anticipate that the easiest way in hardware to disable/enable the REVERB function would be to switch those WRITE into READ. ### Voices ``` Read Header word in current ADPCM block. Read Current Sample 16 bit word in current ADPCM block. Read [UNRELATED ADR ? Not related to current block...] ``` ### Notes - Remaining cycles. - With 24x8 + 4x8 + 14x8 = 720 cycles out of 768 cycles. - That would mean 6 READ/WRITE should still be possible. - UNRELATED READ in voices : probably used for transfer from [CPU->SPU RAM] or [SPU RAM->CPU] - That would equate to a transfer performance of 24 x 2 byte x 44100 Khz = 2,116,800 bytes/sec - The fixed READ timing would explain also why CPU can't read directly SPU RAM. As the SPU need to be the master to push the data. - It only works with DMA waiting for the data to be sent. Everything is not fully clear yet, testing of SPU with proper tests to validate/invalidate various assumption. Our finding are based on a logic analyzer log using the PSX boot sounds, knowing the values of the registers thanks to emulators.