cleanup oscillators

This commit is contained in:
2026-01-17 21:41:37 -06:00
parent be8cb0f890
commit c199a9b42f
6 changed files with 157 additions and 29 deletions

View File

@@ -9,6 +9,15 @@ enum class ParamId : uint16_t {
Osc1Frequency, Osc1Frequency,
Osc1WaveSelector1, Osc1WaveSelector1,
Osc1WaveSelector2, Osc1WaveSelector2,
Osc1OctaveOffset,
Osc1SemitoneOffset,
Osc1PitchOffset,
Osc2OctaveOffset,
Osc2SemitoneOffset,
Osc2PitchOffset,
Osc3OctaveOffset,
Osc3SemitoneOffset,
Osc3PitchOffset,
Osc1VolumeDepth, Osc1VolumeDepth,
Osc1VolumeEnvA, Osc1VolumeEnvA,
Osc1VolumeEnvD, Osc1VolumeEnvD,
@@ -63,24 +72,33 @@ struct ParamDefault {
// TODO: make these configurable via yml file too // TODO: make these configurable via yml file too
// later TODO: and then when I have full on profile saving there will be a default profile to load from // later TODO: and then when I have full on profile saving there will be a default profile to load from
constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{ constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{
{ 100.0f, 20.0f, 600.0f}, // Osc1Freq { 100.0f, 20.0f, 600.0f}, // Osc1Freq
{ 2.0f, 0.0f, 0.0f}, // Osc1WaveSelector1 { 2.0f, 0.0f, 0.0f}, // OscWaveSelector1
{ 1.0f, 0.0f, 0.0f}, // Osc1WaveSelector2 { 1.0f, 0.0f, 0.0f}, // OscWaveSelector2
{ 1.0f, 0.0f, 2.0f}, // Osc1VolumeDepth { 0.0f, -5.0f, 5.0f}, // Osc1OctaveOffset
{ 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA { 0.0f, -12.0f, 12.0f}, // Osc1SemitoneOffset
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD { 0.0f, -100.0f, 100.0f}, // Osc1PitchOffset
{ 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS { 1.0f, -5.0f, 5.0f}, // Osc2OctaveOffset
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR { 0.0f, -12.0f, 12.0f}, // Osc2SemitoneOffset
{ 4.0f, 0.0f, 8.0f}, // FilterCutoffDepth { 0.0f, -100.0f, 100.0f}, // Osc2PitchOffset
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvA { 1.0f, -5.0f, 5.0f}, // Osc3OctaveOffset
{ 0.20f, 0.0f, 2.0f}, // FilterCutoffEnvD { 7.0f, -12.0f, 12.0f}, // Osc3SemitoneOffset
{ 0.2f, 0.0f, 1.0f}, // FilterCutoffEnvS { 1.96f, -100.0f, 100.0f}, // Osc3PitchOffset
{ 0.25f, 0.0f, 2.0f}, // FilterCutoffEnvR { 1.0f, 0.0f, 2.0f}, // Osc1VolumeDepth
{ 3.0f, 0.0f, 8.0f}, // FilterResonanceDepth { 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvA { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD
{ 0.20f, 0.0f, 2.0f}, // FilterResonanceEnvD { 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS
{ 0.5f, 0.0f, 1.0f}, // FilterResonanceEnvS { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR
{ 0.30f, 0.0f, 2.0f}, // FilterResonanceEnvR { 4.0f, 0.0f, 8.0f}, // FilterCutoffDepth
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvA
{ 0.20f, 0.0f, 2.0f}, // FilterCutoffEnvD
{ 0.2f, 0.0f, 1.0f}, // FilterCutoffEnvS
{ 0.25f, 0.0f, 2.0f}, // FilterCutoffEnvR
{ 3.0f, 0.0f, 8.0f}, // FilterResonanceDepth
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvA
{ 0.20f, 0.0f, 2.0f}, // FilterResonanceEnvD
{ 0.5f, 0.0f, 1.0f}, // FilterResonanceEnvS
{ 0.30f, 0.0f, 2.0f}, // FilterResonanceEnvR
}}; }};
constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count); constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count);

View File

@@ -17,8 +17,8 @@ float Oscillator::frequency() {
return frequency_; return frequency_;
} }
float Oscillator::process(uint8_t note, bool& scopeTrigger) { float Oscillator::process(uint8_t note, float detune, bool& scopeTrigger) {
frequency_ = noteToFrequency(note); frequency_ = noteToFrequency(note, detune);
return process(frequency_, scopeTrigger); return process(frequency_, scopeTrigger);
} }
@@ -38,7 +38,6 @@ float Oscillator::process(float frequency, bool& scopeTrigger) {
return sampleOut; return sampleOut;
} }
inline float Oscillator::noteToFrequency(uint8_t note, float detune) {
inline float Oscillator::noteToFrequency(uint8_t note) { return SYNTH_PITCH_STANDARD * pow(2.0f, (static_cast<float>(note - SYNTH_MIDI_HOME) + detune) / static_cast<float>(SYNTH_NOTES_PER_OCTAVE));
return SYNTH_PITCH_STANDARD * pow(2.0f, static_cast<float>(note - SYNTH_MIDI_HOME) / static_cast<float>(SYNTH_NOTES_PER_OCTAVE));
} }

View File

