/* 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 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"); } 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; uint16_t i; uint32_t osc; uint8_t lfo; // oscillator outputs float saw, sqr, sub, pwg; // counter bits derived from osc float bit4, bit5, bit6, bit7, bit8, bit9; float out, in; // 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); // calculate an entire block of samples for (i = 0; i < frames; i++) { // 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 = phase >> 14; // LFO is 7-bit triangle lfo = (lfoPhase >> 16) & 0x7f; lfo = (lfoPhase & 0x00800000) ? lfo : 127 - lfo; 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 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 // pulse width gate // lower seven bits of the saw osc, compared with PW setting 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 // 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 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: // fundamental sqr = bit7; 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 in = (sub * sublevel) + (saw * 0.8) + (sqr * 0.63); // 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*0.5; } // printf("%f %f\n", sqr, saw); } // create the plugin Plugin *createPlugin() { return new AlphaOsc(); } END_NAMESPACE_DISTRHO