Sound Processing Unit (SPU)
SPU Overview
SPU ADPCM Samples
SPU ADPCM Pitch
SPU Volume and ADSR Generator
SPU Voice Flags
SPU Noise Generator
SPU Control and Status Register
SPU Memory Access
SPU Interrupt
SPU Reverb Registers
SPU Reverb Formula
SPU Reverb Examples
SPU Unknown Registers
SPU Internal State Machine from SPU RAM 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
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.
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.
Unstable and Delayed I/O
The SPU occasionally seems to "miss" 32bit I/O writes (not sure if that can be
fixed by any Memory Control settings?), a stable workaround is to split each
32bit write into two 16bit 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).
SPU Bus-Width
The SPU is connected to a 16bit databus. 8bit/16bit/32bit reads and 16bit
writes are implemented; 32bit writes are also supported but seem to be
particularly unstable (see above). 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 (e.g.
li v0, 12345678h; sb v0, spu\_port
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)
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, \<after>
playing the current ADPCM block.
15-0 Address sample loops to at end (in 8-byte units)
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)
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)
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
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.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)
-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 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")
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)
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 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)
Envelope Operation depending on Shift/Step/Mode/Direction
; Precalculation, can be cached on phase begin.
AdsrStep = 7 - StepValue
IF Decreasing XOR PhaseNegative THEN
AdsrStep = NOT AdsrStep ; +7,+6,+5,+4 => -8,-7,-6,-5
AdsrStep = StepValue SHL Max(0,11-ShiftValue)
CounterIncrement = 8000h SHR Max(0,ShiftValue-11)
IF exponential AND increase AND AdsrLevel>6000h THEN
IF ShiftValue < 10 THEN
AdsrStep /= 4 ; SHR 2
ELSE IF ShiftValue >= 11 THEN
CounterIncrement /= 4 ; SHR 2
ELSE
AdsrStep /= 4 ; SHR 2
CounterIncrement /= 4 ; SHR 2
ELSE IF exponential AND decrease THEN
AdsrStep=AdsrStep*AdsrLevel/8000h
IF (StepValue | (StepValue SHL 2)) != ALL_BITS THEN
CounterIncrement = MAX(CounterIncrement, 1)
; Runs once per 44.1kHz clock.
Counter += CounterIncrement
IF (Counter & 8000h) == 0 THEN
RETURN ; No step this cycle.
; Saturate depending on mode.
AdsrLevel = AdsrLevel + AdsrStep
IF NOT decreasing THEN
AdsrLevel = CLAMP(AdsrLevel, -8000h..+7FFFh)
ELSE IF PhaseNegative THEN
AdsrLevel = CLAMP(AdsrLevel, -8000h..0h)
ELSE ; decreasing
AdsrLevel = MAX(AdsrLevel, 0)
Exponential Increase is a fake (simply changes to a slower linear increase rate at higher volume levels).
Phase invert cause the step to be positive in decreasing mode, otherwise negative.
Using a step value of all-ones causes the volume to never step, and additionally never saturate. i.e. 0x7f, or 0x1f for decay/release.
The step counter has very strange behaviour. Initially this was documented
as AdsrCycles = 1 SHL Max(0,ShiftValue-11)
, however, this is incorrect
for shift values above 26. Hardware tests show that a rate of 0x76 behaves
like 0x6A, seems it's dependent on the Bit15=1.
Phase invert acts very strange. If the volume is positive, it will decrease to zero, then increase back to maximum negative (inverted) volume. Except when decrementing, then it snaps straight to zero. Simply clamping to int16 range will be fine for incrementing, because the volume never decreases past zero. If the volume was negative, and is incrementing, hardware tests show that it only clamps to max, not 0.
Phase inversion is commonly used in "Dolby Surround" for simulating sound effects that should play through the rear speakers. There are also some cases where it is incorrectly used, such as Wipeout 3, where it sets a positive volume with an inverted sweep, but since all the rate bits are set to 1, the volume never steps, and it stays positive. If the rate had any bits clear, then the volume would slowly decrease to zero, then up to -8000h, growing louder but with phase inversion.
1F801C0Ch+N*10h - Voice 0..23 Current ADSR volume (R/W)
15-0 Current ADSR Volume (0..+7FFFh) (or -8000h..+7FFFh on manual write)
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)
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
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
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 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
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)
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)
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
1F801DA8h - Sound RAM Data Transfer Fifo
15-0 Data (max 32 halfwords)
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)
__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,...
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).
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.
1st block: FFFFh, halfwords[00h..1Eh], twice halfword[1Fh]
2nd block: FFFFh, halfwords[20h..3Eh], twice halfword[3Fh]
etc.
1st block: FFFFh, halfwords[00h,00h,..0Eh,0Eh], triple halfword[0Fh]
2nd block: FFFFh, halfwords[10h,10h,..1Eh,1Eh], triple halfword[1Fh]
etc.
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
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 \<which> 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 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
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.
Reverb Buffer Resampling
Input and output to/from the reverb unit is resampled using a 39-tap FIR filter
with the following coefficients.
-0001h, 0000h, 0002h, 0000h, -000Ah, 0000h, 0023h, 0000h,
-0067h, 0000h, 010Ah, 0000h, -0268h, 0000h, 0534h, 0000h,
-0B90h, 0000h, 2806h, 4000h, 2806h, 0000h, -0B90h, 0000h,
0534h, 0000h, -0268h, 0000h, 010Ah, 0000h, -0067h, 0000h,
0023h, 0000h, -000Ah, 0000h, 0002h, 0000h, -0001h,
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
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
SPU Unknown Registers
1F801DA0h - Some kind of a read-only status register.. or just garbage..?
0-15 Unknown?
Other day: Usually 9CF8h, or occassionally 9CFAh.
Another day: Usually 0000h, or occassionally 4000h.
1F801DBCh - 4 bytes - Unknown? (R/W)
80 21 4B DF
.. 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
.. .. .. .. .. .. .. .. .. .. .. .. .. .. 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.
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.