S-DSP registers

From SNESdev Wiki
Revision as of 12:03, 5 November 2024 by Undisbeliever (talk | contribs) (→‎VxADSR1, VxADSR2 - ADSR enable and settings ($x5, $x6): Document no sustain decay when SR=0)
Jump to navigationJump to search

The S-DSP registers are accessed via the DSPADDR and DSPDATA S-SMP registers.

S-DSP global register summary
Name Address Bits Type Notes
MVOLL MVOL (L) $0C VVVV VVVV RW Left channel main volume, signed.
MVOLR MVOL (R) $1C VVVV VVVV RW Right channel main volume, signed.
EVOLL EVOL (L) $2C VVVV VVVV RW Left channel echo volume, signed.
EVOLR EVOL (R) $3C VVVV VVVV RW Right channel main volume, signed.
KON $4C 7654 3210 RW Key on. Writing this with any bit set will start a new note for the corresponding voice.
KOFF KOF $5C 7654 3210 RW Key off. Writing this with any bit set will put the corresponding voice into its release state.
FLG $6C RMEN NNNN RW Flags: soft reset (R), mute all (M), echo disable (E), noise frequency (N).
ENDX $7C 7654 3210 R Read for end of sample flag for each channel.
EFB $0D VVVV VVVV RW Echo feedback, signed.
- - $1D ---- ---- RW Unused.
PMON $2D 7654 321- RW Enables pitch modulation for each channel, controlled by OUTX of the next lower channel.
NON $3D 7654 3210 RW For each channel, replaces the sample waveform with the noise generator output.
EON $4D 7654 3210 RW For each channel, sends to the echo unit.
DIR $5D DDDD DDDD RW Pointer to the sample source directory page at $DD00.
ESA $6D EEEE EEEE RW Pointer to the start of the echo memory region at $EE00.
EDL $7D ---- DDDD RW Echo delay time (D).
FIR0 C0 $0F VVVV VVVV RW Echo filter coefficient.
FIR1 C1 $1F VVVV VVVV RW Echo filter coefficient.
FIR2 C2 $2F VVVV VVVV RW Echo filter coefficient.
FIR3 C3 $3F VVVV VVVV RW Echo filter coefficient.
FIR4 C4 $4F VVVV VVVV RW Echo filter coefficient.
FIR5 C5 $5F VVVV VVVV RW Echo filter coefficient.
FIR6 C6 $6F VVVV VVVV RW Echo filter coefficient.
FIR7 C7 $7F VVVV VVVV RW Echo filter coefficient.
(table source)


There are 8 voices, numbered 0 to 7. Each voice X has 10 registers in the range $X0-$X9.

S-DSP voice register summary
Name Address Bits Type Notes
VxVOLL VOL (L) $X0 SVVV VVVV RW Left channel volume, signed.
VxVOLR VOL (R) $X1 SVVV VVVV RW Right channel volume, signed.
VxPITCHL P (L) $X2 LLLL LLLL RW Low 8 bits of sample pitch.
VxPITCHH P (H) $X3 --HH HHHH RW High 6 bits of sample pitch.
VxSRCN SRCN $X4 SSSS SSSS RW Selects a sample source entry from the directory (see DIR below).
VxADSR1 ADSR (1) $X5 EDDD AAAA RW ADSR enable (E), decay rate (D), attack rate (A).
VxADSR2 ADSR (2) $X6 SSSR RRRR RW Sustain level (S), sustain rate (R).
VxGAIN GAIN $X7 0VVV VVVV
1MMV VVVV
RW Mode (M), value (V).
VxENVX ENVX $X8 0VVV VVVV R Reads current 7-bit value of ADSR/GAIN envelope.
VxOUTX OUTX $X9 SVVV VVVV R Reads signed 8-bit value of current sample wave multiplied by ENVX, before applying VOL.
(table source)


Register types:

  • RW - Readable and Writable
  • R - Readable (technically writable, but not intended to be written to)


Reading and writing S-DSP registers

The S-DSP registers are accessed via the DSPADDR and DSPDATA S-SMP registers.

Writing to the S-SMP $F2 DSPADDR register will set the selected S-DSP register address. The S-SMP $F3 DSPDATA register is then used to read or write the selected S-DSP register.


