From 210401d6edafa006707cd0031c6c7f7065f71385 Mon Sep 17 00:00:00 2001 From: Gordon JC Pearce Date: Tue, 10 Sep 2024 00:14:25 +0100 Subject: [PATCH] filter control calculations seem about right --- plugin/chassis.cpp | 2 + plugin/voice.cpp | 13 +- plugin/voice.hpp | 19 +- plugin/voicecpu.cpp | 667 ++++++++++++++++++++++++++++---------------- 4 files changed, 448 insertions(+), 253 deletions(-) diff --git a/plugin/chassis.cpp b/plugin/chassis.cpp index 2a229ef..51d4acf 100644 --- a/plugin/chassis.cpp +++ b/plugin/chassis.cpp @@ -146,6 +146,8 @@ void Chassis::run(const float **, float **outputs, uint32_t frames, const MidiEv // printf("voice %d note = %02x ff71 = %04x\n",i, s.voice[i].note, s.voice[i].ff71 ); s.voice[i].envelope(s); + s.voice[i].calcFilter(s); + //printf("voice %d vcf level = %04x\n", i, s.voice[i].vcfenv ); } } diff --git a/plugin/voice.cpp b/plugin/voice.cpp index 3fa216b..4bad19f 100644 --- a/plugin/voice.cpp +++ b/plugin/voice.cpp @@ -41,8 +41,15 @@ void Voice::run(Synth &s, float *buffer, uint32_t samples) { // there's a resistor on the panel board to sprag the range float pw = s.ff4f / 32768.0f; - float fb, res = s.patchRam.vcfReso / 24.0f; // guess - float cut = s.patchRam.vcfFreq / 400.0f; // guess + float fb, res = s.patchRam.vcfReso / 28.0f; // guess + //float cut = s.patchRam.vcfFreq / 400.0f; // guess + + float cut = 248.0f * (powf(2,(vcfenv-0x1880)/1143.0f)); + // now radians + cut = 0.25 * 6.2832 * cut / 48000.0f; + + // now correct + cut = cut/(1+cut); float sqr = (s.patchRam.switch1 & 0x08) ? 0.63 : 0; //? 0.175 : 0; float saw = (s.patchRam.switch1 & 0x10) ? 0.8 : 0; //? 0.220 : 0; @@ -114,7 +121,7 @@ void Voice::run(Synth &s, float *buffer, uint32_t samples) { vr58c106 += ((vcaEnv - vr58c106) * 0.0075); lastpw = pw; - out = b4 * (0.275); + out = b4 * (0.2); buffer[i] += (gain * b4 * vr58c106); } diff --git a/plugin/voice.hpp b/plugin/voice.hpp index fda6c6b..8775a25 100644 --- a/plugin/voice.hpp +++ b/plugin/voice.hpp @@ -34,10 +34,12 @@ class Voice { void run(Synth &s, float *buffer, uint32_t samples); void envelope(Synth &s); void calcPitch(Synth &s); + void calcFilter(Synth &s); uint16_t ff71 = 0; // stores pitch + fraction float omega; + uint16_t vcfenv; private: enum { ATTACK, @@ -49,12 +51,13 @@ class Voice { K_ON, K_SUSTAIN } keyState = K_OFF; - bool ff00 = 0; // reset DCO clock flag - bool ff07 = 0; // set to indicate attack phase - bool ff08 = 0; // set to indicate decay/sustain phase - bool ff10 = 0; // note on bit - bool ff11 = 0; // drives the "gate" signal for VCA - bool ff33 = 0; // releasing flag + bool ff00 = 0; // reset DCO clock flag + bool ff07 = 0; // set to indicate attack phase + bool ff08 = 0; // set to indicate decay/sustain phase + bool ff10 = 0; // note on bit + bool ff11 = 0; // drives the "gate" signal for VCA + uint16_t ff27 = 0; // envelope for voice + bool ff33 = 0; // releasing flag uint16_t env; @@ -91,6 +94,9 @@ class Synth { uint16_t ff56 = 0; // LFO Delay envelope uint16_t ff5a = 0; // LFO Delay holdoff uint8_t ff64 = 0; // LFO mod sens amount + uint16_t ff65 = 0; // computed VCF bend amount + uint8_t ff6a = 0; + uint8_t ff6b = 0; uint8_t ff6e = 0; // fractional pitch temp uint16_t ff6f = 0; // computed pitch amount // uint16_t ff71 = 0; // unsure, to do with pitch @@ -126,7 +132,6 @@ class Synth { uint8_t switch2 = 24; } patchRam; - float pitchCV[104]; void runLFO(); diff --git a/plugin/voicecpu.cpp b/plugin/voicecpu.cpp index e19e6a1..da8b63f 100644 --- a/plugin/voicecpu.cpp +++ b/plugin/voicecpu.cpp @@ -36,249 +36,6 @@ bool Voice::isFree() { return ff10 == false; } -void Synth::lfoDelay() { - // compute LFO delay - - uint16_t a, bc, d, ea, tos; - - // 030d: 45 11 3f ONIW $0011,$3F ; are any notes enabled - // 0310: 4e 59 JRE $036B ; no, just run LFO - if (!keyon /* ff11 */) goto h036b; // skip ahead if no notes are pressed - - // 0312 - if (!(ff1e & 0x08)) goto h0323; // a note is running, don't reset - - // 0315 no note is running, reset it all - bc = 0; - ff56 = 0; // delay envelope - ff5a = 0; // holdoff timer - ff1e &= 0xf1; // mask bits in flag byte - -h0323: - if (!(ff1e & 0x02)) goto h0370; // compute delay envelope - if (!(ff1e & 0x04)) goto h0388; // compute holdoff timer - - // 032b - bc |= 0xff00; // initial scaling value? - -h032d: - tos = bc; // push bc - - // 032e - a = lfoDepthTable[patchRam.vcoLfo]; - - // MUL B; MOV A, EAH - a = (a * (bc >> 8)) >> 8; - - // 0333 ADDNCW $0064; MVI A, $FF - a += ff64; - if (a > 0xff) a = 0xff; - - // 0338 sets up HL to store computed pitch LFO output - - // 33b - bc = ff4d; // current LFO output - - // 033f - ea = (bc & 0xff) * a; // MUL C - d = a; // MOV D,A - a = (ea >> 8); // MOV A,EAH - bc &= 0xff00; - bc |= a; // MOV C,A - a = d; // MOV A,D - - // 0345 - ea = (bc >> 8) * a; // MUL B - ea += (bc & 0xff); // EADD EA, C - - // 0349 - ea >>= 3; // divide by eight - - // 034f - ff51 = ea; // save scaled pitch LFO - - bc = tos; // pop BC, contains scaling amount - a = patchRam.vcfLfo << 1; // amount is doubled and stored at ff48 - - // 0354 - ea = (bc >> 8) * a; // MUL B - a = ea >> 8; // MOV A, EAH - bc = ff4d; // current LFO output - - // 035b - ea = (bc & 0xff) * a; // MUL C - d = a; // MOV D,A - a = ea >> 8; // MOV A, EAH - bc &= 0xff00; - bc |= a; // MOV C,A - a = d; // MOV A,D - ea = (bc >> 8) * a; // MUL B - ea += (bc & 0xff); // EADD EA,C - ea >>= 1; // DSLR A - ff53 = ea; // save scaled VCF LFO - goto h03a1; - -h036b: - ff1e |= 0x08; // set LFO flag - goto h0323; - -h0370: // calculate holdoff time - // printf("0370 "); - ea = ff56; // holdoff time - bc = attackTable[patchRam.lfoDelay]; // stored at ff58 - // 0379 - ea += bc; // DADD EA,BC - ff56 = ea; // STEAX (DE) which still holds ff56 from 0x370 - - a = ea >> 8; // MOV A, EAH - if (a & 0xc0) goto h0385; // OFFI A, $C0 - bc &= 0xff; // MOV B, 0 - goto h032d; -h0385: - ff1e |= 0x02; // stop predelay flag - -h0388: - // printf("0388 "); - ea = ff5a; // envelope speed - - // 038d - bc = lfoDelayTable[patchRam.lfoDelay >> 4]; // delay setting divided by 8 and saved at ff6c - - // printf("---------------------------------------- %04x %04x\n", ea, bc); - - // 0391 DADDNC EA, BC - if ((ea + bc) > 0xffff) goto h039a; - ea += bc; - - // 394 - ff5a = ea; // STEAX (HL) hl still contains ff5a - - bc |= (ea & 0xff00); // MOV A, EAH; MOV B, A - goto h032d; - -h039a: - ff1e |= 0x04; - bc |= 0xff; // MVI B, $ff - goto h032d; - -h03a1: - // printf("LFO=%04x VCF=%04x flags=%02x holdoff=%04x envelope=%04x\n", ff51, ff53, ff1e, ff56, ff5a); - return; -} - -void Voice::calcPitch(Synth &s) { - uint32_t bc, ea, a; - - // 03ad - ea = 0x1818; - - // add in tuning value from ff61, not implemented - - // 03ba - bc = s.ff51; // computed pitch LFO - if (s.ff4a & 0x02) { - ea -= bc; - } else { - ea += bc; - } - - // 03c6 - // add in bender from ff68 - - // 03d2 - s.ff6f = ea; - - ea = ff71; - a = note; - - // 03e3 - bc = a << 8; - - // 03e6 - a = 0; // set from porta coefficient ff7d - - if (a != 0) goto h03f5; - - // 3eb - ea = bc; -h03ec: - ff71 = ea; // STEAX (DE++) - - // we're not looping so we can ignore until - // 03ee inrw ff0f voice counter - // 03f0 eqiw ff0f, 06 - // 03f3 jr 03e0 - - goto h0407; - -h03f5: - if (ea == bc) goto h03ec; // DNE EA, BC; JR 03EC store value - if (!(ea > bc)) goto h0401; // DGT EA, BC; JR 0401 - ea -= a; - if (!(ea > bc)) ea = bc; - goto h03ec; -h0401: - ea += a; - if (!(ea < bc)) ea = bc; - goto h03ec; - -h0407: - // this outputs the Sub Osc CV - // ignore until - // 0413 mviw ff0f, 0 reset voice counter - s.ff34 = 1; // unsure what this is used for - - // 0419 - ea = ff71; // pitch + fraction per voice - bc = s.ff6f; // tune + lfo + bend - ea += bc; - // 0424 - s.ff6e = ea & 0xff; // MOV A, EAL; STAW 006e - a = ea >> 8; // mov a, EAH - - // 0428 - if (a <= 0x2f) goto h04a5; // GTI A,$2F; JRE $04A5 - if (a >= 0x97) goto h04ac; // LTI A,$97; JRE $04AC - - a -= 0x30; - -h0432: - // printf("setting omega for note %d \n", a); - omega = ((s.pitchCV[a + 1] - s.pitchCV[a]) * (s.ff6e / 256.0)) + s.pitchCV[a]; - // 0432 onwards calculates the address for the CV - // table at E60 and stacks it - // 043a onwards fetches the value from the divider - // table and computes a linear interpolation with the next one up - // using the fractional value stored in ff6e - - // 045a onwards decides which divider to program - - // 0471 unstacks the CV table address and calculates a linear - // interpolation of this and the next CV value using ff6e - // 048b onwards sends it to the correct DAC - - // 0496 onwards works out which vocie to do next and loops - - // 04a3 - goto h04d5; - -h04a5: // pitch too low - s.ff6e = 0; - a = 0; - goto h0432; - -h04ac: // pitch too high - s.ff6e = 0; - a = 0x66; - goto h0432; - - // 04b3 programs the dividers somehow - // ignore until 04d5 - -h04d5: - return; -} - void Voice::on(uint32_t key, bool reset = 0) { // this current implementation doesn't reset the voice (void)reset; @@ -318,6 +75,428 @@ void Voice::off() { } } +void Synth::lfoDelay() { + // compute LFO delay + + uint16_t a, bc, d, ea, tos; + + // 030d: 45 11 3f ONIW $0011,$3F ; are any notes enabled + // 0310: 4e 59 JRE $036B ; no, just run LFO + if (!keyon /* ff11 */) goto h036b; // skip ahead if no notes are pressed + + // 0312: 5b 1e BIT 3,$001E ; ramp-up is complete? + // 0314: ce JR $0323 ; no + if (!(ff1e & 0x08)) goto h0323; + + // no not was playing so reset + bc = 0; + ff56 = 0; // delay envelope + ff5a = 0; // holdoff timer + ff1e &= 0xf1; // mask bits in flag byte + +h0323: + if (!(ff1e & 0x02)) goto h0370; // compute delay envelope + if (!(ff1e & 0x04)) goto h0388; // compute holdoff timer + + // 032b + bc |= 0xff00; // initial scaling value? + +h032d: + tos = bc; // push bc + + // 032e + // 032e: 01 49 LDAW $0049 ; DCO LFO depth + a = lfoDepthTable[patchRam.vcoLfo]; + + // MUL B; MOV A, EAH + a = (a * (bc >> 8)) >> 8; + + // add in the modwheel amount, clamp if it exceeds 0xff + // 0333 ADDNCW $0064; MVI A, $FF + a += ff64; + if (a > 0xff) a = 0xff; + + // 0338 sets up HL to store computed pitch LFO output + + // 33b + bc = ff4d; // current LFO output + + // 033f + ea = (bc & 0xff) * a; // MUL C + d = a; // MOV D,A + a = (ea >> 8); // MOV A,EAH + bc &= 0xff00; + bc |= a; // MOV C,A + a = d; // MOV A,D + + // 0345 + ea = (bc >> 8) * a; // MUL B + ea += (bc & 0xff); // EADD EA, C + + // 0349 + ea >>= 3; // divide by eight + + // 034f + ff51 = ea; // save scaled pitch LFO + + bc = tos; // pop BC, contains scaling amount + // 0352: 01 48 LDAW $0048 ; VCF LFO amount + a = patchRam.vcfLfo << 1; // amount is doubled and stored at ff48 + + // 0354 + ea = (bc >> 8) * a; // MUL B + a = ea >> 8; // MOV A, EAH + bc = ff4d; // current LFO output + + // 035b + ea = (bc & 0xff) * a; // MUL C + d = a; // MOV D,A + a = ea >> 8; // MOV A, EAH + bc &= 0xff00; + bc |= a; // MOV C,A + a = d; // MOV A,D + ea = (bc >> 8) * a; // MUL B + ea += (bc & 0xff); // EADD EA,C + ea >>= 1; // DSLR A + ff53 = ea; // save scaled VCF LFO + goto h03a1; + +h036b: + ff1e |= 0x08; // set LFO flag + goto h0323; + +h0370: // calculate holdoff time + ea = ff56; // holdoff time + + // 0375: 70 1f 58 ff LBCD $FF58 ; add on delay amount + bc = attackTable[patchRam.lfoDelay]; // stored at ff58 + // 0379 + ea += bc; // DADD EA,BC + ff56 = ea; // STEAX (DE) which still holds ff56 from 0x370 + + a = ea >> 8; // MOV A, EAH + if (a & 0xc0) goto h0385; // OFFI A, $C0 + bc &= 0xff; // MOV B, 0 + goto h032d; +h0385: + ff1e |= 0x02; // stop predelay flag + +h0388: + ea = ff5a; // envelope speed + + // 038d: 70 1f 6c ff LBCD $FF6C ; value from "short" LFO lookup table + bc = lfoDelayTable[patchRam.lfoDelay >> 4]; // delay setting divided by 8 and saved at ff6c + + // 0391 DADDNC EA, BC + if ((ea + bc) > 0xffff) goto h039a; + ea += bc; + + // 0394 + ff5a = ea; // STEAX (HL) hl still contains ff5a + + bc |= (ea & 0xff00); // MOV A, EAH; MOV B, A + goto h032d; + +h039a: + ff1e |= 0x04; + bc |= 0xff; // MVI B, $ff + goto h032d; + +h03a1: + return; +} + +void Voice::calcPitch(Synth &s) { + uint32_t bc, ea, a; + + // 03ad + ea = 0x1818; + + // add in tuning value from ff61, not implemented + + // 03ba + // 03ba: 70 1f 51 ff LBCD $FF51 ; computed pitch LFO? + bc = s.ff51; // computed pitch LFO + if (s.ff4a & 0x02) { + ea -= bc; + } else { + ea += bc; + } + + // 03c6 + // add in bender from ff68 + + // 03d2: 24 6f ff LXI DE,$FF6F + // 03d5: 48 92 STEAX (DE) ; save final value + s.ff6f = ea; + + // these are set, because in the uPD7811 code it loops around all six voices + // 03d7: 71 0f 00 MVIW $000F,$00 ; voice counter + // 03da: 24 71 ff LXI DE,$FF71 ; DAC pitch table for voices + // 03dd: 34 09 ff LXI HL,$FF09 ; note pitch table for voices + // 03e0: 48 82 LDEAX (DE) ; fetch + // 03e2: 2d LDAX (HL+) ; fetch note + + ea = ff71; + a = note; + + // 03e3 MOV B,A + bc = a << 8; + + // 03e6: 01 7d LDAW $007D ; porta coefficient + a = 0x00; // set from porta coefficient ff7d + + if (a != 0) goto h03f5; + + // 3eb + ea = bc; +h03ec: + ff71 = ea; // STEAX (DE++) + + // if we were handling all voices in this loop we'd do + // 03ee: 20 0f INRW $000F ; voice counter + // 03f0: 75 0f 06 EQIW $000F,$06 ; loop + // 03f3: ec JR $03E0 ; loop around note + // 03f4: d2 JR $0407 ; jump ahead + + goto h0407; + +h03f5: + // portamento down + if (ea == bc) goto h03ec; // DNE EA, BC; JR 03EC store value + if (!(ea > bc)) goto h0401; // DGT EA, BC; JR 0401 + ea -= a; + if (!(ea > bc)) ea = bc; + goto h03ec; +h0401: + // portamento up + ea += a; + if (!(ea < bc)) ea = bc; + goto h03ec; + +h0407: + // bit of code that outputs sub osc CV + // 0413: 71 0f 00 MVIW $000F,$00 ; reset voice counter + // 0416: 71 34 01 MVIW $0034,$01 ; voice selector, first voice + + // 0419 + ea = ff71; // pitch + fraction per voice + bc = s.ff6f; // tune + lfo + bend + ea += bc; + // 0424 + s.ff6e = ea & 0xff; // MOV A, EAL; STAW 006e + a = ea >> 8; // mov a, EAH + + // 0428 + if (a <= 0x2f) goto h04a5; // GTI A,$2F; JRE $04A5 + if (a >= 0x97) goto h04ac; // LTI A,$97; JRE $04AC + + a -= 0x30; + +h0432: + // printf("setting omega for note %d \n", a); + omega = ((s.pitchCV[a + 1] - s.pitchCV[a]) * (s.ff6e / 256.0)) + s.pitchCV[a]; + // 0432 onwards calculates the address for the CV + // table at E60 and stacks it + // 043a onwards fetches the value from the divider + // table and computes a linear interpolation with the next one up + // using the fractional value stored in ff6e + + // 045a onwards decides which divider to program + + // 0471 unstacks the CV table address and calculates a linear + // interpolation of this and the next CV value using ff6e + // 048b onwards sends it to the correct DAC + + // 0496 onwards works out which voice to do next and loops + + // 04a3: 4e 30 JRE $04D5 ; calculate filter + return; + +h04a5: // pitch too low + s.ff6e = 0; + a = 0; + goto h0432; + +h04ac: // pitch too high + s.ff6e = 0; + a = 0x66; + goto h0432; + + // 04b3 programs the dividers somehow +} + +void Voice::calcFilter(Synth &s) { + // 04d5 + + uint16_t a, bc, ea, hl, tos; + + goto h04d5; + +h04d5: + s.ff6a = 0; + // 04d8: 70 1f 3d ff LBCD $FF3D ; VCF cutoff + // 04dc: a5 DMOV EA,BC + ea = s.patchRam.vcfFreq << 7; // stored exended to two bytes + bc = s.ff53; // scaled VCF LFO + + // 04e1: 59 4a BIT 1,$004A ; LFO add/sub flag + // 04e3: e7 JR $04CB + // 04e4: 74 b5 DSUBNB EA,BC ; add, skip if no borrow + // 04e6: 71 6a 01 MVIW $006A,$01 ; set a flag? + if (!(s.ff4a & 0x02)) { + ea += bc; + } else { + if ((ea - bc) < bc) { + s.ff6a = 1; + } + ea -= bc; + } + + bc = s.ff65; + if (!(s.ff1e & 0x20)) { + if ((ea + bc) > 0xffff) { + s.ff6a = 0; + } + ea += bc; + } else { + if ((ea - bc) < bc) { + s.ff6a = 1; + } + ea -= bc; + } + + // 04f6 + tos = ea; + + // 04f7: 71 0f 00 MVIW $000F,$00 ; voice counter + // 04fa: 71 34 01 MVIW $0034,$01 ; voice enable bit + // 04fd: 34 71 ff LXI HL,$FF71 ; pitch + fraction table + // 0500: 24 25 ff LXI DE,$FF25 ; release time + // 0503: b3 PUSH HL ; TOS = address of DAC note, then VCF Bias + + // 0504 + ea = ff27; // current envelope level + + // in the real ROM the envelope code would run here + + // there's something about the first voice, I'd need to emulate that a bit more + + // 05a6 sets the DAC depending on the state of the ENV/GATE switch + // 05c0: a3 POP HL ; HL might have started as FF71 + + // hl = ff71; // this was stored on the stack back at 0503 + + a = s.ff6a; + s.ff6b = a; + + ea = ff27; + bc = ea; + + // 05c8: 01 41 LDAW $0041 ; VCF ENV MOD + a = s.patchRam.vcfEnv << 1; // stored doubled + + // 05c8 + ea = (bc & 0xff) * a; // MUL C + a = ea >> 8; // MOV A, EAH + bc &= 0xff00; + bc |= a; // MOV C,A + a = s.patchRam.vcfEnv << 1; // stored doubled + ea = (bc >> 8) * a; // MUL B + ea += (bc & 0xff); // EADD EA,C + bc = ea; + ea = tos; // precomputed VCF knob + LFO + bend + + if (!(s.patchRam.switch2 & 0x02)) { + if ((ea + bc) > 0xffff) { + s.ff6b = 0; + } + ea += bc; + } else { + if ((ea - bc) < bc) { + s.ff6b = 1; + } + ea -= bc; + } + + // 05e0 + tos = ea; // save + ea = ff71; // pitch value + + ea >>= 2; + bc = ea; + ea >>= 1; + ea += bc; // multiplied by 0.375 + + bc = 0x1680; // this is 0x3c00 * 0.375, middle C * 0.375 + + if (ea <= bc) goto h0620; + + // 05f3 + ea -= bc; + bc = ea; + + // 05f6 + a = s.patchRam.vcfKey << 1; // stored doubled at ff42 + ea = (bc & 0xff) * a; // MUL C + a = ea >> 8; // MOV A, EAH + bc &= 0xff00; + bc |= a; // MOV C,A + a = s.patchRam.vcfKey << 1; // needs lookup table + ea = (bc >> 8) * a; // MUL B + ea += (bc & 0xff); // EADD EA,C + bc = ea; + + // 0603 + ea = tos; // get saved VCF back + + // 0604: 74 a5 DADDNC EA,BC + // 0606: 71 6b 00 MVIW $006B,$00 + + if ((ea + bc) > 0xffff) { + s.ff6b = 0; + } + ea += bc; + + h0609: + if (!(ea & 0xc000)) goto h063c; + ea = 0; + // 0611 + if (!(s.ff6b & 0x01)) + ea = 0x3fff; + goto h063c; + +h0620: + bc = ea; + ea = 0x1680; + ea -= bc; + bc = ea; + + // 0627 + a = s.patchRam.vcfKey << 1; // stored doubled at ff42 + ea = (bc & 0xff) * a; // MUL C + a = ea >> 8; // MOV A, EAH + bc &= 0xff00; + bc |= a; // MOV C,A + a = s.patchRam.vcfKey << 1; // needs lookup table + ea = (bc >> 8) * a; // MUL B + ea += (bc & 0xff); // EADD EA,C + bc = ea; + + // 0634 + ea = tos; + if ((ea - bc) < bc) { + s.ff6b = 1; + } + ea -= bc; + goto h0609; + + + h063c: + vcfenv = ea; + return; +} + void Voice::envelope(Synth &s) { uint16_t bc, ea = env; @@ -375,6 +554,8 @@ h0563: h0590: env = ea; + ff27 = ea; + // printf("%04x %d %d %d %d %d \n", ea, ff07, ff08, ff10, ff11, ff33); }