diff --git a/plugin/DistrhoPluginInfo.h b/plugin/DistrhoPluginInfo.h index 75ced88..85962c7 100644 --- a/plugin/DistrhoPluginInfo.h +++ b/plugin/DistrhoPluginInfo.h @@ -23,7 +23,7 @@ #define DISTRHO_PLUGIN_URI "https://gjcp.net/plugins/alphaosc" #define DISTRHO_PLUGIN_NUM_INPUTS 0 -#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 1 #define DISTRHO_PLUGIN_IS_SYNTH 1 #define DISTRHO_PLUGIN_IS_RT_SAFE 1 diff --git a/plugin/Makefile b/plugin/Makefile index 17b0913..13a14ca 100644 --- a/plugin/Makefile +++ b/plugin/Makefile @@ -10,6 +10,7 @@ NAME = alphaosc FILES_DSP = \ + parameters.cpp \ alphaosc.cpp diff --git a/plugin/alphaosc.cpp b/plugin/alphaosc.cpp index dc9b1d4..e8e4e2e 100644 --- a/plugin/alphaosc.cpp +++ b/plugin/alphaosc.cpp @@ -38,6 +38,9 @@ void AlphaOsc::initAudioPort(bool input, uint32_t index, AudioPort &port) { void AlphaOsc::activate() { // calculate filter coefficients and stuff printf("called activate()\n"); + omega = (1 << 31) / sampleRate * 130; + lfoOmega = (1 << 31) / sampleRate * 3.51; + omega = (130 / sampleRate) * (1 << 23); } void AlphaOsc::deactivate() { @@ -48,8 +51,109 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE bzero(outputs[0], sizeof(float) * frames); // cast unused parameters to void for now to stop the compiler complaining - (void) midiEventCount; - (void) midiEvents; + (void)midiEventCount; + (void)midiEvents; + + uint16_t i; + uint32_t osc; + uint8_t lfo; + float saw, sqr, sub, wave; + float oct1, oct3, pwg; + + float out, in; + + // calculate an entire block of samples + + for (i = 0; i < frames; i++) { + // increment phase of saw counter + // phase is a 32-bit counter because we're on a PC and we can afford to be profligate with silicon + // the actual Alpha Juno oscillators might well have been 8-bit for reasons loosely explained in the README + + phase += omega; + lfoPhase += lfoOmega; + + // now osc is a ten-bit counter, to give room for the sub osc outputs + // square output will be bit 7 of osc, 25% will be bit 7 & bit 6 + // PWM square will be bit 7 & comparator, with the PWM being compared against bits 0-6 + osc = phase >> 14; + + // LFO is 7-bit triangle + lfo = (lfoPhase >> 24) & 0x7f; + lfo = (lfoPhase & 0x80000000) ? lfo : 127 - lfo; + + pw = 0; + + // the oscillator outputs in the chip are probably digital signals + // with the saw being the 8-bit outputs of the counter + // the square and sub signals picked off the counter bits + // and a couple of flipflops to generate the sub osc signals + + // 8-bit saw scaled + saw = (osc & 0xff) / 256.0f; + + // square scaled to 0-1, along with one and three octaves up + sqr = (float)(osc & 0x80) != 0; + oct1 = (float)(osc & 0x40) != 0; + oct3 = (float)(osc & 0x10) != 0; + + // pulse width gate + // lower seven bits of the saw osc, compared with PW setting + pw = lfo*0.5; + pwg = (float)((osc & 0x7f) >= pw) != 0; + + // calculate the oscillator output + in = wave = 0; + switch (sqrmode) { + case 0: + case 4: + wave = 0; + break; // oscillator is off + case 1: + wave = sqr; + break; + case 2: + wave = sqr * oct1; // 25% pulse + break; + case 3: + wave = sqr * pwg; // pwm + break; + } + + in += (wave * 0.63); // scaled similarly to Juno 106 + + switch (sawmode) { + case 0: + wave = 0; + break; // oscillator is off + case 1: + wave = saw; + break; + case 2: + wave = saw * oct1; // pulsed + break; + case 3: + wave = saw * pwg; // pwm + break; + case 4: + wave = saw * oct3; // oct3 pulse + break; + case 5: + wave = saw * oct1 * oct3; // both pulse + break; + } + + in += (wave * 0.8); // scaled similarly to Juno 106 + + // DC removal highpass filter + // this is very approximately 6Hz at 44.1kHz and 48kHz + // honestly it doesn't matter all that much if it's wrong at higher sample rates + out = in - hpfx + .99915 * hpfy; + hpfx = in; + hpfy = out; + + outputs[0][i] = out; + } + // printf("%f %f\n", sqr, saw); } // create the plugin diff --git a/plugin/alphaosc.hpp b/plugin/alphaosc.hpp index 951397c..c1b7dfa 100644 --- a/plugin/alphaosc.hpp +++ b/plugin/alphaosc.hpp @@ -25,8 +25,12 @@ START_NAMESPACE_DISTRHO class AlphaOsc : public Plugin { public: enum Parameters { - paramLFORate, - paramLFODelay, + pSqrMode, + pSawMode, + //pSubMode, + + //pLFORate, + //pLFODepth, parameterCount }; @@ -45,10 +49,10 @@ class AlphaOsc : public Plugin { // Initialisation void initAudioPort(bool input, uint32_t index, AudioPort &port) override; - //void initParameter(uint32_t index, Parameter ¶meter) override; - //void setParameterValue(uint32_t index, float value) override; - //float getParameterValue(uint32_t index) const override; + void initParameter(uint32_t index, Parameter ¶meter) override; + void setParameterValue(uint32_t index, float value) override; + float getParameterValue(uint32_t index) const override; // Processing void activate() override; @@ -58,11 +62,16 @@ class AlphaOsc : public Plugin { private: double sampleRate; - uint32_t omega; - uint32_t phase; + uint32_t omega, lfoOmega; + uint32_t phase, lfoPhase; + + uint8_t pw; + + uint8_t sqrmode, sawmode, submode; + + float hpfx, hpfy; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AlphaOsc); }; -END_NAMESPACE_DISTRHO - +END_NAMESPACE_DISTRHO \ No newline at end of file diff --git a/plugin/parameters.cpp b/plugin/parameters.cpp new file mode 100644 index 0000000..6da3793 --- /dev/null +++ b/plugin/parameters.cpp @@ -0,0 +1,103 @@ +/* + Alpha Oscillator POC + + Copyright 2024 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 "alphaosc.hpp" + +void AlphaOsc::initParameter(uint32_t index, Parameter& parameter) { + // define all the different control input parameters + switch (index) { + case pSqrMode: + // set up Square slider + parameter.hints = kParameterIsAutomatable | kParameterIsInteger; + parameter.name = "Square"; + parameter.symbol = "ao_sqr"; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 4.0f; + parameter.ranges.def = 1.0f; + parameter.enumValues.count = 5; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[5]; + enumValues[0].value = 0; + enumValues[0].label = "Off"; + enumValues[1].value = 1; + enumValues[1].label = "50%"; + enumValues[2].value = 2; + enumValues[2].label = "25%"; + enumValues[3].value = 3; + enumValues[3].label = "PWM"; + // dummy value because Carla won't display integer knobs with less than 5 steps + enumValues[4].value = 3; + enumValues[4].label = "Off"; + parameter.enumValues.values = enumValues; + } + break; + case pSawMode: + // set up Saw slider + parameter.hints = kParameterIsAutomatable | kParameterIsInteger; + parameter.name = "Saw"; + parameter.symbol = "ao_saw"; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 5.0f; + parameter.ranges.def = 1.0f; + parameter.enumValues.count = 6; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[6]; + enumValues[0].value = 0; + enumValues[0].label = "Off"; + enumValues[1].value = 1; + enumValues[1].label = "On"; + enumValues[2].value = 2; + enumValues[2].label = "Oct1"; + enumValues[3].value = 3; + enumValues[3].label = "PWM"; + enumValues[3].value = 4; + enumValues[3].label = "Oct3"; + enumValues[3].value = 5; + enumValues[3].label = "Oct1+3"; + parameter.enumValues.values = enumValues; + } + break; + } +} + +void AlphaOsc::setParameterValue(uint32_t index, float value) { + switch (index) { + case pSqrMode: + sqrmode = value; + break; + case pSawMode: + sawmode = value; + break; + } + // we don't need to handle a default condition + // if there's a parameter left unhandled, it's probably + // a mistake by the plugin host, and we don't much care +} + +float AlphaOsc::getParameterValue(uint32_t index) const { + switch (index) { + case pSqrMode: + return sqrmode; + case pSawMode: + return sawmode; + } + // if we fall all the way through... + return 0; +} \ No newline at end of file