CAUTIONS:

  • Writing to DSPADDR with the high bit set will disable DSPDATA writes.
  • Reading DSPDATA will return the value stored in the S-DSP's 128 byte register memory. Some DSPDATA reads might not match the internal S-DSP state until the S-DSP register has been written to.
  • S-DSP registers are polled by the S-DSP at different points within the 32-cycle sample loop.
    • Most S-DSP register writes do not take effect immediately.
    • Some S-DSP registers are polled every second sample.
    • Overriding a S-DSP register write too early can skip the previous S-DSP register write (with audible glitches with the KON, KOFF registers).
    • The echo buffer registers are polled at unexpected times and should be treated with care.


    ; Select the KON ($4c) S-DSP register
    mov  a,       #$4c
    mov  DSPADDR, a

    ; Write 1 to the KON S-DSP register
    mov  a,       #$01
    mov  DSPDATA, a


The SPC-700 direct page move instructions can be used to simplify S-DSP register writes.

    ; Write zpTmp to the MVOLL ($0c) and MVOLR ($1c) S-DSP registers

    mov  DSPADDR, #$0c              ; Select MVOLL register
    mov  DSPDATA, zpTmp             ; Write zpTmp to MVOLL

    mov  DSPADDR, #$1c              ; Select MVOLR register
    mov  DSPDATA, zpTmp             ; Write zpTmp to MVOLR


The movw dp,ya 16 bit write instruction can be used to write to DSPADDR and DSPDATA in a single SPC-700 instruction. movw DSPADDR,ya will write A to DSPADDR and Y to DSPDATA. This instruction is useful when setting multiple S-DSP registers to the same value.

    mov  y,       #$7f

    ; Write Y to the MVOLL ($0c) and MVOLR ($1c) S-DSP registers

    mov  a,       #$0c
    movw DSPADDR, ya                ; DSPADDR = a, DSPDATA = y

    mov  a,       #$1c
    movw DSPADDR, ya                ; DSPADDR = a, DSPDATA = y


Another advantage of movw DSPADDR,ya is that you can do arithmetic on A to select the next DSPADDR to write to. For example, incrementing A by 1 to select the next voice S-DSP register or adding $10 to A to select the next global S-DSP register.

    mov  y,       #100

    ; Write Y to the VxVOLL ($x0) and VxVOLR ($x1) S-DSP registers

    mov  a,       #(voice << 4)     ; a = $x0

    movw DSPADDR, ya                ; DSPADDR = a, DSPDATA = y
    inc  a                          ; a = $x1
    movw DSPADDR, ya                ; DSPADDR = a, DSPDATA = y


The DSPADDR register is readable and writable. The direct-page read-modify-write SPC-700 instructions can be applied to the DSPADDR register to advance the selected S-DSP register without modifying the A, X or Y registers.

    ; Copy 8 bytes from `zpPtr` to the FIR S-DSP registers

    mov  y,       #0

    ; Select C0 ($0f) S-DSP register
    mov  DSPADDR, #$0f

    Loop:
        ; Write zpPtr[y] to the S-DSP
        mov  a,       [zpPtr]+y
        mov  DSPDATA, a
        inc  y

        ; Add $10 to DSPADDR and loop if DSPADDR <= $7f (C7 S-DSP register)
        clrc
        adc  DSPADDR, #$10
        bpl  Loop


Finally, the DSPDATA register is readable and writeable. Allowing modifications of S-DSP registers without shadow variables.

    ; Enable noise on selected voices
    ; IN: A = bitmask of voices to enable

    ; Select NON ($3d) S-DSP register
    mov  DSPADDR, #$3d

    ; DSPDATA = DSPDATA | a
    or   a,       DSPDATA
    mov  DSPDATA, a
    ; Adds 8 bit `Y` to voice `A`'s VxPITCH registers
    ; IN: A = voice to change
    ; IN: Y = amount to add to VxPITCH
    ; REQUIRES: A < 8

    ; Select VxPITCHL register
    ; DSPADDR = ((A & 7) << 4) | 2
    and  a,       #$07
    xcn  a                          ; swap high and low nibbles of A
    or   a,       #$02
    mov  DSPADDR, a

    ; A = Y
    mov  a,       y

    ; Add A to DSPDATA (VxPITCHL)
    clrc
    adc  a,       DSPDATA
    mov  DSPDATA, a

    bcc  SkipHighByte
        ; Select VxPITCHH by setting bit 0 of DSPADDR. (VxPITCHH = VxPITCHL | 1)
        set1 DSPADDR.0

        ; Increment VxPITCHH register
        inc  DSPDATA
SkipHighByte:

Global registers

MVOLL, MVOLR - Main volume ($0C, $1C)

7  bit  0
---- ----
VVVV VVVV
|||| ||||
++++-++++- left/right channel volume (signed)


