This commit is contained in:
Gordon JC Pearce 2025-01-07 14:50:57 +00:00
parent 5cc2a5c9ca
commit 99339d6aef
2 changed files with 62 additions and 59 deletions

View File

@ -29,23 +29,10 @@ AlphaOsc::AlphaOsc() : Plugin(parameterCount, 0, 0), sampleRate(getSampleRate())
void AlphaOsc::initAudioPort(bool input, uint32_t index, AudioPort &port) { void AlphaOsc::initAudioPort(bool input, uint32_t index, AudioPort &port) {
// port.groupId = kPortGroupStereo; // port.groupId = kPortGroupStereo;
Plugin::initAudioPort(input, index, port); Plugin::initAudioPort(input, index, port);
if (!input && index == 0) port.name = "Osc Out"; if (!input && index == 0) port.name = "Osc Out";
} }
// Processing functions // Processing function
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) { void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) {
bzero(outputs[0], sizeof(float) * frames); bzero(outputs[0], sizeof(float) * frames);
@ -53,9 +40,9 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
(void)midiEventCount; (void)midiEventCount;
(void)midiEvents; (void)midiEvents;
uint16_t i; uint32_t i;
uint32_t osc; uint32_t osc;
uint8_t lfo; uint8_t lfo, pw;
// oscillator outputs // oscillator outputs
float saw, sqr, sub, pwg; float saw, sqr, sub, pwg;
@ -65,11 +52,15 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
float out, in; float out, in;
// handle any MIDI events
// steeply "logarithmic" curve similar to the Juno 106 LFO rate curve // steeply "logarithmic" curve similar to the Juno 106 LFO rate curve
// goes from about 0.1Hz to about 60Hz because this "feels about right" // goes from about 0.1Hz to about 60Hz because this "feels about right"
// totally unscientific // totally unscientific
lfoOmega = (0.1 + (pwmrate / (1 + (1 - pwmrate) * 4.75) * 60)) / sampleRate * (1 << 23); lfoOmega = (0.1 + (pwmrate / (1 + (1 - pwmrate) * 4.75) * 60)) / sampleRate * (1 << 23);
omega = (130 / sampleRate) * (1 << 23);
// set the frequency for the phase counter
omega = (freq / sampleRate) * (1 << 23);
// calculate an entire block of samples // 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 // 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 // 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 // the actual Alpha Juno oscillators might well have been 8-bit for reasons loosely explained in the README
phase += omega; phase += omega;
lfoPhase += lfoOmega; lfoPhase += lfoOmega;
// now osc is a ten-bit counter, to give room for the sub osc outputs // osc is the top ten bits of the phase counter, to give room for the sub osc outputs
// square output will be bit 7 of osc, 25% will be bit 7 & bit 6 // bits 8 and 9 will be used for the sub squares
// PWM square will be bit 7 & comparator, with the PWM being compared against bits 0-6 // 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; osc = phase >> 14;
// LFO is 7-bit triangle // LFO is 7-bit triangle
lfo = (lfoPhase >> 16) & 0x7f; // the counter is 24-bit, but we take the top byte for 0-255 with fine control of speed
lfo = (lfoPhase & 0x00800000) ? lfo : 127 - lfo; // 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; pw = lfo * pwmdepth;
// the oscillator outputs in the chip are probably digital signals // the oscillator outputs in the chip are probably digital signals
@ -97,10 +95,11 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
// the square and sub signals picked off the counter bits // the square and sub signals picked off the counter bits
// and a couple of flipflops to generate the sub osc signals // 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; saw = (osc & 0xff) / 256.0f;
// various counter bits scaled from 0-1 // various counter bits scaled from 0-1
// these generate various squarewaves to gate the signals
bit4 = (float)(osc & 0x010) != 0; // 3 octaves up bit4 = (float)(osc & 0x010) != 0; // 3 octaves up
bit5 = (float)(osc & 0x020) != 0; // 2 octaves up bit5 = (float)(osc & 0x020) != 0; // 2 octaves up
bit6 = (float)(osc & 0x040) != 0; // 1 octave up bit6 = (float)(osc & 0x040) != 0; // 1 octave up
@ -110,18 +109,21 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
// pulse width gate // pulse width gate
// lower seven bits of the saw osc, compared with PW setting // 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; pwg = (float)((osc & 0x7f) >= pw) != 0;
// calculate the oscillator output // calculate the oscillator output
// because all the "bits" are scaled to floats from 0 to 1 // because all the "bits" are scaled to floats from 0 to 1,
// we can just multiply them // we can just multiply them together to get our gating
// in the real chip it probably uses AND gates to control outputs // in the real chip it probably uses AND gates to control outputs
// including an AND gate driving the DAC latch pin // including an AND gate driving the DAC latch pin
switch (submode) { switch (submode) {
case 0: case 0:
default: default:
sub = bit8; sub = bit8; // one octave down
break; // one octave down break;
case 1: case 1:
sub = bit8 * bit7; // one octave down, 25% PW sub = bit8 * bit7; // one octave down, 25% PW
break; break;
@ -145,8 +147,8 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
default: default:
sqr = 0; // oscillator is off sqr = 0; // oscillator is off
break; break;
case 1: // fundamental case 1:
sqr = bit7; sqr = bit7; // fundamental
break; break;
case 2: case 2:
sqr = bit7 * bit6; // 25% pulse sqr = bit7 * bit6; // 25% pulse
@ -177,22 +179,23 @@ void AlphaOsc::run(const float **, float **outputs, uint32_t frames, const MidiE
break; break;
} }
// mix the signals, probably done with some resistors in the chip // 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 // DC removal highpass filter
// this is very approximately 6Hz at 44.1kHz and 48kHz // 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 // honestly it doesn't matter all that much if it's wrong at higher sample rates
out = in - hpfx + .99915 * hpfy; out = in - hpfx + .99915 * hpfy;
hpfx = in; hpfx = in;
hpfy = out; hpfy = out;
// scale the output and write it to the buffer
outputs[0][i] = out * 0.5; outputs[0][i] = out * 0.5;
} }
// printf("%f %f\n", sqr, saw);
} }
// create the plugin // create the plugin

View File

@ -56,8 +56,6 @@ class AlphaOsc : public Plugin {
float getParameterValue(uint32_t index) const override; float getParameterValue(uint32_t index) const override;
// Processing // Processing
void activate() override;
void deactivate() override;
void run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) override; void run(const float **, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) override;
private: private:
@ -65,7 +63,9 @@ class AlphaOsc : public Plugin {
uint32_t omega, lfoOmega; uint32_t omega, lfoOmega;
uint32_t phase, lfoPhase; 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; float pwmrate, pwmdepth, sublevel;
uint8_t sqrmode, sawmode, submode; uint8_t sqrmode, sawmode, submode;