Compare commits

...

8 Commits
voice ... main

Author SHA1 Message Date
Gordon JC Pearce
5914d49cdb fixed audio socket labelling 2024-10-17 23:24:53 +01:00
Gordon JC Pearce
be894c562e added dpf controls, which are not connected to anything yet 2024-10-17 23:22:08 +01:00
Gordon JC Pearce
e806584a46 start work on DPF controls 2024-10-17 00:03:10 +01:00
Gordon JC Pearce
18dd0947d4 improved PWM code 2024-10-17 00:01:46 +01:00
Gordon JC Pearce
b8265f6938 fixed envelope sustain level, basic LFO + PWM 2024-10-16 21:41:08 +01:00
Gordon JC Pearce
34800af7a1 tidied up envelope 2024-10-16 14:15:38 +01:00
Gordon JC Pearce
84eef25f8d add ROM lookup tables 2024-10-16 00:10:25 +01:00
Gordon JC Pearce
effdce42be basics of oscillator 2024-10-15 22:56:18 +01:00
8 changed files with 634 additions and 39 deletions

View File

@ -11,7 +11,9 @@ NAME = peacock
FILES_DSP = \
peacock.cpp \
controls.cpp \
ic1.cpp \
oscillator.cpp \
ic29.cpp
include ../dpf/Makefile.plugins.mk

313
plugin/controls.cpp Normal file
View File