EVOLL, EVOLR - Echo volume ($2C, $3C)

7  bit  0
---- ----
VVVV VVVV
|||| ||||
++++-++++- left/right channel volume (signed)


KON - Key on ($4C)

7  bit  0
---- ----
7654 3210
|||| ||||
|||| |||+- key-on voice 0
|||| ||+-- key-on voice 1
|||| |+--- key-on voice 2
|||| +---- key-on voice 3
|||+------ key-on voice 4
||+------- key-on voice 5
|+-------- key-on voice 6
+--------- key-on voice 7

Writing a 1 to a KON voice bit will:

  • Set the voice's envelope to 0
  • Change the voice's ADSR state to Attack
  • Reset the voice's BRR decoder to the start of the sample (as determined by DIR and VxSRCN)

KON is polled every second sample.


ERRATA:[1]

  • Clearing KON too early can cause the voice to not key-on.
  • If a voice's KON and KOFF bits are both set; the key-on will be followed by a key-off, silencing the channel.


KOFF - Key off ($5C)

7  bit  0
---- ----
7654 3210
|||| ||||
|||| |||+- key-off voice 0
|||| ||+-- key-off voice 1
|||| |+--- key-off voice 2
|||| +---- key-off voice 3
|||+------ key-off voice 4
||+------- key-off voice 5
|+-------- key-off voice 6
+--------- key-off voice 7

Setting a voice bit in KON will:

  • Change the voice's envelope state to Release

KOFF is polled every second sample.


ERRATA:[2]

  • Clearing KOFF too early can cause the voice to not key-off.
  • Clearing the KOFF bits after writing to KON might key-on, key-off or silence the channel.
  • If a voice's KON and KOFF bits are both set; the key-on will be followed by a key-off, silencing the channel.


FLG - Flags register ($6C)

7  bit  0
---- ----
RMEN NNNN
|||| ||||
|||+-++++- Noise frequency
||+------- Disable echo write
|+-------- Mute all
+--------- Soft reset


ENDX - End of sample flags ($7C, read-only)

7  bit  0
---- ----
7654 3210
|||| ||||
|||| |||+- Voice 0 BRR block has end-flag set
|||| ||+-- Voice 1 BRR block has end-flag set
|||| |+--- Voice 2 BRR block has end-flag set
|||| +---- Voice 3 BRR block has end-flag set
|||+------ Voice 4 BRR block has end-flag set
||+------- Voice 5 BRR block has end-flag set
|+-------- Voice 6 BRR block has end-flag set
+--------- Voice 7 BRR block has end-flag set

The voice bits are set when the current BRR block has the end-flag set (not at the end of the BRR sample).

If the voice is recently keyed-on, the ENDX bits will be clear (even if the BRR sample is a single BRR block).[3]

ENDX is technically writable and not intended to be written to. The S-DSP updates this register 8 times per sample.


EFB - Echo feedback ($0D)

7  bit  0
---- ----
VVVV VVVV
|||| ||||
++++-++++- echo feedback (signed)


PMON - Pitch modulation enable ($2D)

7  bit  0
---- ----
7654 321-
|||| |||
|||| ||+-- Enable pitch modulation on voice 1
|||| |+--- Enable pitch modulation on voice 2
|||| +---- Enable pitch modulation on voice 3
|||+------ Enable pitch modulation on voice 4
||+------- Enable pitch modulation on voice 5
|+-------- Enable pitch modulation on voice 6
+--------- Enable pitch modulation on voice 7


NON - Noise enable ($3D)

7  bit  0
---- ----
7654 3210
|||| ||||
|||| |||+- Replace voice 0 with noise generator
|||| ||+-- Replace voice 1 with noise generator
|||| |+--- Replace voice 2 with noise generator
|||| +---- Replace voice 3 with noise generator
|||+------ Replace voice 4 with noise generator
||+------- Replace voice 5 with noise generator
|+-------- Replace voice 6 with noise generator
+--------- Replace voice 7 with noise generator


EON - Echo enable ($3D)

7  bit  0
---- ----
7654 3210
|||| ||||
|||| |||+- Enable echo on voice 0
|||| ||+-- Enable echo on voice 1
|||| |+--- Enable echo on voice 2
|||| +---- Enable echo on voice 3
|||+------ Enable echo on voice 4
||+------- Enable echo on voice 5
|+-------- Enable echo on voice 6
+--------- Enable echo on voice 7


DIR - Sample directory page ($5D)

7  bit  0
---- ----
DDDD DDDD
|||| ||||
++++-++++- Page pointer to the sample source directory
           (directory address = DDDDDDDD << 8)


