Compare commits

...

3 Commits

Author SHA1 Message Date
Gordon JC Pearce
5d80fe5870 working modwheel and pitchbend 2024-10-18 23:57:14 +01:00
Gordon JC Pearce
3cfe118acc various envelope and control range fixes, also an audio PWM bug 2024-10-18 00:56:37 +01:00
Gordon JC Pearce
66d2cec2c9 now pulls some patch params from patchram 2024-10-17 23:57:28 +01:00
7 changed files with 215 additions and 65 deletions

View File

@ -16,6 +16,7 @@
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic29.hpp"
#include "peacock.hpp"
void Peacock::initParameter(uint32_t index, Parameter& parameter) {
@ -290,24 +291,124 @@ void Peacock::initParameter(uint32_t index, Parameter& parameter) {
parameter.midiCC = 26;
break;
case p_modWheel:
parameter.hints = kParameterIsAutomatable | kParameterIsHidden;
parameter.name = "Mod wheel";
parameter.symbol = "pfau_modwheel";
case p_vcoBend:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger;
parameter.name = "VCO Bend";
parameter.symbol = "pfau_vcobend";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 1;
parameter.ranges.def = 43.0f;
break;
case p_holdPedal:
parameter.hints = kParameterIsAutomatable | kParameterIsHidden;
parameter.name = "Hold Pedal";
parameter.symbol = "pfau_holdpedal";
case p_vcfBend:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger;
parameter.name = "VCF Bend";
parameter.symbol = "pfau_vcfbend";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 64;
parameter.ranges.def = 43.0f;
break;
}
// 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 << (uint8_t)(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 |= (uint8_t)value << 3;
break;
case p_vcoBend:
ic29.vcoBend = (uint8_t)value;
break;
}
}

View File

