various envelope and control range fixes, also an audio PWM bug

This commit is contained in:
Gordon JC Pearce 2024-10-18 00:56:37 +01:00
parent 66d2cec2c9
commit 3cfe118acc
6 changed files with 126 additions and 12 deletions

View File

@ -17,6 +17,7 @@
*/
#include "peacock.hpp"
#include "ic29.hpp"
void Peacock::initParameter(uint32_t index, Parameter& parameter) {
switch (index) {
@ -311,3 +312,107 @@ void Peacock::initParameter(uint32_t index, Parameter& parameter) {
}
// chorus, porta, bend range, key mode still to do
}
void Peacock::setParameterValue(uint32_t index, float value) {
// should be trapped by host, but let's be safe
if (value < 0.0f) value = 0.0f;
if (value > 127.0f) value = 127.0f;
switch (index) {
case p_lfoRate:
ic29.patchRam.lfoRate = value;
break;
case p_lfoDelay:
ic29.patchRam.lfoDelay = value;
break;
case p_vcoLfoMod:
ic29.patchRam.vcoLfoMod = value;
break;
case p_pwmLfoMod:
ic29.patchRam.pwmLfoMod = value / 1.27;
break;
case p_subLevel:
ic29.patchRam.subLevel = value;
break;
case p_noiseLevel:
ic29.patchRam.noiseLevel = value;
break;
case p_vcfCutoff:
ic29.patchRam.vcfCutoff = value;
break;
case p_vcfReso:
ic29.patchRam.vcfReso = value;
break;
case p_vcfEnvMod:
ic29.patchRam.vcfEnvMod = value;
break;
case p_vcfLFoMod:
ic29.patchRam.vcfLfoMod = value;
break;
case p_vcfKeyTrk:
ic29.patchRam.vcfKeyTrk = value;
break;
case p_vcaLevel:
ic29.patchRam.vcaLevel = value;
break;
case p_attack:
ic29.patchRam.attack = value;
break;
case p_decay:
ic29.patchRam.decay = value;
break;
case p_sustain:
ic29.patchRam.sustain = value;
break;
case p_release:
ic29.patchRam.release = value;
break;
// switch 1 params
case p_vcoRange: // bits 0-2 of switch 1
// doesn't look great in Carla because of odd behaviour with small integer knobs
ic29.patchRam.switch1 &= 0xf8;
ic29.patchRam.switch1 |= (1 << (int)(value - 1));
break;
case p_sqrOn: // bit 3 of switch 1
ic29.patchRam.switch1 &= 0xf7;
ic29.patchRam.switch1 |= (value >= 0.5) << 3;
break;
case p_sawOn: // bit 4 of switch 1
ic29.patchRam.switch1 &= 0xef;
ic29.patchRam.switch1 |= (value >= 0.5) << 4;
break;
// missing chorus switch
// switch 2 params
case p_pwmMode: // bit 0 of switch 2
ic29.patchRam.switch2 &= 0xfe;
ic29.patchRam.switch2 |= (value >= 0.5);
break;
case p_vcfEnvPol: // bit 1 of switch 2
ic29.patchRam.switch2 &= 0xfd;
ic29.patchRam.switch2 |= (value >= 0.5) << 1;
break;
case p_vcaEnvGate:
ic29.patchRam.switch2 &= 0xfb;
ic29.patchRam.switch2 |= (value >= 0.5) << 2;
break;
case p_hpfMode: // bits 3-4 of switch 2
// doesn't look great in Carla because of odd behaviour with small integer knobs
if (value > 3) value = 3;
ic29.patchRam.switch2 &= 0xf3;
ic29.patchRam.switch2 |= (int)value << 3;
break;
case p_modWheel:
//s.ff64 = (int)value << 1;
break;
}
}

View File

@ -49,6 +49,8 @@ void Assigner::handleMidi(const MidiEvent *ev) {
case 0x90:
noteOn(ev->data[1]);
break;
case 0xb0:
break; // nothing to do here except in special cases where we don't expect the host to pass on controls
default:
d_debug("unhandled MIDI event, status %02x value %02x\n", ev->data[0], ev->data[1]);
break;

View File

@ -25,7 +25,6 @@ Synth ic29;
Synth::Synth() {
d_debug("initialising synth\n");
portaCoeff = 0x0;
lfo.speed = 0x06;
}
void Synth::buildTables(double sampleRate) {
@ -54,7 +53,7 @@ void Synth::run() {
uint16_t pwmVal = 0x2000 - ic29.lfo.lfoOut;
if (ic29.patchRam.switch2 & 0x01) pwmVal = 0x3fff;
ic29.pwm = 0.5 - pwmVal / 40960.0f * (ic29.patchRam.pwmLfoMod/128.0f);
ic29.pwm = 0.5 - pwmVal / 32768.0f * (ic29.patchRam.pwmLfoMod / 106.0f);
for (uint8_t i = 0; i < NUM_VOICES; i++) {
ic29.voices[i].update();
@ -88,7 +87,7 @@ void LFO::run() {
// slightly different from the real synth code which does not use signed
// variables, since the CPU doesn't support them
lfoOut += phase ? lfoRateTable[speed] : -lfoRateTable[speed];
lfoOut += phase ? lfoRateTable[rate] : -lfoRateTable[rate];
if (lfoOut > 0x1fff) {
lfoOut = 0x1fff;
phase = 0;
@ -103,7 +102,7 @@ void LFO::run() {
Envelope::Envelope() {
level = 0;
phase = ENV_IDLE;
phase = ENV_RLS;
}
void Envelope::run() {
@ -180,19 +179,26 @@ void Voice::update() {
// calculate the once-per-block values
env.run();
calcPitch();
if (ic29.patchRam.switch1 & 0x08) pw = ic29.pwm;
else pw = 0;
saw = (float)((ic29.patchRam.switch1 & 0x10) == true);
pw = (ic29.patchRam.switch1 & 0x08) ? ic29.pwm : 0.0f;
saw = (ic29.patchRam.switch1 & 0x10) ? 1.0f : 0.0f;
sub = ic29.patchRam.subLevel / 128.0f;
ic29.lfo.rate = ic29.patchRam.lfoRate;
// do filter values
}
void Voice::on(uint8_t key) {
voiceState = V_ON;
if (note != key) {
phase = 0;
}
note = key;
if (env.phase == env.ENV_RLS) {
env.on(); // FIXME move to synth update code
}
}
void Voice::off() {

View File

@ -25,7 +25,7 @@ class LFO {
LFO();
void run();
int16_t lfoOut;
uint8_t speed;
uint8_t rate;
private:
uint8_t
@ -56,13 +56,14 @@ class Envelope {
uint16_t level;
// private:
enum {
ENV_ATK,
ENV_DCY,
ENV_RLS,
ENV_IDLE
} phase;
private:
static const uint16_t atkTable[128];
static const uint16_t dcyTable[128];
};

View File

@ -65,7 +65,7 @@ void Voice::run(float *buffer, uint32_t samples) {
}
delay += saw * (0.8 - (1.6 * phase)); // magic numbers observed on oscilloscope from real synth
delay += (0.63 - (pw * 1.26)) + (pulseStage ? -0.63f : 0.63f); // add in the scaled pulsewidth to restore DC level
delay += (0.63 - (pwrc * 1.26)) + (pulseStage ? -0.63f : 0.63f); // add in the scaled pulsewidth to restore DC level
// the DC correction is important because the hardware synth is AC-coupled effectively high-passing
// the signal at about 10Hz or so, preventing any PWM rumble from leaking through!
delay += subosc;

View File

@ -74,7 +74,7 @@ class Peacock : public Plugin {
// void initAudioPort(bool input, uint32_t index, AudioPort &port) override;
void initParameter(uint32_t index, Parameter &parameter) override;
// void setParameterValue(uint32_t index, float value) override;
void setParameterValue(uint32_t index, float value) override;
// float getParameterValue(uint32_t index) const override;
// Processing