DIR is the high-byte of the 256-byte-aligned address of the BRR samples directory. The VxSRCN register selects the directory entry for each voice.

Each entry in the directory is 4 bytes, two 16-bit (little-endian) addresses pointing to BRR sample blocks:

  • The first address is the start of the sample. It is used when a voice is key-on (KON).
  • The second address is the loop address of the sample. It is used when the current BRR block ends with the end flag set.

The BRR directory entry address for each voice is DIR * 0x100 + VxSRCN * 4.

If DIR or VxSRCN is changed while a voice is playing it has no immediate effect. The next time the voice reaches a BRR end block, the new DIR and/or VxSRCN is used to fetch the loop address.


ESA - Echo start address ($6D)

7  bit  0
---- ----
EEEE EEEE
|||| ||||
++++-++++- Page pointer to start of the echo buffer


ERRATA:

  • Care must be taken when writing to the EDL and ESA echo buffer registers.
  • The ESA (echo buffer address) register is accessed once per sample and ESA writes can be delayed by a single sample.[4]
  • The echo buffer wraps around the 16-bit Audio-RAM address boundary, clobbering zeropage.
  • The echo buffer will write a minimum 4 bytes to the start of ESA, unless echo writes are disabled in FLG.
  • See EDL errata for more details.


EDL - Echo delay ($7D)

7  bit  0
---- ----
---- DDDD
     ||||
     ++++- Echo delay time (delay = DDDD * 512 samples)

EDL controls the size (DDDD * 2048 bytes) and length (DDDD * 512 samples or 16ms @ 32000Hz) of the echo buffer.


ERRATA:

  • Care must be taken when writing to the EDL and ESA echo buffer registers.
  • Setting echo delay (EDL, register $7D) to 0 continuously overwrites 4 bytes of ARAM at the start of the echo buffer page (selected by ESA, $6D). In particular, ESA = $00 and EDL = $00 overwrites zero page locations $0000-$0003. If not using echo, remember to set the echo write protect bit of FLG ($6C bit 5) to 1.
  • EDL (echo delay / echo buffer size) register writes take effect when the S-DSP reaches the end of the echo buffer.[5]
    • Writing to EDL can take up to 7680 samples (240ms @ 32000Hz) to take effect.
  • The ESA (echo buffer address) register is accessed once per sample and ESA writes can be delayed by a single sample.[6]
  • The echo buffer wraps around the 16-bit Audio-RAM address boundary, clobbering zeropage.
  • To prevent the echo buffer from clobbering memory when initialing the echo buffer, echo buffer writes should be disabled (FLG bit 5 set) before writing to ESA and EDL. There should be a minimum 7680 sample (240ms @ 32000Hz) delay before echo buffer writes are enabled (FLG bit 5 clear).


C0, C1, C2, C3, C4, C5, C6, C7 - Echo FIR filter coefficients ($xF)

7  bit  0
---- ----
VVVV VVVV
|||| ||||
++++-++++- FIR filter tap (signed)


ERRATA:

  • The FIR filter uses a clipped-sum for the first 7 tap calculations and a clamped-sum for only the last tap. [7]
  • This can cause audio clicks if the FIR filter gain exceeds 0dB.
  • To prevent FIR clicks ensure the absolute sum of the FIR taps (S-DSP registers C0-C7) is <= 128.


Voice registers

VxVOLL, VxVOLR - Voice volume ($x0, $x1)

7  bit  0
---- ----
VVVV VVVV
|||| ||||
++++-++++- left/right channel volume (signed)


VxPITCHL, VxPITCHH - Voice pitch ($x2, $x3)

15  bit  8   7  bit  0
 ---- ----   ---- ----
 ..PP pppp   pppp pppp
   || ||||   |||| ||||
   ++-++++---++++-++++- Voice pitch (2.12 fixed point)

VxPITCH sets the speed of the 4-point Gaussian interpolation and controls the sample rate of the BRR sound sample.

Sample rate: VxPITCH * 32000 Hz / $1000

A VxPITCH of $1000 will play back the sample at the SNES native sample rate of 32,000 Hz.

The pitch can go as high as $3FFF, almost two octaves above $1000. Pitches above $1000 will be subject to some aliasing from samples that are skipped over.

The pitch can go all the way down to 0, where it is halted. Pitches below $1000 will be lacking in higher frequencies, and there is not very much precision as the pitch value approaches 0.

To play a BRR sample at a specific sample rate, set VxPITCH to sample_rate / 32000Hz * $1000

