Compare commits

...

5 Commits

Author SHA1 Message Date
Gordon JC Pearce
1c3c6adf3c envelopes work again 2024-10-20 22:12:22 +01:00
Gordon JC Pearce
af2968c23f better 2024-10-20 01:08:50 +01:00
Gordon JC Pearce
08712969c7 builds after starting cleanup 2024-10-19 23:11:28 +01:00
Gordon JC Pearce
fd7634671f add filter file 2024-10-19 19:49:39 +01:00
Gordon JC Pearce
d15b80a46b added filter, not properly connected 2024-10-19 19:49:24 +01:00
12 changed files with 179 additions and 99 deletions

View File

@ -9,6 +9,7 @@
include dpf/Makefile.base.mk
CFLAGS += -ggdb
all: plugins gen
plugins:

View File

@ -12,9 +12,10 @@ NAME = peacock
FILES_DSP = \
peacock.cpp \
controls.cpp \
ic1.cpp \
assigner.cpp \
oscillator.cpp \
ic29.cpp
filter.cpp \
voiceboard.cpp
include ../dpf/Makefile.plugins.mk

View File

@ -16,14 +16,13 @@
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic1.hpp"
#include "assigner.hpp"
#include "ic29.hpp"
#include "voiceboard.hpp"
#include "peacock.hpp"
Assigner ic1;
void Assigner::printMap() {
return;
printf("note memory:\n");
for (uint8_t i = 0; i < NUM_VOICES; i++) printf("%02x ", voicemap[i]);
printf("\n");
@ -35,7 +34,7 @@ Assigner::Assigner() {
// set up the assigner
for (uint8_t i = 0; i < NUM_VOICES; i++) {
voicemap[i] = 0x80 + i;
keymap[i] = 0x3c | 0x80;
keymap[i] = 0xff;
}
}
@ -52,14 +51,14 @@ void Assigner::handleMidi(const MidiEvent *ev) {
case 0xb0:
switch (ev->data[1]) {
case 0x01:
ic29.modWheel = ev->data[2];
_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];
_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]);
@ -75,7 +74,8 @@ void Assigner::noteOff(uint8_t note) {
memmove(keymap + i, keymap + i + 1, NUM_VOICES - i - 1);
voicemap[NUM_VOICES - 1] = voice;
keymap[NUM_VOICES - 1] = note | 0x80;
ic29.voiceOff(voice);
_ic29->voiceOff(voice);
printMap();
}
}
}
@ -91,7 +91,8 @@ void Assigner::noteOn(uint8_t note) {
memmove(keymap + 1, keymap, i);
keymap[0] = note; // show note as on
voicemap[0] = voice;
ic29.voiceOn(voice, note);
_ic29->voiceOn(voice, note);
printMap();
return;
}
}
@ -105,7 +106,8 @@ void Assigner::noteOn(uint8_t note) {
memmove(keymap + 1, keymap, i);
keymap[0] = note; // show note as on
voicemap[0] = voice;
ic29.voiceOn(voice, note);
_ic29->voiceOn(voice, note);
printMap();
return;
}
}

View File

@ -18,20 +18,27 @@
#pragma once
#include "peacock.hpp"
#include "DistrhoPlugin.hpp"
#define NUM_VOICES 8
#include "voiceboard.hpp"
class Assigner {
public:
Assigner();
void setVoiceBoard(Synth& s) {
_ic29 = &s;
}
void handleMidi(const MidiEvent *ev);
private:
void noteOn(uint8_t note);
void noteOff(uint8_t note);
void printMap();
Synth* _ic29;
uint8_t keymap[NUM_VOICES];
uint8_t voicemap[NUM_VOICES];
};
extern Assigner ic1;

View File