@ -0,0 +1,313 @@
/*
Peacock-8 VA polysynth
Copyright 2024 Gordon JC Pearce <gordonjcp@gjcp.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "peacock.hpp"
void Peacock::initParameter(uint32_t index, Parameter& parameter) {
switch (index) {
case p_lfoRate:
parameter.hints = kParameterIsAutomatable;
parameter.name = "LFO Rate";
parameter.symbol = "pfau_lforate";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 48.0f;
parameter.midiCC = 3;
break;
case p_lfoDelay:
parameter.hints = kParameterIsAutomatable;
parameter.name = "LFO Delay";
parameter.symbol = "pfau_lfodelay";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 9;
break;
case p_vcoRange:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger;
parameter.name = "Range";
parameter.symbol = "pfau_vcorange";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 2.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 12;
parameter.enumValues.count = 3;
parameter.enumValues.restrictedMode = true;
{
ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[3];
enumValues[0].value = 0.0f;
enumValues[0].label = "16'";
enumValues[1].value = 1.0f;
enumValues[1].label = "8'";
enumValues[2].value = 2.0f;
enumValues[2].label = "4'";
parameter.enumValues.values = enumValues;
}
break;
case p_vcoLfoMod:
parameter.hints = kParameterIsAutomatable;
parameter.name = "LFO";
parameter.symbol = "pfau_lfo";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 10.0f;
parameter.midiCC = 13;
break;
case p_pwmLfoMod:
parameter.hints = kParameterIsAutomatable;
parameter.name = "PWM";
parameter.symbol = "pfau_pwm";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 48.0f;
parameter.midiCC = 14;
break;
case p_pwmMode:
parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
parameter.name = "PWM Mode";
parameter.symbol = "pfau_pwmmode";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 1.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 15;
parameter.enumValues.count = 2;
parameter.enumValues.restrictedMode = true;
{
ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[2];
enumValues[0].value = 0.0f;
enumValues[0].label = "LFO";
enumValues[1].value = 1.0f;
enumValues[1].label = "MAN";
parameter.enumValues.values = enumValues;
}
break;
case p_sawOn:
parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
parameter.name = "Saw";
parameter.symbol = "pfau_saw";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 1.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 17;
break;
case p_sqrOn:
parameter.hints = kParameterIsAutomatable | kParameterIsBoolean;
parameter.name = "Square";
parameter.symbol = "pfau_sqr";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 1.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 16;
break;
case p_subLevel:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Sub Osc";
parameter.symbol = "pfau_sub";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 18;
break;
case p_noiseLevel:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Noise";
parameter.symbol = "pfau_noise";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 19;
break;
case p_hpfMode:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger;
parameter.name = "HPF";
parameter.symbol = "pfau_hpf";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 3.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 20;
break;
case p_vcfCutoff:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Freq";
parameter.symbol = "pfau_freq";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 60.0f;
parameter.midiCC = 74;
break;
case p_vcfReso:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Res";
parameter.symbol = "pfau_reso";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 71;
break;
case p_vcfEnvPol:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger;
parameter.name = "Polarity";
parameter.symbol = "pfau_vcfmode";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 1.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 21;
parameter.enumValues.count = 2;
parameter.enumValues.restrictedMode = true;
{
ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[2];
enumValues[0].value = 0.0f;
enumValues[0].label = "POS";
enumValues[1].value = 1.0f;
enumValues[1].label = "INV";
parameter.enumValues.values = enumValues;
}
break;
case p_vcfEnvMod:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Env";
parameter.symbol = "pfau_vcfenvmod";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 46.0f;
parameter.midiCC = 22;
break;
case p_vcfLFoMod:
parameter.hints = kParameterIsAutomatable;
parameter.name = "LFO";
parameter.symbol = "pfau_vcflfomod";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 23;
break;
case p_vcfKeyTrk:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Kybd";
parameter.symbol = "pfau_vcfkeymod";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 71.0f;
parameter.midiCC = 24;
break;
case p_attack:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Attack";
parameter.symbol = "pfau_attack";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 27.0f;
parameter.midiCC = 73;
break;
case p_decay:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Decay";
parameter.symbol = "pfau_decay";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 57.0f;
parameter.midiCC = 75;
break;
case p_sustain:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Sustain";
parameter.symbol = "pfau_sustain";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 57.0f;
parameter.midiCC = 27;
break;
case p_release:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Release";
parameter.symbol = "pfau_release";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 48.0f;
parameter.midiCC = 72;
break;
case p_vcaEnvGate:
parameter.hints = kParameterIsAutomatable | kParameterIsInteger; // | kParameterIsBoolean;
parameter.name = "Gate";
parameter.symbol = "pfau_envgate";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 1.0f;
parameter.ranges.def = 1.0f;
parameter.midiCC = 25;
parameter.enumValues.count = 2;
parameter.enumValues.restrictedMode = true;
{
ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[2];
enumValues[0].value = 0.0f;
enumValues[0].label = "ENV";
enumValues[1].value = 1.0f;
enumValues[1].label = "GATE";
parameter.enumValues.values = enumValues;
}
break;
case p_vcaLevel:
parameter.hints = kParameterIsAutomatable;
parameter.name = "VCA Level";
parameter.symbol = "pfau_vcalevel";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 40.0f;
parameter.midiCC = 26;
break;
case p_modWheel:
parameter.hints = kParameterIsAutomatable | kParameterIsHidden;
parameter.name = "Mod wheel";
parameter.symbol = "pfau_modwheel";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 1;
break;
case p_holdPedal:
parameter.hints = kParameterIsAutomatable | kParameterIsHidden;
parameter.name = "Hold Pedal";
parameter.symbol = "pfau_holdpedal";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 127.0f;
parameter.ranges.def = 0.0f;
parameter.midiCC = 64;
break;
}
// chorus, porta, bend range, key mode still to do
}

View File

@ -18,14 +18,18 @@
#include "ic29.hpp"
#include "ic29tables.hpp"
Synth ic29;
Synth::Synth() {
d_debug("initialising synth\n");
envAtk = 0x007f;
envDcy = envRls = 0xfe90;
envStn = 0x1fff;
envAtk = 0x00;
envDcy = 0x1f;
envStn = 0x00;
envRls = 0x1f;
portaCoeff = 0x0;
lfo.speed = 0x06;
}
void Synth::buildTables(double sampleRate) {
@ -33,7 +37,7 @@ void Synth::buildTables(double sampleRate) {
// slightly flat middle C from ROM divider table
// actually adjusted a little so that the notes are bang on
// on the real synth the tuning knob is tweaked a little off to pull it in
pitchTable[i] = 260.15f * powf(2, (i - 36) / 12.0f) / sampleRate;
pitchTable[i] = 260.15f * powf(2, (i - 36) / 12.0f) / sampleRate;
}
}
@ -41,6 +45,21 @@ void Synth::run() {
// handle a "loop" worth of envelopes, pitch calculations, etc
// callled once every 4.3ms block of samples
ic29.lfo.run();
masterPitch = 0x1818;
// 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;
ic29.pwm = pwmVal / 40960.0f * (1); // 0.5 is knob val
for (uint8_t i = 0; i < NUM_VOICES; i++) {
ic29.voices[i].update();
}
@ -58,14 +77,32 @@ void Synth::voiceOff(uint8_t voice) {
ic29.voices[voice].off();
}
void Synth::basePitch() {
uint16_t pitch = 0x1818;
LFO::LFO() {
lfoOut = 0;
phase = 0;
pitch += lfoPitch;
pitch += bendPitch;
// tuning too but that's zero by default;
// phase is where we are in the LFO delay cycle
// the delay envelope sets the depth of pitch and VCF modulation
// running normally the amplitude is maxed out, and when the first
// key is struck the holdoff timer and envelope will be reset to zero
delayPhase = LFO_RUN;
}
masterPitch = pitch;
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];
if (lfoOut > 0x1fff) {
lfoOut = 0x1fff;
phase = 0;
}
if (lfoOut < -0x1fff) {
lfoOut = -0x1fff;
phase = 1;
}
// printf("lfoOut=%04x\n", lfoOut);
}
Envelope::Envelope() {
@ -74,25 +111,24 @@ Envelope::Envelope() {
}
void Envelope::run() {
uint16_t tempStn = ic29.envStn << 7;
switch (phase) {
case ENV_ATK:
level += ic29.envAtk;
level += atkTable[ic29.envAtk];
if (level > 0x3fff) {
level = 0x3fff;
phase = ENV_DCY;
}
break;
case ENV_DCY:
if (level > ic29.envStn) {
level -= ic29.envStn;
level = (level * ic29.envDcy) >> 16;
level += ic29.envStn;
if (level > tempStn) {
level = (((level - tempStn) * dcyTable[ic29.envDcy]) >> 16) + tempStn;
} else {
level = ic29.envStn;
level = tempStn;
}
break;
case ENV_RLS:
level = (level * ic29.envRls) >> 16;
level = (level * dcyTable[ic29.envRls]) >> 16;
break;
case ENV_IDLE:
default:
@ -101,18 +137,26 @@ void Envelope::run() {
}
Voice::Voice() {
subosc = .1;
}
void Voice::calcPitch() {
uint16_t target = note << 8;
// Portamento is a linear change of pitch - it'll take twice as long
// to jump two octaves as it takes to jump one
// By comparison "glide" is like an RC filter, for example in the TB303
// This is implemented here by adding on a step value until you pass
// the desired final pitch. Once that happens the value is clamped to the
// desired pitch.
if (ic29.portaCoeff != 0) {
// porta up
// portamento up
if (pitch < target) {
pitch += ic29.portaCoeff;
if (pitch > target) pitch = target;
}
// porta down
// portamento down
if (pitch > target) {
pitch -= ic29.portaCoeff;
if (pitch < target) pitch = target;
@ -121,17 +165,17 @@ void Voice::calcPitch() {
pitch = target;
}
pitch += 0x1818; //ic29.masterPitch;
pitch += ic29.masterPitch;
if (pitch < 0x3000) pitch = 0x3000; // lowest note
if (pitch > 0x9700) pitch = 0x6700; // highest note
pitch -= 0x3000;
//pitch &= 0xff00;
// interpolate between the two table values
double o1 = ic29.pitchTable[pitch >> 8];
double o2 = ic29.pitchTable[(pitch >> 8) + 1];
double frac = (pitch & 0xff) / 255.0;
double frac = (pitch & 0xff) / 256.0f;
omega = ((o2 - o1) * frac) + o1;
}
@ -140,29 +184,20 @@ void Voice::update() {
// calculate the once-per-block values
env.run();
calcPitch();
pw = 0.5 - ic29.pwm;
// do filter values
}
void Voice::on(uint8_t key) {
voiceState = V_ON;
note = key;
env.on();
env.on(); // FIXME move to synth update code
}
void Voice::off() {
// I need to rethink this bit FIXME
// sustain - I need to rethink this bit FIXME
voiceState = V_OFF;
if (!ic29.sustained) {
env.off();
}
}
void Voice::run(float *buffer, uint32_t samples) {
float gain = env.level / 16384.0;
gain *= 0.125;
for (uint32_t i = 0; i < samples; i++) {
phase += omega;
if (phase > 1.0f) phase -= 1.0f;
buffer[i] += phase * gain;
}
}

View File

@ -20,6 +20,24 @@
#include "peacock.hpp"
class LFO {
public:
LFO();
void run();
int16_t lfoOut;
uint8_t speed;
private:
uint8_t
phase;
uint16_t holdoff;
uint16_t envelope;
enum { LFO_RUN,
LFO_HOLDOFF,
LFO_RAMP } delayPhase;
static const uint16_t lfoRateTable[128];
};
class Envelope {
public:
Envelope();
@ -45,6 +63,8 @@ class Envelope {
ENV_RLS,
ENV_IDLE
} phase;
static const uint16_t atkTable[128];
static const uint16_t dcyTable[128];
};
class Voice {
@ -59,6 +79,10 @@ 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 subosc = -1;
float phase = 0, omega = 0;
enum { V_DONE,
V_OFF,
@ -93,9 +117,10 @@ class Synth {
int16_t lfoPitch;
int16_t bendPitch;
Voice voices[NUM_VOICES];
LFO lfo;
float pwm;
void runLfo();
void basePitch();
};
// global

106
plugin/ic29tables.hpp Normal file
View File

@ -0,0 +1,106 @@
/*
Chassis polysynth framework
Copyright 2024 Gordon JC Pearce <gordonjcp@gjcp.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "ic29.hpp"
const uint16_t Envelope::atkTable[128] = {
0x4000, 0x2000, 0x1000, 0x0aaa, 0x0800, 0x0666, 0x0555, 0x0492, 0x0400,
0x038e, 0x0333, 0x02e9, 0x02ab, 0x0276, 0x0249, 0x0222, 0x0200, 0x01e2,
0x01c7, 0x01af, 0x0199, 0x0186, 0x0174, 0x0164, 0x0155, 0x0148, 0x013b,
0x012f, 0x0124, 0x011a, 0x0111, 0x0108, 0x0100, 0x00f8, 0x00f1, 0x00ea,
0x00e4, 0x00dd, 0x00d8, 0x00d2, 0x00cd, 0x00c8, 0x00c3, 0x00bf, 0x00ba,
0x00b6, 0x00b2, 0x00ae, 0x00ab, 0x00a7, 0x00a4, 0x00a1, 0x009e, 0x009b,
0x0098, 0x0095, 0x0092, 0x0090, 0x008d, 0x008b, 0x0089, 0x0086, 0x0084,
0x0082, 0x007f, 0x007d, 0x007a, 0x0077, 0x0074, 0x0072, 0x006f, 0x006c,
0x0069, 0x0067, 0x0064, 0x0061, 0x005e, 0x005c, 0x0059, 0x0056, 0x0053,
0x0050, 0x004e, 0x004b, 0x0048, 0x0045, 0x0042, 0x0040, 0x003f, 0x003d,
0x003c, 0x003a, 0x0039, 0x0037, 0x0036, 0x0034, 0x0033, 0x0031, 0x0030,
0x002e, 0x002d, 0x002b, 0x002a, 0x0028, 0x0027, 0x0025, 0x0024, 0x0022,
0x0021, 0x0021, 0x0020, 0x0020, 0x001f, 0x001f, 0x001e, 0x001e, 0x001d,
0x001d, 0x001c, 0x001c, 0x001b, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017,
0x0016, 0x0015};
const uint16_t Envelope::dcyTable[128] = {
0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xa000, 0xa800, 0xb000, 0xb800,
0xc000, 0xc800, 0xd000, 0xd800, 0xe000, 0xe800, 0xf000, 0xf080, 0xf100,
0xf180, 0xf200, 0xf280, 0xf300, 0xf380, 0xf400, 0xf480, 0xf500, 0xf580,
0xf600, 0xf680, 0xf700, 0xf780, 0xf800, 0xf880, 0xf900, 0xf980, 0xfa00,
0xfa80, 0xfb00, 0xfb80, 0xfc00, 0xfc80, 0xfd00, 0xfd80, 0xfe00, 0xfe0c,
0xfe18, 0xfe24, 0xfe30, 0xfe3c, 0xfe48, 0xfe54, 0xfe60, 0xfe6c, 0xfe78,
0xfe84, 0xfe90, 0xfe9c, 0xfea8, 0xfeb4, 0xfec0, 0xfecc, 0xfed8, 0xfee4,
0xfef0, 0xfefc, 0xff08, 0xff0c, 0xff10, 0xff14, 0xff18, 0xff1c, 0xff20,
0xff24, 0xff28, 0xff2c, 0xff30, 0xff34, 0xff38, 0xff3c, 0xff40, 0xff44,
0xff48, 0xff4c, 0xff50, 0xff54, 0xff58, 0xff5c, 0xff60, 0xff64, 0xff68,
0xff6c, 0xff70, 0xff74, 0xff78, 0xff7c, 0xff80, 0xff84, 0xff88, 0xff8c,
0xff90, 0xff94, 0xff98, 0xff9c, 0xffa0, 0xffa4, 0xffa8, 0xffac, 0xffb0,
0xffb4, 0xffb8, 0xffbc, 0xffc0, 0xffc4, 0xffc8, 0xffcc, 0xffd0, 0xffd4,
0xffd8, 0xffdc, 0xffe0, 0xffe4, 0xffe8, 0xffec, 0xfff0, 0xfff1, 0xfff2,
0xfff3, 0xfff4};
extern const uint8_t 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,
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c,
0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x62, 0x64,
0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c,
0x80, 0x84, 0x88, 0x8c, 0x90, 0x94, 0x98, 0x9c, 0xa0, 0xa4, 0xa8, 0xac,
0xb0, 0xb4, 0xb8, 0xbc, 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf8, 0xff, 0xff};
const uint16_t LFO::lfoRateTable[128] = {
0x0005, 0x000f, 0x0019, 0x0028, 0x0037, 0x0046, 0x0050, 0x005a, 0x0064,
0x006e, 0x0078, 0x0082, 0x008c, 0x0096, 0x00a0, 0x00aa, 0x00b4, 0x00be,
0x00c8, 0x00d2, 0x00dc, 0x00e6, 0x00f0, 0x00fa, 0x0104, 0x010e, 0x0118,
0x0122, 0x012c, 0x0136, 0x0140, 0x014a, 0x0154, 0x015e, 0x0168, 0x0172,
0x017c, 0x0186, 0x0190, 0x019a, 0x01a4, 0x01ae, 0x01b8, 0x01c2, 0x01cc,
0x01d6, 0x01e0, 0x01ea, 0x01f4, 0x01fe, 0x0208, 0x0212, 0x021c, 0x0226,
0x0230, 0x023a, 0x0244, 0x024e, 0x0258, 0x0262, 0x026c, 0x0276, 0x0280,
0x028a, 0x029a, 0x02aa, 0x02ba, 0x02ca, 0x02da, 0x02ea, 0x02fa, 0x030a,
0x031a, 0x032a, 0x033a, 0x034a, 0x035a, 0x036a, 0x037a, 0x038a, 0x039a,
0x03aa, 0x03ba, 0x03ca, 0x03da, 0x03ea, 0x03fa, 0x040a, 0x041a, 0x042a,
0x043a, 0x044a, 0x045a, 0x046a, 0x047a, 0x048a, 0x04be, 0x04f2, 0x0526,
0x055a, 0x058e, 0x05c2, 0x05f6, 0x062c, 0x0672, 0x06b8, 0x0708, 0x0758,
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};

78
plugin/oscillator.cpp Normal file
View File

@ -0,0 +1,78 @@
/*
Peacock-8 VA polysynth
Copyright 2024 Gordon JC Pearce <gordonjcp@gjcp.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic29.hpp"
static inline float poly3blep0(float t) {
float t2 = t * t;
return 2 * (t * t2 - 0.5f * t2 * t2);
}
static inline float poly3blep1(float t) {
return -poly3blep0(1 - t);
}
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;
// this uses an adaptation of Mystran's Polyblep oscillator
for (uint32_t i = 0; i < samples; i++) {
y = delay;
delay = 0;
phase += omega;
pwrc = ((pw - pwrc) *.01 ) + pwrc;
// this is the clever bit
while (true) {
if (!pulseStage) {
if (phase < pwrc) break; // it's not time for the PWM output to step
t = (phase - pwrc) / (lastpw - pwrc + omega); // calculate fractional sample allowing for PW amount
y -= 0.63 * poly3blep0(t); // magic numbers observed on oscilloscope from real synth
delay -= 0.63 * poly3blep1(t);
pulseStage = true;
}
if (pulseStage) {
if (phase < 1) break; // it's not time to reset the saw
t = (phase - 1) / omega;
y += poly3blep0(t) * (0.8 * saw + 0.63 - subosc);
delay += poly3blep1(t) * (0.8 * saw + 0.63 - subosc);
pulseStage = 0;
phase -= 1;
subosc = -subosc;
}
}
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
// 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;
out = y * 0.15;
lastpw = pwrc;
buffer[i] += out * gain;
}
}

View File

@ -28,6 +28,8 @@ Peacock::Peacock() : Plugin(paramCount, 0, 0), sampleRate(getSampleRate()) {
ic29.buildTables(getSampleRate());
}
void Peacock::runMidi(const MidiEvent *ev, uint32_t count, uint32_t timeLimit) {
// handle MIDI events, starting at lastEvent and continuing until timeLimit
uint32_t i;
@ -41,6 +43,15 @@ void Peacock::runMidi(const MidiEvent *ev, uint32_t count, uint32_t timeLimit) {
lastEvent = i;
}
void Peacock::initAudioPort(bool input, uint32_t index, AudioPort &port) {
port.groupId = kPortGroupStereo;
Plugin::initAudioPort(input, index, port);
if (!input && index == 0) port.name = "Left Out";
if (!input && index == 1) port.name = "Right Out";
}
void Peacock::run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) {
// calculate an entire jack period's worth of samples
// harder than it sounds because for short jack periods there may be many

View File

@ -28,6 +28,32 @@ START_NAMESPACE_DISTRHO
class Peacock : public Plugin {
public:
enum Parameters {
p_lfoRate,
p_lfoDelay,
p_vcoRange, // 16'/8'/4'
p_vcoLfoMod,
p_pwmLfoMod,
p_pwmMode, // LFO/Fixed
p_sawOn,
p_sqrOn,
p_subLevel,
p_noiseLevel,
p_hpfMode, // 0 = bass boost, 1 = flat, 2 = 170Hz, 3 = 350Hz
p_vcfCutoff,
p_vcfReso,
p_vcfEnvPol, // positive / inverted
p_vcfEnvMod,
p_vcfLFoMod,
p_vcfKeyTrk,
p_vcaEnvGate, // env or gate
p_vcaLevel,
p_attack,
p_decay,
p_sustain,
p_release,
p_chorus, // 0, 1, 2
p_modWheel,
p_holdPedal,
paramCount
};
@ -35,7 +61,6 @@ class Peacock : public Plugin {
float sampleRate;
protected:
const char *getLabel() const override { return "peacock-8"; }
const char *getDescription() const override {
return "simple polysynth";
@ -46,8 +71,8 @@ class Peacock : public Plugin {
int64_t getUniqueId() const override { return d_cconst('P', 'f', 'a', 'u'); }
// Initialisation
// void initAudioPort(bool input, uint32_t index, AudioPort &port) override;
// void initParameter(uint32_t index, Parameter &parameter) override;
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;
// float getParameterValue(uint32_t index) const override;