To play a specific note for a BRR sample, set VxPITCH to note_frequency / sample_frequency * $1000 (where sample_frequency is the frequency of the BRR sample when played at 32000Hz).


VxSRCN - Voice sample source ($x4)

7  bit  0
---- ----
SSSS SSSS
|||| ||||
++++-++++- DIR sample source table entry


VxADSR1, VxADSR2 - ADSR enable and settings ($x5, $x6)

 VxADSR1
7  bit  0
---- ----
EDDD AAAA
|||| ||||
|||| ++++- Attack rate (A)
|+++------ Decay rate (D)
+--------- ADSR enable
 VxADSR2
7  bit  0
---- ----
LLLR RRRR
|||| ||||
|||+-++++- Sustain rate (SR)
+++------- Sustain level (SL)
  • If the ADSR enable bit is clear, the envelope is controlled by the VxGAIN register.
  • If the ADSR enable bit is set, the envelope will be changed depending on the ADSR state
    • Attack:
      • If AAAA == 15, Linear increase +1024 at a rate of 31
      • If AAAA < 15, Linear increase +32 at a rate of AAAA1 (equivalent to VxGAIN == 110_AAAA1)
    • Decay: Exponential decrease at a rate of 1DDD0 (equivalent to VxGAIN == 101_1DDD0)
      • Sustain Level controls when the ADSR switches from the decay to sustain state.
      • Decay ends when the upper 3 bits of the envelope match SL
    • Sustain: Exponential decrease at a rate of RRRRR (equivalent to VxGAIN == 101_RRRRR)
      • A Sustain Rate of 0 has no exponential decay.
    • Release: Linear decrease at a fixed rate of -8 every sample.

Adsr envelope.svg


ERRATA:

  • There is a race-condition when changing the ADSR/GAIN envelope mode (bit 7 of ADSR1) in the middle of a note. If the S-DSP registers are written in the order ADSR1 followed by ADSR2/GAIN, the S-DSP might read the old ADSR2/GAIN value before the ADSR2/GAIN write, potentially glitching the rest of the envelope (especially if the previous GAIN was a fixed envelope).[8]
    • Workaround: Write to the ADSR2/GAIN register before the ADSR1 register.
    • Workaround: Only change the ADSR/GAIN envelope mode bit when the channel is in the release state.


See: DSP envelopes


VxGAIN - GAIN envelope settings ($x7)

7  bit  0
---- ----
0VVV VVVV
 ||| ||||
 +++ ++++- Fixed envelope

7  bit  0
---- ----
1MMr rrrr
 ||| ||||
 ||+ ++++- GAIN rate
 ++------- GAIN mode

GAIN envelope is used if the VxADSR1 ADSR enable bit is clear.

GAIN modes
Mode bits Name envelope change per rate
0?? Fixed envelope = VVVVVVV << 4 every sample
100 Linear decrease -32
101 Exponential decrease -1 - ((envelope - 1) >> 8)
110 Linear increase +32
111 Bent increase if envelope < $600 (75%) { +32 } else { +8 }

If the voice is in the release state, the envelope is linear decrease at a fixed rate of -8 every sample.


See: DSP envelopes


VxENVX - Voice envelope value ($x8, read-only)

7  bit  0
---- ----
0EEE EEEE
 ||| ||||
 +++-++++- Upper 7 bits of envelope

VxENVX is technically writable and not intended to be written to. The S-DSP updates this register once per sample.


VxOUTX - Voice sample value ($x9, read-only)

7  bit  0
---- ----
OOOO OOOO
|||| ||||
++++-++++- Upper 8 bits of current sample (signed)

VxOUTX contains the upper 8 bits of the current sample after the envelope has been applied to the Gaussian Interpolation (or noise) output and before the VxVOL channel volume multiplication.

VxOUTX is technically writable and not intended to be written to. The S-DSP updates this register once per sample.

References

  • Anomie's S-DSP Doc
  • ares source code, ares/sfc/dsp directory, by Near and ares team
  1. Anomie's S-DSP Doc: KON and KOFF registers
  2. Anomie's S-DSP Doc: KON and KOFF registers
  3. ares source code, ares::SuperFamicom::Dsp::voice5(), by Near and ares team
  4. Anomie's S-DSP Doc: ESA register
  5. Anomie's S-DSP Doc: EDL register
  6. Anomie's S-DSP Doc: ESA register
  7. SnesLab Wiki - FIR Filter - S-DSP Implementation
  8. Terrific Audio Driver - I found a race condition