@ -16,7 +16,7 @@
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic29.hpp"
#include "assigner.hpp"
#include "peacock.hpp"
void Peacock::initParameter(uint32_t index, Parameter& parameter) {
@ -316,6 +316,7 @@ void Peacock::setParameterValue(uint32_t index, float value) {
if (value < 0.0f) value = 0.0f;
if (value > 127.0f) value = 127.0f;
#if 0
switch (index) {
case p_lfoRate:
ic29.patchRam.lfoRate = value;
@ -409,6 +410,8 @@ void Peacock::setParameterValue(uint32_t index, float value) {
break;
case p_vcoBend:
ic29.vcoBend = (uint8_t)value;
break;
}
#endif
}

38
plugin/filter.cpp Normal file
View File

@ -0,0 +1,38 @@
/*
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 "voiceboard.hpp"
void Voice::filter(float *buffer, uint32_t pos, uint32_t frames) {
for (uint32_t i = 0; i < frames; i++) {
for (uint8_t ovs = 0; ovs < 4; ovs++) {
fb = b4;
// hard clip
if (fb > 1) fb = 1;
if (fb < -1) fb = -1;
fb = buffer[i+pos] - (fb * reso);
b1 = ((fb - b1) * cut) + b1;
b2 = ((b1 - b2) * cut) + b2;
b3 = ((b2 - b3) * cut) + b3;
b4 = ((b3 - b4) * cut) + b4;
}
buffer[i+pos] = b4;
}
}

View File

@ -18,8 +18,6 @@
#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,

View File

@ -16,7 +16,7 @@
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic29.hpp"
#include "voiceboard.hpp"
static inline float poly3blep0(float t) {
float t2 = t * t;
@ -30,8 +30,7 @@ static inline float poly3blep1(float t) {
void Voice::run(float *buffer, uint32_t pos, uint32_t samples) {
// generate a full block of samples for the oscillator
float y, out, t;
float gain = env.level / 16384.0;
float y, t;
if (subosc > 0) subosc = sub; else subosc = -sub;
@ -69,11 +68,10 @@ void Voice::run(float *buffer, uint32_t pos, uint32_t samples) {
// 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;
delay += ic29.noise[i+pos];
//delay += ic29.noise[i+pos];
out = y * 0.15;
lastpw = pwrc;
buffer[i + pos] += out * gain;
buffer[i + pos] = y * 0.707;
}
}

View File

@ -18,19 +18,18 @@
#include "peacock.hpp"
#include "ic1.hpp"
#include "ic29.hpp"
#include "assigner.hpp"
#include "voiceboard.hpp"
START_NAMESPACE_DISTRHO
Peacock::Peacock() : Plugin(paramCount, 0, 0), sampleRate(getSampleRate()) {
printf("peacock constructor\n");
ic1.setVoiceBoard(ic29);
ic29.buildTables(getSampleRate());
ic29.bufferSize = getBufferSize();
}
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;
@ -52,7 +51,6 @@ void Peacock::initAudioPort(bool input, uint32_t index, AudioPort &port) {
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
@ -73,7 +71,7 @@ void Peacock::run(const float **, float **outputs, uint32_t frames, const MidiEv
runMidi(midiEvents, midiEventCount, blockLeft);
// generate a buffer's worth of samples
memset(outputs[0], 0, sizeof(float) * frames);
memset(outputs[1], 0, sizeof(float) * frames);
while (framePos < frames) {
if (blockLeft == 0) {
@ -87,7 +85,11 @@ void Peacock::run(const float **, float **outputs, uint32_t frames, const MidiEv
// run every synth voice into the buffer here FIXME
for (uint8_t i = 0; i < NUM_VOICES; i++) {
ic29.voices[i].run(outputs[0], framePos, sizeThisTime);
//ic29->voices[i].run(outputs[0], framePos, sizeThisTime);
//ic29->voices[i].filter(outputs[0], framePos, sizeThisTime);
for (uint8_t j=0; j<sizeThisTime; j++) {
// outputs[1][framePos+j] += outputs[0][framePos+j] * ic29->voices[i].env.level/16384.0;
}
}
framePos += sizeThisTime; // move along the frame
@ -95,7 +97,7 @@ void Peacock::run(const float **, float **outputs, uint32_t frames, const MidiEv
blockLeft -= sizeThisTime;
}
// output processing goes here
memcpy(outputs[1], outputs[0], sizeof(float) * frames);
memcpy(outputs[0], outputs[1], sizeof(float) * frames);
}
Plugin *createPlugin() { return new Peacock(); }

View File

@ -23,6 +23,9 @@
#include "DistrhoPlugin.hpp"
#include "assigner.hpp"
#include "voiceboard.hpp"
START_NAMESPACE_DISTRHO
class Peacock : public Plugin {
@ -87,6 +90,9 @@ class Peacock : public Plugin {
uint32_t blockLeft = 0;
uint32_t lastEvent = 0;
Assigner ic1;
Synth ic29;
void runMidi(const MidiEvent *ev, uint32_t count, uint32_t timeLimit);
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Peacock);

View File

@ -16,16 +16,16 @@
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "ic29.hpp"
#include "voiceboard.hpp"
#include <math.h>
#include "DistrhoPlugin.hpp"
#include "ic29tables.hpp"
Synth ic29;
#define DEBUG
Synth::Synth() {
d_debug("initialising synth\n");
portaCoeff = 0x0;
noise = new float [4096];
}
void Synth::buildTables(double sampleRate) {
@ -35,16 +35,23 @@ void Synth::buildTables(double sampleRate) {
// 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;
}
// precompute a table of values to map the filter value to a filter coefficient
// the ROM preset for adjusting the VCF scale and centre presets cutoff to $31
// and key scale to $7f, which corresponds to C4 = 248Hz and C6 = 992Hz, B3 and B5
for (uint16_t i = 0; i < 256; i++) {
}
}
void Synth::run() {
// handle a "loop" worth of envelopes, pitch calculations, etc
// callled once every 4.3ms block of samples
ic29.lfo.run();
lfo.run();
masterPitch = 0x1818;
uint16_t vcoLfoDepth = ic29.lfoDepthTable[ic29.patchRam.vcoLfoMod] + ic29.modWheel;
uint16_t vcoLfoDepth = lfoDepthTable[patchRam.vcoLfoMod] + modWheel;
vcoLfoDepth = (vcoLfoDepth < 0xff) ? vcoLfoDepth : 0xff;
masterPitch += (lfo.lfoOut * vcoLfoDepth) >> 11;
@ -56,31 +63,36 @@ void Synth::run() {
// PWM is bit 0 sw2, 0 = fixed 1 = lfo
// 0 sets EA to 0x3fff, 1 adds
uint16_t pwmVal = 0x2000 - ic29.lfo.lfoOut;
if (ic29.patchRam.switch2 & 0x01) pwmVal = 0x3fff;
ic29.pwm = 0.5 - pwmVal / 32768.0f * (ic29.patchRam.pwmLfoMod / 106.0f);
uint16_t pwmVal = 0x2000 - lfo.lfoOut;
if (patchRam.switch2 & 0x01) pwmVal = 0x3fff;
pwm = 0.5 - pwmVal / 32768.0f * (patchRam.pwmLfoMod / 106.0f);
// generate the voices, then
for (uint32_t i=0; i<bufferSize; i++) {
tr21 = (tr21*519) + 3;
noise[i] = (1-(tr21 & 0x00ffffff) / 8388608.0f) * (ic29.patchRam.noiseLevel * 0.0063);
for (uint32_t i = 0; i < bufferSize; i++) {
tr21 = (tr21 * 519) + 3;
// noise[i] = (1 - (tr21 & 0x00ffffff) / 8388608.0f) * (patchRam.noiseLevel * 0.0063);
}
//printf("updating\n");
for (uint8_t i = 0; i < NUM_VOICES; i++) {
ic29.voices[i].update();
voices[i].update();
}
}
void Synth::voiceOn(uint8_t voice, uint8_t note) {
// enable synth voice, start it all running
voice &= 0x7f;
ic29.voices[voice].on(note);
d_debug("voiceOn %02x %02x", voice, note);
voices[voice].on(note);
}
void Synth::voiceOff(uint8_t voice) {
// enable synth voice, start it all running
voice &= 0x7f;
ic29.voices[voice].off();
voices[voice].off();
}
LFO::LFO() {
@ -111,30 +123,35 @@ void LFO::run() {
// printf("lfoOut=%04x\n", lfoOut);
}
uint16_t Envelope::atk = 0x10;
uint16_t Envelope::dcy = 0x3f;
uint16_t Envelope::stn = 0x3f;
uint16_t Envelope::rls = 0x1f;
Envelope::Envelope() {
level = 0;
phase = ENV_RLS;
}
void Envelope::run() {
uint16_t tempStn = ic29.patchRam.sustain << 7;
uint16_t temp = stn << 7;
switch (phase) {
case ENV_ATK:
level += atkTable[ic29.patchRam.attack];
level += atkTable[atk];
if (level > 0x3fff) {
level = 0x3fff;
phase = ENV_DCY;
}
break;
case ENV_DCY:
if (level > tempStn) {
level = (((level - tempStn) * dcyTable[ic29.patchRam.decay]) >> 16) + tempStn;
if (level > temp) {
level = (((level - temp) * dcyTable[dcy]) >> 16) + temp;
} else {
level = tempStn;
level = temp;
}
break;
case ENV_RLS:
level = (level * dcyTable[ic29.patchRam.release]) >> 16;
level = (level * dcyTable[rls]) >> 16;
break;
case ENV_IDLE:
default:
@ -143,7 +160,6 @@ void Envelope::run() {
}
Voice::Voice() {
subosc = .1;
}
void Voice::calcPitch() {
@ -155,7 +171,7 @@ void Voice::calcPitch() {
// 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) {
// portamento up
if (pitch < target) {
@ -184,37 +200,39 @@ void Voice::calcPitch() {
double frac = (pitch & 0xff) / 256.0f;
omega = ((o2 - o1) * frac) + o1;
*/
}
void Voice::update() {
// calculate the once-per-block values
env.run();
calcPitch();
pw = (ic29.patchRam.switch1 & 0x08) ? ic29.pwm : 0.0f;
//printf("env=%04x\n", env.level);
/*
pw = (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) {
d_debug("Voice on key %02x\n", key);
voiceState = V_ON;
if (note != key) {
phase = 0;
}
note = key;
if (env.phase == env.ENV_RLS) {
if (env.inRelease()) {
env.on(); // FIXME move to synth update code
}
}
void Voice::off() {
d_debug("Voice off\n");
// sustain - I need to rethink this bit FIXME
voiceState = V_OFF;
if (!ic29.sustained) {
env.off();
}
}

View File

@ -18,8 +18,9 @@
#pragma once
#include "peacock.hpp"
#include <cstdint>
#define NUM_VOICES 8
class LFO {
public:
LFO();
@ -41,6 +42,7 @@ class LFO {
class Envelope {
public:
Envelope();
void run();
void on() {
phase = ENV_ATK;
@ -54,18 +56,20 @@ class Envelope {
return phase == ENV_RLS;
}
static uint16_t atk, dcy, stn, rls;
uint16_t level;
private:
static const uint16_t atkTable[128];
static const uint16_t dcyTable[128];
enum {
ENV_ATK,
ENV_DCY,
ENV_RLS,
ENV_IDLE
} phase;
private:
static const uint16_t atkTable[128];
static const uint16_t dcyTable[128];
};
class Voice {
@ -73,11 +77,13 @@ class Voice {
Voice();
uint8_t note = 0x3c; // middle C
void run(float *buffer, uint32_t pos, uint32_t samples);
void filter(float *buffer, uint32_t pos, uint32_t samples);
void update();
void on(uint8_t note);
void off();
private:
// private:
Envelope env; // calculated envelope value
uint16_t pitch = 0x1818; // calculated pitch value with porta and master pitch etc
float delay = 0; // delay slot for polyblep
@ -91,6 +97,8 @@ class Voice {
V_ON } voiceState;
void calcPitch();
float fb = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, cut = 0.1, reso = 4;
};
class Synth {
@ -113,12 +121,13 @@ class Synth {
int8_t portaCoeff;
bool sustained;
double pitchTable[104];
double filterTable[256];
float *noise;
// private:
uint8_t vcoBend=42;
uint8_t vcfBend=42;
uint8_t vcoBend = 42;
uint8_t vcfBend = 42;
Voice voices[NUM_VOICES];
LFO lfo;
@ -152,6 +161,3 @@ class Synth {
const static uint8_t lfoDepthTable[128];
const static uint8_t portaTable[128];
};
// global
extern Synth ic29;