S-DSP registers: Difference between revisions
(Add register sections, documented envelope registers, added errata (most of it was copied from the Errata page).) |
(Add Sound category) |
||
(19 intermediate revisions by the same user not shown) | |||
Line 38: | Line 38: | ||
* '''R''' - Readable (technically writable, but not intended to be written to) | * '''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 [[S-SMP#$F2-F3_DSP|<tt>DSPADDR</tt> and <tt>DSPDATA</tt>]] S-SMP registers. | |||
Writing to the S-SMP <tt>$F2</tt> <tt>DSPADDR</tt> register will set the selected S-DSP register address. The S-SMP <tt>$F3</tt> <tt>DSPDATA</tt> register is then used to read or write the selected S-DSP register. | |||
CAUTIONS: | |||
* Writing to <tt>DSPADDR</tt> with the high bit set will disable <tt>DSPDATA</tt> writes. | |||
* Reading <tt>DSPDATA</tt> will return the value stored in the S-DSP's 128 byte register memory. Some <tt>DSPDATA</tt> 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 <tt>KON</tt>, <tt>KOFF</tt> registers). | |||
** The echo buffer registers are polled at unexpected times and should be treated with care. | |||
<pre> | |||
; 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 | |||
</pre> | |||
The SPC-700 direct page move instructions can be used to simplify S-DSP register writes. | |||
<pre> | |||
; 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 | |||
</pre> | |||
The <tt>movw dp,ya</tt> 16 bit write instruction can be used to write to <tt>DSPADDR</tt> and <tt>DSPDATA</tt> in a single SPC-700 instruction. | |||
<tt>movw DSPADDR,ya</tt> will write '''A''' to <tt>DSPADDR</tt> and '''Y''' to <tt>DSPDATA</tt>. | |||
This instruction is useful when setting multiple S-DSP registers to the same value. | |||
<pre> | |||
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 | |||
</pre> | |||
Another advantage of <tt>movw DSPADDR,ya</tt> is that you can do arithmetic on <tt>A</tt> to select the next <tt>DSPADDR</tt> to write to. For example, incrementing <tt>A</tt> by 1 to select the next voice S-DSP register or adding $10 to <tt>A</tt> to select the next global S-DSP register. | |||
<pre> | |||
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 | |||
</pre> | |||
The <tt>DSPADDR</tt> register is readable and writable. The direct-page read-modify-write SPC-700 instructions can be applied to the <tt>DSPADDR</tt> register to advance the selected S-DSP register without modifying the <tt>A</tt>, <tt>X</tt> or <tt>Y</tt> registers. | |||
<pre> | |||
; Copy 8 bytes from `zpPtr` to the FIR S-DSP registers | |||
mov y, #0 | |||
; Select the FIR0 ($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 (FIR7 S-DSP register) | |||
clrc | |||
adc DSPADDR, #$10 | |||
bpl Loop | |||
</pre> | |||
Finally, the <tt>DSPDATA</tt> register is readable and writeable. Allowing modifications of S-DSP registers without shadow variables. | |||
<pre> | |||
; 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 | |||
</pre> | |||
<pre> | |||
; 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: | |||
</pre> | |||
== Global registers == | == Global registers == | ||
Line 80: | Line 220: | ||
* Set the voice's envelope to 0 | * Set the voice's envelope to 0 | ||
* Change the voice's ADSR state to Attack | * Change the voice's ADSR state to Attack | ||
* Reset the voice's BRR decoder to the start of the sample (as determined by <tt>[[#DIR|DIR]]</tt> and <tt>[[# | * Reset the voice's BRR decoder to the start of the sample (as determined by <tt>[[#DIR|DIR]]</tt> and <tt>[[#VxSRCN|VxSRCN]]</tt>) | ||
<tt>KON</tt> is polled every second sample. | <tt>KON</tt> is polled every second sample. | ||
Line 91: | Line 231: | ||
{{Anchor|KOFF|KOF}} | {{Anchor|KOFF|KOF}} | ||
=== KOFF - Key off ($5C) === | === KOFF - Key off ($5C) === | ||
Line 129: | Line 270: | ||
|+-------- Mute all | |+-------- Mute all | ||
+--------- Soft reset | +--------- Soft reset | ||
On power-on: internal FLG = $E0 | |||
On reset: internal FLG = $E0 | |||
* '''Soft Reset''': Silences all voices | |||
** All voices will be forced to the release state with an envelope of 0 | |||
** Echo will still be processed | |||
* '''Mute all''': Disables audio output | |||
** Mute-all disables the external amplifier | |||
** Mute-all also disables audio from the cartridge slot and expansion port | |||
* '''Disable echo write''': When set, echo buffer writes are disabled | |||
** Echo buffer reads are not disabled | |||
** The echo buffer position will continue to advance every sample | |||
** '''Do not''' clear this bit unless the <tt>[[#ESA|ESA]]</tt> and <tt>[[#EDL|EDL]]</tt> registers are setup '''and the echo buffer's position has reset at least once'''. | |||
*** The ''disable echo write'' flag should be cleared a minimum 7680 samples (240ms @ 32000Hz) after the <tt>ESA</tt> and <tt>EDL</tt> writes. | |||
*** See <tt>[[#EDL|EDL]]</tt> errata for more details. | |||
* '''Noise frequency''': Sets the noise generator frequency | |||
** ''Noise frequency'' sets the rate at which the noise generator will generate a new noise sample (using the same [[DSP_envelopes#Period_Table|rate as the DSP envelopes]]) | |||
{| class="wikitable" | |||
|+ Noise generator frequencies <ref>Noise generator frequency source: [https://wiki.superfamicom.org/spc700-reference#dsp-register:-flg-1318 Super Famicom Development Wiki - SPC700 Reference - DSP Register: FLG]</ref> | |||
|- | |||
! NNNNN !! Frequency !! NNNNN !! Frequency | |||
|- | |||
| $00 || 0 Hz || $10 || 500 Hz | |||
|- | |||
| $01 || 16 Hz || $11 || 667 Hz | |||
|- | |||
| $02 || 21 Hz || $12 || 800 Hz | |||
|- | |||
| $03 || 25 Hz || $13 || 1.0 kHz | |||
|- | |||
| $04 || 31 Hz || $14 || 1.3 kHz | |||
|- | |||
| $05 || 42 Hz || $15 || 1.6 kHz | |||
|- | |||
| $06 || 50 Hz || $16 || 2.0 kHz | |||
|- | |||
| $07 || 63 Hz || $17 || 2.7 kHz | |||
|- | |||
| $08 || 83 Hz || $18 || 3.2 kHz | |||
|- | |||
| $09 || 100 Hz || $19 || 4.0 kHz | |||
|- | |||
| $0A || 125 Hz || $1A || 5.3 kHz | |||
|- | |||
| $0B || 167 Hz || $1B || 6.4 kHz | |||
|- | |||
| $0C || 200 Hz || $1C || 8.0 kHz | |||
|- | |||
| $0D || 250 Hz || $1D || 10.7 kHz | |||
|- | |||
| $0E || 333 Hz || $1E || 16.0 kHz | |||
|- | |||
| $0F || 400 Hz || $1F || 32.0 kHz | |||
|- | |||
|} | |||
On reset, <tt>FLG</tt> is believed to be set to <tt>$E0</tt>; disabling voices, audio output and echo writes. If the S-SMP reads <tt>FLG</tt> before writing to it, the value read will not match the internal S-DSP state. | |||
The [[S-SMP#IPL_Boot_ROM|IPL Boot ROM]] does not set the <tt>FLG</tt> register. When switching to the IPL a spc700 program should set <tt>FLG</tt> to <tt>$E0</tt> before jumping to the IPL. | |||
{{Anchor|ENDX}} | {{Anchor|ENDX}} | ||
=== ENDX - End of sample flags ($7C, read-only) === | === ENDX - End of sample flags ($7C, read-only) === | ||
Line 169: | Line 374: | ||
7 bit 0 | 7 bit 0 | ||
---- ---- | ---- ---- | ||
7654 321 | 7654 321. | ||
|||| ||| | |||| ||| | ||
|||| ||+-- Enable pitch modulation on voice 1 | |||| ||+-- Enable pitch modulation on voice 1 | ||
Line 178: | Line 383: | ||
|+-------- Enable pitch modulation on voice 6 | |+-------- Enable pitch modulation on voice 6 | ||
+--------- Enable pitch modulation on voice 7 | +--------- Enable pitch modulation on voice 7 | ||
Pitch modulation adjusts the voice's pitch every sample using the output of the previous voice. | |||
pitch = VxPITCH + (OUTX[x-1] >> 5) * VxPITCH >> 10; | |||
OUTX is the 16-bit output of the previous voice after the envelope has been applied to the Gaussian Interpolated BRR sample (or noise if <tt>NON[x-1]</tt> is set) and before the <tt>VxVOL</tt> channel volume multiplication. | |||
* An OUTX of 0 will not modify <tt>VxPITCH</tt> | |||
* A positive OUTX will linearly increment the pitch | |||
** A maximumly positive OUTX will nearly double <tt>VxPITCH</tt> | |||
** A 50% positive OUTX will set pitch to 150% of <tt>VxPITCH</tt> | |||
* A negative OUTX will linearly decrement the pitch | |||
** A minimally negative OUTX will set the pitch to 0 | |||
** A 50% negative OUTX will set pitch to 50% of <tt>VxPITCH</tt> | |||
* Pitch-modulation does not silence the previous voice. Usually the previous voice's <tt>VxVOL</tt> is set to 0 before pitch-modulation is enabled (but not required). <tt>VxVOL</tt> has no effect no effect on pitch-modulation. | |||
* A pitch-modulated voice can be used as a pitch-modulation source. | |||
* Voice 0 does not support pitch modulation as there is no previous voice. | |||
* Voice 7 cannot be used as a pitch-modulation source. | |||
{{Anchor|NON}} | {{Anchor|NON}} | ||
=== NON - Noise enable ($3D) === | === NON - Noise enable ($3D) === | ||
Line 195: | Line 420: | ||
|+-------- Replace voice 6 with noise generator | |+-------- Replace voice 6 with noise generator | ||
+--------- Replace voice 7 with noise generator | +--------- Replace voice 7 with noise generator | ||
Enabling noise will replace the voice's Gaussian Interpolated BRR output with the output of the noise generator. | |||
* There is only 1 noise generator | |||
* The BRR decoder will still decode BRR samples when noise is enabled | |||
* If the BRR decoder encounters a non-looping end BRR block, the voice will be silenced (by switching to the release state with a 0 envelope) | |||
** <tt>VxSRCN</tt> and <tt>VxPITCH</tt> will affect when the BRR sample ends | |||
** Workaround: Set <tt>VxSRCN</tt> to a looping BRR sample when enabling noise | |||
{{Anchor|EON}} | {{Anchor|EON}} | ||
=== EON - Echo enable ($3D) === | === EON - Echo enable ($3D) === | ||
Line 222: | Line 457: | ||
|||| |||| | |||| |||| | ||
++++-++++- Page pointer to the sample source directory | ++++-++++- Page pointer to the sample source directory | ||
(directory address = DDDDDDDD << 8) | |||
<tt>DIR</tt> is the high-byte of the 256-byte-aligned address of the [[BRR samples]] directory. The <tt>[[#VxSRCN|VxSRCN]]</tt> 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|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 <tt>DIR * 0x100 + VxSRCN * 4</tt>. | |||
If <tt>DIR</tt> or <tt>VxSRCN</tt> is changed while a voice is playing it has no immediate effect. The next time the voice reaches a BRR end block, the new <tt>DIR</tt> and/or <tt>VxSRCN</tt> is used to fetch the loop address. | |||
{{Anchor|ESA}} | {{Anchor|ESA}} | ||
=== ESA - Echo start address ($6D) === | === ESA - Echo start address ($6D) === | ||
Line 247: | Line 495: | ||
7 bit 0 | 7 bit 0 | ||
---- ---- | ---- ---- | ||
.... DDDD | |||
|||| | |||| | ||
++++- Echo delay time (delay = DDDD * 512 samples) | ++++- Echo delay time (delay = DDDD * 512 samples) | ||
Line 265: | Line 513: | ||
{{Anchor|FIR}} | {{Anchor|FIR}} | ||
=== | |||
=== FIRx - Echo FIR filter coefficients ($xF) === | |||
7 bit 0 | 7 bit 0 | ||
Line 272: | Line 521: | ||
|||| |||| | |||| |||| | ||
++++-++++- FIR filter tap (signed) | ++++-++++- FIR filter tap (signed) | ||
<tt>FIRx</tt> are the signed coefficients of an 8-tap [[wikipedia:Finite impulse response|Finite impulse response]] (FIR) filter. | |||
The FIR filter is applied to the 15-bit stereo output of the echo buffer. | |||
The output of the FIR filter is multiplied by <tt>EVOL</tt> for the echo output and <tt>EFB</tt> for echo feedback. | |||
The FIR filter must be set before echo is enabled. The FIR identity filter (<tt>$7f $00 $00 $00 $00 $00 $00 $00</tt>) will not not modify the echo buffer output. | |||
Line 277: | Line 534: | ||
* The FIR filter uses a clipped-sum for the first 7 tap calculations and a clamped-sum for only the last tap. <ref>[https://sneslab.net/wiki/FIR_Filter#S-DSP_Implementation SnesLab Wiki - FIR Filter - S-DSP Implementation]</ref> | * The FIR filter uses a clipped-sum for the first 7 tap calculations and a clamped-sum for only the last tap. <ref>[https://sneslab.net/wiki/FIR_Filter#S-DSP_Implementation SnesLab Wiki - FIR Filter - S-DSP Implementation]</ref> | ||
* This can cause audio clicks if the FIR filter gain exceeds 0dB. | * 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 <tt> | * To prevent FIR clicks ensure the absolute sum of the FIR taps (S-DSP registers <tt>FIR0</tt>-<tt>FIR7</tt>) is <= 128. | ||
Further reading: [https://sneslab.net/wiki/FIR_Filter SnesLab Wiki - FIR Filter] | |||
== Voice registers == | == Voice registers == | ||
Line 302: | Line 561: | ||
++-++++---++++-++++- Voice pitch (2.12 fixed point) | ++-++++---++++-++++- Voice pitch (2.12 fixed point) | ||
<tt>VxPITCH</tt> sets the speed of the 4-point Gaussian interpolation and controls the sample rate of the BRR sound sample. | |||
{{Anchor| | Sample rate: <tt>VxPITCH * 32000 Hz / $1000</tt> | ||
=== | |||
A <tt>VxPITCH</tt> 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 <tt>VxPITCH</tt> to <tt>sample_rate / 32000Hz * $1000</tt> | |||
To play a specific note for a BRR sample, set <tt>VxPITCH</tt> to <tt>note_frequency / sample_frequency * $1000</tt> | |||
(where sample_frequency is the frequency of the BRR sample when played at 32000Hz). | |||
{{Anchor|VxSRCN}} | |||
=== VxSRCN - Voice sample source ($x4) === | |||
7 bit 0 | 7 bit 0 | ||
Line 311: | Line 586: | ||
|||| |||| | |||| |||| | ||
++++-++++- DIR sample source table entry | ++++-++++- DIR sample source table entry | ||
The <tt>VxSRCN</tt> register selects the voice's sample or instrument from the BRR sample directory. | |||
If <tt>DIR</tt> or <tt>VxSRCN</tt> is changed while a voice is playing it has no immediate effect. The next time the voice reaches a BRR end block, the new <tt>DIR</tt> and/or <tt>VxSRCN</tt> is used to fetch the loop address. | |||
See: [[#DIR|DIR]] S-DSP register. | |||
{{Anchor|VxADSR|VxADSR1|VxADSR2}} | {{Anchor|VxADSR|VxADSR1|VxADSR2}} | ||
=== VxADSR1, VxADSR2 - ADSR enable and settings ($x5, $x6) === | === VxADSR1, VxADSR2 - ADSR enable and settings ($x5, $x6) === | ||
Line 339: | Line 622: | ||
*** If <tt>AAAA < 15</tt>, Linear increase +32 at a rate of <tt>AAAA1</tt> (equivalent to <tt>VxGAIN == 110_AAAA1</tt>) | *** If <tt>AAAA < 15</tt>, Linear increase +32 at a rate of <tt>AAAA1</tt> (equivalent to <tt>VxGAIN == 110_AAAA1</tt>) | ||
** '''Decay''': Exponential decrease at a rate of <tt>1DDD0</tt> (equivalent to <tt>VxGAIN == 101_1DDD0</tt>) | ** '''Decay''': Exponential decrease at a rate of <tt>1DDD0</tt> (equivalent to <tt>VxGAIN == 101_1DDD0</tt>) | ||
*** ''Sustain | *** ''Sustain Level'' controls when the ADSR switches from the decay to sustain state. | ||
*** Decay ends when the upper 3 bits of the envelope match <tt>SL</tt> | |||
** '''Sustain''': Exponential decrease at a rate of <tt>RRRRR</tt> (equivalent to <tt>VxGAIN == 101_RRRRR</tt>) | ** '''Sustain''': Exponential decrease at a rate of <tt>RRRRR</tt> (equivalent to <tt>VxGAIN == 101_RRRRR</tt>) | ||
*** A ''Sustain Rate'' of 0 has no exponential decay. | |||
** '''Release''': Linear decrease at a fixed rate of -8 every sample. | ** '''Release''': Linear decrease at a fixed rate of -8 every sample. | ||
Line 356: | Line 641: | ||
{{Anchor|VxGAIN}} | {{Anchor|VxGAIN}} | ||
=== VxGAIN - GAIN envelope settings ($x7) === | === VxGAIN - GAIN envelope settings ($x7) === | ||
Line 419: | Line 705: | ||
<tt>VxOUTX</tt> is technically writable and not intended to be written to. The S-DSP updates this register once per sample. | <tt>VxOUTX</tt> is technically writable and not intended to be written to. The S-DSP updates this register once per sample. | ||
== References == | == References == | ||
* Anomie's S-DSP Doc | * Anomie's S-DSP Doc | ||
* ares source code, <tt>[https://github.com/ares-emulator/ares/tree/master/ares/sfc/dsp ares/sfc/dsp]</tt> directory, by Near and ares team | * ares source code, <tt>[https://github.com/ares-emulator/ares/tree/master/ares/sfc/dsp ares/sfc/dsp]</tt> directory, by Near and ares team | ||
[[Category:Sound]] |
Latest revision as of 22:22, 15 November 2024
The S-DSP registers are accessed via the DSPADDR and DSPDATA S-SMP registers.
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.
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 the FIR0 ($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 (FIR7 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 On power-on: internal FLG = $E0 On reset: internal FLG = $E0
- Soft Reset: Silences all voices
- All voices will be forced to the release state with an envelope of 0
- Echo will still be processed
- Mute all: Disables audio output
- Mute-all disables the external amplifier
- Mute-all also disables audio from the cartridge slot and expansion port
- Disable echo write: When set, echo buffer writes are disabled
- Echo buffer reads are not disabled
- The echo buffer position will continue to advance every sample
- Do not clear this bit unless the ESA and EDL registers are setup and the echo buffer's position has reset at least once.
- The disable echo write flag should be cleared a minimum 7680 samples (240ms @ 32000Hz) after the ESA and EDL writes.
- See EDL errata for more details.
- Noise frequency: Sets the noise generator frequency
- Noise frequency sets the rate at which the noise generator will generate a new noise sample (using the same rate as the DSP envelopes)
NNNNN | Frequency | NNNNN | Frequency |
---|---|---|---|
$00 | 0 Hz | $10 | 500 Hz |
$01 | 16 Hz | $11 | 667 Hz |
$02 | 21 Hz | $12 | 800 Hz |
$03 | 25 Hz | $13 | 1.0 kHz |
$04 | 31 Hz | $14 | 1.3 kHz |
$05 | 42 Hz | $15 | 1.6 kHz |
$06 | 50 Hz | $16 | 2.0 kHz |
$07 | 63 Hz | $17 | 2.7 kHz |
$08 | 83 Hz | $18 | 3.2 kHz |
$09 | 100 Hz | $19 | 4.0 kHz |
$0A | 125 Hz | $1A | 5.3 kHz |
$0B | 167 Hz | $1B | 6.4 kHz |
$0C | 200 Hz | $1C | 8.0 kHz |
$0D | 250 Hz | $1D | 10.7 kHz |
$0E | 333 Hz | $1E | 16.0 kHz |
$0F | 400 Hz | $1F | 32.0 kHz |
On reset, FLG is believed to be set to $E0; disabling voices, audio output and echo writes. If the S-SMP reads FLG before writing to it, the value read will not match the internal S-DSP state.
The IPL Boot ROM does not set the FLG register. When switching to the IPL a spc700 program should set FLG to $E0 before jumping to the IPL.
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).[4]
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
Pitch modulation adjusts the voice's pitch every sample using the output of the previous voice.
pitch = VxPITCH + (OUTX[x-1] >> 5) * VxPITCH >> 10;
OUTX is the 16-bit output of the previous voice after the envelope has been applied to the Gaussian Interpolated BRR sample (or noise if NON[x-1] is set) and before the VxVOL channel volume multiplication.
- An OUTX of 0 will not modify VxPITCH
- A positive OUTX will linearly increment the pitch
- A maximumly positive OUTX will nearly double VxPITCH
- A 50% positive OUTX will set pitch to 150% of VxPITCH
- A negative OUTX will linearly decrement the pitch
- A minimally negative OUTX will set the pitch to 0
- A 50% negative OUTX will set pitch to 50% of VxPITCH
- Pitch-modulation does not silence the previous voice. Usually the previous voice's VxVOL is set to 0 before pitch-modulation is enabled (but not required). VxVOL has no effect no effect on pitch-modulation.
- A pitch-modulated voice can be used as a pitch-modulation source.
- Voice 0 does not support pitch modulation as there is no previous voice.
- Voice 7 cannot be used as a pitch-modulation source.
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
Enabling noise will replace the voice's Gaussian Interpolated BRR output with the output of the noise generator.
- There is only 1 noise generator
- The BRR decoder will still decode BRR samples when noise is enabled
- If the BRR decoder encounters a non-looping end BRR block, the voice will be silenced (by switching to the release state with a 0 envelope)
- VxSRCN and VxPITCH will affect when the BRR sample ends
- Workaround: Set VxSRCN to a looping BRR sample when enabling noise
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.[5]
- 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.[6]
- 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.[7]
- 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).
FIRx - Echo FIR filter coefficients ($xF)
7 bit 0 ---- ---- VVVV VVVV |||| |||| ++++-++++- FIR filter tap (signed)
FIRx are the signed coefficients of an 8-tap Finite impulse response (FIR) filter.
The FIR filter is applied to the 15-bit stereo output of the echo buffer.
The output of the FIR filter is multiplied by EVOL for the echo output and EFB for echo feedback.
The FIR filter must be set before echo is enabled. The FIR identity filter ($7f $00 $00 $00 $00 $00 $00 $00) will not not modify the echo buffer output.
ERRATA:
- The FIR filter uses a clipped-sum for the first 7 tap calculations and a clamped-sum for only the last tap. [8]
- 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 FIR0-FIR7) is <= 128.
Further reading: SnesLab Wiki - FIR Filter
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
The VxSRCN register selects the voice's sample or instrument from the BRR sample directory.
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.
See: DIR S-DSP register.
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.
- Attack:
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).[9]
- 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.
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
- ↑ Anomie's S-DSP Doc: KON and KOFF registers
- ↑ Anomie's S-DSP Doc: KON and KOFF registers
- ↑ Noise generator frequency source: Super Famicom Development Wiki - SPC700 Reference - DSP Register: FLG
- ↑ ares source code, ares::SuperFamicom::Dsp::voice5(), by Near and ares team
- ↑ Anomie's S-DSP Doc: ESA register
- ↑ Anomie's S-DSP Doc: EDL register
- ↑ Anomie's S-DSP Doc: ESA register
- ↑ SnesLab Wiki - FIR Filter - S-DSP Implementation
- ↑ Terrific Audio Driver - I found a race condition