/* Alpha Juno 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" START_NAMESPACE_DISTRHO AlphaOsc::AlphaOsc() : Plugin(parameterCount, 0, 0), sampleRate(getSampleRate()) { // initial code here } // Initialisation functions 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 function void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { bzero(outputs[0], sizeof(float) * frames); // cast unused parameters to void for now to stop the compiler complaining //(void)midiEventCount; //(void)midiEvents; uint32_t i; uint32_t osc; 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 for(i=0; i> 14; // LFO is 7-bit triangle // 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 // 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 to 0-1 saw = (osc & 0xff) / 256.0f; // various counter bits scaled from 0-1 // 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 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; // 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 break; case 3: sub = bit8 * bit5; // one octave down modulated by two octaves up break; case 4: sub = bit9; // two octaves down break; case 5: sub = bit9 * bit8; // two octaves down, 25% PW break; } switch (sqrmode) { case 0: case 4: default: sqr = 0; // oscillator is off break; case 1: sqr = bit7; // fundamental break; case 2: sqr = bit7 * bit6; // 25% pulse break; case 3: sqr = bit7 * pwg; // pwm break; } switch (sawmode) { case 0: default: saw = 0; break; // oscillator is off case 1: break; // saw is fine, do nothing case 2: saw *= bit6; // pulsed break; case 3: saw *= pwg; // pwm break; case 4: saw *= bit4; // oct3 pulse break; case 5: saw *= bit6 * bit4; // both pulse break; } // mix the signals, probably done with some resistors in the chip // 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; // scale the output and write it to the buffer outputs[0][i] = gate * out * 0.5; } } // create the plugin Plugin *createPlugin() { return new AlphaOsc(); } END_NAMESPACE_DISTRHO