diff --git a/plugin/DistrhoPluginInfo.h b/plugin/DistrhoPluginInfo.h index a502743..46813cf 100644 --- a/plugin/DistrhoPluginInfo.h +++ b/plugin/DistrhoPluginInfo.h @@ -31,4 +31,7 @@ #define DISTRHO_PLUGIN_HAS_UI 0 #define DISTRHO_PLUGIN_WANT_PROGRAMS 0 +// synth configuration +#define NUM_VOICES 8 + #endif \ No newline at end of file diff --git a/plugin/Makefile b/plugin/Makefile index 2994a8b..ba26ee0 100644 --- a/plugin/Makefile +++ b/plugin/Makefile @@ -10,6 +10,7 @@ NAME = peacock FILES_DSP = \ + assigner.cpp \ peacock.cpp include ../dpf/Makefile.plugins.mk diff --git a/plugin/assigner.cpp b/plugin/assigner.cpp new file mode 100644 index 0000000..6cad869 --- /dev/null +++ b/plugin/assigner.cpp @@ -0,0 +1,139 @@ +/* + Peacock-8 VA polysynth + + Copyright 2025 Gordon JC Pearce + + 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 "assigner.hpp" + +#define DEBUG + +void Assigner::dumpTables() { +#ifdef DEBUG + printf("table state is:\n"); + for (uint8_t i = 0; i < NUM_VOICES; i++) { + uint8_t e = voiceTbl[i]; + uint8_t a = noteTbl[e]; + printf("%2d=%3d%c ", e, a & 0x7f, (a & 0x80) ? '^' : 'v'); + } + printf("\n"); +#endif +} + +Assigner::Assigner() { + d_debug("assigner constructor"); + + // set up the assigner tables + for (uint8_t i = 0; i < NUM_VOICES; i++) { + voiceTbl[i] = i; + noteTbl[i] = 0x80; + } +} + +void Assigner::handleMidi(MidiEvent *ev) { + uint8_t status = ev->data[0]; + switch (status & 0xf0) { + case 0x80: + noteOff(ev->data[1]); + dumpTables(); + break; + case 0x90: + // dumpTables(); + noteOn(ev->data[1]); + dumpTables(); + break; + case 0xb0: + switch (ev->data[1]) { + // handle the following + // CC 1 - modwheel + // CC 64 - sustain + // possibly JU-06 CC values + default: + break; + } + 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; + break; + case 0xf0: // sysex + break; + + default: + d_debug("unhandled MIDI event, status %02x value %02x\n", ev->data[0], ev->data[1]); + break; + } +} +void Assigner::noteOff(uint8_t note) { + d_debug("note off %3d", note); + + uint8_t i, v; + // scan for note in table + for (i = 0; i < NUM_VOICES; i++) { + if ((noteTbl[voiceTbl[i]] & 0x7f) == note) break; + } + if (i == NUM_VOICES) return; // got note off for note that isn't in the table + + // get the voice at this slot + // then move the rest down and place this at the end + // and mark the note for this voice as "off" by setting bit 7 + v = voiceTbl[i]; + memmove(voiceTbl + i, voiceTbl + i + 1, NUM_VOICES - i - 1); + voiceTbl[NUM_VOICES - 1] = v; + noteTbl[v] |= 0x80; +} + +void Assigner::noteOn(uint8_t note) { + d_debug("note on %3d", note); + + int8_t i, a, v; + + // don't even attempt to steal voices + // by overwriting the last voice in the table + // we could actually do voice stealing + if ((noteTbl[voiceTbl[NUM_VOICES - 1]] & 0x80) == 0x00) { + d_debug("note table full"); + return; + } + + // loop around the voices + for (i = NUM_VOICES - 1; i >= 0; i--) { + v = voiceTbl[i]; + a = noteTbl[v]; + + // if this note is playing we've gone too far + // we need to back up a slot and consider it for use + if ((a & 0x80) == 0) { + i++; + + // shunt them all up to the end + v = voiceTbl[i]; + memmove(voiceTbl + 1, voiceTbl, i); + voiceTbl[0] = v; + break; // note is already playing in table + } + + if ((a & 0x7f) == note) { + memmove(voiceTbl + 1, voiceTbl, i); + voiceTbl[0] = v; + a &= 0x7f; + break; + } + } + // printf("at end, l=%d e=%d\n", l,e); + noteTbl[v] = note; + + d_debug("send voice on %3d to voice %d", note, v); +} diff --git a/plugin/assigner.hpp b/plugin/assigner.hpp new file mode 100644 index 0000000..d742be4 --- /dev/null +++ b/plugin/assigner.hpp @@ -0,0 +1,38 @@ +/* + Peacock-8 VA polysynth + + Copyright 2025 Gordon JC Pearce + + 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. +*/ + +#ifndef _ASSIGNER_HPP +#define _ASSIGNER_HPP + +#include "DistrhoPlugin.hpp" + +class Assigner { + public: + Assigner(); + void handleMidi(MidiEvent *ev); + + private: + void noteOn(uint8_t note); // incoming note on (or off, if velocity = 0) + void noteOff(uint8_t note); // incoming note off + uint8_t voiceTbl[NUM_VOICES]; // voices in order of use + uint8_t noteTbl[NUM_VOICES]; // note played by voice + + void dumpTables(); +}; + +#endif \ No newline at end of file diff --git a/plugin/peacock.cpp b/plugin/peacock.cpp index b3d1e47..341369d 100644 --- a/plugin/peacock.cpp +++ b/plugin/peacock.cpp @@ -22,8 +22,7 @@ START_NAMESPACE_DISTRHO Peacock::Peacock() : Plugin(0, 0, 0) { d_debug("peacock constructor\n"); - - + ic1 = new Assigner; } void Peacock::initAudioPort(bool input, uint32_t index, AudioPort &port) { @@ -34,10 +33,25 @@ void Peacock::initAudioPort(bool input, uint32_t index, AudioPort &port) { if (!input && index == 1) port.name = "Right Out"; } +void Peacock::runMidi(const MidiEvent *ev, uint32_t ev_count, uint32_t timeLimit) { + // handle a sample chunk of MIDI events + uint32_t i; + if (ev_count == 0) return; // nothing to do anyway + + // there are MIDI events, loop through them + for (i = lastEvent; i < ev_count; i++) { + if (ev[i].frame > timeLimit) break; // only up to the start of the next chunk + ic1->handleMidi((MidiEvent *)&ev[i]); + } + lastEvent = i; +} + void Peacock::run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { memset(outputs[0], 0, frames * sizeof(float)); memset(outputs[1], 0, frames * sizeof(float)); + lastEvent = 0; + runMidi(midiEvents, midiEventCount, frames); } Plugin *createPlugin() { return new Peacock(); } diff --git a/plugin/peacock.hpp b/plugin/peacock.hpp index 57621f8..b260759 100644 --- a/plugin/peacock.hpp +++ b/plugin/peacock.hpp @@ -22,6 +22,7 @@ #define DEBUG #include "DistrhoPlugin.hpp" +#include "assigner.hpp" START_NAMESPACE_DISTRHO @@ -43,9 +44,11 @@ class Peacock : public Plugin { void initAudioPort(bool input, uint32_t index, AudioPort &port) override; void run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) override; + void runMidi(const MidiEvent *ev, uint32_t ev_count, uint32_t timeLimit); private: - + uint32_t lastEvent = 0; // event number of last MIDI event processed in a chunk + Assigner *ic1; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Peacock); };