Hi everyone, so… I managed to make an oscillator and implement some functions here. Great start! Now I’m finding that I overlooked aliasing! I’ve tried to implement some saturation as well (which i believe i did successfully) since I get some harmonics even for my sine wave. Now, you can start to see where I’m going with this…
After a certain frequency I start to encounter aliasing and the harmonics start to fold down. Not to mention this happens for my saw wave as well, obviously.
What are my options here? Oversampling? I’m a little unsure about anti aliasing filtering since I guess I’d be losing the harmonics introduced by saturation?? I took a peek at the fundamental WTOSC since I felt like that might approximate what I’m trying to do here (just without the square wave as part of the WT)… which might solve this aliasing issue? But I really have no idea how to start implementing that here.
I’ve done some C# but this is my first time doing any C++ so excuse if my implementation is super shoddy.
#include "plugin.hpp"
#include "Wavetable.hpp"
using simd::float_4;
struct Model_158 : Module {
float phaseA = 0.f; // Phase for Oscillator A
float phaseB = 0.f; // Phase for Oscillator B
enum ParamId {
OSC_A_PITCH_PARAM,
FM_A_AMOUNT,
WAVESHAPE_A,
OSC_B_PITCH_PARAM,
FM_B_AMOUNT,
WAVESHAPE_B,
PARAMS_LEN
};
enum InputId {
IN_FM_A_INPUT,
PITCH_A_INPUT_INPUT,
IN_FM_B_INPUT,
PITCH_B_INPUT_INPUT,
INPUTS_LEN
};
enum OutputId {
OUT_1_A_OUTPUT,
OUT_2_A_OUTPUT,
OUT_1_B_OUTPUT,
OUT_2_B_OUTPUT,
OUTPUTS_LEN
};
enum LightId {
LIGHTS_LEN
};
Wavetable wavetable;
float_4 phases[4] = {};
float lastPos = 0.f;
dsp::MinBlepGenerator<16, 16, float_4> syncMinBleps[4];
Model_158() {
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
// Oscillator A parameters and outputs
configParam(OSC_A_PITCH_PARAM, std::log2(5.f / dsp::FREQ_C4), std::log2(20000.f / dsp::FREQ_C4), 0.f, "Osc A Frequency");
configParam(FM_A_AMOUNT, 0.f, 1.f, 0.f, "FM Amount A");
configParam(WAVESHAPE_A, 0.f, 1.f, 0.5f, "Waveshape A");
configInput(IN_FM_A_INPUT, "");
configInput(PITCH_A_INPUT_INPUT, "");
configOutput(OUT_1_A_OUTPUT, "Osc A Main Out");
configOutput(OUT_2_A_OUTPUT, "Osc A Second Output");
// Oscillator B parameters and outputs
configParam(OSC_B_PITCH_PARAM, std::log2(5.f / dsp::FREQ_C4), std::log2(20000.f / dsp::FREQ_C4), 0.f, "Osc B Frequency");
configParam(FM_B_AMOUNT, 0.f, 1.f, 0.f, "FM Amount B");
configParam(WAVESHAPE_B, 0.f, 1.f, 0.5f, "Waveshape B");
configInput(IN_FM_B_INPUT, "");
configInput(PITCH_B_INPUT_INPUT, "");
configOutput(OUT_1_B_OUTPUT, "Osc B Main Out");
configOutput(OUT_2_B_OUTPUT, "Osc B Second Output");
}
float getWave(float index, float pos, float octave) {
// Get wave indexes
float indexF = index - std::trunc(index);
size_t index0 = std::trunc(index);
size_t index1 = (index0 + 1) % (wavetable.waveLen * wavetable.quality);
// Get position indexes
float posF = pos - std::trunc(pos);
size_t pos0 = std::trunc(pos);
size_t pos1 = pos0 + 1;
// Get octave index
// float octaveF = octave - std::trunc(octave);
size_t octave0 = std::trunc(octave);
octave0 = std::min(octave0, wavetable.octaves - 1);
// size_t octave1 = octave0 + 1;
// Linearly interpolate wave index
float out = crossfade(wavetable.interpolatedAt(octave0, pos0, index0), wavetable.interpolatedAt(octave0, pos0, index1), indexF);
// Interpolate octave
// if (octaveF > 0.f && octave1 < wavetable.octaves) {
// float out1 = crossfade(wavetable.interpolatedAt(octave1, pos0, index0), wavetable.interpolatedAt(octave1, pos0, index1), indexF);
// out = crossfade(out, out1, octaveF);
// }
// Linearly interpolate position if needed
if (posF > 0.f) {
float out1 = crossfade(wavetable.interpolatedAt(octave0, pos1, index0), wavetable.interpolatedAt(octave0, pos1, index1), indexF);
// Interpolate octave
// if (octaveF > 0.f && octave1 < wavetable.octaves) {
// float out2 = crossfade(wavetable.interpolatedAt(octave1, pos1, index0), wavetable.interpolatedAt(octave1, pos1, index1), indexF);
// out1 = crossfade(out1, out2, octaveF);
// }
out = crossfade(out, out1, posF);
}
return out;
}
void process(const ProcessArgs& args) override {
// Process Oscillator A
float pitchA = params[OSC_A_PITCH_PARAM].getValue() + inputs[PITCH_A_INPUT_INPUT].getVoltage();
float freqA = dsp::FREQ_C4 * std::pow(2.f, pitchA);
float fmInputA = inputs[IN_FM_A_INPUT].getVoltage();
float fmAmountA = params[FM_A_AMOUNT].getValue();
freqA *= std::pow(2.f, fmAmountA * fmInputA);
phaseA += freqA * args.sampleTime;
if (phaseA >= 1.f)
phaseA -= 1.f;
float waveshapeA = params[WAVESHAPE_A].getValue();
float waveformValueA;
if (waveshapeA < 0.5) {
float sineValueA = std::sin(2.f * M_PI * phaseA);
float sawValueA = 2.f * (phaseA - std::floor(phaseA + 0.5));
waveformValueA = (1.0 - 2.0 * waveshapeA) * sineValueA + (2.0 * waveshapeA) * sawValueA;
} else {
waveformValueA = 2.f * (phaseA - std::floor(phaseA + 0.5));
}
waveformValueA = std::tanh(waveformValueA);
outputs[OUT_1_A_OUTPUT].setVoltage(5.f * waveformValueA);
outputs[OUT_2_A_OUTPUT].setVoltage(5.f * waveformValueA);
// Process Oscillator B (similar to Oscillator A)
float pitchB = params[OSC_B_PITCH_PARAM].getValue() + inputs[PITCH_B_INPUT_INPUT].getVoltage();
float freqB = dsp::FREQ_C4 * std::pow(2.f, pitchB);
float fmInputB = inputs[IN_FM_B_INPUT].getVoltage();
float fmAmountB = params[FM_B_AMOUNT].getValue();
freqB *= std::pow(2.f, fmAmountB * fmInputB);
phaseB += freqB * args.sampleTime;
if (phaseB >= 1.f)
phaseB -= 1.f;
float waveshapeB = params[WAVESHAPE_B].getValue();
float waveformValueB;
if (waveshapeB < 0.5) {
float sineValueB = std::sin(2.f * M_PI * phaseB);
float sawValueB = 2.f * (phaseB - std::floor(phaseB + 0.5));
waveformValueB = (1.0 - 2.0 * waveshapeB) * sineValueB + (2.0 * waveshapeB) * sawValueB;
} else {
waveformValueB = 2.f * (phaseB - std::floor(phaseB + 0.5));
}
waveformValueB = std::tanh(waveformValueB);
outputs[OUT_1_B_OUTPUT].setVoltage(5.f * waveformValueB);
outputs[OUT_2_B_OUTPUT].setVoltage(5.f * waveformValueB);
}
};
struct Model_158Widget : ModuleWidget {
Model_158Widget(Model_158* module) {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/Model_158.svg")));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
// Use BuchlaKnob to create the oscillator frequency knob
BuchlaKnobText* FreqknobA = createParam<BuchlaKnobText>(mm2px(Vec(4.893, 92.804)), module, Model_158::OSC_A_PITCH_PARAM);
// Set the minimum and maximum angles for the knob graphic
FreqknobA->minAngle = -0.75 * M_PI;
FreqknobA->maxAngle = 0.75 * M_PI;
addParam(FreqknobA);
BuchlaKnobTiny* knobTiny = createParam<BuchlaKnobTiny>(mm2px(Vec(10.5, 62)), module, Model_158::FM_A_AMOUNT);
addParam(knobTiny);
addParam(createParam<BuchlaKnobTiny>(mm2px(Vec(10.5, 42)), module, Model_158::WAVESHAPE_A));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(9.378, 27.808)), module, Model_158::IN_FM_A_INPUT));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.795, 27.972)), module, Model_158::PITCH_A_INPUT_INPUT));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.223, 15.733)), module, Model_158::OUT_1_A_OUTPUT));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(25.223, 15.733)), module, Model_158::OUT_2_A_OUTPUT));
//Oscilator B
BuchlaKnobText* FreqknobB = createParam<BuchlaKnobText>(mm2px(Vec(4.893 + 40, 92.804)), module, Model_158::OSC_B_PITCH_PARAM);
// Set the minimum and maximum angles for the knob graphic
FreqknobB->minAngle = -0.75 * M_PI;
FreqknobB->maxAngle = 0.75 * M_PI;
addParam(FreqknobB);
BuchlaKnobTiny* knobTinyB = createParam<BuchlaKnobTiny>(mm2px(Vec(10.5 + 40, 62)), module, Model_158::FM_B_AMOUNT);
addParam(knobTinyB);
addParam(createParam<BuchlaKnobTiny>(mm2px(Vec(10.5 + 40, 42)), module, Model_158::WAVESHAPE_B));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(9.378 + 40, 27.808)), module, Model_158::IN_FM_B_INPUT));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.795 + 40, 27.972)), module, Model_158::PITCH_B_INPUT_INPUT));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.223 + 40, 15.733)), module, Model_158::OUT_1_B_OUTPUT));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(25.223 + 40, 15.733)), module, Model_158::OUT_2_B_OUTPUT));
}
};
Model* modelModel_158 = createModel<Model_158, Model_158Widget>("Model_158");