Files
tlib/synth/triplex.h
2025-11-06 11:05:54 +01:00

818 lines
22 KiB
C++

/*
* triplex.h
* Copyright (c) 2025 Christopher Herb
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include "../util/audio_buffer.h"
#include "../util/audio_math.h"
#include "voice_allocator.h"
#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_envelope_process_sample(tx_envelope& e, bool gate, bool trigger,
float _attack_mod = 0, float _decay_mod = 0)
{
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 = 1.f;
float amplitude = 1.f;
};
inline void tx_operator_init(tx_operator& op, double samplerate)
{
tx_envelope_init(op.envelope, samplerate);
tx_sineosc_init(op.oscillator, samplerate);
}
inline float tx_operator_process_sample(tx_operator& op, bool gate, bool trigger,
float frequency, float velocity, float pm = 0.f)
{
float env = tx_envelope_process_sample(op.envelope, gate, trigger);
// drifts and sounds better!
if (op.envelope.state != idle) {
float osc = tx_sineosc_process_sample(op.oscillator, trigger, frequency, pm);
return osc * env * velocity;
} else {
return 0.f;
}
}
////////////
// VOICE //
////////////
constexpr float MOD_INDEX_COEFF = 4.f;
struct tx_state {
float additional_pitch_mod = 0.f; // modulates pitch in frequency
int algorithm = 0;
float pitch_env_amt = 0.f;
float pitch_mod = 0.f;
float feedback_amt = 0.f;
float bit_resolution = 12.f;
tx_sineosc feedback_osc;
tx_envelope pitch_env;
tx_operator op1;
tx_operator op2;
tx_operator op3;
};
inline void tx_voice_init(tx_state& s, double samplerate)
{
tx_sineosc_init(s.feedback_osc, samplerate);
tx_envelope_init(s.pitch_env, samplerate);
tx_operator_init(s.op1, samplerate);
tx_operator_init(s.op2, samplerate);
tx_operator_init(s.op3, samplerate);
}
inline void tx_voice_process_block(tx_state& t, voice_state& s, float** audio,
size_t num_frames,
const vector<audio_buffer<float>>& mods = {})
{
float frequency =
midi_to_frequency(s.midi_note + t.pitch_mod + t.additional_pitch_mod);
for (int i = 0; i < num_frames; i++) {
voice_process_event_for_frame(s, i);
float pitch_env_signal =
tx_envelope_process_sample(t.pitch_env, s.gate, s.trigger) * t.pitch_env_amt;
float pitched_freq = frequency + pitch_env_signal;
float output = 0.f;
// mix operator signals according to selected algorithm
if (t.algorithm == 0) {
float fb_freq = frequency * t.op3.ratio;
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
float fb_signal =
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
fb_mod_index;
float op3_Freq = frequency * t.op3.ratio;
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
float op3_signal =
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_Freq, s.velocity,
fb_signal) *
op3_mod_index;
float op2_freq = frequency * t.op2.ratio;
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
float op2_signal =
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity,
op3_signal) *
op2_mod_index;
float op1_freq = frequency * t.op1.ratio;
output = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq,
s.velocity, op2_signal) *
t.op1.amplitude;
} else if (t.algorithm == 1) {
float fb_freq = frequency * t.op3.ratio;
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
float fb_signal =
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
fb_mod_index;
float op3_freq = frequency * t.op3.ratio;
float op3_signal =
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
fb_signal) *
t.op3.amplitude;
float op2_freq = frequency * t.op2.ratio;
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
op2_freq, s.velocity) *
op2_mod_index;
float op1_freq = frequency * t.op1.ratio;
float op1_signal =
tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity,
op2_signal) *
t.op1.amplitude;
output = op1_signal + op3_signal;
} else if (t.algorithm == 2) {
float fb_freq = frequency * t.op3.ratio;
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
float fb_signal =
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
fb_mod_index;
float op3_freq = frequency * t.op3.ratio;
float op3_signal =
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
fb_signal) *
t.op3.amplitude;
float op2_freq = frequency * t.op2.ratio;
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
op2_freq, s.velocity) *
t.op2.amplitude;
float op1_freq = frequency * t.op1.ratio;
float op1_signal = tx_operator_process_sample(t.op1, s.gate, s.trigger,
op1_freq, s.velocity) *
t.op1.amplitude;
output = op1_signal + op2_signal + op3_signal;
} else if (t.algorithm == 3) {
float fb_freq = frequency * t.op3.ratio;
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
float fb_signal =
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
fb_mod_index;
float op3_freq = frequency * t.op3.ratio;
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
float op3_signal =
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
fb_signal) *
op3_mod_index;
float op2_freq = frequency * t.op2.ratio;
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
op2_freq, s.velocity) *
op2_mod_index;
float op1_freq = frequency * t.op1.ratio;
output = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq,
s.velocity, op2_signal + op3_signal) *
t.op1.amplitude;
}
// reset trigger
s.trigger = false;
float res = powf(2, t.bit_resolution);
output = roundf(output * res) / res;
audio[0][i] += output / 3.;
audio[1][i] = audio[0][i];
}
}
enum tx_parameter {
BIT_RESOLUTION = 0,
FEEDBACKOSC_PHASE_RESOLUTION,
FEEDBACK,
ALGORITHM,
PITCH_ENVELOPE_AMOUNT,
PITCH_ENVELOPE_SKIP_SUSTAIN,
PITCH_ENVELOPE_ATTACK1_RATE,
PITCH_ENVELOPE_ATTACK1_LEVEL,
PITCH_ENVELOPE_ATTACK2_RATE,
PITCH_ENVELOPE_HOLD_RATE,
PITCH_ENVELOPE_DECAY1_RATE,
PITCH_ENVELOPE_DECAY1_LEVEL,
PITCH_ENVELOPE_DECAY2_RATE,
PITCH_ENVELOPE_SUSTAIN_LEVEL,
PITCH_ENVELOPE_RELEASE1_RATE,
PITCH_ENVELOPE_RELEASE1_LEVEL,
PITCH_ENVELOPE_RELEASE2_RATE,
OP1_RATIO,
OP1_AMPLITUDE,
OP1_PHASE_RESOLUTION,
OP1_ENVELOPE_SKIP_SUSTAIN,
OP1_ENVELOPE_ATTACK1_RATE,
OP1_ENVELOPE_ATTACK1_LEVEL,
OP1_ENVELOPE_ATTACK2_RATE,
OP1_ENVELOPE_HOLD_RATE,
OP1_ENVELOPE_DECAY1_RATE,
OP1_ENVELOPE_DECAY1_LEVEL,
OP1_ENVELOPE_DECAY2_RATE,
OP1_ENVELOPE_SUSTAIN_LEVEL,
OP1_ENVELOPE_RELEASE1_RATE,
OP1_ENVELOPE_RELEASE1_LEVEL,
OP1_ENVELOPE_RELEASE2_RATE,
OP2_RATIO,
OP2_AMPLITUDE,
OP2_PHASE_RESOLUTION,
OP2_ENVELOPE_SKIP_SUSTAIN,
OP2_ENVELOPE_ATTACK1_RATE,
OP2_ENVELOPE_ATTACK1_LEVEL,
OP2_ENVELOPE_ATTACK2_RATE,
OP2_ENVELOPE_HOLD_RATE,
OP2_ENVELOPE_DECAY1_RATE,
OP2_ENVELOPE_DECAY1_LEVEL,
OP2_ENVELOPE_DECAY2_RATE,
OP2_ENVELOPE_SUSTAIN_LEVEL,
OP2_ENVELOPE_RELEASE1_RATE,
OP2_ENVELOPE_RELEASE1_LEVEL,
OP2_ENVELOPE_RELEASE2_RATE,
OP3_RATIO,
OP3_AMPLITUDE,
OP3_PHASE_RESOLUTION,
OP3_ENVELOPE_SKIP_SUSTAIN,
OP3_ENVELOPE_ATTACK1_RATE,
OP3_ENVELOPE_ATTACK1_LEVEL,
OP3_ENVELOPE_ATTACK2_RATE,
OP3_ENVELOPE_HOLD_RATE,
OP3_ENVELOPE_DECAY1_RATE,
OP3_ENVELOPE_DECAY1_LEVEL,
OP3_ENVELOPE_DECAY2_RATE,
OP3_ENVELOPE_SUSTAIN_LEVEL,
OP3_ENVELOPE_RELEASE1_RATE,
OP3_ENVELOPE_RELEASE1_LEVEL,
OP3_ENVELOPE_RELEASE2_RATE,
};
struct tx_parameter_mapping {
float range_min;
float range_max;
float exponent;
tx_parameter parameter;
float apply(float _input) const
{
if (range_min == range_max && exponent == 1.f) return _input;
return powf(_input, exponent) * (range_max - range_min) + range_min;
}
};
struct tx_synth {
voice_allocator allocator;
array<tx_state, MAX_VOICES> voices;
};
inline void tx_synth_init(tx_synth& s, double samplerate)
{
voice_allocator_init(s.allocator);
for (int i = 0; i < MAX_VOICES; i++) { tx_voice_init(s.voices[i], samplerate); }
}
inline void tx_synth_process_block(tx_synth& s, float** audio, size_t num_frames,
const vector<midi_event>& midi_events,
const vector<audio_buffer<float>>& mods = {})
{
for (int i = 0; i < num_frames; i++) {
audio[0][i] = audio[1][i] = 0.f;
} // clear audio buffers
voice_allocator_process_block(s.allocator, midi_events);
for (int i = 0; i < s.allocator.active_voice_count; i++) {
tx_voice_process_block(s.voices[i], s.allocator.voices[i], audio, num_frames,
mods);
}
}
inline void tx_apply_parameter_mapping(array<tx_state, MAX_VOICES>& v,
tx_parameter_mapping& m, float value)
{
if (m.range_min != m.range_max || m.exponent != 1.f)
value = powf(value, m.exponent) * (m.range_max - m.range_min) + m.range_min;
for (int i = 0; i < MAX_VOICES; i++) {
tx_state& s = v[i];
switch (m.parameter) {
case tx_parameter::BIT_RESOLUTION:
s.bit_resolution = value;
break;
case tx_parameter::FEEDBACKOSC_PHASE_RESOLUTION:
s.feedback_osc.phase_resolution = value;
break;
case tx_parameter::FEEDBACK:
s.feedback_amt = value;
break;
case tx_parameter::ALGORITHM:
s.algorithm = value;
break;
case tx_parameter::PITCH_ENVELOPE_AMOUNT:
s.pitch_env_amt = value;
break;
case tx_parameter::PITCH_ENVELOPE_SKIP_SUSTAIN:
s.pitch_env.skip_sustain = value;
break;
case tx_parameter::PITCH_ENVELOPE_ATTACK1_RATE:
s.pitch_env.attack1_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_ATTACK1_LEVEL:
s.pitch_env.attack1_level = value;
break;
case tx_parameter::PITCH_ENVELOPE_ATTACK2_RATE:
s.pitch_env.attack2_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_HOLD_RATE:
s.pitch_env.hold_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_DECAY1_RATE:
s.pitch_env.decay1_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_DECAY1_LEVEL:
s.pitch_env.decay1_level = value;
break;
case tx_parameter::PITCH_ENVELOPE_DECAY2_RATE:
s.pitch_env.decay2_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_SUSTAIN_LEVEL:
s.pitch_env.sustain_level = value;
break;
case tx_parameter::PITCH_ENVELOPE_RELEASE1_RATE:
s.pitch_env.release1_rate = value;
break;
case tx_parameter::PITCH_ENVELOPE_RELEASE1_LEVEL:
s.pitch_env.release1_level = value;
break;
case tx_parameter::PITCH_ENVELOPE_RELEASE2_RATE:
s.pitch_env.release2_rate = value;
break;
case tx_parameter::OP1_RATIO:
s.op1.ratio = value;
break;
case tx_parameter::OP1_AMPLITUDE:
s.op1.amplitude = value;
break;
case tx_parameter::OP1_PHASE_RESOLUTION:
s.op1.oscillator.phase_resolution = value;
break;
case tx_parameter::OP1_ENVELOPE_SKIP_SUSTAIN:
s.op1.envelope.skip_sustain = value;
break;
case tx_parameter::OP1_ENVELOPE_ATTACK1_RATE:
s.op1.envelope.attack1_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_ATTACK1_LEVEL:
s.op1.envelope.attack1_level = value;
break;
case tx_parameter::OP1_ENVELOPE_ATTACK2_RATE:
s.op1.envelope.attack2_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_HOLD_RATE:
s.op1.envelope.hold_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_DECAY1_RATE:
s.op1.envelope.decay1_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_DECAY1_LEVEL:
s.op1.envelope.decay1_level = value;
break;
case tx_parameter::OP1_ENVELOPE_DECAY2_RATE:
s.op1.envelope.decay2_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_SUSTAIN_LEVEL:
s.op1.envelope.sustain_level = value;
break;
case tx_parameter::OP1_ENVELOPE_RELEASE1_RATE:
s.op1.envelope.release1_rate = value;
break;
case tx_parameter::OP1_ENVELOPE_RELEASE1_LEVEL:
s.op1.envelope.release1_level = value;
break;
case tx_parameter::OP1_ENVELOPE_RELEASE2_RATE:
s.op1.envelope.release2_rate = value;
break;
case tx_parameter::OP2_RATIO:
s.op2.ratio = value;
break;
case tx_parameter::OP2_AMPLITUDE:
s.op2.amplitude = value;
break;
case tx_parameter::OP2_PHASE_RESOLUTION:
s.op2.oscillator.phase_resolution = value;
break;
case tx_parameter::OP2_ENVELOPE_SKIP_SUSTAIN:
s.op2.envelope.skip_sustain = value;
break;
case tx_parameter::OP2_ENVELOPE_ATTACK1_RATE:
s.op2.envelope.attack1_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_ATTACK1_LEVEL:
s.op2.envelope.attack1_level = value;
break;
case tx_parameter::OP2_ENVELOPE_ATTACK2_RATE:
s.op2.envelope.attack2_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_HOLD_RATE:
s.op2.envelope.hold_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_DECAY1_RATE:
s.op2.envelope.decay1_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_DECAY1_LEVEL:
s.op2.envelope.decay1_level = value;
break;
case tx_parameter::OP2_ENVELOPE_DECAY2_RATE:
s.op2.envelope.decay2_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_SUSTAIN_LEVEL:
s.op2.envelope.sustain_level = value;
break;
case tx_parameter::OP2_ENVELOPE_RELEASE1_RATE:
s.op2.envelope.release1_rate = value;
break;
case tx_parameter::OP2_ENVELOPE_RELEASE1_LEVEL:
s.op2.envelope.release1_level = value;
break;
case tx_parameter::OP2_ENVELOPE_RELEASE2_RATE:
s.op2.envelope.release2_rate = value;
break;
case tx_parameter::OP3_RATIO:
s.op3.ratio = value;
break;
case tx_parameter::OP3_AMPLITUDE:
s.op3.amplitude = value;
break;
case tx_parameter::OP3_PHASE_RESOLUTION:
s.op3.oscillator.phase_resolution = value;
break;
case tx_parameter::OP3_ENVELOPE_SKIP_SUSTAIN:
s.op3.envelope.skip_sustain = value;
break;
case tx_parameter::OP3_ENVELOPE_ATTACK1_RATE:
s.op3.envelope.attack1_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_ATTACK1_LEVEL:
s.op3.envelope.attack1_level = value;
break;
case tx_parameter::OP3_ENVELOPE_ATTACK2_RATE:
s.op3.envelope.attack2_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_HOLD_RATE:
s.op3.envelope.hold_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_DECAY1_RATE:
s.op3.envelope.decay1_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_DECAY1_LEVEL:
s.op3.envelope.decay1_level = value;
break;
case tx_parameter::OP3_ENVELOPE_DECAY2_RATE:
s.op3.envelope.decay2_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_SUSTAIN_LEVEL:
s.op3.envelope.sustain_level = value;
break;
case tx_parameter::OP3_ENVELOPE_RELEASE1_RATE:
s.op3.envelope.release1_rate = value;
break;
case tx_parameter::OP3_ENVELOPE_RELEASE1_LEVEL:
s.op3.envelope.release1_level = value;
break;
case tx_parameter::OP3_ENVELOPE_RELEASE2_RATE:
s.op3.envelope.release2_rate = value;
break;
}
}
}
inline void tx_apply_parameter_mappings(array<tx_state, MAX_VOICES>& v,
std::vector<tx_parameter_mapping>& m, float value)
{
for (int i = 0; i < m.size(); i++) { tx_apply_parameter_mapping(v, m[i], value); }
}
} // namespace trnr