diff --git a/plugin/alphaosc.cpp b/plugin/alphaosc.cpp index af9afed..0ae80de 100644 --- a/plugin/alphaosc.cpp +++ b/plugin/alphaosc.cpp @@ -29,23 +29,10 @@ AlphaOsc::AlphaOsc() : Plugin(parameterCount, 0, 0), sampleRate(getSampleRate()) void AlphaOsc::initAudioPort(bool input, uint32_t index, AudioPort &port) { // port.groupId = kPortGroupStereo; Plugin::initAudioPort(input, index, port); - if (!input && index == 0) port.name = "Osc Out"; } -// Processing functions - -void AlphaOsc::activate() { - // calculate filter coefficients and stuff - printf("called activate()\n"); - lfoOmega = (1 << 31) / sampleRate * 3.51; - omega = (130 / sampleRate) * (1 << 23); -} - -void AlphaOsc::deactivate() { - printf("called deactivate()\n"); -} - +// Processing function void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { bzero(outputs[0], sizeof(float) * frames); @@ -53,23 +40,27 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE (void)midiEventCount; (void)midiEvents; - uint16_t i; + uint32_t i; uint32_t osc; - uint8_t lfo; + uint8_t lfo, pw; // oscillator outputs float saw, sqr, sub, pwg; // counter bits derived from osc float bit4, bit5, bit6, bit7, bit8, bit9; - + float out, in; + // handle any MIDI events + // steeply "logarithmic" curve similar to the Juno 106 LFO rate curve // goes from about 0.1Hz to about 60Hz because this "feels about right" // totally unscientific - lfoOmega = (0.1 + (pwmrate / (1 + (1 - pwmrate) * 4.75)*60)) / sampleRate * (1<<23); - omega = (130 / sampleRate) * (1 << 23); + lfoOmega = (0.1 + (pwmrate / (1 + (1 - pwmrate) * 4.75) * 60)) / sampleRate * (1 << 23); + + // set the frequency for the phase counter + omega = (freq / sampleRate) * (1 << 23); // calculate an entire block of samples @@ -77,19 +68,26 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE // increment phase of saw counter // phase is a 24-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 is the top ten bits of the phase counter, to give room for the sub osc outputs + // bits 8 and 9 will be used for the sub squares + // bit 7 for the squarewave + // bits 0-7 of this will be used for the saw wave + // and bits 6, 5, and 4 for the "modulators" osc = phase >> 14; // LFO is 7-bit triangle - lfo = (lfoPhase >> 16) & 0x7f; - lfo = (lfoPhase & 0x00800000) ? lfo : 127 - lfo; + // the counter is 24-bit, but we take the top byte for 0-255 with fine control of speed + // by taking bits 0-6 we have a value that counts from 0-127 twice as fast as the + // desired LFO speed, which is fine + // by then taking bit 7 and using that to set whether we're counting up or down (subtract + // the counter from 127) we get a lovely triangle wave + lfo = (lfoPhase >> 16) & 0x7f; // top eight bits of the counter, keep only 0-6 + lfo = (lfoPhase & 0x00800000) ? lfo : 0x7f - lfo; // bit 7 is the polarity + // scale the LFO output to get our adjustable PWM pw = lfo * pwmdepth; // the oscillator outputs in the chip are probably digital signals @@ -97,42 +95,46 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE // 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 + // 8-bit saw scaled to 0-1 saw = (osc & 0xff) / 256.0f; // various counter bits scaled from 0-1 - bit4 = (float)(osc & 0x010) != 0; // 3 octaves up - bit5 = (float)(osc & 0x020) != 0; // 2 octaves up - bit6 = (float)(osc & 0x040) != 0; // 1 octave up - bit7 = (float)(osc & 0x080) != 0; // square wave, top bit of saw counter - bit8 = (float)(osc & 0x100) != 0; // 1 octave down - bit9 = (float)(osc & 0x200) != 0; // 2 octaves down + // these generate various squarewaves to gate the signals + bit4 = (float)(osc & 0x010) != 0; // 3 octaves up + bit5 = (float)(osc & 0x020) != 0; // 2 octaves up + bit6 = (float)(osc & 0x040) != 0; // 1 octave up + bit7 = (float)(osc & 0x080) != 0; // square wave, top bit of saw counter + bit8 = (float)(osc & 0x100) != 0; // 1 octave down + bit9 = (float)(osc & 0x200) != 0; // 2 octaves down // pulse width gate // lower seven bits of the saw osc, compared with PW setting + // this is on or off for a variable (by PW) proportion of a half-cycle + // of the square or sawtooth wave, kind of like you see on the diagram + // on the top panel of the Alpha Juno pwg = (float)((osc & 0x7f) >= pw) != 0; // calculate the oscillator output - // because all the "bits" are scaled to floats from 0 to 1 - // we can just multiply them + // because all the "bits" are scaled to floats from 0 to 1, + // we can just multiply them together to get our gating // in the real chip it probably uses AND gates to control outputs // including an AND gate driving the DAC latch pin switch (submode) { case 0: default: - sub = bit8; - break; // one octave down + sub = bit8; // one octave down + break; case 1: sub = bit8 * bit7; // one octave down, 25% PW break; case 2: - sub = bit8 * bit6; // one octave down modulated by one octave up + sub = bit8 * bit6; // one octave down modulated by one octave up break; case 3: sub = bit8 * bit5; // one octave down modulated by two octaves up break; case 4: - sub = bit9; // two octaves down + sub = bit9; // two octaves down break; case 5: sub = bit9 * bit8; // two octaves down, 25% PW @@ -143,16 +145,16 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE case 0: case 4: default: - sqr = 0; // oscillator is off + sqr = 0; // oscillator is off + break; + case 1: + sqr = bit7; // fundamental break; - case 1: // fundamental - sqr = bit7; - break; case 2: - sqr = bit7 * bit6; // 25% pulse + sqr = bit7 * bit6; // 25% pulse break; case 3: - sqr = bit7 * pwg; // pwm + sqr = bit7 * pwg; // pwm break; } @@ -160,39 +162,40 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE case 0: default: saw = 0; - break; // oscillator is off + break; // oscillator is off case 1: - break; // saw is fine, do nothing + break; // saw is fine, do nothing case 2: - saw *= bit6; // pulsed + saw *= bit6; // pulsed break; case 3: - saw *= pwg; // pwm + saw *= pwg; // pwm break; case 4: - saw *= bit4; // oct3 pulse + saw *= bit4; // oct3 pulse break; case 5: - saw *= bit6 * bit4; // both pulse + saw *= bit6 * bit4; // both pulse break; } - - - // mix the signals, probably done with some resistors in the chip - in = (sub * sublevel) + (saw * 0.8) + (sqr * 0.63); // scaled similarly to Juno 106 + // these are scaled similarly to my Juno 106 (HS60 really) because it's all + // very much just guesswork, and it "feels about right" + in = (sub * sublevel) + (saw * 0.8) + (sqr * 0.63); // DC removal highpass filter // this is very approximately 6Hz at 44.1kHz and 48kHz + // which corresponds with the 2.2uF capacitor and 12k + 100 ohm resistor between + // the voice chip output and filter input in a real Alpha Juno // 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*0.5; + // scale the output and write it to the buffer + outputs[0][i] = out * 0.5; } - // printf("%f %f\n", sqr, saw); } // create the plugin diff --git a/plugin/alphaosc.hpp b/plugin/alphaosc.hpp index 4960844..1c2a628 100644 --- a/plugin/alphaosc.hpp +++ b/plugin/alphaosc.hpp @@ -56,8 +56,6 @@ class AlphaOsc : public Plugin { float getParameterValue(uint32_t index) const override; // Processing - void activate() override; - void deactivate() override; void run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) override; private: @@ -65,7 +63,9 @@ class AlphaOsc : public Plugin { uint32_t omega, lfoOmega; uint32_t phase, lfoPhase; - uint8_t pw; + uint8_t note = 48; // last heard MIDI note + float freq = 130.8; // C3, an octave below Middle C + float gate = 0; // output attenuation float pwmrate, pwmdepth, sublevel; uint8_t sqrmode, sawmode, submode;