@ -49,6 +49,18 @@ void Assigner::handleMidi(const MidiEvent *ev) {
case 0x90:
noteOn(ev->data[1]);
break;
case 0xb0:
switch (ev->data[1]) {
case 0x01:
ic29.modWheel = ev->data[2];
break;
default:
break;
}
break; // nothing to do here except in special cases where we don't expect the host to pass on controls
case 0xe0: // pitch bend;
ic29.pitchBend = ev->data[2] << 7 | ev->data[1];
break;
default:
d_debug("unhandled MIDI event, status %02x value %02x\n", ev->data[0], ev->data[1]);
break;

View File

@ -24,12 +24,7 @@ Synth ic29;
Synth::Synth() {
d_debug("initialising synth\n");
envAtk = 0x00;
envDcy = 0x1f;
envStn = 0x00;
envRls = 0x1f;
portaCoeff = 0x0;
lfo.speed = 0x06;
}
void Synth::buildTables(double sampleRate) {
@ -48,17 +43,22 @@ void Synth::run() {
ic29.lfo.run();
masterPitch = 0x1818;
uint16_t vcoLfoDepth = ic29.lfoDepthTable[ic29.patchRam.vcoLfoMod] + ic29.modWheel;
vcoLfoDepth = (vcoLfoDepth < 0xff) ? vcoLfoDepth : 0xff;
masterPitch += (lfo.lfoOut * vcoLfoDepth) >> 11;
if (pitchBend < 0x0100) pitchBend = 0x0100;
masterPitch += (((pitchBend >> 8) - 0x20) * vcoBend) / 1.281;
// need to calculate VCF "base" setting
// need to calculate PWM
// various on/off switches
// PWM is bit 0 sw2, 0 = fixed 1 = lfo
// 0 sets EA to 0x3fff, 1 adds
uint16_t pwmVal = 0x2000 - ic29.lfo.lfoOut;
if (0) pwmVal = 0x3fff;
if (ic29.patchRam.switch2 & 0x01) pwmVal = 0x3fff;
ic29.pwm = pwmVal / 40960.0f * (1); // 0.5 is knob val
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();
@ -92,7 +92,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;
@ -107,14 +107,14 @@ void LFO::run() {
Envelope::Envelope() {
level = 0;
phase = ENV_IDLE;
phase = ENV_RLS;
}
void Envelope::run() {
uint16_t tempStn = ic29.envStn << 7;
uint16_t tempStn = ic29.patchRam.sustain << 7;
switch (phase) {
case ENV_ATK:
level += atkTable[ic29.envAtk];
level += atkTable[ic29.patchRam.attack];
if (level > 0x3fff) {
level = 0x3fff;
phase = ENV_DCY;
@ -122,13 +122,13 @@ void Envelope::run() {
break;
case ENV_DCY:
if (level > tempStn) {
level = (((level - tempStn) * dcyTable[ic29.envDcy]) >> 16) + tempStn;
level = (((level - tempStn) * dcyTable[ic29.patchRam.decay]) >> 16) + tempStn;
} else {
level = tempStn;
}
break;
case ENV_RLS:
level = (level * dcyTable[ic29.envRls]) >> 16;
level = (level * dcyTable[ic29.patchRam.release]) >> 16;
break;
case ENV_IDLE:
default:
@ -184,14 +184,25 @@ void Voice::update() {
// calculate the once-per-block values
env.run();
calcPitch();
pw = 0.5 - ic29.pwm;
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;
env.on(); // FIXME move to synth update code
if (env.phase == env.ENV_RLS) {
env.on(); // FIXME move to synth update code
}
}
void Voice::off() {

View File

@ -25,11 +25,11 @@ class LFO {
LFO();
void run();
int16_t lfoOut;
uint8_t speed;
uint8_t rate;
private:
uint8_t
phase;
phase;
uint16_t holdoff;
uint16_t envelope;
enum { LFO_RUN,
@ -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];
};
@ -79,10 +80,11 @@ class Voice {
private:
Envelope env; // calculated envelope value
uint16_t pitch = 0x1818; // calculated pitch value with porta and master pitch etc
float delay; // delay slot for polyblep
bool pulseStage;
float pw, lastpw, pwrc;
float delay = 0; // delay slot for polyblep
bool pulseStage = 0;
float pw = 0, lastpw = 0, pwrc = 0;
float subosc = -1;
float sub = 0, saw = 0;
float phase = 0, omega = 0;
enum { V_DONE,
V_OFF,
@ -101,8 +103,6 @@ class Synth {
void voiceOn(uint8_t voice, uint8_t note);
void voiceOff(uint8_t voice);
void sustainSwitch(uint8_t val);
void pitchBend(int16_t bend);
void modWheel(uint8_t wheel);
void buildTables(double sampleRate);
double sampleRate;
@ -114,13 +114,39 @@ class Synth {
double pitchTable[104];
// private:
int16_t lfoPitch;
int16_t bendPitch;
uint8_t vcoBend=42;
uint8_t vcfBend=42;
Voice voices[NUM_VOICES];
LFO lfo;
uint16_t pitchBend = 0x2000;
uint8_t modWheel = 0x00;
float pwm;
struct {
uint8_t lfoRate = 0x3f;
uint8_t lfoDelay = 0x00;
uint8_t vcoLfoMod = 0x00;
uint8_t pwmLfoMod = 0x27;
uint8_t noiseLevel = 0x00;
uint8_t vcfCutoff = 0x4d;
uint8_t vcfReso = 0x14;
uint8_t vcfEnvMod = 0x04;
uint8_t vcfLfoMod = 0x00;
uint8_t vcfKeyTrk = 0x6f;
uint8_t vcaLevel = 0x22;
uint8_t attack = 0x0d;
uint8_t decay = 0x57;
uint8_t sustain = 0x58;
uint8_t release = 0x23;
uint8_t subLevel = 0x0e;
uint8_t switch1 = 0x1a;
uint8_t switch2 = 0x10;
} patchRam;
const static uint16_t lfoDelayTable[8];
const static uint8_t lfoDepthTable[128];
const static uint8_t portaTable[128];
};
// global

View File

@ -54,7 +54,7 @@ const uint16_t Envelope::dcyTable[128] = {
0xffd8, 0xffdc, 0xffe0, 0xffe4, 0xffe8, 0xffec, 0xfff0, 0xfff1, 0xfff2,
0xfff3, 0xfff4};
extern const uint8_t lfoDepthTable[128] = {
const uint8_t Synth::lfoDepthTable[128] = {
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
@ -67,6 +67,27 @@ extern const uint8_t lfoDepthTable[128] = {
0xb0, 0xb4, 0xb8, 0xbc, 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf8, 0xff, 0xff};
const uint16_t Synth::lfoDelayTable[8] = {
0xffff, 0x0419, 0x020c, 0x015e, 0x0100, 0x0100, 0x0100, 0x0100};
const uint8_t Synth::portaTable[128] = {
0x00, 0xff, 0xf7, 0xef, 0xe7, 0xdf, 0xd7, 0xcf,
0xc7, 0xbf, 0xb7, 0xaf, 0xa7, 0x9f, 0x97, 0x8f,
0x87, 0x7f, 0x77, 0x6f, 0x67, 0x5f, 0x57, 0x4f,
0x47, 0x3f, 0x3d, 0x3b, 0x39, 0x37, 0x35, 0x33,
0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25, 0x23,
0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13,
0x12, 0x11, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x0f,
0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a,
0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08,
0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07,
0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05,
0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04,
0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02,
0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
const uint16_t LFO::lfoRateTable[128] = {
0x0005, 0x000f, 0x0019, 0x0028, 0x0037, 0x0046, 0x0050, 0x005a, 0x0064,
0x006e, 0x0078, 0x0082, 0x008c, 0x0096, 0x00a0, 0x00aa, 0x00b4, 0x00be,
@ -83,24 +104,3 @@ const uint16_t LFO::lfoRateTable[128] = {
0x07a8, 0x07f8, 0x085c, 0x08c0, 0x0924, 0x0988, 0x09ec, 0x0a50, 0x0ab4,
0x0b18, 0x0b7c, 0x0be0, 0x0c58, 0x0cd0, 0x0d48, 0x0dde, 0x0e74, 0x0f0a,
0x0fa0, 0x1000};
extern const uint16_t lfoDelayTable[8] = {
0xffff, 0x0419, 0x020c, 0x015e, 0x0100, 0x0100, 0x0100, 0x0100};
extern const uint8_t portaTable[128] = {
0x00, 0xff, 0xf7, 0xef, 0xe7, 0xdf, 0xd7, 0xcf,
0xc7, 0xbf, 0xb7, 0xaf, 0xa7, 0x9f, 0x97, 0x8f,
0x87, 0x7f, 0x77, 0x6f, 0x67, 0x5f, 0x57, 0x4f,
0x47, 0x3f, 0x3d, 0x3b, 0x39, 0x37, 0x35, 0x33,
0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25, 0x23,
0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13,
0x12, 0x11, 0x10, 0x10, 0x10, 0x10, 0x0f, 0x0f,
0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a,
0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08,
0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07,
0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05,
0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04,
0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02,
0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};

View File

@ -31,9 +31,9 @@ void Voice::run(float *buffer, uint32_t samples) {
// generate a full block of samples for the oscillator
float y, out, t;
float saw = 0;
float gain = env.level / 16384.0;
if (subosc > 0) subosc = sub; else subosc = -sub;
// this uses an adaptation of Mystran's Polyblep oscillator
@ -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

@ -52,8 +52,8 @@ class Peacock : public Plugin {
p_sustain,
p_release,
p_chorus, // 0, 1, 2
p_modWheel,
p_holdPedal,
p_vcoBend,
p_vcfBend,
paramCount
};
@ -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