initial triplex procedural reimplementation

This commit is contained in:
2025-08-03 17:15:08 +02:00
parent 3d79d24eff
commit 51de37bfb6

286
synth/triplex.h Normal file
View File

@@ -0,0 +1,286 @@
#pragma once
#include <cmath>
#include <random>
namespace trnr {
//////////////
// SINE OSC //
//////////////
struct tx_sineosc {
bool phase_reset;
double samplerate;
float phase_resolution;
float phase;
float history;
};
inline void tx_randomize_phase(float& phase)
{
std::random_device random;
std::mt19937 gen(random());
std::uniform_real_distribution<> dis(0.0, 1.0);
phase = dis(gen);
}
inline void tx_sineosc_init(tx_sineosc& s, double samplerate)
{
s.phase_reset = false;
s.samplerate = samplerate;
s.phase_resolution = 16.f;
s.phase = 0.f;
s.history = 0.f;
tx_randomize_phase(s.phase);
}
inline float tx_wrap(float& phase)
{
while (phase < 0.) phase += 1.;
while (phase >= 1.) phase -= 1.;
return phase;
}
inline float tx_sineosc_process_sample(tx_sineosc& s, bool trigger, float frequency, float phase_modulation = 0.f)
{
if (trigger) {
if (s.phase_reset) s.phase = 0.f;
else tx_randomize_phase(s.phase);
}
float lookup_phase = s.phase + phase_modulation;
tx_wrap(lookup_phase);
s.phase += frequency / s.samplerate;
tx_wrap(s.phase);
// redux
s.phase = static_cast<int>(s.phase * s.phase_resolution) / s.phase_resolution;
// x is scaled 0<=x<4096
float output;
float x = lookup_phase;
const float a = -0.40319426317E-08;
const float b = 0.21683205691E+03;
const float c = 0.28463350538E-04;
const float d = -0.30774648337E-02;
float y;
bool negate = false;
if (x > 2048) {
negate = true;
x -= 2048;
}
if (x > 1024) x = 2048 - x;
y = (a + x) / (b + c * x * x) + d * x;
if (negate) output = (-y);
else output = y;
// filter
output = 0.5 * (output + s.history);
s.history = output;
return output;
}
//////////////
// ENVELOPE //
//////////////
enum tx_env_state {
idle = 0,
attack1,
attack2,
hold,
decay1,
decay2,
sustain,
release1,
release2
};
struct tx_envelope {
tx_env_state state = idle;
float attack1_rate = 0;
float attack1_level = 0;
float attack2_rate = 0;
float hold_rate = 0;
float decay1_rate = 0;
float decay1_level = 0;
float decay2_rate = 0;
float sustain_level = 0;
float release1_rate = 0;
float release1_level = 0;
float release2_rate = 0;
bool skip_sustain = false;
double samplerate = 44100.;
size_t phase = 0;
float level = 0.f;
float start_level = 0.f;
float h1 = 0.f;
float h2 = 0.f;
float h3 = 0.f;
bool retrigger;
};
inline void tx_envelope_init(tx_envelope& e, double samplerate, bool retrigger = false)
{
e.samplerate = samplerate;
e.retrigger = retrigger;
}
inline size_t tx_mtos(float ms, double samplerate) { return static_cast<size_t>(ms * samplerate / 1000.f); }
inline float tx_lerp(float x1, float y1, float x2, float y2, float x)
{
return y1 + (((x - x1) * (y2 - y1)) / (x2 - x1));
}
inline float tx_enveloope_process_sample(tx_envelope& e, bool gate, bool trigger, float _attack_mod, float _decay_mod)
{
size_t attack_mid_x1 = tx_mtos(e.attack1_rate + (float)_attack_mod, e.samplerate);
size_t attack_mid_x2 = tx_mtos(e.attack2_rate + (float)_attack_mod, e.samplerate);
size_t hold_samp = tx_mtos(e.hold_rate, e.samplerate);
size_t decay_mid_x1 = tx_mtos(e.decay1_rate + (float)_decay_mod, e.samplerate);
size_t decay_mid_x2 = tx_mtos(e.decay2_rate + (float)_decay_mod, e.samplerate);
size_t release_mid_x1 = tx_mtos(e.release1_rate + (float)_decay_mod, e.samplerate);
size_t release_mid_x2 = tx_mtos(e.release2_rate + (float)_decay_mod, e.samplerate);
// if note on is triggered, transition to attack phase
if (trigger) {
if (e.retrigger) e.start_level = 0.f;
else e.start_level = e.level;
e.phase = 0;
e.state = attack1;
}
// attack 1st half
if (e.state == attack1) {
// while in attack phase
if (e.phase < attack_mid_x1) {
e.level = tx_lerp(0, e.start_level, (float)attack_mid_x1, e.attack1_level, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > attack_mid_x1) { e.phase = attack_mid_x1; }
// if attack phase is done, transition to decay phase
if (e.phase == attack_mid_x1) {
e.state = attack2;
e.phase = 0;
}
}
// attack 2nd half
if (e.state == attack2) {
// while in attack phase
if (e.phase < attack_mid_x2) {
e.level = tx_lerp(0, e.attack1_level, (float)attack_mid_x2, 1, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > attack_mid_x2) { e.phase = attack_mid_x2; }
// if attack phase is done, transition to decay phase
if (e.phase == attack_mid_x2) {
e.state = hold;
e.phase = 0;
}
}
// hold
if (e.state == hold) {
if (e.phase < hold_samp) {
e.level = 1.0;
e.phase += 1;
}
if (e.phase > hold_samp) { e.phase = hold_samp; }
if (e.phase == hold_samp) {
e.state = decay1;
e.phase = 0;
}
}
// decay 1st half
if (e.state == decay1) {
// while in decay phase
if (e.phase < decay_mid_x1) {
e.level = tx_lerp(0, 1, (float)decay_mid_x1, e.decay1_level, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > decay_mid_x1) { e.phase = decay_mid_x1; }
// if decay phase is done, transition to sustain phase
if (e.phase == decay_mid_x1) {
e.state = decay2;
e.phase = 0;
}
}
// decay 2nd half
if (e.state == decay2) {
// while in decay phase
if (e.phase < decay_mid_x2) {
e.level = tx_lerp(0, e.decay1_level, (float)decay_mid_x2, e.sustain_level, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > decay_mid_x2) { e.phase = decay_mid_x2; }
// if decay phase is done, transition to sustain phase
if (e.phase == decay_mid_x2) {
e.state = sustain;
e.phase = 0;
e.level = e.sustain_level;
}
}
// while sustain phase: if note off is triggered, transition to release phase
if (e.state == sustain && (!gate || e.skip_sustain)) {
e.state = release1;
e.level = e.sustain_level;
}
// release 1st half
if (e.state == release1) {
// while in release phase
if (e.phase < release_mid_x1) {
e.level = tx_lerp(0, e.sustain_level, (float)release_mid_x1, e.release1_level, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > release_mid_x1) { e.phase = release_mid_x1; }
// transition to 2nd release half
if (e.phase == release_mid_x1) {
e.phase = 0;
e.state = release2;
}
}
// release 2nd half
if (e.state == release2) {
// while in release phase
if (e.phase < release_mid_x2) {
e.level = tx_lerp(0, e.release1_level, (float)release_mid_x2, 0, (float)e.phase);
e.phase += 1;
}
// reset phase if parameter was changed
if (e.phase > release_mid_x2) { e.phase = release_mid_x2; }
// reset
if (e.phase == release_mid_x2) {
e.phase = 0;
e.state = idle;
e.level = 0;
}
}
// smooth output
e.h3 = e.h2;
e.h2 = e.h1;
e.h1 = e.level;
return (e.h1 + e.h2 + e.h3) / 3.f;
}
//////////////
// OPERATOR //
//////////////
struct tx_operator {
tx_envelope envelope;
tx_sineosc oscillator;
float ratio;
float amplitude;
};
} // namespace trnr