#include "pch.h" #include "SNES/DSP/DspVoice.h" #include "SNES/DSP/Dsp.h" #include "SNES/DSP/DspTypes.h" #include "SNES/DSP/DspInterpolation.h" #include "SNES/Spc.h" #include "Shared/Emulator.h" #include "Shared/EmuSettings.h" void DspVoice::Init(uint8_t voiceIndex, Spc* spc, Dsp* dsp, uint8_t* dspVoiceRegs, SnesConfig* cfg) { _voiceIndex = voiceIndex; _voiceBit = 1 << voiceIndex; _spc = spc; _dsp = dsp; _regs = dspVoiceRegs; _cfg = cfg; _shared = &_dsp->GetState(); } void DspVoice::DecodeBrrSample() { uint8_t nextBrrData = _spc->DspReadRam(_brrAddress + _brrOffset + 1); //Sign-extend all four 4-bit samples encoded in these 2 bytes int16_t samples[4] = { (int16_t)((int16_t)((_shared->BrrData & 0xF0) << 8) >> 12), (int16_t)((int16_t)((_shared->BrrData & 0xF) << 12) >> 12), (int16_t)((int16_t)((nextBrrData & 0xF0) << 8) >> 12), (int16_t)((int16_t)((nextBrrData & 0xF) << 12) >> 12) }; int shift = _shared->BrrHeader >> 4; int filter = _shared->BrrHeader & 0x0C; int32_t prev1 = _sampleBuffer[_bufferPos > 0 ? _bufferPos - 1 : 11] >> 1; int32_t prev2 = _sampleBuffer[_bufferPos > 1 ? _bufferPos - 2 : 10] >> 1; for(int i = 0; i < 4; i++) { //Values 0-12 work normally, 16-bit RD=(D<>1 int32_t s = ((int32_t)samples[i] << shift) >> 1; if(shift >= 0x0D) { //"Values 13-15 force RD to either 0x0000 or 0xF800 depending on the sign of the input D" s = s < 0 ? -0x800 : 0; } switch(filter) { //Filter 0 (Direct): S(x) = RD case 0x00: break; //Filter 1 (15/16): S(x) = RD + S(x-1) + ((-S(x-1))>>4) case 0x04: s += prev1 + (-prev1 >> 4); break; //Filter 2 (61/32-15/16): S(x) = RD + (S(x-1)<<1) + ((-((S(x-1)<<1)+S(x-1)))>>5) - S(x-2) + (S(x-2)>>4) case 0x08: s += (prev1 << 1) + ((-((prev1 << 1) + prev1)) >> 5) - prev2 + (prev2 >> 4); break; //Filter 3 (115/64-13/16): S(x) = RD + (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) case 0x0C: s += (prev1 << 1) + ((-(prev1 + (prev1 << 2) + (prev1 << 3))) >> 6) - prev2 + (((prev2 << 1) + prev2) >> 4); break; } //"The calculations above are preformed in some higher number of bits, clamped to //16 bits at the end and then clipped 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." _sampleBuffer[_bufferPos + i] = Dsp::Clamp16(s) * 2; prev2 = prev1; prev1 = _sampleBuffer[_bufferPos + i] >> 1; } if(_bufferPos <= 4) { _bufferPos += 4; } else { _bufferPos = 0; } } void DspVoice::ProcessEnvelope() { int32_t env = _envVolume; if(_envMode == EnvelopeMode::Release) { //"When the envelope is in the Release state, this overrides all settings //of these registers. In this case, the counter rate R = 31 (i.e. adjust //every sample), and the adjustment is E -= 8." env -= 8; _envVolume = env < 0 ? 0 : env; } else { int32_t rate = 0; uint8_t sustain; //"Load VxGAIN or VxADSR2 register depending on ADSR1.7." if(_shared->Adsr1 & 0x80) { //"The most complex method ("ADSR") is used when VxADSR1 bit 7 is 1." uint8_t adsr2 = ReadReg(DspVoiceRegs::Adsr2); sustain = adsr2; switch(_envMode) { case EnvelopeMode::Attack: //"Attack: If aaaa == %1111, R=31 and E+=1024. Otherwise, pretend VxGAIN = %110aaaa1." if((_shared->Adsr1 & 0x0F) == 0x0F) { rate = 31; env += 1024; } else { rate = ((_shared->Adsr1 & 0x0F) << 1) | 0x01; env += 32; } break; case EnvelopeMode::Decay: //"Pretend VxGAIN = %1011ddd0." env -= ((env - 1) >> 8) + 1; rate = ((_shared->Adsr1 >> 3) & 0x0E) | 0x10; break; case EnvelopeMode::Sustain: //"Pretend VxGAIN = %101rrrrr." env -= ((env - 1) >> 8) + 1; rate = adsr2 & 0x1F; break; } } else { uint8_t gain = ReadReg(DspVoiceRegs::Gain); sustain = gain; if(gain & 0x80) { //"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." rate = gain & 0x1F; switch(gain & 0x60) { //00 = Linear Decrease. R=g, E-=32 case 0x00: env -= 32; break; //01 = Exp Decrease. R=g, E-=((E-1)>>8)+1 case 0x20: env -= ((env - 1) >> 8) + 1; break; //10 = Linear Increase. R=g, E+=32 case 0x40: env += 32; break; //11 = Bent Increase. R=g, E+=(E<0x600)?32:8 case 0x60: env += ((uint16_t)_prevCalculatedEnv < 0x600 ? 32 : 8); break; } } else { //"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." env = (uint16_t)gain << 4; rate = 31; } } //"If the mode is Decay and the Sustain Level is matched, change to the Sustain state." //"When the upper 3 bits of E equal the Sustain Level (see above), enter the Sustain state." //"Note: the "lll" bits are the Sustain Level only when bit 'e' is set. If 'e' is clear, the top 3 bits of VxGAIN are used instead. if(_envMode == EnvelopeMode::Decay && (env >> 8) == (sustain >> 5)) { _envMode = EnvelopeMode::Sustain; } //"Save the new value, *pre-clamp*, to determine the //increment for GAIN Bent Increase mode's next sample." _prevCalculatedEnv = env; //Clamp value if(env < 0 || env > 0x7FF) { //"In all cases, clip E to 0 or 0x7FF rather than wrapping." env = env < 0 ? 0 : 0x7FF; if(_envMode == EnvelopeMode::Attack) { //"If the mode is Attack and the new value is greater than 0x7FF, change to the Decay state. //CRITICAL NOTE: Negative values also trigger this." _envMode = EnvelopeMode::Decay; } } if(_dsp->CheckCounter(rate)) { //"If the counter specifies the envelope is to be updated, the //envelope is set to the new value, clamped to 11 bits." _envVolume = env; } } } void DspVoice::UpdateOutput(bool right) { //"Load and apply VxVOL[L/R] register." int32_t voiceOut = ((int32_t)_shared->VoiceOutput * (int8_t)ReadReg((DspVoiceRegs)((int)DspVoiceRegs::VolLeft + (int)right))) >> 7; voiceOut = voiceOut * (int32_t)_cfg->ChannelVolumes[_voiceIndex] / 100; _shared->OutSamples[(int)right] = Dsp::Clamp16(_shared->OutSamples[(int)right] + voiceOut); if(_shared->EchoOn & _voiceBit) { _shared->EchoOut[(int)right] = Dsp::Clamp16(_shared->EchoOut[(int)right] + voiceOut); } } void DspVoice::Step1() { //"Load VxSRCN register" _shared->SampleAddress = (_shared->DirSampleTableAddress * 0x100) + (_shared->SourceNumber * 4); _shared->SourceNumber = ReadReg(DspVoiceRegs::SourceNumber); } void DspVoice::Step2() { //"Load the sample pointer (using previously loaded DIR and VxSRCN)" uint16_t addr = _shared->SampleAddress; if(_keyOnDelay == 0) { //"the second word points to the 'restart' point for when the BRR end block is reached." addr += 2; } _shared->BrrNextAddress = _spc->DspReadRam(addr) | (_spc->DspReadRam(addr + 1) << 8); //"Load VxADSR1 register." _shared->Adsr1 = ReadReg(DspVoiceRegs::Adsr1); //"Load VxPITCHL register." _shared->Pitch = ReadReg(DspVoiceRegs::PitchLow); } void DspVoice::Step3() { Step3a(); Step3b(); Step3c(); } void DspVoice::Step3a() { //"This 14-bit number adjusts the pitch of the sounds output for this voice" //Ignore top 2 bits _shared->Pitch |= (ReadReg(DspVoiceRegs::PitchHigh) & 0x3F) << 8; } void DspVoice::Step3b() { //"Load the BRR header byte (every time), and the first of the two BRR bytes that will be decoded." _shared->BrrHeader = _spc->DspReadRam(_brrAddress); _shared->BrrData = _spc->DspReadRam(_brrAddress + _brrOffset); } void DspVoice::Step3c() { if(_shared->PitchModulationOn & _voiceBit) { //"When the bit is set, the VxPITCH value will be adjusted by the //output of the PREVIOUS voice (i.e x - 1). The exact formula seems to be : //P = VxPITCH + (((OutX[x - 1] >> 5) * VxPITCH) >> 10)" _shared->Pitch += ((_shared->VoiceOutput >> 5) * _shared->Pitch) >> 10; } if(_keyOnDelay) { if(_keyOnDelay == 5) { //Key was just enabled, prepare next BRR sample decoding _brrAddress = _shared->BrrNextAddress; _brrOffset = 1; _bufferPos = 0; _shared->BrrHeader = 0; } _envVolume = 0; _prevCalculatedEnv = 0; _keyOnDelay--; _interpolationPos = (_keyOnDelay & 0x03) ? 0x4000 : 0; _shared->Pitch = 0; } int32_t output = 0; switch(_cfg->InterpolationType) { case DspInterpolationType::Gauss: output = DspInterpolation::Gauss(_interpolationPos, _sampleBuffer, _bufferPos); break; case DspInterpolationType::Cubic: output = DspInterpolation::Cubic(_interpolationPos, _sampleBuffer, _bufferPos); break; case DspInterpolationType::Sinc: output = DspInterpolation::Sinc(_interpolationPos, _sampleBuffer, _bufferPos); break; case DspInterpolationType::None: output = _sampleBuffer[((_interpolationPos >> 12) + _bufferPos) % 12]; break; } //"If applicable, replace the current sample with the noise sample." if(_shared->NoiseOn & _voiceBit) { //"And the output noise sample at any point is N (after which is volume adjustment then the left - shift to 'restore' the low bit)" output = (int16_t)(_shared->NoiseLfsr * 2); } //"Apply the volume envelope. - This is the value used for modulating the next voice's pitch, if applicable." _shared->VoiceOutput = ((output * _envVolume) >> 11) & ~0x01; _envOut = (uint8_t)(_envVolume >> 4); //"Check FLG bit 7 (NOT previously loaded). //Check BRR header 'e' and 'l' bits to determine if the voice ends." if((_dsp->ReadReg(DspGlobalRegs::Flags) & 0x80) || (_shared->BrrHeader & 0x03) == 0x01) { //"When a BRR end-without-loop block is reached, the state is set to Release." _envMode = EnvelopeMode::Release; _envVolume = 0; } if(_shared->EveryOtherSample) { //"Handle KOFF and KON using previously loaded values. If KON, ENDX.x will be cleared in step S7." if(_shared->KeyOff & _voiceBit) { //When the voice is keyed off [...], the state is set to Release." _envMode = EnvelopeMode::Release; } if(_shared->KeyOn & _voiceBit) { _keyOnDelay = 5; //"When the voice is keyed on, the state is set to Attack." _envMode = EnvelopeMode::Attack; } } if(!_keyOnDelay) { //"Update the volume envelope, using previously loaded values." ProcessEnvelope(); } } void DspVoice::Step4() { _shared->Looped = 0; if(_interpolationPos >= 0x4000) { //"If a new group of BRR samples is required, load the second BRR byte and decode the group of 4 BRR samples." DecodeBrrSample(); if(_brrOffset >= 7) { //"If necessary, adjust the BRR pointer to the next block" if(_shared->BrrHeader & 0x01) { //"Flag the loop address for loading next step S2and set ENDX.x in step S7." _brrAddress = _shared->BrrNextAddress; _shared->Looped = _voiceBit; } else { _brrAddress += 9; } _brrOffset = 1; } else { _brrOffset += 2; } } //"Increment interpolation sample position as specified by pitch values." _interpolationPos = (_interpolationPos & 0x3FFF) + _shared->Pitch; if(_interpolationPos > 0x7FFF) { _interpolationPos = 0x7FFF; } UpdateOutput(false); } void DspVoice::Step5() { UpdateOutput(true); //"The new ENDX.x value is prepared, and can be overwritten. Reads will not see it yet." uint8_t voiceEnd = _dsp->ReadReg(DspGlobalRegs::VoiceEnd) | _shared->Looped; if(_keyOnDelay == 5) { //Clear bit for this voice when key on starts voiceEnd &= ~_voiceBit; } _shared->VoiceEndBuffer = voiceEnd; } void DspVoice::Step6() { //"The new VxOUTX value is prepared, and can be overwritten. Reads will not see it yet." _shared->OutRegBuffer = (uint8_t)(_shared->VoiceOutput >> 8); } void DspVoice::Step7() { //"The new ENDX.x value may now be read." _dsp->WriteGlobalReg(DspGlobalRegs::VoiceEnd, _shared->VoiceEndBuffer); //"The new VxENVX value is prepared, and can be overwritten. Reads will not see it yet." _shared->EnvRegBuffer = _envOut; } void DspVoice::Step8() { //"The new VxOUTX value may now be read." _dsp->WriteVoiceReg(_voiceIndex, DspVoiceRegs::Out, _shared->OutRegBuffer); } void DspVoice::Step9() { //"The new VxENVX value may now be read." _dsp->WriteVoiceReg(_voiceIndex, DspVoiceRegs::Envelope, _shared->EnvRegBuffer); } void DspVoice::Serialize(Serializer& s) { SV(_envVolume); SV(_prevCalculatedEnv); SV(_interpolationPos); SV(_envMode); SV(_brrAddress); SV(_brrOffset); SV(_voiceBit); SV(_keyOnDelay); SV(_envOut); SV(_bufferPos); SVArray(_sampleBuffer, 12); }