alphaosc/plugin/alphaosc.cpp

219 lines
7.9 KiB
C++
Raw Normal View History

2025-01-06 21:33:28 +00:00
/*
Alpha Juno Oscillator POC
Copyright 2024 Gordon JC Pearce <gordonjcp@gjcp.net>
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";
}
2025-01-07 14:50:57 +00:00
// Processing function
2025-01-06 21:33:28 +00:00
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
2025-01-07 14:58:50 +00:00
//(void)midiEventCount;
//(void)midiEvents;
2025-01-07 14:50:57 +00:00
uint32_t i;
uint32_t osc;
2025-01-07 14:50:57 +00:00
uint8_t lfo, pw;
2025-01-07 14:19:50 +00:00
// oscillator outputs
float saw, sqr, sub, pwg;
// counter bits derived from osc
float bit4, bit5, bit6, bit7, bit8, bit9;
2025-01-07 14:50:57 +00:00
float out, in;
2025-01-07 14:50:57 +00:00
// handle any MIDI events
2025-01-07 14:58:50 +00:00
for(i=0; i<midiEventCount; i++) {
if (midiEvents[i].data[0] == 0x90) {
// note on
note = midiEvents[i].data[1];
freq = 130.81*powf(2, (note-48)/12.0f);
gate = 1;
}
if (midiEvents[i].data[0] == 0x80 && midiEvents[1].data[1] == note) {
// note off for same note
gate = 0;
}
}
2025-01-07 14:50:57 +00:00
2025-01-07 14:02:31 +00:00
// 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
2025-01-07 14:50:57 +00:00
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);
2025-01-07 14:02:31 +00:00
// calculate an entire block of samples
for (i = 0; i < frames; i++) {
// increment phase of saw counter
2025-01-07 14:02:31 +00:00
// 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;
2025-01-07 14:50:57 +00:00
// 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
2025-01-07 14:50:57 +00:00
// 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
2025-01-07 14:02:31 +00:00
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
2025-01-07 14:50:57 +00:00
// 8-bit saw scaled to 0-1
saw = (osc & 0xff) / 256.0f;
2025-01-07 14:19:50 +00:00
// various counter bits scaled from 0-1
2025-01-07 14:50:57 +00:00
// 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
2025-01-07 14:50:57 +00:00
// 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
2025-01-07 14:50:57 +00:00
// because all the "bits" are scaled to floats from 0 to 1,
// we can just multiply them together to get our gating
2025-01-07 14:19:50 +00:00
// in the real chip it probably uses AND gates to control outputs
// including an AND gate driving the DAC latch pin
2025-01-07 14:02:31 +00:00
switch (submode) {
case 0:
2025-01-07 14:19:50 +00:00
default:
2025-01-07 14:50:57 +00:00
sub = bit8; // one octave down
break;
2025-01-07 14:02:31 +00:00
case 1:
2025-01-07 14:19:50 +00:00
sub = bit8 * bit7; // one octave down, 25% PW
break;
2025-01-07 14:02:31 +00:00
case 2:
2025-01-07 14:50:57 +00:00
sub = bit8 * bit6; // one octave down modulated by one octave up
2025-01-07 14:02:31 +00:00
break;
case 3:
2025-01-07 14:19:50 +00:00
sub = bit8 * bit5; // one octave down modulated by two octaves up
2025-01-07 14:02:31 +00:00
break;
case 4:
2025-01-07 14:50:57 +00:00
sub = bit9; // two octaves down
2025-01-07 14:02:31 +00:00
break;
case 5:
2025-01-07 14:19:50 +00:00
sub = bit9 * bit8; // two octaves down, 25% PW
2025-01-07 14:02:31 +00:00
break;
}
switch (sqrmode) {
case 0:
case 4:
2025-01-07 14:19:50 +00:00
default:
2025-01-07 14:50:57 +00:00
sqr = 0; // oscillator is off
break;
case 1:
sqr = bit7; // fundamental
break;
case 2:
2025-01-07 14:50:57 +00:00
sqr = bit7 * bit6; // 25% pulse
break;
case 3:
2025-01-07 14:50:57 +00:00
sqr = bit7 * pwg; // pwm
break;
}
switch (sawmode) {
case 0:
2025-01-07 14:19:50 +00:00
default:
2025-01-07 12:25:10 +00:00
saw = 0;
2025-01-07 14:50:57 +00:00
break; // oscillator is off
case 1:
2025-01-07 14:50:57 +00:00
break; // saw is fine, do nothing
case 2:
2025-01-07 14:50:57 +00:00
saw *= bit6; // pulsed
break;
case 3:
2025-01-07 14:50:57 +00:00
saw *= pwg; // pwm
break;
case 4:
2025-01-07 14:50:57 +00:00
saw *= bit4; // oct3 pulse
break;
case 5:
2025-01-07 14:50:57 +00:00
saw *= bit6 * bit4; // both pulse
break;
}
2025-01-07 12:25:10 +00:00
// mix the signals, probably done with some resistors in the chip
2025-01-07 14:50:57 +00:00
// 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
2025-01-07 14:50:57 +00:00
// 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;
2025-01-07 14:50:57 +00:00
// scale the output and write it to the buffer
2025-01-07 14:58:50 +00:00
outputs[0][i] = gate * out * 0.5;
}
2025-01-06 21:33:28 +00:00
}
// create the plugin
Plugin *createPlugin() { return new AlphaOsc(); }
END_NAMESPACE_DISTRHO