cleanup oscillators
This commit is contained in:
@@ -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,
|
||||||
@@ -64,8 +73,17 @@ struct ParamDefault {
|
|||||||
// 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
|
||||||
|
{ 0.0f, -5.0f, 5.0f}, // Osc1OctaveOffset
|
||||||
|
{ 0.0f, -12.0f, 12.0f}, // Osc1SemitoneOffset
|
||||||
|
{ 0.0f, -100.0f, 100.0f}, // Osc1PitchOffset
|
||||||
|
{ 1.0f, -5.0f, 5.0f}, // Osc2OctaveOffset
|
||||||
|
{ 0.0f, -12.0f, 12.0f}, // Osc2SemitoneOffset
|
||||||
|
{ 0.0f, -100.0f, 100.0f}, // Osc2PitchOffset
|
||||||
|
{ 1.0f, -5.0f, 5.0f}, // Osc3OctaveOffset
|
||||||
|
{ 7.0f, -12.0f, 12.0f}, // Osc3SemitoneOffset
|
||||||
|
{ 1.96f, -100.0f, 100.0f}, // Osc3PitchOffset
|
||||||
{ 1.0f, 0.0f, 2.0f}, // Osc1VolumeDepth
|
{ 1.0f, 0.0f, 2.0f}, // Osc1VolumeDepth
|
||||||
{ 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA
|
{ 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA
|
||||||
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD
|
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
70
src/synth/WavetableController.cpp
Normal file
70
src/synth/WavetableController.cpp
Normal 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];
|
||||||
|
}
|
||||||
34
src/synth/WavetableController.h
Normal file
34
src/synth/WavetableController.h
Normal 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_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user