Compare commits

...

25 Commits

Author SHA1 Message Date
Gordon JC Pearce 4bf634a767 bunch of stuff to do with controls 2026-01-09 23:56:07 +00:00
Gordon JC Pearce d0a259a960 rescaled lookup table 2026-01-09 00:44:19 +00:00
Gordon JC Pearce 6a8e0686a6 pitch bender for oscillator, no range setting 2026-01-09 00:29:54 +00:00
Gordon JC Pearce c5ca40d22c calibration 2026-01-08 16:23:26 +00:00
Gordon JC Pearce 58ee2d4ca8 Merge branch 'gui' into calibration 2026-01-08 16:19:59 +00:00
Gordon JC Pearce 4077447102 levels balanced a bit better 2026-01-08 16:17:42 +00:00
Gordon JC Pearce 38ddbf91b1 fixed levels 2026-01-08 15:31:12 +00:00
Gordon JC Pearce e3c54e3ef1 noise level 2026-01-08 15:29:10 +00:00
Gordon JC Pearce 8ca166a662 fix level 2026-01-08 15:27:14 +00:00
Gordon JC Pearce 7d60cac407 update windows and linux runners 2026-01-08 10:45:22 +00:00
Gordon JC Pearce 1199cc1fc0 update macos workflow 2026-01-07 12:37:40 +00:00
Gordon JC Pearce 168fe2912d add github workflow 2026-01-07 12:30:40 +00:00
Gordon JC Pearce d2e8e6126d adjustments to timing 2026-01-07 12:25:46 +00:00
Gordon JC Pearce e452e282f4 fixed button update bug in Windows 2026-01-07 11:38:04 +00:00
Gordon JC Pearce a3a2e0dd04 fix lfo and pitch calculation 2026-01-06 22:16:39 +00:00
Gordon JC Pearce 7ff0bf0659 better LFO and PW 2026-01-06 20:20:30 +00:00
Gordon JC Pearce 8c2263c129 chorus a bit more like the real thing 2026-01-05 23:08:50 +00:00
Gordon JC Pearce ffe4026b18 fix module being updated at the wrong time causing incorrect LFO speed 2026-01-05 21:34:30 +00:00
Gordon JC Pearce 8d987fa5b0 handle midi, reject >3 byte, but falktx has explained it better 2026-01-04 18:57:42 +00:00
Gordon JC Pearce a8464e6d12 better noise, sticking with original filter, lfo cleanup 2026-01-01 01:06:51 +00:00
Gordon JC Pearce 2ebf7fac1c fix CLAP features macro 2025-12-31 21:18:21 +00:00
Gordon JC Pearce 9f86360611 more make options for different plugin formats 2025-12-31 20:39:06 +00:00
Gordon JC Pearce 8fb00c1499 nice bass patch to test with 2025-12-30 01:11:57 +00:00
Gordon JC Pearce fd68c59d0c Merge branch 'chorusboard' 2025-12-30 01:04:41 +00:00
Gordon JC Pearce dc89ca6cf7 Merge branch 'gui' 2025-12-30 00:24:19 +00:00
13 changed files with 300 additions and 282 deletions

43
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: build
on: [push, pull_request]
jobs:
linux:
strategy:
matrix:
target: [linux-arm64, linux-armhf, linux-i686, linux-riscv64, linux-x86_64]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: distrho/dpf-makefile-action@v1
with:
target: ${{ matrix.target }}
macos:
strategy:
matrix:
target: [macos-intel, macos-universal]
runs-on: macos-15
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: distrho/dpf-makefile-action@v1
with:
target: ${{ matrix.target }}
windows:
strategy:
matrix:
target: [win32, win64]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: distrho/dpf-makefile-action@v1
with:
target: ${{ matrix.target }}

View File

@ -23,6 +23,15 @@
#define DISTRHO_PLUGIN_NAME "peacock-8"
#define DISTRHO_PLUGIN_URI "https://gjcp.net/plugins/peacock"
#define DISTRHO_PLUGIN_CLAP_ID "net.gjcp.peacock"
#define DISTRHO_PLUGIN_CLAP_FEATURES "instrument","synthesizer","stereo"
#define DISTRHO_PLUGIN_BRAND_ID GJCP
#define DISTRHO_PLUGIN_UNIQUE_ID Pfau
#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:InstrumentPlugin"
#define DISTRHO_PLUGIN_VST_CATEGORY "Fx|Instrument"
#define DISTRHO_PLUGIN_NUM_INPUTS 0
#define DISTRHO_PLUGIN_NUM_OUTPUTS 2
#define DISTRHO_PLUGIN_IS_SYNTH 1
@ -65,6 +74,10 @@
pChorusMode,
pVcoBend,
pVcfBend,
pModDepth,
parameterCount
};

View File

@ -34,7 +34,8 @@ include ../dpf/Makefile.plugins.mk
SKIP_NATIVE_AUDIO_FALLBACK = true
TARGETS += jack lv2_sep
# omitting LV2 for the moment until I figure out cross-compiling
TARGETS += jack vst2 vst3 clap
all: $(TARGETS)

