ADSR and filters
This commit is contained in:
parent
c8bdf3ee13
commit
f80daed222
98
main.cpp
98
main.cpp
@ -28,6 +28,10 @@ float knob_vol_val = 0.5f;
|
|||||||
SynthEngine::Waveform current_waveform = SynthEngine::SAWTOOTH;
|
SynthEngine::Waveform current_waveform = SynthEngine::SAWTOOTH;
|
||||||
const char* waveform_names[] = {"Saw", "Square", "Sine"};
|
const char* waveform_names[] = {"Saw", "Square", "Sine"};
|
||||||
|
|
||||||
|
// ADSR (A, D, R in seconds, S in 0-1)
|
||||||
|
float adsr_vals[4] = {0.05f, 0.2f, 0.6f, 0.5f};
|
||||||
|
float filter_vals[2] = {1.0f, 0.0f}; // LP (1.0=Open), HP (0.0=Open)
|
||||||
|
|
||||||
// --- MIDI / Keyboard Input State ---
|
// --- MIDI / Keyboard Input State ---
|
||||||
std::map<SDL_Scancode, int> key_to_note_map;
|
std::map<SDL_Scancode, int> key_to_note_map;
|
||||||
int current_key_scancode = 0; // 0 for none
|
int current_key_scancode = 0; // 0 for none
|
||||||
@ -174,6 +178,21 @@ void drawToggle(SDL_Renderer* renderer, int x, int y, int size, bool active) {
|
|||||||
SDL_RenderDrawLine(renderer, m_x + m_w, m_y, m_x + m_w, m_y + m_h); // Right leg
|
SDL_RenderDrawLine(renderer, m_x + m_w, m_y, m_x + m_w, m_y + m_h); // Right leg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void drawSlider(SDL_Renderer* renderer, int x, int y, int w, int h, float val, const char* label) {
|
||||||
|
// Track
|
||||||
|
SDL_SetRenderDrawColor(renderer, 80, 80, 80, 255);
|
||||||
|
SDL_Rect track = {x + w/2 - 2, y, 4, h};
|
||||||
|
SDL_RenderFillRect(renderer, &track);
|
||||||
|
|
||||||
|
// Handle
|
||||||
|
int handleH = 10;
|
||||||
|
int handleY = y + h - (int)(val * h) - handleH/2;
|
||||||
|
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
|
||||||
|
SDL_Rect handle = {x, handleY, w, handleH};
|
||||||
|
SDL_RenderFillRect(renderer, &handle);
|
||||||
|
SDL_RenderDrawRect(renderer, &handle);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
(void)argc; (void)argv;
|
(void)argc; (void)argv;
|
||||||
|
|
||||||
@ -233,6 +252,10 @@ int main(int argc, char* argv[]) {
|
|||||||
engine.setVolume(knob_vol_val);
|
engine.setVolume(knob_vol_val);
|
||||||
engine.setGate(false); // Start with silence
|
engine.setGate(false); // Start with silence
|
||||||
|
|
||||||
|
// Init engine with default UI values
|
||||||
|
engine.setADSR(adsr_vals[0]*2.0f, adsr_vals[1]*2.0f, adsr_vals[2], adsr_vals[3]*2.0f);
|
||||||
|
engine.setFilter(20.0f + filter_vals[0]*19980.0f, 20.0f + filter_vals[1]*19980.0f);
|
||||||
|
|
||||||
// --- Main Loop ---
|
// --- Main Loop ---
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
@ -285,9 +308,9 @@ int main(int argc, char* argv[]) {
|
|||||||
SDL_GetMouseState(&mouseX, &mouseY);
|
SDL_GetMouseState(&mouseX, &mouseY);
|
||||||
|
|
||||||
// Check Toggle Click
|
// Check Toggle Click
|
||||||
int toggleX = WINDOW_WIDTH / 2;
|
int toggleX = 580;
|
||||||
int toggleY = WINDOW_HEIGHT * 3 / 4;
|
int toggleY = 450;
|
||||||
int toggleSize = 40;
|
int toggleSize = 30;
|
||||||
|
|
||||||
if (mouseX >= toggleX - toggleSize/2 && mouseX <= toggleX + toggleSize/2 &&
|
if (mouseX >= toggleX - toggleSize/2 && mouseX <= toggleX + toggleSize/2 &&
|
||||||
mouseY >= toggleY - toggleSize/2 && mouseY <= toggleY + toggleSize/2) {
|
mouseY >= toggleY - toggleSize/2 && mouseY <= toggleY + toggleSize/2) {
|
||||||
@ -298,10 +321,52 @@ int main(int argc, char* argv[]) {
|
|||||||
if (auto_melody_enabled) {
|
if (auto_melody_enabled) {
|
||||||
auto_melody_next_event_time = SDL_GetTicks(); // Start immediately
|
auto_melody_next_event_time = SDL_GetTicks(); // Start immediately
|
||||||
}
|
}
|
||||||
} else if (e.button.button == SDL_BUTTON_LEFT && mouseX < WINDOW_WIDTH / 2) {
|
} else if (e.button.button == SDL_BUTTON_LEFT) {
|
||||||
// Left knob click emulates encoder switch: cycle waveform
|
// Check Left Knob (Octave) or Waveform Icon
|
||||||
current_waveform = (SynthEngine::Waveform)(((int)current_waveform + 1) % 3);
|
// Knob: 100, 450, r=40. Waveform: 75, 500, 50x20
|
||||||
engine.setWaveform(current_waveform);
|
bool clickedKnob = (pow(mouseX - 100, 2) + pow(mouseY - 450, 2)) <= pow(40, 2);
|
||||||
|
bool clickedWave = (mouseX >= 75 && mouseX <= 125 && mouseY >= 500 && mouseY <= 520);
|
||||||
|
if (clickedKnob || clickedWave) {
|
||||||
|
// Left knob click emulates encoder switch: cycle waveform
|
||||||
|
current_waveform = (SynthEngine::Waveform)(((int)current_waveform + 1) % 3);
|
||||||
|
engine.setWaveform(current_waveform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.type == SDL_MOUSEMOTION) {
|
||||||
|
if (e.motion.state & SDL_BUTTON_LMASK) {
|
||||||
|
int mouseX = e.motion.x;
|
||||||
|
int mouseY = e.motion.y;
|
||||||
|
|
||||||
|
// Handle Sliders
|
||||||
|
// ADSR: x=200, 250, 300, 350. y=380, h=150
|
||||||
|
// Filters: x=450, 500.
|
||||||
|
int sliderY = 380;
|
||||||
|
int sliderH = 150;
|
||||||
|
int sliderW = 30;
|
||||||
|
|
||||||
|
auto checkSlider = [&](int idx, int sx, float* val) {
|
||||||
|
if (mouseX >= sx && mouseX <= sx + sliderW && mouseY >= sliderY - 20 && mouseY <= sliderY + sliderH + 20) {
|
||||||
|
*val = 1.0f - (float)(mouseY - sliderY) / (float)sliderH;
|
||||||
|
if (*val < 0.0f) *val = 0.0f;
|
||||||
|
if (*val > 1.0f) *val = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
if (checkSlider(0, 200, &adsr_vals[0])) changed = true;
|
||||||
|
if (checkSlider(1, 250, &adsr_vals[1])) changed = true;
|
||||||
|
if (checkSlider(2, 300, &adsr_vals[2])) changed = true;
|
||||||
|
if (checkSlider(3, 350, &adsr_vals[3])) changed = true;
|
||||||
|
if (changed) engine.setADSR(adsr_vals[0]*2.0f, adsr_vals[1]*2.0f, adsr_vals[2], adsr_vals[3]*2.0f);
|
||||||
|
|
||||||
|
if (checkSlider(0, 450, &filter_vals[0]) || checkSlider(1, 500, &filter_vals[1])) {
|
||||||
|
// Map 0-1 to 20Hz-20kHz
|
||||||
|
float lpFreq = 20.0f + pow(filter_vals[0], 2.0f) * 19980.0f; // Exponential feel
|
||||||
|
float hpFreq = 20.0f + pow(filter_vals[1], 2.0f) * 19980.0f;
|
||||||
|
engine.setFilter(lpFreq, hpFreq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (e.type == SDL_KEYDOWN) {
|
} else if (e.type == SDL_KEYDOWN) {
|
||||||
if (e.key.repeat == 0) { // Ignore key repeats
|
if (e.key.repeat == 0) { // Ignore key repeats
|
||||||
@ -390,11 +455,22 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// --- Draw Controls ---
|
// --- Draw Controls ---
|
||||||
// Draw in the bottom half of the window
|
// Draw in the bottom half of the window
|
||||||
|
// Knobs moved to edges
|
||||||
float normalized_octave = (float)current_octave / 8.0f; // Max octave 8
|
float normalized_octave = (float)current_octave / 8.0f; // Max octave 8
|
||||||
drawKnob(renderer, WINDOW_WIDTH / 4, WINDOW_HEIGHT * 3 / 4, 50, normalized_octave);
|
drawKnob(renderer, 100, 450, 40, normalized_octave);
|
||||||
drawWaveformIcon(renderer, WINDOW_WIDTH / 4 - 25, WINDOW_HEIGHT * 3 / 4 + 60, 50, 20, current_waveform);
|
drawWaveformIcon(renderer, 100 - 25, 450 + 50, 50, 20, current_waveform);
|
||||||
drawToggle(renderer, WINDOW_WIDTH / 2, WINDOW_HEIGHT * 3 / 4, 40, auto_melody_enabled);
|
|
||||||
drawKnob(renderer, WINDOW_WIDTH * 3 / 4, WINDOW_HEIGHT * 3 / 4, 50, knob_vol_val);
|
drawKnob(renderer, 700, 450, 40, knob_vol_val);
|
||||||
|
|
||||||
|
drawToggle(renderer, 580, 450, 30, auto_melody_enabled);
|
||||||
|
|
||||||
|
drawSlider(renderer, 200, 380, 30, 150, adsr_vals[0], "A");
|
||||||
|
drawSlider(renderer, 250, 380, 30, 150, adsr_vals[1], "D");
|
||||||
|
drawSlider(renderer, 300, 380, 30, 150, adsr_vals[2], "S");
|
||||||
|
drawSlider(renderer, 350, 380, 30, 150, adsr_vals[3], "R");
|
||||||
|
|
||||||
|
drawSlider(renderer, 450, 380, 30, 150, filter_vals[0], "LP");
|
||||||
|
drawSlider(renderer, 500, 380, 30, 150, filter_vals[1], "HP");
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
}
|
}
|
||||||
|
|||||||
100
synth_engine.cpp
100
synth_engine.cpp
@ -24,11 +24,20 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
|
|||||||
_increment(0),
|
_increment(0),
|
||||||
_volume(0.5f),
|
_volume(0.5f),
|
||||||
_waveform(SAWTOOTH),
|
_waveform(SAWTOOTH),
|
||||||
_isGateOpen(false)
|
_isGateOpen(false),
|
||||||
|
_envState(ENV_IDLE),
|
||||||
|
_envLevel(0.0f),
|
||||||
|
_attackInc(0.0f),
|
||||||
|
_decayDec(0.0f),
|
||||||
|
_sustainLevel(1.0f),
|
||||||
|
_releaseDec(0.0f),
|
||||||
|
_lpAlpha(1.0f), _hpAlpha(0.0f),
|
||||||
|
_lpVal(0.0f), _hpVal(0.0f)
|
||||||
{
|
{
|
||||||
fill_sine_table();
|
fill_sine_table();
|
||||||
// Initialize with a default frequency
|
// Initialize with a default frequency
|
||||||
setFrequency(440.0f);
|
setFrequency(440.0f);
|
||||||
|
setADSR(0.05f, 0.1f, 0.7f, 0.2f); // Default envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
void SynthEngine::setFrequency(float freq) {
|
void SynthEngine::setFrequency(float freq) {
|
||||||
@ -52,6 +61,31 @@ void SynthEngine::setWaveform(Waveform form) {
|
|||||||
|
|
||||||
void SynthEngine::setGate(bool isOpen) {
|
void SynthEngine::setGate(bool isOpen) {
|
||||||
_isGateOpen = isOpen;
|
_isGateOpen = isOpen;
|
||||||
|
if (isOpen) {
|
||||||
|
_envState = ENV_ATTACK;
|
||||||
|
} else {
|
||||||
|
_envState = ENV_RELEASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthEngine::setADSR(float attack, float decay, float sustain, float release) {
|
||||||
|
// Calculate increments per sample based on time in seconds
|
||||||
|
// Avoid division by zero
|
||||||
|
_attackInc = (attack > 0.001f) ? (1.0f / (attack * _sampleRate)) : 1.0f;
|
||||||
|
_decayDec = (decay > 0.001f) ? (1.0f / (decay * _sampleRate)) : 1.0f;
|
||||||
|
_sustainLevel = sustain;
|
||||||
|
_releaseDec = (release > 0.001f) ? (1.0f / (release * _sampleRate)) : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynthEngine::setFilter(float lpCutoff, float hpCutoff) {
|
||||||
|
// Simple one-pole filter coefficient calculation: alpha = 2*PI*fc/fs
|
||||||
|
_lpAlpha = 2.0f * M_PI * lpCutoff / _sampleRate;
|
||||||
|
if (_lpAlpha > 1.0f) _lpAlpha = 1.0f;
|
||||||
|
if (_lpAlpha < 0.0f) _lpAlpha = 0.0f;
|
||||||
|
|
||||||
|
_hpAlpha = 2.0f * M_PI * hpCutoff / _sampleRate;
|
||||||
|
if (_hpAlpha > 1.0f) _hpAlpha = 1.0f;
|
||||||
|
if (_hpAlpha < 0.0f) _hpAlpha = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float SynthEngine::getFrequency() const {
|
float SynthEngine::getFrequency() const {
|
||||||
@ -63,22 +97,56 @@ void SynthEngine::process(int16_t* buffer, uint32_t numFrames) {
|
|||||||
_phase += _increment;
|
_phase += _increment;
|
||||||
|
|
||||||
int16_t sample = 0;
|
int16_t sample = 0;
|
||||||
if (_isGateOpen) {
|
|
||||||
switch (_waveform) {
|
// Oscillator Generation
|
||||||
case SAWTOOTH:
|
switch (_waveform) {
|
||||||
sample = static_cast<int16_t>(_phase >> 16);
|
case SAWTOOTH:
|
||||||
break;
|
sample = static_cast<int16_t>(_phase >> 16);
|
||||||
case SQUARE:
|
break;
|
||||||
sample = (_phase < 0x80000000) ? 32767 : -32768;
|
case SQUARE:
|
||||||
break;
|
sample = (_phase < 0x80000000) ? 32767 : -32768;
|
||||||
case SINE:
|
break;
|
||||||
// Use top 8 bits of phase as index into sine table
|
case SINE:
|
||||||
sample = sine_table[(_phase >> 24) & 0xFF];
|
sample = sine_table[(_phase >> 24) & 0xFF];
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply volume and write to buffer
|
float sampleF = static_cast<float>(sample);
|
||||||
buffer[i] = static_cast<int16_t>(sample * _volume);
|
|
||||||
|
// Apply Filters (One-pole)
|
||||||
|
// Low Pass
|
||||||
|
_lpVal += _lpAlpha * (sampleF - _lpVal);
|
||||||
|
sampleF = _lpVal;
|
||||||
|
|
||||||
|
// High Pass (implemented as Input - LowPass(hp_cutoff))
|
||||||
|
_hpVal += _hpAlpha * (sampleF - _hpVal);
|
||||||
|
sampleF = sampleF - _hpVal;
|
||||||
|
|
||||||
|
// Apply ADSR Envelope
|
||||||
|
switch (_envState) {
|
||||||
|
case ENV_ATTACK:
|
||||||
|
_envLevel += _attackInc;
|
||||||
|
if (_envLevel >= 1.0f) { _envLevel = 1.0f; _envState = ENV_DECAY; }
|
||||||
|
break;
|
||||||
|
case ENV_DECAY:
|
||||||
|
_envLevel -= _decayDec;
|
||||||
|
if (_envLevel <= _sustainLevel) { _envLevel = _sustainLevel; _envState = ENV_SUSTAIN; }
|
||||||
|
break;
|
||||||
|
case ENV_SUSTAIN:
|
||||||
|
_envLevel = _sustainLevel;
|
||||||
|
break;
|
||||||
|
case ENV_RELEASE:
|
||||||
|
_envLevel -= _releaseDec;
|
||||||
|
if (_envLevel <= 0.0f) { _envLevel = 0.0f; _envState = ENV_IDLE; }
|
||||||
|
break;
|
||||||
|
case ENV_IDLE:
|
||||||
|
_envLevel = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleF *= _envLevel;
|
||||||
|
|
||||||
|
// Apply Master Volume and write to buffer
|
||||||
|
buffer[i] = static_cast<int16_t>(sampleF * _volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,6 +66,22 @@ public:
|
|||||||
*/
|
*/
|
||||||
float getFrequency() const;
|
float getFrequency() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the ADSR envelope.
|
||||||
|
* @param attack Attack time in seconds.
|
||||||
|
* @param decay Decay time in seconds.
|
||||||
|
* @param sustain Sustain level (0.0 to 1.0).
|
||||||
|
* @param release Release time in seconds.
|
||||||
|
*/
|
||||||
|
void setADSR(float attack, float decay, float sustain, float release);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the filters.
|
||||||
|
* @param lpCutoff Low-pass cutoff frequency in Hz.
|
||||||
|
* @param hpCutoff High-pass cutoff frequency in Hz.
|
||||||
|
*/
|
||||||
|
void setFilter(float lpCutoff, float hpCutoff);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _sampleRate;
|
uint32_t _sampleRate;
|
||||||
uint32_t _phase; // Phase accumulator for the oscillator.
|
uint32_t _phase; // Phase accumulator for the oscillator.
|
||||||
@ -73,6 +89,18 @@ private:
|
|||||||
float _volume;
|
float _volume;
|
||||||
Waveform _waveform;
|
Waveform _waveform;
|
||||||
bool _isGateOpen;
|
bool _isGateOpen;
|
||||||
|
|
||||||
|
// ADSR State
|
||||||
|
enum EnvState { ENV_IDLE, ENV_ATTACK, ENV_DECAY, ENV_SUSTAIN, ENV_RELEASE };
|
||||||
|
EnvState _envState;
|
||||||
|
float _envLevel;
|
||||||
|
float _attackInc, _decayDec, _sustainLevel, _releaseDec;
|
||||||
|
|
||||||
|
// Filter State
|
||||||
|
float _lpAlpha;
|
||||||
|
float _hpAlpha;
|
||||||
|
float _lpVal;
|
||||||
|
float _hpVal;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SYNTH_ENGINE_H
|
#endif // SYNTH_ENGINE_H
|
||||||
Loading…
Reference in New Issue
Block a user