============================================================================= Anomie's S-DSP Doc Version WIP ============================================================================= The S-DSP is the actual sound generator for the SNES. It shares RAM with the SPC700, and can be poked at via SPC700 registers $00F2 and $00F3. It has an input clock running nominally at 24576000 Hz, and supplies the SPC700 1024000 Hz clock and the 8192000(?) Hz clock to the expansion port. Note that this clock has been indirectly observed to vary, with rates of anywhere from 24592800 Hz to 24645600 Hz. Credit to libopenspc, via SNEeSe and snes9x, for much of the below information. I've since re-verified and re-interpreted much of it. SOUND GENERATION ================ The S-DSP can mix and output up to 8 voices to product stereo sound (later on, sound generated by a device on the cart or the expansion port device may also be mixed in, but the S-DSP has no knowledge of or control over this). Output is nominally at 32000 Hz, realistically once every 768 clock cycles (32 SPC700 cycles). The signal flow is roughly as follows. This process runs at about 32000 Hz. 1. For each enabled voice: A. BRR data is decoded. The amount of data decoded depends on VxPITCH and PMON. B. Interpolate from the previous 4 raw BRR samples to get the output sample value (see below for details). C. If NON is set for this voice, replace the sample from step B with the current noise sample. D. Update the volume envelope (and update VxENVX). E. Adjust the sample by the envelope (and this is OUTX). F. The sample is adjusted independantly in the left and right stereo channels by VxVOLL/VxVOLR. G. Left-shift the sample by 1. 2. Take the current echo buffer stereo sample, and input it into the FIR filters. 3. Take the FIR output, adjust by the echo feedback volume, mix with all voices enabled in EON, and overwrite the just-used ring buffer stereo sample. And increment the ring buffer position to point to the next sample. 4. Take the FIR output, adjust each stereo channel by the appropriate echo volume, and mix with the voice outputs. This is your audio output for this cycle. Feed into the DAC for output to the speakers. One note on voices: they never actually stop decoding. The best you can do is set VxPITCH to 0 so they never get around to requesting a new sample. The proof is simple, just watch how ENDX is still updated just as if the voice were playing even after you've keyed it off or BRR decoding has hit a end-without-loop block. Keying off or BRR ending just transitions the volume envelope to the Release state, which brings the volume to 0 in about 0.008 seconds and holds it there until a key on. COUNTERS ======== The S-DSP has a number of counters with variable rates. In general, they all count up to (or down from, it's equivalent) 0x7800 and use the following lookup table to determine the adjustment per tick: RateTable[] = { 0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C, 0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180, 0x01E0, 0x0280, 0x0300, 0x03C0, 0x0500, 0x0600, 0x0780, 0x0A00, 0x0C00, 0x0F00, 0x1400, 0x1800, 0x1E00, 0x2800, 0x3C00, 0x7800 } Or if you want to think of it as "samples until next event", that would be: Table[] = { Inf, 2048, 1536, 1280, 1024, 768, 640, 512, 384, 320, 256, 192, 160, 128, 96, 80, 64, 48, 40, 32, 24, 20, 16, 12, 10, 8, 6, 5, 4, 3, 2, 1 } The counters are adjusted once per sample output, and some action is taken when the counter wraps 0x77FF->0. I don't know whether overflows are accumulated or not, although I suspect they are based on the envelope behavior observed when changing from Attack to Decay states. VOLUME CONTROL & ECHO ===================== In all cases, volume samples are adjusted in a simple linear fashion: Sout = (Sin * vol) >> vol_bits. There are several layers to S-DSP volume control. First, the sample is adjusted by the volume envelope (11 bits, no sign). Then each sample is adjusted by the per-voice volume (7 bits and sign) separately for the left and right channels (which may invert the phase of the signal). After all voices are mixed the volume is adjusted by the master volume (7 bits and sign) separately for the left and right channels. And finally, the whole thing can be muted by the FLG register. Echo splits off the main audio path after the per-voice volume, before all enabled voices are mixed together. The final sample from the echo ring buffer (specified by ESA and EDL) is fed into the FIR filter, and that output is adjusted by the echo volume (7 bits with sign) and mixed back into the main output (after master volume adjustment). Then (if echo write is enabled in FLG) the FIR output is adjusted by the echo feedback volume (7 bits with sign) and mixed with all voices enabled in EON, and output into the end of the echo ring buffer. So note that if echo write is disabled, the "echo ring buffer" becomes a static sample buffer up to 0.96 seconds long. BRR DECODING ============ The input samples to the S-DSP are compressed via a method known as "bit rate reduction", compressing 16 16-bit samples into 9 byte blocks. The block format is: ssssffle 00001111 22223333 44445555 .... EEEEFFFF ssss = shift ff = filter l = loop (really "don't end") e = end 0000 = (D) data for sample #0 in this block, signed 2's complement ... FFFF = (D) data for sample #15 in this block, signed 2's complement While the pre-BRR samples were supposedly 16-bit, the BRR decoder seems to lose the low bit. This can be seen below, in that the input D loses a bit at the low end. The bit is 'recovered' after the VxVOLL/VxVOLR volume adjustment. The 'shift' value scales the sample data D. Values 0-12 work normally, 16-bit D=(D<>1. Values 13-15 force D to either 0x0000 or 0xF800 depending on the sign of the input D (i.e. they give the same values as 0 or F do with shift=12). There are 4 possible 'filters' to use in decoding the blocks. Some filters use previous samples in decoding, this does carry over between blocks and is separate for each voice. Filter 0 (Direct): S(x) = D Filter 1 (15/16): S(x) = D + S(x-1) + ((-S(x-1))>>4) Filter 2 (61/32-15/16): S(x) = D + (S(x-1)<<1) + ((-((S(x-1)<<1)+S(x-1)))>>5) - S(x-2) + (S(x-2)>>4) Filter 3 (115/64-13/16): S(x) = D + (S(x-1)<<1) + ((-(S(x-1)+(S(x-1)<<2)+(S(x-1)<<3)))>>6) - S(x-2) + (((S(x-2)<<1) + S(x-2))>>4) Certain games do seem to depend on these exact formulas, trying to simplify will break some sound effects. The calculations above are preformed in some higher number of bits, clipped to 32767>=S>=-32768 (no wrapping) at the end and reduced to 15 bits. This 15-bit value is the value output and the value used as S(x-1) or S(x-2) as needed for future filter iterations. Now, as for the remaining two bits. If 'e' is set for the block, the bit in ENDX is set and the next block will be that pointed to by the loop pointer for this sample (see DIR and VxSRCN). If 'l' is clear, the voice goes into the Release state and the envelope goes to 0 immediately. Two other notes: 1. The 'e' bit seems to be recognized enough ahead of time that at VxPITCH=0x1000 the final 8 samples of the _previous_ block will not be seen in the echo buffer. OTOH, at VxPITCH=0x3fff they will be seen. 2. When a voice is keyed on, it seems to generate ~8 empty samples before we get actual BRR data. This is independant of the VxPITCH setting. The envelope is not updated during this time too, BTW. Then the first sample output uses the first 4 BRR samples interpolated with d=-256 in the formula below. PITCH ADJUSTMENTS ================= The S-DSP has two methods to adjust the 'pitch' of the input sound. Each voice has a 14-bit pitch control, and for voices 1-7 this can be further tweaked by the output sample of the previous voice. The pitch adjustment is fairly simple: if(PMON&~NON&~1&(1<> 15; } else { voice[x].pitch_counter += voice[X].PITCH; } while(voice[x].pitch_counter>=0){ *(++voice[x].BRRptr) = GetBRRSampleForVoice(x); voice[x].pitch_counter-=0x1000; } The last for samples in the voice[x].BRRbuffer are then interpolated using a 4-point gaussian interpolation. Note that pitch adjustment does not function on noise voices (see NON) or on voice 0. The exact interpolation table from libopenspc is: // gaussian table by libopenspc static const int32 gauss[512] = { 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004, 0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007, 0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00A, 0x00A, 0x00A, 0x00B, 0x00B, 0x00B, 0x00C, 0x00C, 0x00D, 0x00D, 0x00E, 0x00E, 0x00F, 0x00F, 0x00F, 0x010, 0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016, 0x017, 0x017, 0x018, 0x018, 0x019, 0x01A, 0x01B, 0x01B, 0x01C, 0x01D, 0x01D, 0x01E, 0x01F, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026, 0x027, 0x028, 0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F, 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03A, 0x03B, 0x03C, 0x03D, 0x03E, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04A, 0x04C, 0x04D, 0x04E, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05A, 0x05C, 0x05E, 0x05F, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06A, 0x06B, 0x06D, 0x06F, 0x071, 0x073, 0x075, 0x076, 0x078, 0x07A, 0x07C, 0x07E, 0x080, 0x082, 0x084, 0x086, 0x089, 0x08B, 0x08D, 0x08F, 0x091, 0x093, 0x096, 0x098, 0x09A, 0x09C, 0x09F, 0x0A1, 0x0A3, 0x0A6, 0x0A8, 0x0AB, 0x0AD, 0x0AF, 0x0B2, 0x0B4, 0x0B7, 0x0BA, 0x0BC, 0x0BF, 0x0C1, 0x0C4, 0x0C7, 0x0C9, 0x0CC, 0x0CF, 0x0D2, 0x0D4, 0x0D7, 0x0DA, 0x0DD, 0x0E0, 0x0E3, 0x0E6, 0x0E9, 0x0EC, 0x0EF, 0x0F2, 0x0F5, 0x0F8, 0x0FB, 0x0FE, 0x101, 0x104, 0x107, 0x10B, 0x10E, 0x111, 0x114, 0x118, 0x11B, 0x11E, 0x122, 0x125, 0x129, 0x12C, 0x130, 0x133, 0x137, 0x13A, 0x13E, 0x141, 0x145, 0x148, 0x14C, 0x150, 0x153, 0x157, 0x15B, 0x15F, 0x162, 0x166, 0x16A, 0x16E, 0x172, 0x176, 0x17A, 0x17D, 0x181, 0x185, 0x189, 0x18D, 0x191, 0x195, 0x19A, 0x19E, 0x1A2, 0x1A6, 0x1AA, 0x1AE, 0x1B2, 0x1B7, 0x1BB, 0x1BF, 0x1C3, 0x1C8, 0x1CC, 0x1D0, 0x1D5, 0x1D9, 0x1DD, 0x1E2, 0x1E6, 0x1EB, 0x1EF, 0x1F3, 0x1F8, 0x1FC, 0x201, 0x205, 0x20A, 0x20F, 0x213, 0x218, 0x21C, 0x221, 0x226, 0x22A, 0x22F, 0x233, 0x238, 0x23D, 0x241, 0x246, 0x24B, 0x250, 0x254, 0x259, 0x25E, 0x263, 0x267, 0x26C, 0x271, 0x276, 0x27B, 0x280, 0x284, 0x289, 0x28E, 0x293, 0x298, 0x29D, 0x2A2, 0x2A6, 0x2AB, 0x2B0, 0x2B5, 0x2BA, 0x2BF, 0x2C4, 0x2C9, 0x2CE, 0x2D3, 0x2D8, 0x2DC, 0x2E1, 0x2E6, 0x2EB, 0x2F0, 0x2F5, 0x2FA, 0x2FF, 0x304, 0x309, 0x30E, 0x313, 0x318, 0x31D, 0x322, 0x326, 0x32B, 0x330, 0x335, 0x33A, 0x33F, 0x344, 0x349, 0x34E, 0x353, 0x357, 0x35C, 0x361, 0x366, 0x36B, 0x370, 0x374, 0x379, 0x37E, 0x383, 0x388, 0x38C, 0x391, 0x396, 0x39B, 0x39F, 0x3A4, 0x3A9, 0x3AD, 0x3B2, 0x3B7, 0x3BB, 0x3C0, 0x3C5, 0x3C9, 0x3CE, 0x3D2, 0x3D7, 0x3DC, 0x3E0, 0x3E5, 0x3E9, 0x3ED, 0x3F2, 0x3F6, 0x3FB, 0x3FF, 0x403, 0x408, 0x40C, 0x410, 0x415, 0x419, 0x41D, 0x421, 0x425, 0x42A, 0x42E, 0x432, 0x436, 0x43A, 0x43E, 0x442, 0x446, 0x44A, 0x44E, 0x452, 0x455, 0x459, 0x45D, 0x461, 0x465, 0x468, 0x46C, 0x470, 0x473, 0x477, 0x47A, 0x47E, 0x481, 0x485, 0x488, 0x48C, 0x48F, 0x492, 0x496, 0x499, 0x49C, 0x49F, 0x4A2, 0x4A6, 0x4A9, 0x4AC, 0x4AF, 0x4B2, 0x4B5, 0x4B7, 0x4BA, 0x4BD, 0x4C0, 0x4C3, 0x4C5, 0x4C8, 0x4CB, 0x4CD, 0x4D0, 0x4D2, 0x4D5, 0x4D7, 0x4D9, 0x4DC, 0x4DE, 0x4E0, 0x4E3, 0x4E5, 0x4E7, 0x4E9, 0x4EB, 0x4ED, 0x4EF, 0x4F1, 0x4F3, 0x4F5, 0x4F6, 0x4F8, 0x4FA, 0x4FB, 0x4FD, 0x4FF, 0x500, 0x502, 0x503, 0x504, 0x506, 0x507, 0x508, 0x50A, 0x50B, 0x50C, 0x50D, 0x50E, 0x50F, 0x510, 0x511, 0x511, 0x512, 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518, 0x518, 0x518, 0x518, 0x518, 0x519, 0x519 }; static const int32 *G1 = &gauss[256], *G2 = &gauss[512], *G3 = &gauss[255], *G4 = &gauss[-1]; // 4-point gaussian interpolation d = voice[x].pitch_counter >> 4; // -256 <= d <= -1 outx = ((G4[-d] * voice[x].BRRptr[-3]) >> 11); outx += ((G3[-d] * voice[x].BRRptr[-2]) >> 11); outx += ((G2[ d] * voice[x].BRRptr[-1]) >> 11); // The above 3 wrap at 15 bits signed. The last is added to that, and is // clipped rather than wrapped. outx = ((outx & 0x7FFF) ^ 0x4000) - 0x4000; outx += ((G1[ d] * voice[x].BRRptr[0]) >> 11); CLIP15(outx); S-DSP REGISTERS =============== All registers are accessed by the SPC700 setting the address in $00F2, then reading/writing $00F3. Note that the register addresses use only 7 bits: $80-$ff are read-only mirrors of $00-$7f. Any unspecified registers/bits are read/write with no known effect. On power on, most registers are uninitialized. There does seem to be something of a pattern, for example $x0-$x3 is frequently "ff 00 ff ff" for at least one x, and other registers similarly have common values. But it's nothing I can easily make sense of, and it's never constant. On reset, most registers retain their previous values. Some notable exceptions: FLG will always act as if set to 0xE0 after power on or reset, even if the value read back indicates otherwise. VxENVX and VxOUTX are of course 0, since all channels are in the Release state due to FLG. And ENDX will be 0 on power on or reset, but recall that the voices are still running even when keyed off so the various bits may have been set by BRR decoding by the time you get to read it. First, the 10 per-voice registers. These occupy $00-$09, $10-$19, and so on up to $70-$79. $x0 rw VxVOLL - Left volume for Voice x $x1 rw VxVOLR - Right volume for Voice x vvvvvvvv These are the volumes of the voice in the left/right stereo channel. The value is 2's-complement, negative values invert the phase of the signal in the channel. Volume adjustment is SL = (S * VL)>>7 and SR = (S * VR)>>7. $x2 rw VxPITCHL - Pitch scaler for Voice x low byte $x3 rw VxPITCHH - Pitch scaler for Voice x high byte --pppppp pppppppp This 14-bit number adjusts the pitch of the sounds output for this voice, as the function: Fout = Fin * P / 0x1000 Considering things on the normal 12-note scale, P=0x2000 will increase the pitch by one octave, P=0x3FFF will increase by (just about) two octaves, P=0x0800 will reduce by one octave, P=0x0400 will reduce by two octaves, and so on. Note that even though the high bits of $x3 are not significant, they are still read back as written. $x4 rw VxSRCN - Source number for Voice x nnnnnnnn This selects the "instrument" this voice is to play. The number set here is used as an offset into the table pointed to by DIR. Changing this while the channel is playing has some interesting effects. It seems that if the voice has not yet looped (even if it's in the middle of the end-with-loop block), the new sample will start over from the beginning. If the voice has looped, the new sample will start from the loop position instead of the normal start position. Note that the above only seems to apply when the value is CHANGED, writing the same value seems to have no effect. $x5 rw VxADSR1 - Attack-Decay-Sustain-Release settings for Voice x (part 1) edddaaaa $x6 rw VxADSR2 - Attack-Decay-Sustain-Release settings for Voice x (part 2) lllrrrrr $x7 rw VxGAIN - Gain settings for Voice x EGGGGGGG or Emmggggg e/E = Envelope adjustment method bits. ddd = Decay rate: R=RateTable[16+d*2] aaaa = Attack rate: R=RateTable[a*2+1] lll = Sustain level: L=0x100*(l+1) rrrrr = Sustain rate: R=RateTable[r] mm = Gain mode ggggg = Gain rate: R=RateTable[g] GGGGGGG = Direct Gain mode gain setting: E=g*16 These three registers give control over the volume envelope. The volume envelope is 11 bits unsigned: volume adjustment is S = (S * E)>>11. Various settings of these registers will automatically adjust the envelope after a certain number of samples, based on a counter as described above. The volume envelope adjustment has 4 states: Attack, Decay, Sustain, and Release. When the voice is keyed off or a BRR end-without-loop block is reached, the state is set to Release. When the voice is keyed on, the state is set to Attack. When the envelope is in the Release state, this overrides all settings of these registers. In this case, the counter rate R=0x7800 (i.e. adjust every sample), and the adjustment is E-=8. The simplest method of envelope control ("Direct Gain") is available when VxADSR1 bit 7 and VxGAIN bit 7 are both clear. In this case, the volume envelope is simply E=%GGGGGGG0000, and R does not matter. The second method ("Gain", usually with one of the 4 names below) is available when VxADSR1 bit 7 is clear, but VxGAIN bit 7 is set. In this case, we have 4 options, chosen based on the 'm' bits. 00 = Linear Decrease. R=RateTable[g], E-=32 01 = Exp Decrease. R=RateTable[g], E-=((E-1)>>8)+1 10 = Linear Increase. R=RateTable[g], E+=32 11 = Bent Increase. R=RateTable[g], E+=(E<0x600)?32:8 In all cases, clip E to 0 or 0x7ff rather than wrapping. The most complex method ("ADSR") is used when VxADSR1 bit 7 is 1. You can think of this method as loading VxGAIN with different values at different times based on the value of the volume envelope. VxGAIN is not actually altered, however. Attack: If aaaa == %1111, R=0x7800 and E+=1024. Otherwise, pretend VxGAIN = %110aaaa1. In either case, when E==0x7ff enter the Decay state. Decay: Pretend VxGAIN = %101ddd0. When E<=L (the Sustain level), enter the Sustain state. Sustain: Pretend VxGAIN = %10rrrrr. Note that the state transitions described above occur only while ADSR mode is adjusting the envelope. Setting the envelope via Gain modes will not change the state (although when ADSR is enabled, E begins from wherever the Gain mode left it). $x8 r- VxENVX - Current envelope value for Voice X 0eeeeeee This returns the high 7 bits of the current volume envelope value (IOW, E>>4) for this voice. Note that the high bit will always be 0. Also note that (obviously) there is no way to directly determine the low 4 bits unless you're using Direct Gain. Technically, this register IS writable. But whatever value you write will be overwritten at 32000 Hz. $x9 r- VxOUTX - Current sample value for Voice X oooooooo This returns the high byte of the current sample for this voice, after envelope volume adjustment but before VxVOL[LR] is applied. Technically, this register IS writable. But whatever value you write will be overwritten at 32000 Hz. Now, the general-purpose registers: $0c rw MVOLL - Left channel master volume $1c rw MVOLR - Right channel master volume vvvvvvvv These are the master volumes of the left/right stereo channel. The value is 2's-complement, negative values invert the phase of the channel. Volume adjustment is ML = (SL * VL)>>7 and MR = (SR * VR)>>7. $2c rw EVOLL - Left channel echo volume $3c rw EVOLR - Right channel echo volume vvvvvvvv These are the echo volumes of the left/right stereo channel. The value is 2's-complement, negative values invert the phase of the channel. This is the adjustment applied to the FIR filter outputs before mixing with the main signal (after master volume adjustment). Volume adjustment is EL = (SL * VL)>>7 and ER = (SR * VR)>>7. $4c rw KON - Key on for all voices $5c rw KOFF - Key off for all voices 76543210 Each bit of KON/KOFF corresponds to one voice. Setting 1 to the KOFF bit will transition the voice to the Release state. Writing 1 to the KON bit when the KOFF bit is clear will set the envelope to 0, the state to Attack, and will start the current sample from the beginning. If the KOFF bit is set, there will be no effect. In either case, the corresponding ENDX bit will be cleared. These registers seem to be polled only at 16000 Hz, when every other sample is due to be output. Thus, the following odd effects: ; assume KOFF = $ff for some time mov $f2, #$4c ; KON = 1 then KOFF = 0 mov $f3, #$01 mov $f2, #$5c mov $f3, #$00 ; -> *usually* voice 1 WILL be keyed on And: ; assume KOFF = 0, but no voices playing mov $f2, #$4c ; KON = 1 then KOFF = 2 mov $f3, #$01 mov $f3, #$02 ; -> *usually* only voice 2 is keyed on And: ; assume various voices playing mov $f2, #$5c ; KOFF = $ff then KOFF = 0 mov $f3, #$ff mov $f3, #$00 ; -> *usually* all voices remain playing The basis for 16000 rather than 32000, in the second example above when both voices end up playing, we can see in the echo buffer that the second voice start is delayed 2 samples, rather than just one. Also note that KON (tries to) take effect immediately, the value is not persistant (much like "clear port" bits of SPC700 register $00f1). $6c rw FLG - Reset, Mute, Echo-Write flags and Noise Clock rmennnnn r = When set, the S-DSP soft-resets itself. Mostly, this seems to mean the S-DSP acts as if KOFF=$ff and forces all envelopes to 0; echo proccessing still continues, and any remaining echo data will continue to echo and generate samples. You must clear the bit to resume normal operation. m = When set, no sound will be output. Samples will still be decoded, echos processed, and such; just no sounds will be output. e = When set, the echo ring buffer (see ESA and EDL) will not be written. Echo processing on the buffer will continue as normal, just the buffer itself will not be updated and so the echo samples will loop forever. nnnnn = Noise frequency. This is the index into RateTable to determine when to generate a new noise sample. Note that there is ony one noise source shared by all voices for which noise is enabled (see NON). On reset, this register seems to have a value resembling $E0, even though this may not be read back. At least, 'r' is 'set' so we can't begin any samples, 'e' is 'set' so the echo buffer is not being updated, and 'm' is 'set' because even whatever static data is in the echo buffer gives no sound. 'n' is '0', since the noise sample is constant until this is set non-zero. $7c r* ENDX - Voice end flags 76543210 When the BRR block including the end flag is decoded in a voice, the corresponding bit is set in this register. When the voice is keyed on (successfully or not), the corresponding bit is cleared. Any write to this register will clear ALL bits, no matter what value is written. Note that the bit is set at the START of decoding the BRR block, not at the end. Recall that BRR processing, and therefore the setting of bits in this register, continues even for voices in the Release state. On reset, all bits are cleared. $0d rw EFB - Echo feedback volume vvvvvvvv When echo buffer write is enabled, the FIR output will be adjusted by this volume and mixed into the buffer. The value is 2's-complement, negative values invert the phase of the signal. Volume adjustment is E = (E * V)>>7. $2d rw PMON - Pitch modulation enable 7654321- Each bit corresponds to the corresponding voice. When the bit is set, the VxPITCH value will be adjusted by the output of the voice x-1. The exact formula seems to be: P = (VxPITCH * (0x8000 + OutX[x-1])) >> 15 For the purposes of pitch adjustment, a voice not playing is all zeros and thus has no effect on the pitch. $3d rw NON - Noise enable 76543210 Each bit corresponds to the corresponding voice. When the bit is set, the samples produced by BRR decoding will not be used. Instead, the output sample will be the current value of the noise generator (see FLG). The noise generator seems to function something like this: On reset, N=0x4000. Each update (see FLG), N=(N>>1)|(((N<<14)^(N<<13))&0x4000). And the output noise sample at any point is N (after which is volume adjustment then the left-shift to 'restore' the low bit). Note that the noise sample is not affected by VxPITCH or PMON, but VxPITCH and PMON still control the speed of BRR decoding and the end-without-loop of BRR decoding will still transition to Release (and update ENDX). $4d rw EON - Echo enable 76543210 Each bit corresponds to the corresponding voice. When the bit is set and echo buffer write is enabled, this voice will be mixed into the sample to be written to the echo buffer for later echo processing. $5d rw DIR - Sample table address aaaaaaaa This forms the high byte of the start address of the sample pointer table (the low byte is always 0). The sample pointer table is indexed for each voice by VxSRCN to determine which BRR data to decode and play. Each entry is 4 bytes. The first word points to the start of the BRR data, and the second word points to the 'restart' point for when the BRR end block is reached. Changing this will voices are playing will have no immediate effect, but when any voice afterwards loops or is keyed on it will use the new table. $6d rw ESA - Echo ring buffer address aaaaaaaa This forms the high byte of the start address of the echo ring buffer (the low byte is always 0). When echo buffer write is enabled in FLG, all voices marked in EON will be mixed together, adjusted by the echo volumes, mixed with the FIR output (adjusted by the echo feedback volume), and output into the ring buffer (4 bytes, 2 per stereo channel). And every sample, one entry (4 bytes) will be removed from the ring buffer and passed into the FIR filter. The size of the buffer is controlled by EDL. $7d rw EDL - Echo delay (ring buffer size) ----dddd This controls the size of the echo ring buffer, and therefore the delay between when a sample is first output and when it enters the echo FIR filter. The size of the buffer is simply D<<11 bytes (D<<9 16-bit stereo samples), however when D=0 the buffer is 4 bytes (1 sample) rather than 0. Note that only the low 4 bits are used to determine the buffer length. What happens to the current echo index when you change this is unknown, although reports are that it will continue counting to the old EDL value, wrap, then proceed to count to the new value as normal. $xf rw FIRx - Echo FIR filter coefficient X cccccccc These 8 registers specify the 8 coefficients of the 8-tap FIR filter used to calculate the echo signal. Each time a sample is generated by the voices, one sample is taken from the echo ring buffer and input to the FIR filter (this is S(x)). The FIR filter output is then mixed with the outputs of the voices to generate the output sound, and mixed with the sample being input into the echo buffer for echo feedback. The FIR calculation is: FIR = (S(x) * FIR0 + S(x-1) * FIR1 + S(x-2) * FIR2 + S(x-3) * FIR3 + S(x-4) * FIR4 + S(x-5) * FIR5 + S(x-6) * FIR6 + S(x-7) * FIR7) >> 7 Note that the left and right stereo channels are filtered separately (no crosstalk), but with identical coefficients.