filter control calculations seem about right

This commit is contained in:
Gordon JC Pearce 2024-09-10 00:14:25 +01:00
parent ca65162e3f
commit 210401d6ed
4 changed files with 448 additions and 253 deletions

View File

@ -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 );
}
}

View File

@ -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);
}

View File

@ -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,
@ -54,6 +56,7 @@ class Voice {
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();

View File

@ -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);
}