View File

@ -43,7 +43,12 @@ Assigner::Assigner() {
}
void Assigner::handleMidi(MidiEvent* ev) {
uint8_t status = ev->data[0];
//printf("called with event %04x (%02x): %02x %02x %02x\n", ev->frame, ev->size, ev->data[0], ev->data[1], ev->data[2]);
if (ev->size > 3) return; // sysex bug
switch (status & 0xf0) {
case 0x80:
noteOff(ev->data[1]);
@ -56,9 +61,11 @@ void Assigner::handleMidi(MidiEvent* ev) {
break;
case 0xb0:
switch (ev->data[1]) {
case 0x01: // modwheel
printf("mod wheel %02x\n", ev->data[2]);
m->modWheel = ev->data[2];
// handle the following
// CC 1 - modwheel
// CC 64 - sustain
// CC 64 - sustain // FIXME sustain not implemented
// possibly JU-06 CC values
default:
break;
@ -66,7 +73,8 @@ void Assigner::handleMidi(MidiEvent* ev) {
break; // nothing to do here except in special cases where we don't expect the host to pass on controls
case 0xc0: // program change
break;
case 0xe0: // pitch bend;
case 0xe0: // pitch bend
m->bend = ((ev->data[1] + (ev->data[2]<<7))>>5)-0x100;
break;
case 0xf0: // sysex
break;

View File

@ -19,9 +19,8 @@
#include "chorus.hpp"
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
Chorus::Chorus() {
lpfOut1 = new float[bufferSize];
lpfOut2 = new float[bufferSize];
@ -30,7 +29,8 @@ Chorus::Chorus() {
lfoPhase = 1;
lfoSpeed = 6.283 * 10.7 / sampleRate; // plainly silly value to show if it hasn't been set
gainTC = 1 - exp(-6.283 * 10 / sampleRate);
gainTC = 1 - exp(-M_PI * 10 / sampleRate); // 1/10th of a second declick
bbdTC = 1 - exp(-M_PI * 60 / sampleRate); // hpf into BBD
// not quite Butterworth but you'd never hear the difference
// these are calculated from the real-world component values
@ -59,7 +59,7 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) {
// run highpass / bass boost and stereo chorus effect for one full block
float s0 = 0, s1 = 0;
float lfoMod, dly1, frac, flt;
float dly1, frac, flt;
uint16_t tap, delay;
for (uint32_t i = 0; i < frames; i++) {
@ -76,11 +76,14 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) {
hpDelay = flt;
input[i] += (flt * hpGain);
ram[delayptr] = input[i];
flt = ((input[i] - bbdRC) * bbdTC) + bbdRC;
bbdRC = flt;
ram[delayptr] = input[i] - flt;
// delays in milliseconds
#define BASE 0.005
#define AMT 0.00175
#define BASE 0.0035
#define AMT 0.002
dly1 = (BASE + (AMT * lfoPhase)) * sampleRate;
delay = (int)dly1;
@ -103,8 +106,6 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) {
delayptr++;
delayptr &= 0x3ff;
}
//printf("dly1 = %f\n", dly1);
postFilter1l->runSVF(lpfOut1, lpfOut1, frames);
postFilter2l->runSVF(lpfOut1, lpfOut1, frames);
postFilter1r->runSVF(lpfOut2, lpfOut2, frames);
@ -113,7 +114,6 @@ void Chorus::run(float* input, float** outputs, uint32_t frames) {
for (uint32_t i = 0; i < frames; i++) {
float y = input[i];
gainRC = (gain - gainRC) * gainTC + gainRC;
outputs[0][i] = y + (gainRC * lpfOut1[i]);
outputs[1][i] = y + (gainRC * lpfOut2[i]);
}
@ -127,11 +127,11 @@ void Chorus::setHpf(uint8_t mode) {
// k = 1-exp(-2pi * Fc * sampleRate)
switch (mode) {
case 0x00:
hpCut = 1 - exp(-6.283 * 720 / sampleRate);
hpCut = 1 - exp(-M_PI * 720 / sampleRate);
hpGain = -1;
break;
case 0x08:
hpCut = 1 - exp(-6.283 * 225 / sampleRate);
hpCut = 1 - exp(-M_PI * 225 / sampleRate);
hpGain = -1;
break;
case 0x10:
@ -140,7 +140,7 @@ void Chorus::setHpf(uint8_t mode) {
break;
case 0x18:
hpCut = 1 - exp(-6.283 * 85 / sampleRate);
hpGain = 1.707;
hpGain = 1.0;
break;
}
}
@ -154,11 +154,11 @@ void Chorus::setChorus(uint8_t mode) {
break;
case 0x40:
gain = 1.2;
lfoSpeed = 6.283 * 0.3 / sampleRate;
lfoSpeed = M_PI * 0.525 / sampleRate;
break;
case 0x00:
gain = 1.2;
lfoSpeed = 6.283 * 0.5 / sampleRate;
lfoSpeed = M_PI * 0.85 / sampleRate;
break;
}
}

View File

@ -46,6 +46,8 @@ class Chorus {
float gainRC = 0;
float gainTC = 0;
float bbdRC=0, bbdTC=0;
uint16_t delayptr = 0;

View File

@ -25,13 +25,14 @@
Module::Module() {
// cutoff frequencies for various RC networks
vcaTC = 1 - exp(-6.283 * 159 / sampleRate); // VCA and VCF 10k/0.1u time constant
subTC = 1 - exp(-6.283 * 15 / sampleRate); // Main VCA and Sub Level 1k + 10u time constant
pwmTC = 1 - exp(-6.283 * 40 / sampleRate); // integrator with 100k/0.047u time constant
vcaTC = 1 - exp(-M_PI * 159 / sampleRate); // VCA and VCF 10k/0.1u time constant
subTC = 1 - exp(-M_PI * 15 / sampleRate); // Main VCA and Sub Level 1k + 10u time constant
pwmTC = 1 - exp(-M_PI * 40 / sampleRate); // integrator with 100k/0.047u time constant
vcaBuf = new float[bufferSize];
subBuf = new float[bufferSize];
pwmBuf = new float[bufferSize];
noiseBuf = new float[bufferSize];
}
Module::~Module() {
@ -41,27 +42,56 @@ Module::~Module() {
delete pwmBuf;
}
void Module::genNoise() {
for (uint32_t i = 0; i < bufferSize; i++) {
noiseRNG *= 0x8088405;
noiseRNG++;
noiseBuf[i] = 1 - (noiseRNG & 0xffff) / 32768.0f;
}
}
void Module::lfoRampOn() {
lfoDelayState = 1;
lfoDelayTimer = 0;
lfoDelay = 0;
}
void Module::runLFO() {
if (lfoDelayState == 1) {
lfoDelayTimer += attackTable[patchRam.lfoDelay];
if (lfoDelayTimer > 0x3fff) lfoDelayState = 2;
}
if ((lfoDelayState == 2)) {
lfoDelay += lfoDelayTable[patchRam.lfoDelay >> 4];
}
if (lfoDelay > 0xff) {
lfoDelayState = 0;
lfoDelay = 0xff;
}
lfoRate = lfoRateTable[patchRam.lfoRate]; // FIXME move to parameters
lfoPhase += (lfoState & 0x01) ? -lfoRate : lfoRate;
if (lfoPhase > 0x1fff) {
lfoPhase = 0x1fff;
lfoState++;
}
if (lfoPhase < 0x0000) {
lfoPhase = 0x0000;
lfoState++;
}
lfo = (lfoState & 0x02) ? -lfoPhase : lfoPhase;
pw = (lfoState & 0x02) ? lfoPhase + 0x2000 : 0x2000 - lfoPhase; // PW LFO is unipolar
pw = (patchRam.switch2 & 0x01) ? 0x3fff : pw; // either LFO or "all on"
pw = 0x3fff - ((pw * (int)(patchRam.pwmLfo*0.9125)) >> 7); // FIXME tidy up this bit
}
void Module::run(Voice* voices, uint32_t blockSize) {
// run updates for module board
if (lfoDelayState == 1) {
lfoDelayTimer += lfoDelayTable[patchRam.lfoDelay >> 4];
if (lfoDelayTimer & 0xc000) lfoDelayState = 2;
}
if ((lfoDelayState == 2)) {
lfoDelay += attackTable[patchRam.lfoDelay];
}
if (lfoDelay & 0xc000) {
lfoDelayState = 0;
lfoDelay = 0x3fff;
}
int16_t lfoToVco = 0, lfoToVcf = 0;
// FIXME break these out to the patch setter
a = attackTable[patchRam.env_a]; // attack time coeff looked up in table
@ -69,35 +99,26 @@ void Module::run(Voice* voices, uint32_t blockSize) {
r = decayTable[patchRam.env_r]; // release time coeff looked up in table
s = patchRam.env_s << 7; // scale 0x00-0x7f to 0x0000-0x3f80
square = (patchRam.switch1 & 0x08) ? 0.63 : 0;
saw = (patchRam.switch1 & 0x10) ? 0.8 : 0;
sub = patchRam.sub / 127.0f;
lfoPhase += lfoRateTable[patchRam.lfoRate];
master = powf(2, (patchRam.vca / 31.75 - 4.0f)) * 0.1;
square = (patchRam.switch1 & 0x08) ? 1 : 0;
saw = (patchRam.switch1 & 0x10) ? 1 : 0;
sub = (patchRam.sub / 127.0f) * 1.4;
res = patchRam.vcfReso / 127.0;
noise = patchRam.noise / 127.0;
noise = (patchRam.noise / 127.0);
// FIXME the exp in these is expensive, don't call it all the time
chorus->setChorus(patchRam.switch1 & 0x60);
chorus->setHpf(patchRam.switch2 & 0x18);
if (lfoPhase & 0x4000)
lfo = 0x1fff - (lfoPhase & 0x3fff);
else
lfo = (lfoPhase & 0x3fff) - 0x1fff;
// FIXME represent PW as int until we calculate the block?
pw = 0.5 - ((0x2000 + lfo) * patchRam.pwmLfo) / (32768.0f * 128);
pw = (patchRam.switch2 & 0x01) ? 0.5 - (patchRam.pwmLfo / 256.0f) : pw;
lfo = (lfo * lfoDelay) >> 14;
float master = powf(2, (patchRam.vca / 31.75 - 4.0f));
float sub = patchRam.sub / 127.0f;
runLFO();
// calculate "smoothed" parameters
// these are single outputs with heavy RC smoothing
for (uint32_t i = 0; i < blockSize; i++) {
vcaRC = (master - vcaRC) * subTC + vcaRC;
pwmRC = (pw - pwmRC) * pwmTC + pwmRC;
pwmRC = ((pw / 32768.0f) - pwmRC) * pwmTC + pwmRC;
subRC = (sub - subRC) * vcaTC + subRC;
vcaBuf[i] = vcaRC;
@ -107,13 +128,28 @@ void Module::run(Voice* voices, uint32_t blockSize) {
if (bufPtr < bufferSize) bufPtr++;
}
int16_t vcf = (patchRam.vcfEnv << 7) * ((patchRam.switch2 & 0x02) ? -1 : 1);
lfoToVco = (lfoDepthTable[patchRam.vcoLfo] * lfoDelay) >> 8; // lookup table is 0-255
lfoToVco += ((int)(modWheel * modDepth));
int16_t pitchBase = 0x1818;
pitchBase += (lfo * lfoDepthTable[patchRam.vcoLfo]) >> 11;
if (lfoToVco > 0xff) lfoToVco = 0xff;
lfoToVco = (lfo * lfoToVco) >> 11; // 8 for normalisation plus three additional DSLR EA
lfoToVcf = (patchRam.vcfLfo * lfoDelay) >> 7; // value is 0-127
lfoToVcf = (lfo * lfoToVcf) >> 9; // 8 for normalisation plus one additional DSLR EA
int16_t pitchBase = 0x1818, vcfBase = 0;
pitchBase += lfoToVco;
pitchBase += vcoBendDepth * bend;
vcfBase = (patchRam.vcfFreq << 7);
vcfBase += lfoToVcf;
vcfBase += vcfBendDepth * bend;
if (vcfBase > 0x3fff) vcfBase = 0x3fff;
if (vcfBase < 0x0000) vcfBase = 0x0000;
// per-voice calculations
for (uint32_t i = 0; i < NUM_VOICES; i++) {
// maybe move all this into voice.cpp FIXME
// run one step of the envelope
Voice* v = &voices[i];
switch (v->envPhase) {
case 0: // release phase FIXME use an enum I guess
@ -132,23 +168,30 @@ void Module::run(Voice* voices, uint32_t blockSize) {
}
// pitch
// FIXME clean this all up a bit
int16_t pitch = pitchBase + (v->note << 8);
int16_t semi = pitch >> 8;
uint16_t pitch = pitchBase + (v->note << 8);
int8_t semi = pitch >> 8;
semi -= 36;
float frac = (pitch & 0xff) / 256.0;
if (semi < 0) {
semi = 0;
frac = 0;
}
if (semi >= 103) {
semi = 103;
frac = 0;
};
float p1 = pitchTable[semi], p2 = pitchTable[semi + 1];
int16_t px = ((p2 - p1) * frac + p1);
int16_t px = ((p2 - p1) * frac + p1); // interpolated pitch from table
// octave divider
px *= (patchRam.switch1 & 0x07);
v->omega = px / (sampleRate * 8.0f); // fixme use proper scaler
v->omega = px / sampleRate; // FIXME recalculate table using proper scaler
// per voice we need to calculate the key follow amount and envelope amount
v->vcfCut = (patchRam.vcfFreq << 7) + ((vcf * v->env) >> 14);
v->vcfCut += (lfo * patchRam.vcfLfo) >> 9;
v->vcfCut = vcfBase + (((v->env * patchRam.vcfEnv) >> 7) * ((patchRam.switch2 & 0x02) ? -1 : 1));
v->vcfCut += (int)((v->note - 36) * (patchRam.vcfKey << 1) * 0.375);
v->vcfCut += (int)((v->note - 60) * (patchRam.vcfKey << 1) * 0.375);
if (v->vcfCut > 0x3fff) v->vcfCut = 0x3fff;
if (v->vcfCut < 0) v->vcfCut = 0;

View File

@ -33,87 +33,37 @@ class Module {
Module();
~Module();
void genNoise();
void lfoRampOn();
void run(Voice* voices, uint32_t blockLeft);
float res = 0;
// precomputed values for all voices
float pw; //, saw, square, sub;
// "internal state" values for patch parameters
uint16_t a, d, s, r;
int16_t lfo;
uint32_t lfoPhase;
float saw = 0, square = 0, sub = 0, noise = 0;
/*
#if 0
struct {
uint8_t lfoRate = 0x58;
uint8_t lfoDelay = 0x00;
uint8_t vcoLfo = 0x00;
uint8_t pwmLfo = 0x3b;
uint8_t noise = 0x00;
uint8_t vcfFreq = 0x25; // 1c; // 0x3f80
uint8_t vcfReso = 0x6a;
uint8_t vcfEnv = 0x25; // 4e;
uint8_t vcfLfo = 0x00;
uint8_t vcfKey = 0x00; // 47;
uint8_t vca = 0x35;
uint8_t env_a = 0x00;
uint8_t env_d = 0x3c;
uint8_t env_s = 0x00; // 0x3f80
uint8_t env_r = 0x3c;
uint8_t sub = 0x7f;
uint8_t switch1 = 0x4a;
uint8_t switch2 = 0x18;
} patchRam;
#else
struct {
uint8_t lfoRate = 0x40;
uint8_t lfoDelay = 0x00;
uint8_t vcoLfo = 0x00;
uint8_t pwmLfo = 0x00;
uint8_t noise = 0x01;
uint8_t vcfFreq = 0x31;
uint8_t vcfReso = 0x7f;
uint8_t vcfEnv = 0x00;
uint8_t vcfLfo = 0x00;
uint8_t vcfKey = 0x7f;
uint8_t vca = 0x40;
uint8_t env_a = 0x00;
uint8_t env_d = 0x00;
uint8_t env_s = 0x00; // 0x3f80
uint8_t env_r = 0x00;
uint8_t sub = 0x00;
uint8_t switch1 = 0x22;
uint8_t switch2 = 0x1d;
} patchRam;
#endif
*/
float saw = 0, square = 0, sub = 0, noise = 0, master = 0;
int16_t bend = 0, modWheel=0;
float vcoBendDepth = 4, vcfBendDepth=1.5, modDepth=.5;
struct {
uint8_t lfoRate = 0x58;
uint8_t lfoRate = 0x1f;
uint8_t lfoDelay = 0x00;
uint8_t vcoLfo = 0x00;
uint8_t pwmLfo = 0x00;
uint8_t pwmLfo = 0x3c;
uint8_t noise = 0x00;
uint8_t vcfFreq = 0x00; // 1c; // 0x3f80
uint8_t vcfReso = 0x7f;
uint8_t vcfEnv = 0x7f; // 4e;
uint8_t vcfFreq = 0x25; // 1c; // 0x3f80
uint8_t vcfReso = 0x1d;
uint8_t vcfEnv = 0x1c; // 4e;
uint8_t vcfLfo = 0x00;
uint8_t vcfKey = 0x00; // 47;
uint8_t vca = 0x20;
uint8_t vcfKey = 0x2b; // 47;
uint8_t vca = 0x5c;
uint8_t env_a = 0x00;
uint8_t env_d = 0x5c;
uint8_t env_s = 0x00; // 0x3f80
uint8_t env_r = 0x3c;
uint8_t sub = 0x7f;
uint8_t switch1 = 0x3a;
uint8_t switch2 = 0x19;
uint8_t env_d = 0x2a;
uint8_t env_s = 0x23; // 0x3f80
uint8_t env_r = 0x00;
uint8_t sub = 0x40;
uint8_t switch1 = 0x19;
uint8_t switch2 = 0x18;
} patchRam;
Chorus* chorus;
@ -124,12 +74,22 @@ class Module {
float* vcaBuf;
float* subBuf;
float* pwmBuf;
float* noiseBuf;
private:
void runLFO();
// precalculated coefficients for RC networks
float pwmTC = 0, subTC = 0, mVcaTC = 0;
float pwmRC = 0, subRC = 0, vcaRC = 0;
int16_t lfo, pw;
int16_t lfoPhase;
uint8_t lfoState = 0;
uint16_t lfoRate;
uint32_t noiseRNG = 1;
uint16_t lfoDelay = 0;
uint8_t lfoDelayState = 0;
uint16_t lfoDelayTimer = 0;
@ -142,7 +102,7 @@ class Voice {
Voice();
void on(uint8_t midiNote);
void off();
void run(Module* m, float* buffer, uint32_t samples);
void run(Module* m, float* buffer, uint32_t framePos, uint32_t samples);
private:
float omega = 0, theta = 0; // phase increment and angle FIXME better names
@ -150,18 +110,16 @@ class Voice {
uint8_t pulseStage = 1; // pulse wave phase
float subosc = 1; // sub oscillator flipflop output
uint8_t envPhase = 0;
int16_t env = 0; // output amplitude
uint16_t vcfCut;
int16_t vcaEnv;
float vcaRC = 0, vcfRC = 0;
uint8_t envPhase = 0; // current running state of envelope
int16_t env = 0; // calculated envelope amount
int16_t vcfCut; // calculated cutoff to filter
int16_t vcaEnv; // calculated level to VCA (env/gate)
float vcaRC = 0, vcfRC = 0; // RC circuit state values
uint8_t note = 0;
// filter
float y0 = 0, y1 = 0, y2 = 0, y3 = 0;
double s[4] = {0, 0, 0, 0};
float zi = 0;
};
#endif

View File

@ -309,19 +309,35 @@ void Peacock::initParameter(uint32_t index, Parameter& parameter) {
enumValues[2].label = "Fast";
parameter.enumValues.values = enumValues;
}
/*
case pModWheel:
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;
*/
break;
case pVcoBend:
parameter.hints = kParameterIsAutomatable;
parameter.name = "VCO Bend";
parameter.symbol = "pfau_vcobend";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 12.0f;
parameter.ranges.def = 4.0f;
break;
case pVcfBend:
parameter.hints = kParameterIsAutomatable;
parameter.name = "VCF Bend";
parameter.symbol = "pfau_vcfbend";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 42.0f;
parameter.ranges.def = 4.0f;
break;
case pModDepth:
parameter.hints = kParameterIsAutomatable;
parameter.name = "Mod Depth";
parameter.symbol = "pfau_moddepth";
parameter.ranges.min = 0.0f;
parameter.ranges.max = 4.0f;
parameter.ranges.def = 0.5f;
break;
}
// chorus, porta, bend range, key mode still to do
}
void Peacock::setParameterValue(uint32_t index, float value) {
@ -341,7 +357,7 @@ void Peacock::setParameterValue(uint32_t index, float value) {
m->patchRam.vcoLfo = value;
break;
case pPWMDepth:
m->patchRam.pwmLfo = value / 1.27;
m->patchRam.pwmLfo = value;
break;
case pSubLevel:
m->patchRam.sub = value;
@ -434,10 +450,15 @@ void Peacock::setParameterValue(uint32_t index, float value) {
m->patchRam.switch2 &= 0xe7;
m->patchRam.switch2 |= (3 - (int)value) << 3;
break;
/*
case pModWheel:
//s.ff64 = (int)value << 1;
break;*/
case pVcoBend:
m->vcoBendDepth = value;
break;
case pVcfBend:
m->vcfBendDepth = value / 2.66f;
break;
case pModDepth:
m->modDepth = value;
break;
}
}
@ -466,7 +487,7 @@ float Peacock::getParameterValue(uint32_t index) const {
return m->patchRam.vcoLfo;
break;
case pPWMDepth:
return m->patchRam.pwmLfo * 1.27f;
return m->patchRam.pwmLfo;
break;
case pPWMMode:
@ -536,6 +557,16 @@ float Peacock::getParameterValue(uint32_t index) const {
default:
break;
}
break;
case pVcoBend:
return m->vcoBendDepth;
break;
case pVcfBend:
return m->vcfBendDepth * 2.66f;
break;
case pModDepth:
return m->modDepth;
break;
}
return 0;
}

View File

@ -28,6 +28,7 @@ Peacock::Peacock() : Plugin(parameterCount, 0, 0) {
sampleRate = getSampleRate();
bufferSize = getBufferSize();
chorus = new Chorus();
m = new Module();
ic1 = new Assigner;
@ -70,27 +71,27 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve
memset(outputs[0], 0, frames * sizeof(float));
memset(outputs[1], 0, frames * sizeof(float));
m->genNoise();
// if there were any events that happen between now and the end of this block, process them
lastEvent = 0;
m->bufPtr = 0;
m->bufPtr = 0; // reset the output buffer pointer
runMidi(midiEvents, midiEventCount, blockLeft);
while (framePos < frames) {
if (blockLeft == 0) {
// no more samples to calculate in this update period
blockLeft = sampleRate / 238; // update rate in Hz
blockLeft = sampleRate / 233.5; // update rate in Hz, measured
runMidi(midiEvents, midiEventCount, framePos + blockLeft);
m->run(voice, blockLeft);
}
// how many frames to do? Are we about to run off an update block
sizeThisTime = (framesLeft < blockLeft) ? framesLeft : blockLeft;
m->run(voice, sizeThisTime);
// now run all the voices for this chunk of samples
for (uint32_t i = 0; i < NUM_VOICES; i++) {
voice[i].run(m, outputs[0] + framePos, sizeThisTime);
voice[i].run(m, outputs[0], framePos, sizeThisTime);
}
framePos += sizeThisTime;
@ -99,7 +100,7 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve
}
// now we've assembled a full chunk of audio
//memcpy(outputs[0], m->vcaBuf, sizeof(float)* frames);
// memcpy(outputs[0], m->vcaBuf, sizeof(float)* frames);
chorus->run(outputs[0], outputs, frames);
}

View File

@ -89,19 +89,19 @@ uint16_t lfoDelayTable[8] = {
0xffff, 0x0419, 0x020c, 0x015e, 0x0100, 0x0100, 0x0100, 0x0100};
float pitchTable[104] = {
32.494, 34.430, 36.486, 38.658, 40.962, 43.399, 45.990, 48.731,
51.633, 54.711, 57.969, 61.419, 65.072, 68.944, 73.059, 77.405,
82.014, 86.892, 92.077, 97.556, 103.365, 109.529, 116.043, 122.933,
130.242, 137.988, 146.220, 154.919, 164.136, 173.898, 184.264, 195.217,
206.847, 219.178, 232.207, 245.972, 260.586, 276.091, 292.569, 309.981,
328.407, 347.947, 368.664, 390.549, 413.822, 438.500, 464.576, 492.005,
521.241, 552.334, 585.309, 620.155, 657.030, 696.136, 737.463, 781.250,
827.815, 877.193, 929.368, 984.252, 1042.753, 1104.972, 1170.960, 1240.695,
1314.060, 1392.758, 1474.926, 1562.500, 1655.629, 1754.386, 1858.736, 1968.504,
2085.506, 2209.945, 2341.920, 2481.390, 2628.121, 2785.515, 2949.853, 3125.000,
3311.258, 3508.772, 3717.472, 3937.008, 4175.365, 4424.779, 4683.841, 4962.779,
5263.158, 5571.031, 5899.705, 6250.000, 6622.517, 7017.544, 7434.944, 7874.016,
8333.333, 8849.558, 9389.671, 9950.249, 10526.316, 11173.184, 11834.320, 12500.000
8.123, 8.607, 9.122, 9.664, 10.240, 10.850, 11.497, 12.183,
12.908, 13.678, 14.492, 15.355, 16.268, 17.236, 18.265, 19.351,
20.504, 21.723, 23.019, 24.389, 25.841, 27.382, 29.011, 30.733,
32.561, 34.497, 36.555, 38.730, 41.034, 43.474, 46.066, 48.804,
51.712, 54.795, 58.052, 61.493, 65.147, 69.023, 73.142, 77.495,
82.102, 86.987, 92.166, 97.637, 103.455, 109.625, 116.144, 123.001,
130.310, 138.083, 146.327, 155.039, 164.258, 174.034, 184.366, 195.312,
206.954, 219.298, 232.342, 246.063, 260.688, 276.243, 292.740, 310.174,
328.515, 348.189, 368.732, 390.625, 413.907, 438.596, 464.684, 492.126,
521.376, 552.486, 585.480, 620.347, 657.030, 696.379, 737.463, 781.250,
827.815, 877.193, 929.368, 984.252, 1043.841, 1106.195, 1170.960, 1240.695,
1315.789, 1392.758, 1474.926, 1562.500, 1655.629, 1754.386, 1858.736, 1968.504,
2083.333, 2212.389, 2347.418, 2487.562, 2631.579, 2793.296, 2958.580, 3125.000,
};
#endif

View File

@ -258,15 +258,17 @@ void DistrhoUIPeacock::parameterChanged(uint32_t index, float value) {
sw1 &= 0xf8; // mask
if (value > 2) value = 2;
sw1 |= (1 << (int)value);
xBtn16ft->repaint(); // will repaint all the panel
repaint();
break;
case pSqr:
sw1 &= 0xf7;
sw1 |= ((value >= 0.5)) << 3;
repaint();
break;
case pSaw:
sw1 &= 0xef;
sw1 |= (value > 0.5) << 4;
repaint();
break;
case pChorusMode:
@ -283,6 +285,8 @@ void DistrhoUIPeacock::parameterChanged(uint32_t index, float value) {
sw1 |= 0x00;
break;
}
repaint();
break;
}
}
@ -340,6 +344,7 @@ void DistrhoUIPeacock::imageButtonClicked(ImageButton* imgBtn, int) {
default:
break;
}
repaint();
}
void DistrhoUIPeacock::onDisplay() {

View File

@ -40,9 +40,9 @@ Voice::Voice() {
}
void Voice::on(uint8_t midiNote) {
while (midiNote < 24) midiNote += 12;
while (midiNote > 108) midiNote -= 12;
note = midiNote - 24;
while (midiNote < 24) midiNote += 12; // limit lowest note to C1
while (midiNote > 108) midiNote -= 12; // limit highest note to C8
note = midiNote;
envPhase = 1;
}
@ -50,28 +50,19 @@ void Voice::off() {
envPhase = 0;
}
// tanh(x)/x approximation, flatline at very high inputs
// so might not be safe for very large feedback gains
// [limit is 1/15 so very large means ~15 or +23dB]
double tanhXdX(double x) {
float s = 0.0333, d = 30.0;
return 1.0f - s * (d + 1.0f) * x * x / (d + x * x);
}
void Voice::run(Module* m, float* buffer, uint32_t samples) {
void Voice::run(Module* m, float* buffer, uint32_t framePos, uint32_t samples) {
// carry out per-voice calculations for each block of samples
float out, t, fb;
// calculate cutoff frequency
float cut = 261.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f));
float cut = 261.0f * (powf(2, (vcfCut - 0x1880) / 1143.0f)); // FIXME explain magic numbers
cut = M_PI * cut / sampleRate;
cut = cut / (1 + cut); // correct tuning warp
// if (cut > 0.7) cut = 0.7;
double r = 5 * m->res;
if (cut > 0.7) cut = 0.7;
float amp = vcaEnv / 4096.0f;
float r = 6 * m->res;
float amp = vcaEnv / 32768.0f;
for (uint32_t i = 0; i < samples; i++) {
out = delay;
@ -103,109 +94,31 @@ void Voice::run(Module* m, float* buffer, uint32_t samples) {
}
}
// FIXME DC offset removal
delay += m->saw * (1 - (2 * theta));
delay += m->square * ((pulseStage ? -1.f : 1.f) - m->pwmBuf[i] + 0.5);
delay += m->subBuf[i] * subosc;
out += m->noise * (0.8 - 1.6 * (rand() & 0xffff) / 65536.0);
// out *= 0.1;
out += m->noise * m->noiseBuf[i + framePos];
// same time constant for both VCF and VCF RC circuits
vcfRC = (cut - vcfRC) * m->vcaTC + vcfRC;
#if 1
//// LICENSE TERMS: Copyright 2012 Teemu Voipio
//
// You can use this however you like for pretty much any purpose,
// as long as you don't claim you wrote it. There is no warranty.
//
// Distribution of substantial portions of this code in source form
// must include this copyright notice and list of conditions.
//
// input delay and state for member variables
// cutoff as normalized frequency (eg 0.5 = Nyquist)
// resonance from 0 to 1, self-oscillates at settings over 0.9
// void transistorLadder(
// double cutoff, double resonance,
// double * in, double * out, unsigned nsamples)
//{
// tuning and feedback
//------------------------------------------------------------------------------ sample loop
// for(unsigned n = 0; n < nsamples; ++n)
//{
out *= 0.025;
// input with half delay, for non-linearities
double ih = 0.5 * (out + zi);
zi = out;
// double ih = out;
// evaluate the non-linear gains
double t0 = tanhXdX((ih * (r + 1)) - r * s[3]);
double t1 = tanhXdX(s[0]);
double t2 = tanhXdX(s[1]);
double t3 = tanhXdX(s[2]);
double t4 = tanhXdX(s[3]);
double f = vcfRC;
// g# the denominators for solutions of individual stages
double g0 = 1 / (1 + f * t1), g1 = 1 / (1 + f * t2);
double g2 = 1 / (1 + f * t3), g3 = 1 / (1 + f * t4);
// f# are just factored out of the feedback solution
double f3 = f * t3 * g3, f2 = f * t2 * g2 * f3, f1 = f * t1 * g1 * f2, f0 = f * t0 * g0 * f1;
// solve feedback
double y3 = (g3 * s[3] + f3 * g2 * s[2] + f2 * g1 * s[1] + f1 * g0 * s[0] + f0 * out) / (1 + r * f0);
// then solve the remaining outputs (with the non-linear gains here)
double xx = t0 * ((out * (r + 1)) - r * y3);
double y0 = t1 * g0 * (s[0] + f * xx);
double y1 = t2 * g1 * (s[1] + f * y0);
double y2 = t3 * g2 * (s[2] + f * y1);
// update state
s[0] += 2 * f * (xx - y0);
s[1] += 2 * f * (y0 - y1);
s[2] += 2 * f * (y1 - y2);
s[3] += 2 * f * (y2 - t4 * y3);
// out[n] = y3;
// }
// out *= 0.1;
out = y3;
#else
out *= 0.5;
for (uint8_t ovs = 0; ovs < 2; ovs++) {
fb = y3;
// hard clip
fb = ((out * 0.5) - fb) * r;
if (fb > 4) fb = 4;
if (fb < -4) fb = -4;
// fb = 1.5 * fb - 0.5 * fb * fb * fb;
//
if (fb > 6) fb = 6;
if (fb < -6) fb = -6;
y0 = ((out + fb - y0) * vcfRC) + y0;
y1 = ((y0 - y1) * vcfRC) + y1;
y2 = ((y1 - y2) * vcfRC) + y2;
y3 = ((y2 - y3) * vcfRC) + y3;
}
#endif
vcaRC = (amp - vcaRC) * m->vcaTC + vcaRC;
buffer[i] += m->vcaBuf[i] * vcaRC * out;
buffer[framePos + i] += m->vcaBuf[i] * vcaRC * y3;
lastpw = m->pwmBuf[i];
}
// buffer[0] += 1;
// buffer[0] += 1; // buzzing noise to test
}