@@ -11,6 +11,8 @@
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #endif
#define SYNTH_TWELFTH_ROOT_TWO 1.05946309435929526463
// TODO: you get it, also in a yml config // TODO: you get it, also in a yml config
#define SYNTH_PITCH_STANDARD 440.0f // frequency of home pitch #define SYNTH_PITCH_STANDARD 440.0f // frequency of home pitch
#define SYNTH_MIDI_HOME 69 // midi note index of home pitch #define SYNTH_MIDI_HOME 69 // midi note index of home pitch
@@ -27,14 +29,14 @@ public:
void setSampleRate(float sampleRate); void setSampleRate(float sampleRate);
float frequency(); float frequency();
float process(uint8_t note, bool& scopeTrigger); float process(uint8_t note, float detune, bool& scopeTrigger); // detune (-1, 1)
float process(float frequency, bool& scopeTrigger); float process(float frequency, bool& scopeTrigger);
private: private:
float sampleRate_ = 44100.0f; float sampleRate_ = 44100.0f;
inline float noteToFrequency(uint8_t note); inline float noteToFrequency(uint8_t note, float detune);
// internal state tracking // internal state tracking
float phase_ = 0.0f; float phase_ = 0.0f;

View File

@@ -87,12 +87,17 @@ float Voice::process(float* params, bool& scopeTrigger) {
o.setWavetable(osc1Wave); o.setWavetable(osc1Wave);
} }
// TODO: oscillator pitch offset // calculate the note/pitch of the oscillators
bool temp = false; bool temp = false;
float osc1 = oscillators_[0].process(note_, scopeTrigger); uint8_t osc1NoteOffset = static_cast<uint8_t>((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc1OctaveOffset) + getParam(ParamId::Osc1SemitoneOffset));
float osc2 = oscillators_[1].process(static_cast<uint8_t>(note_+12), temp); uint8_t osc2NoteOffset = static_cast<uint8_t>((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc2OctaveOffset) + getParam(ParamId::Osc2SemitoneOffset));
float osc3 = oscillators_[2].process(static_cast<uint8_t>(note_+19), temp); uint8_t osc3NoteOffset = static_cast<uint8_t>((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc3OctaveOffset) + getParam(ParamId::Osc3SemitoneOffset));
// sample oscillators
float osc1 = oscillators_[0].process(osc1NoteOffset + note_, getParam(ParamId::Osc1PitchOffset)/100.0f, scopeTrigger);
float osc2 = oscillators_[1].process(osc2NoteOffset + note_, getParam(ParamId::Osc2PitchOffset)/100.0f, temp);
float osc3 = oscillators_[2].process(osc3NoteOffset + note_, getParam(ParamId::Osc3PitchOffset)/100.0f, temp);
// mix oscillators
float sampleOut = (osc1 + osc2*0.5f + osc3*0.25f) * gain; float sampleOut = (osc1 + osc2*0.5f + osc3*0.25f) * gain;
// filter sample // filter sample

View File

@@ -0,0 +1,70 @@
#include "WavetableController.h"
#include <cmath>
#include <iostream>
WavetableController::WavetableController() {
// load from files
init();
std::cout << "wavetable init" << std::endl;
}
void WavetableController::init() {
wavetables_.resize(4); // resize for however many files we find
float phase = 0.0f;
float phaseInc = 2.0f * M_PI / static_cast<float>(SYNTH_WAVETABLE_SIZE);
for(int i = 0; i < SYNTH_WAVETABLE_SIZE; i++) {
wavetables_[0][i] = std::sin(phase) / 0.707f; // sine
wavetables_[1][i] = (phase >= M_PI) ? 1.0f : -1.0f; // square
wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw
// triangle
float tri = 0.0f;
if(phase <= M_PI/2.0f) {
tri = phase * 2.0f/M_PI;
} else if(phase <= 3.0f*M_PI/2.0f) {
tri = phase * -2.0f/M_PI + 2.0f;
} else {
tri = phase * 2.0f/M_PI - 4.0f;
}
wavetables_[3][i] = tri / 0.577f;
phase += phaseInc;
}
}
float WavetableController::sample(uint8_t wavetableIndex, float phase) {
float sampleValue = 0.0f;
if(phase >= 0.0f) {
while(phase >= 2.0f*M_PI) {
phase -= 2.0f*M_PI;
}
} else {
while(phase <= 0.0f*M_PI) {
phase += 2.0f*M_PI;
}
}
float scaledPhase = phase * static_cast<float>(SYNTH_WAVETABLE_SIZE) / (2.0f*M_PI);
uint32_t index = static_cast<uint32_t>(round(scaledPhase));
if(index >= SYNTH_WAVETABLE_SIZE) index = SYNTH_WAVETABLE_SIZE - 1;
if(wavetableIndex >= 4) {
wavetableIndex = 3;
} else if(wavetableIndex < 0) {
wavetableIndex = 0;
}
return wavetables_[wavetableIndex][index];
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <array>
#include <vector>
#define SYNTH_WAVETABLE_SIZE 2048
#ifndef M_PI // I hate my stupid chungus life
#define M_PI 3.14159265358979323846
#endif
typedef std::array<float, SYNTH_WAVETABLE_SIZE> Wavetable;
class WavetableController {
private:
/* data */
public:
WavetableController();
~WavetableController() = default;
void init();
// phase = [0, 2pi)
float sample(uint8_t wavetableIndex, float phase);
private:
std::vector<Wavetable> wavetables_;
};