diff --git a/scripts/perf_math.sh b/scripts/perf_math.sh index af612cb1..c8b48512 100644 --- a/scripts/perf_math.sh +++ b/scripts/perf_math.sh @@ -9,7 +9,7 @@ main() if [[ "$platform" = "" ]] then - platform="x86_64-w64-mingw32" + platform="x86_64-gpp" fi executable=./build/"$platform"/perf_math @@ -17,6 +17,7 @@ main() if [[ ! -x "$executable" ]] then echo "Unable to find $executable, run \"make check\" first" >&2 + echo "or pass a platform name in the first argument." >&2 return 1 fi diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b1c3e9d9..49855f93 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -420,6 +420,13 @@ char const* const GUI::PARAMS[] = { [Synth::ParamId::L8SYN] = "LFO 8 Tempo Synchronization", [Synth::ParamId::EESYN] = "Echo Tempo Sync", + + [Synth::ParamId::MF1LOG] = "Modulator Filter 1 Logarithmic Frequency", + [Synth::ParamId::MF2LOG] = "Modulator Filter 2 Logarithmic Frequency", + [Synth::ParamId::CF1LOG] = "Carrier Filter 1 Logarithmic Frequency", + [Synth::ParamId::CF2LOG] = "Carrier Filter 2 Logarithmic Frequency", + [Synth::ParamId::EF1LOG] = "Effects Filter 1 Logarithmic Frequency", + [Synth::ParamId::EF2LOG] = "Effects Filter 2 Logarithmic Frequency", }; @@ -974,11 +981,13 @@ void GUI::build_effects_body(ParamEditorKnobStates* knob_states) PE(effects_body, 385 + PE_W * 1, 57, Synth::ParamId::EF1FRQ, LFO_CTLS, "%.1f", 1.0, knob_states); PE(effects_body, 385 + PE_W * 2, 57, Synth::ParamId::EF1Q, LFO_CTLS, "%.2f", 1.0, knob_states); PE(effects_body, 385 + PE_W * 3, 57, Synth::ParamId::EF1G, LFO_CTLS, "%.2f", 1.0, knob_states); + TS(effects_body, 459, 29, 90, 0, Synth::ParamId::EF1LOG); PE(effects_body, 690 + PE_W * 0, 57, Synth::ParamId::EF2TYP, MIDI_CTLS, ft, ftc, knob_states); PE(effects_body, 690 + PE_W * 1, 57, Synth::ParamId::EF2FRQ, LFO_CTLS, "%.1f", 1.0, knob_states); PE(effects_body, 690 + PE_W * 2, 57, Synth::ParamId::EF2Q, LFO_CTLS, "%.2f", 1.0, knob_states); PE(effects_body, 690 + PE_W * 3, 57, Synth::ParamId::EF2G, LFO_CTLS, "%.2f", 1.0, knob_states); + TS(effects_body, 764, 29, 90, 0, Synth::ParamId::EF2LOG); PE(effects_body, 258 + PE_W * 0, 242, Synth::ParamId::EEDEL, LFO_CTLS, "%.3f", 1.0, knob_states); PE(effects_body, 258 + PE_W * 1, 242, Synth::ParamId::EEFB, LFO_CTLS, "%.2f", 100.0, knob_states); @@ -1220,6 +1229,7 @@ void GUI::build_synth_body(ParamEditorKnobStates* knob_states) PE(synth_body, 735 + PE_W * 1, 36, Synth::ParamId::MF1FRQ, ALL_CTLS, "%.1f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 2, 36, Synth::ParamId::MF1Q, ALL_CTLS, "%.2f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 3, 36, Synth::ParamId::MF1G, ALL_CTLS, "%.2f", 1.0, knob_states); + TS(synth_body, 811, 13, 90, 0, Synth::ParamId::MF1LOG); PE(synth_body, 116 + PE_W * 0, 168, Synth::ParamId::MC1, FLEX_CTLS, "%.2f", 100.0, knob_states); PE(synth_body, 116 + PE_W * 1, 168, Synth::ParamId::MC2, FLEX_CTLS, "%.2f", 100.0, knob_states); @@ -1236,6 +1246,7 @@ void GUI::build_synth_body(ParamEditorKnobStates* knob_states) PE(synth_body, 735 + PE_W * 1, 168, Synth::ParamId::MF2FRQ, ALL_CTLS, "%.1f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 2, 168, Synth::ParamId::MF2Q, ALL_CTLS, "%.2f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 3, 168, Synth::ParamId::MF2G, ALL_CTLS, "%.2f", 1.0, knob_states); + TS(synth_body, 811, 145, 90, 0, Synth::ParamId::MF2LOG); PE(synth_body, 87 + PE_W * 0, 316, Synth::ParamId::CWAV, MIDI_CTLS, wf, wfc, knob_states); PE(synth_body, 87 + PE_W * 1, 316, Synth::ParamId::CPRT, FLEX_CTLS, "%.3f", 1.0, knob_states); @@ -1253,6 +1264,7 @@ void GUI::build_synth_body(ParamEditorKnobStates* knob_states) PE(synth_body, 735 + PE_W * 1, 316, Synth::ParamId::CF1FRQ, ALL_CTLS, "%.1f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 2, 316, Synth::ParamId::CF1Q, ALL_CTLS, "%.2f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 3, 316, Synth::ParamId::CF1G, ALL_CTLS, "%.2f", 1.0, knob_states); + TS(synth_body, 811, 293, 90, 0, Synth::ParamId::CF1LOG); PE(synth_body, 116 + PE_W * 0, 448, Synth::ParamId::CC1, FLEX_CTLS, "%.2f", 100.0, knob_states); PE(synth_body, 116 + PE_W * 1, 448, Synth::ParamId::CC2, FLEX_CTLS, "%.2f", 100.0, knob_states); @@ -1269,6 +1281,7 @@ void GUI::build_synth_body(ParamEditorKnobStates* knob_states) PE(synth_body, 735 + PE_W * 1, 448, Synth::ParamId::CF2FRQ, ALL_CTLS, "%.1f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 2, 448, Synth::ParamId::CF2Q, ALL_CTLS, "%.2f", 1.0, knob_states); PE(synth_body, 735 + PE_W * 3, 448, Synth::ParamId::CF2G, ALL_CTLS, "%.2f", 1.0, knob_states); + TS(synth_body, 811, 425, 90, 0, Synth::ParamId::CF2LOG); synth_body->show(); } diff --git a/src/synth.cpp b/src/synth.cpp index 3263fa73..684bea56 100644 --- a/src/synth.cpp +++ b/src/synth.cpp @@ -72,7 +72,7 @@ Synth::Synth() noexcept : SignalProducer( OUT_CHANNELS, 6 // MODE + MIX + PM + FM + AM + bus - + 29 * 2 // Modulator::Params + Carrier::Params + + 31 * 2 // Modulator::Params + Carrier::Params + POLYPHONY * 2 // modulators + carriers + 1 // effects + FLEXIBLE_CONTROLLERS * 6 @@ -148,6 +148,13 @@ Synth::Synth() noexcept create_envelopes(); create_lfos(); + modulator_params.filter_1_log_scale.set_value(ToggleParam::ON); + modulator_params.filter_2_log_scale.set_value(ToggleParam::ON); + carrier_params.filter_1_log_scale.set_value(ToggleParam::ON); + carrier_params.filter_2_log_scale.set_value(ToggleParam::ON); + effects.filter_1_log_scale.set_value(ToggleParam::ON); + effects.filter_2_log_scale.set_value(ToggleParam::ON); + update_param_states(); } @@ -273,6 +280,7 @@ void Synth::register_modulator_params() noexcept register_param_as_child( ParamId::MF1TYP, modulator_params.filter_1_type ); + register_param_as_child(ParamId::MF1LOG, modulator_params.filter_1_log_scale); register_float_param_as_child(ParamId::MF1FRQ, modulator_params.filter_1_frequency); register_float_param_as_child(ParamId::MF1Q, modulator_params.filter_1_q); register_float_param_as_child(ParamId::MF1G, modulator_params.filter_1_gain); @@ -280,6 +288,7 @@ void Synth::register_modulator_params() noexcept register_param_as_child( ParamId::MF2TYP, modulator_params.filter_2_type ); + register_param_as_child(ParamId::MF2LOG, modulator_params.filter_2_log_scale); register_float_param_as_child(ParamId::MF2FRQ, modulator_params.filter_2_frequency); register_float_param_as_child(ParamId::MF2Q, modulator_params.filter_2_q); register_float_param_as_child(ParamId::MF2G, modulator_params.filter_2_gain); @@ -316,6 +325,7 @@ void Synth::register_carrier_params() noexcept register_param_as_child( ParamId::CF1TYP, carrier_params.filter_1_type ); + register_param_as_child(ParamId::CF1LOG, carrier_params.filter_1_log_scale); register_float_param_as_child(ParamId::CF1FRQ, carrier_params.filter_1_frequency); register_float_param_as_child(ParamId::CF1Q, carrier_params.filter_1_q); register_float_param_as_child(ParamId::CF1G, carrier_params.filter_1_gain); @@ -323,6 +333,7 @@ void Synth::register_carrier_params() noexcept register_param_as_child( ParamId::CF2TYP, carrier_params.filter_2_type ); + register_param_as_child(ParamId::CF2LOG, carrier_params.filter_2_log_scale); register_float_param_as_child(ParamId::CF2FRQ, carrier_params.filter_2_frequency); register_float_param_as_child(ParamId::CF2Q, carrier_params.filter_2_q); register_float_param_as_child(ParamId::CF2G, carrier_params.filter_2_gain); @@ -335,12 +346,14 @@ void Synth::register_effects_params() noexcept register_float_param(ParamId::EDG, effects.distortion.level); - register_param::TypeParam>(EF1TYP, effects.filter_1_type); + register_param::TypeParam>(ParamId::EF1TYP, effects.filter_1_type); + register_param(ParamId::EF1LOG, effects.filter_1_log_scale); register_float_param(ParamId::EF1FRQ, effects.filter_1.frequency); register_float_param(ParamId::EF1Q, effects.filter_1.q); register_float_param(ParamId::EF1G, effects.filter_1.gain); - register_param::TypeParam>(EF2TYP, effects.filter_2_type); + register_param::TypeParam>(ParamId::EF2TYP, effects.filter_2_type); + register_param(ParamId::EF2LOG, effects.filter_2_log_scale); register_float_param(ParamId::EF2FRQ, effects.filter_2.frequency); register_float_param(ParamId::EF2Q, effects.filter_2.q); register_float_param(ParamId::EF2G, effects.filter_2.gain); @@ -827,6 +840,12 @@ Number Synth::get_param_default_ratio(ParamId const param_id) const noexcept case ParamId::L7SYN: return lfos_rw[6]->tempo_sync.get_default_ratio(); case ParamId::L8SYN: return lfos_rw[7]->tempo_sync.get_default_ratio(); case ParamId::EESYN: return effects.echo.tempo_sync.get_default_ratio(); + case ParamId::MF1LOG: return modulator_params.filter_1_log_scale.get_default_ratio(); + case ParamId::MF2LOG: return modulator_params.filter_2_log_scale.get_default_ratio(); + case ParamId::CF1LOG: return carrier_params.filter_1_log_scale.get_default_ratio(); + case ParamId::CF2LOG: return carrier_params.filter_2_log_scale.get_default_ratio(); + case ParamId::EF1LOG: return effects.filter_1_log_scale.get_default_ratio(); + case ParamId::EF2LOG: return effects.filter_2_log_scale.get_default_ratio(); default: return 0.0; // This should neacver be reached. } } @@ -865,6 +884,12 @@ Number Synth::get_param_max_value(ParamId const param_id) const noexcept case ParamId::L7SYN: return lfos_rw[6]->tempo_sync.get_max_value(); case ParamId::L8SYN: return lfos_rw[7]->tempo_sync.get_max_value(); case ParamId::EESYN: return effects.echo.tempo_sync.get_max_value(); + case ParamId::MF1LOG: return modulator_params.filter_1_log_scale.get_max_value(); + case ParamId::MF2LOG: return modulator_params.filter_2_log_scale.get_max_value(); + case ParamId::CF1LOG: return carrier_params.filter_1_log_scale.get_max_value(); + case ParamId::CF2LOG: return carrier_params.filter_2_log_scale.get_max_value(); + case ParamId::EF1LOG: return effects.filter_1_log_scale.get_max_value(); + case ParamId::EF2LOG: return effects.filter_2_log_scale.get_max_value(); default: return 0.0; // This should neacver be reached. } } @@ -913,6 +938,12 @@ Byte Synth::int_param_ratio_to_display_value( case ParamId::L7SYN: return lfos_rw[6]->tempo_sync.ratio_to_value(ratio); case ParamId::L8SYN: return lfos_rw[7]->tempo_sync.ratio_to_value(ratio); case ParamId::EESYN: return effects.echo.tempo_sync.ratio_to_value(ratio); + case ParamId::MF1LOG: return modulator_params.filter_1_log_scale.ratio_to_value(ratio); + case ParamId::MF2LOG: return modulator_params.filter_2_log_scale.ratio_to_value(ratio); + case ParamId::CF1LOG: return carrier_params.filter_1_log_scale.ratio_to_value(ratio); + case ParamId::CF2LOG: return carrier_params.filter_2_log_scale.ratio_to_value(ratio); + case ParamId::EF1LOG: return effects.filter_1_log_scale.ratio_to_value(ratio); + case ParamId::EF2LOG: return effects.filter_2_log_scale.ratio_to_value(ratio); default: return 0; // This should never be reached. } } @@ -1019,6 +1050,12 @@ void Synth::handle_set_param(ParamId const param_id, Number const ratio) noexcep case ParamId::L7SYN: lfos_rw[6]->tempo_sync.set_ratio(ratio); break; case ParamId::L8SYN: lfos_rw[7]->tempo_sync.set_ratio(ratio); break; case ParamId::EESYN: effects.echo.tempo_sync.set_ratio(ratio); break; + case ParamId::MF1LOG: modulator_params.filter_1_log_scale.set_ratio(ratio); break; + case ParamId::MF2LOG: modulator_params.filter_2_log_scale.set_ratio(ratio); break; + case ParamId::CF1LOG: carrier_params.filter_1_log_scale.set_ratio(ratio); break; + case ParamId::CF2LOG: carrier_params.filter_2_log_scale.set_ratio(ratio); break; + case ParamId::EF1LOG: effects.filter_1_log_scale.set_ratio(ratio); break; + case ParamId::EF2LOG: effects.filter_2_log_scale.set_ratio(ratio); break; default: break; // This should never be reached. } } @@ -1203,6 +1240,12 @@ Number Synth::get_param_ratio(ParamId const param_id) const noexcept case ParamId::L7SYN: return lfos_rw[6]->tempo_sync.get_ratio(); case ParamId::L8SYN: return lfos_rw[7]->tempo_sync.get_ratio(); case ParamId::EESYN: return effects.echo.tempo_sync.get_ratio(); + case ParamId::MF1LOG: return modulator_params.filter_1_log_scale.get_ratio(); + case ParamId::MF2LOG: return modulator_params.filter_2_log_scale.get_ratio(); + case ParamId::CF1LOG: return carrier_params.filter_1_log_scale.get_ratio(); + case ParamId::CF2LOG: return carrier_params.filter_2_log_scale.get_ratio(); + case ParamId::EF1LOG: return effects.filter_1_log_scale.get_ratio(); + case ParamId::EF2LOG: return effects.filter_2_log_scale.get_ratio(); default: return 0.0; // This should never be reached. } } diff --git a/src/synth.hpp b/src/synth.hpp index 4058a6cc..838ce900 100644 --- a/src/synth.hpp +++ b/src/synth.hpp @@ -404,8 +404,14 @@ class Synth : public Midi::EventHandler, public SignalProducer L7SYN = 278, ///< LFO 7 Tempo Synchronization L8SYN = 279, ///< LFO 8 Tempo Synchronization EESYN = 280, ///< Effects Echo Tempo Synchronization - - MAX_PARAM_ID = 281 + MF1LOG = 281, ///< Modulator Filter 1 Logarithmic Frequency + MF2LOG = 282, ///< Modulator Filter 2 Logarithmic Frequency + CF1LOG = 283, ///< Carrier Filter 1 Logarithmic Frequency + CF2LOG = 284, ///< Carrier Filter 2 Logarithmic Frequency + EF1LOG = 285, ///< Effects Filter 1 Logarithmic Frequency + EF2LOG = 286, ///< Effects Filter 2 Logarithmic Frequency + + MAX_PARAM_ID = 287 }; static constexpr Integer FLOAT_PARAMS = ParamId::MODE; diff --git a/src/synth/biquad_filter.cpp b/src/synth/biquad_filter.cpp index 26d68294..96c319f9 100644 --- a/src/synth/biquad_filter.cpp +++ b/src/synth/biquad_filter.cpp @@ -112,6 +112,45 @@ void BiquadFilter::update_helper_variables() noexcept } +template +BiquadFilter::BiquadFilter( + std::string const name, + InputSignalProducerClass& input, + TypeParam& type, + ToggleParam const& log_scale_toggle +) noexcept + : Filter(input, 3), + frequency( + name + "FRQ", + Constants::BIQUAD_FILTER_FREQUENCY_MIN, + Constants::BIQUAD_FILTER_FREQUENCY_MAX, + Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT, + 0.0, + &log_scale_toggle, + Math::log_biquad_filter_freq_table(), + Math::log_biquad_filter_freq_inv_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + Math::LOG_BIQUAD_FILTER_FREQ_SCALE, + Math::LOG_BIQUAD_FILTER_FREQ_INV_SCALE + ), + q( + name + "Q", + Constants::BIQUAD_FILTER_Q_MIN, + Constants::BIQUAD_FILTER_Q_MAX, + Constants::BIQUAD_FILTER_Q_DEFAULT + ), + gain( + name + "G", + Constants::BIQUAD_FILTER_GAIN_MIN, + Constants::BIQUAD_FILTER_GAIN_MAX, + Constants::BIQUAD_FILTER_GAIN_DEFAULT + ), + type(type), + shared_cache(NULL) +{ + initialize_instance(); +} + template BiquadFilter::BiquadFilter( diff --git a/src/synth/biquad_filter.hpp b/src/synth/biquad_filter.hpp index 380c67dc..0de58f06 100644 --- a/src/synth/biquad_filter.hpp +++ b/src/synth/biquad_filter.hpp @@ -83,6 +83,14 @@ class BiquadFilter : public Filter TypeParam& type, BiquadFilterSharedCache* shared_cache = NULL ) noexcept; + + BiquadFilter( + std::string const name, + InputSignalProducerClass& input, + TypeParam& type, + ToggleParam const& log_scale_toggle + ) noexcept; + BiquadFilter( InputSignalProducerClass& input, TypeParam& type, @@ -91,14 +99,12 @@ class BiquadFilter : public Filter FloatParam& gain_leader, BiquadFilterSharedCache* shared_cache = NULL ) noexcept; + virtual ~BiquadFilter(); - virtual void set_sample_rate( - Frequency const new_sample_rate - ) noexcept override; - virtual void set_block_size( - Integer const new_block_size - ) noexcept override; + virtual void set_sample_rate(Frequency const new_sample_rate) noexcept override; + + virtual void set_block_size(Integer const new_block_size) noexcept override; virtual void reset() noexcept override; diff --git a/src/synth/effects.cpp b/src/synth/effects.cpp index 5658a666..a8dcfcfa 100644 --- a/src/synth/effects.cpp +++ b/src/synth/effects.cpp @@ -29,13 +29,15 @@ template Effects::Effects( std::string const name, InputSignalProducerClass& input -) : Filter< Reverb >(reverb, 8, input.get_channels()), +) : Filter< Reverb >(reverb, 10, input.get_channels()), overdrive(name + "O", 3.0, input), distortion(name + "D", 10.0, overdrive), filter_1_type(name + "F1TYP"), filter_2_type(name + "F2TYP"), - filter_1(name + "F1", distortion, filter_1_type), - filter_2(name + "F2", filter_1, filter_2_type), + filter_1_log_scale(name + "F1LOG", ToggleParam::OFF), + filter_2_log_scale(name + "F2LOG", ToggleParam::OFF), + filter_1(name + "F1", distortion, filter_1_type, filter_1_log_scale), + filter_2(name + "F2", filter_1, filter_2_type, filter_2_log_scale), echo(name + "E", filter_2), reverb(name + "R", echo) { @@ -43,6 +45,8 @@ Effects::Effects( this->register_child(distortion); this->register_child(filter_1_type); this->register_child(filter_2_type); + this->register_child(filter_1_log_scale); + this->register_child(filter_2_log_scale); this->register_child(filter_1); this->register_child(filter_2); this->register_child(echo); diff --git a/src/synth/effects.hpp b/src/synth/effects.hpp index 6da4dd1f..d0cda441 100644 --- a/src/synth/effects.hpp +++ b/src/synth/effects.hpp @@ -62,6 +62,8 @@ class Effects : public Filter< Reverb > Distortion distortion; typename Filter1::TypeParam filter_1_type; typename Filter2::TypeParam filter_2_type; + ToggleParam filter_1_log_scale; + ToggleParam filter_2_log_scale; Filter1 filter_1; Filter2 filter_2; Echo echo; diff --git a/src/synth/math.cpp b/src/synth/math.cpp index 0602eefe..5008842b 100644 --- a/src/synth/math.cpp +++ b/src/synth/math.cpp @@ -35,6 +35,7 @@ Math::Math() noexcept init_sines(); init_randoms(); init_distortion(); + init_log_biquad_filter_freq(); } @@ -81,6 +82,28 @@ void Math::init_distortion() noexcept } +void Math::init_log_biquad_filter_freq() noexcept +{ + constexpr Number max_inv = 1.0 / (Number)LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX; + constexpr Number min = Constants::BIQUAD_FILTER_FREQUENCY_MIN; + constexpr Number max = Constants::BIQUAD_FILTER_FREQUENCY_MAX; + constexpr Number range = max - min; + constexpr Number log2_min = std::log2(min); + constexpr Number log2_max = std::log2(Constants::BIQUAD_FILTER_FREQUENCY_MAX); + constexpr Number log2_range = log2_max - log2_min; + constexpr Number log2_range_inv = 1.0 / log2_range; + + for (int i = 0; i != LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE; ++i) { + Number const x = (Number)i * max_inv; + + log_biquad_filter_freq_inv[i] = ( + (std::log2(min + range * x) - log2_min) * log2_range_inv + ); + log_biquad_filter_freq[i] = std::pow(2.0, log2_min + log2_range * x); + } +} + + Number Math::sin(Number const x) noexcept { return math.sin_impl(x); @@ -144,6 +167,18 @@ Number Math::pow_10_inv(Number const x) noexcept } +Number const* Math::log_biquad_filter_freq_table() noexcept +{ + return math.log_biquad_filter_freq; +} + + +Number const* Math::log_biquad_filter_freq_inv_table() noexcept +{ + return math.log_biquad_filter_freq_inv; +} + + Frequency Math::detune(Frequency const frequency, Number const cents) noexcept { /* diff --git a/src/synth/math.hpp b/src/synth/math.hpp index 30bdc452..fd588998 100644 --- a/src/synth/math.hpp +++ b/src/synth/math.hpp @@ -71,6 +71,17 @@ class Math std::max(LN_OF_10 * POW_10_MAX, -1.0 * LN_OF_10 * POW_10_INV_MIN) ); ///< \warning This limit is not enforced. Values outside the limit may be imprecise. + static constexpr int LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE = 0x0400; + static constexpr int LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX = ( + LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE - 1 + ); + static constexpr Number LOG_BIQUAD_FILTER_FREQ_SCALE = ( + (Number)LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE + ); + static constexpr Number LOG_BIQUAD_FILTER_FREQ_INV_SCALE = ( + 1.0 / (Constants::BIQUAD_FILTER_FREQUENCY_MAX - Constants::BIQUAD_FILTER_FREQUENCY_MIN) + ); + /** * \warning Negative numbers close to multiples of PI are not handled * very well with regards to precision. @@ -87,6 +98,9 @@ class Math static Number pow_10(Number const x) noexcept; static Number pow_10_inv(Number const x) noexcept; + static Number const* log_biquad_filter_freq_table() noexcept; + static Number const* log_biquad_filter_freq_inv_table() noexcept; + static Frequency detune( Frequency const frequency, Number const cents @@ -187,12 +201,15 @@ class Math void init_sines() noexcept; void init_randoms() noexcept; void init_distortion() noexcept; + void init_log_biquad_filter_freq() noexcept; Number sin_impl(Number const x) const noexcept; Number sines[SIN_TABLE_SIZE]; Number randoms[RANDOMS]; Number distortion[DISTORTION_TABLE_SIZE]; + Number log_biquad_filter_freq[LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE]; + Number log_biquad_filter_freq_inv[LOG_BIQUAD_FILTER_FREQ_TABLE_SIZE]; }; } diff --git a/src/synth/param.cpp b/src/synth/param.cpp index 281b54da..c369ff5a 100644 --- a/src/synth/param.cpp +++ b/src/synth/param.cpp @@ -252,9 +252,21 @@ FloatParam::FloatParam( Number const min_value, Number const max_value, Number const default_value, - Number const round_to + Number const round_to, + ToggleParam const* log_scale_toggle, + Number const* log_scale_table, + Number const* log_scale_inv_table, + int const log_scale_table_max_index, + Number const log_scale_table_scale, + Number const log_scale_inv_table_scale ) noexcept : Param(name, min_value, max_value, default_value), + log_scale_toggle(log_scale_toggle), + log_scale_table(log_scale_table), + log_scale_inv_table(log_scale_inv_table), + log_scale_table_max_index(log_scale_table_max_index), + log_scale_table_scale(log_scale_table_scale), + log_scale_inv_table_scale(log_scale_inv_table_scale), leader(NULL), flexible_controller(NULL), envelope(NULL), @@ -277,6 +289,12 @@ FloatParam::FloatParam(FloatParam& leader) noexcept : Param( leader.name, leader.min_value, leader.max_value, leader.default_value ), + log_scale_toggle(leader.log_scale_toggle), + log_scale_table(leader.log_scale_table), + log_scale_inv_table(leader.log_scale_inv_table), + log_scale_table_max_index(leader.log_scale_table_max_index), + log_scale_table_scale(leader.log_scale_table_scale), + log_scale_inv_table_scale(leader.log_scale_inv_table_scale), leader(&leader), flexible_controller(NULL), envelope(NULL), @@ -317,6 +335,15 @@ bool FloatParam::is_following_leader() const noexcept } +bool FloatParam::is_logarithmic() const noexcept +{ + return ( + log_scale_toggle != NULL + && log_scale_toggle->get_value() == ToggleParam::ON + ); +} + + void FloatParam::set_value(Number const new_value) noexcept { latest_event_type = EVT_SET_VALUE; @@ -349,9 +376,39 @@ Number FloatParam::get_ratio() const noexcept flexible_controller->update(); return flexible_controller->get_value(); - } else { - return Param::get_ratio(); + } else if (midi_controller != NULL) { + return midi_controller->get_value(); } + + return std::min(1.0, std::max(0.0, value_to_ratio(get_raw_value()))); +} + + +Number FloatParam::ratio_to_value(Number const ratio) const noexcept +{ + if (is_logarithmic()) { + return Math::lookup( + Math::log_biquad_filter_freq_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + ratio * Math::LOG_BIQUAD_FILTER_FREQ_SCALE + ); + } + + return Param::ratio_to_value(ratio); +} + + +Number FloatParam::value_to_ratio(Number const value) const noexcept +{ + if (is_logarithmic()) { + Number const min = Constants::BIQUAD_FILTER_FREQUENCY_MIN; + Number const max = Constants::BIQUAD_FILTER_FREQUENCY_MAX; + Number const log_range = std::log2(max) - std::log2(min); + + return (std::log2(value) - std::log2(min)) / log_range; + } + + return Param::value_to_ratio(value); } @@ -438,9 +495,16 @@ void FloatParam::schedule_linear_ramp( ) noexcept { Seconds const last_event_time_offset = get_last_event_time_offset(); - schedule( - EVT_LINEAR_RAMP, last_event_time_offset, 0, duration, target_value - ); + if (is_logarithmic()) { + schedule( + EVT_LOG_RAMP, last_event_time_offset, 0, duration, target_value + ); + } else { + schedule( + EVT_LINEAR_RAMP, last_event_time_offset, 0, duration, target_value + ); + } + schedule( EVT_SET_VALUE, last_event_time_offset + duration, 0, 0.0, target_value ); @@ -460,6 +524,10 @@ void FloatParam::handle_event(Event const& event) noexcept handle_linear_ramp_event(event); break; + case EVT_LOG_RAMP: + handle_log_ramp_event(event); + break; + case EVT_CANCEL: handle_cancel_event(event); break; @@ -503,7 +571,44 @@ void FloatParam::handle_linear_ramp_event(Event const& event) noexcept value, target_value, (Number)duration * (Number)sample_rate, - duration + duration, + false + ); +} + + +void FloatParam::handle_log_ramp_event(Event const& event) noexcept +{ + Number const value = value_to_ratio(get_raw_value()); + Number const done_samples = ( + (Number)(current_time - event.time_offset) * (Number)sample_rate + ); + Seconds duration = (Seconds)event.number_param_1; + Number target_value = value_to_ratio(event.number_param_2); + + if (target_value < 0.0) { + Number const min_diff = 0.0 - value; + Number const target_diff = target_value - value; + + duration *= (Seconds)(min_diff / target_diff); + target_value = 0.0; + } else if (target_value > 1.0) { + Number const max_diff = 1.0 - value; + Number const target_diff = target_value - value; + + duration *= (Seconds)(max_diff / target_diff); + target_value = 1.0; + } + + latest_event_type = EVT_LINEAR_RAMP; + linear_ramp_state.init( + event.time_offset, + done_samples, + value, + target_value, + (Number)duration * (Number)sample_rate, + duration, + true ); } @@ -511,11 +616,15 @@ void FloatParam::handle_linear_ramp_event(Event const& event) noexcept void FloatParam::handle_cancel_event(Event const& event) noexcept { if (latest_event_type == EVT_LINEAR_RAMP) { - store_new_value( - linear_ramp_state.get_value_at( - event.time_offset - linear_ramp_state.start_time_offset - ) + Number const stop_value = linear_ramp_state.get_value_at( + event.time_offset - linear_ramp_state.start_time_offset ); + + if (linear_ramp_state.is_logarithmic) { + store_new_value(ratio_to_value(stop_value)); + } else { + store_new_value(stop_value); + } } latest_event_type = EVT_SET_VALUE; @@ -696,8 +805,16 @@ void FloatParam::render( } else if (latest_event_type == EVT_LINEAR_RAMP) { Sample sample; - for (Integer i = first_sample_index; i != last_sample_index; ++i) { - buffer[0][i] = sample = (Sample)linear_ramp_state.get_next_value(); + if (linear_ramp_state.is_logarithmic) { + for (Integer i = first_sample_index; i != last_sample_index; ++i) { + buffer[0][i] = sample = ( + (Sample)ratio_to_value(linear_ramp_state.get_next_value()) + ); + } + } else { + for (Integer i = first_sample_index; i != last_sample_index; ++i) { + buffer[0][i] = sample = (Sample)linear_ramp_state.get_next_value(); + } } if (last_sample_index != first_sample_index) { @@ -718,6 +835,7 @@ FloatParam::LinearRampState::LinearRampState() noexcept duration(0.0), delta(0.0), speed(0.0), + is_logarithmic(false), is_done(false) { } @@ -729,8 +847,11 @@ void FloatParam::LinearRampState::init( Number const initial_value, Number const target_value, Number const duration_in_samples, - Seconds const duration + Seconds const duration, + bool const is_logarithmic ) noexcept { + LinearRampState::is_logarithmic = is_logarithmic; + if (duration_in_samples > 0.0) { is_done = false; diff --git a/src/synth/param.hpp b/src/synth/param.hpp index 8d5af6dd..7a76fc6f 100644 --- a/src/synth/param.hpp +++ b/src/synth/param.hpp @@ -135,6 +135,7 @@ class FloatParam : public Param public: static constexpr Event::Type EVT_SET_VALUE = 1; static constexpr Event::Type EVT_LINEAR_RAMP = 2; + static constexpr Event::Type EVT_LOG_RAMP = 3; /** * \brief Orchestrate rendering signals and handling events. @@ -175,7 +176,13 @@ class FloatParam : public Param Number const min_value = -1.0, Number const max_value = 1.0, Number const default_value = 0.0, - Number const round_to = 0.0 + Number const round_to = 0.0, + ToggleParam const* log_scale_toggle = NULL, + Number const* log_scale_table = NULL, + Number const* log_scale_inv_table = NULL, + int const log_scale_table_max_index = 0, + Number const log_scale_table_scale = 0.0, + Number const log_scale_inv_table_scale = 0.0 ) noexcept; /** @@ -185,10 +192,16 @@ class FloatParam : public Param */ FloatParam(FloatParam& leader) noexcept; + bool is_logarithmic() const noexcept; + void set_value(Number const new_value) noexcept; Number get_value() const noexcept; void set_ratio(Number const ratio) noexcept; Number get_ratio() const noexcept; + + Number ratio_to_value(Number const ratio) const noexcept; + Number value_to_ratio(Number const value) const noexcept; + Integer get_change_index() const noexcept; bool is_constant_in_next_round( Integer const round, Integer const sample_count @@ -246,7 +259,8 @@ class FloatParam : public Param Number const initial_value, Number const target_value, Number const duration_in_samples, - Seconds const duration + Seconds const duration, + bool const is_logarithmic ) noexcept; Number get_next_value() noexcept; @@ -260,6 +274,7 @@ class FloatParam : public Param Seconds duration; Number delta; Number speed; + bool is_logarithmic; bool is_done; }; @@ -267,10 +282,18 @@ class FloatParam : public Param void handle_set_value_event(Event const& event) noexcept; void handle_linear_ramp_event(Event const& event) noexcept; + void handle_log_ramp_event(Event const& event) noexcept; void handle_cancel_event(Event const& event) noexcept; bool is_following_leader() const noexcept; + ToggleParam const* const log_scale_toggle; + Number const* const log_scale_table; + Number const* const log_scale_inv_table; + int const log_scale_table_max_index; + Number const log_scale_table_scale; + Number const log_scale_inv_table_scale; + FloatParam* const leader; FlexibleController* flexible_controller; Envelope const* envelope; diff --git a/src/synth/voice.cpp b/src/synth/voice.cpp index e23d0fb8..e06755a9 100644 --- a/src/synth/voice.cpp +++ b/src/synth/voice.cpp @@ -69,11 +69,19 @@ Voice::Params::Params(std::string const name) noex harmonic_9(name + "C10", -1.0, 1.0, 0.0), filter_1_type(name + "F1TYP"), + filter_1_log_scale(name + "F1LOG", ToggleParam::OFF), filter_1_frequency( name + "F1FRQ", Constants::BIQUAD_FILTER_FREQUENCY_MIN, Constants::BIQUAD_FILTER_FREQUENCY_MAX, - Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT + Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT, + 0.0, + &filter_1_log_scale, + Math::log_biquad_filter_freq_table(), + Math::log_biquad_filter_freq_inv_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + Math::LOG_BIQUAD_FILTER_FREQ_SCALE, + Math::LOG_BIQUAD_FILTER_FREQ_INV_SCALE ), filter_1_q( name + "F1Q", @@ -89,11 +97,19 @@ Voice::Params::Params(std::string const name) noex ), filter_2_type(name + "F2TYP"), + filter_2_log_scale(name + "F2LOG", ToggleParam::OFF), filter_2_frequency( name + "F2FRQ", Constants::BIQUAD_FILTER_FREQUENCY_MIN, Constants::BIQUAD_FILTER_FREQUENCY_MAX, - Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT + Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT, + 0.0, + &filter_2_log_scale, + Math::log_biquad_filter_freq_table(), + Math::log_biquad_filter_freq_inv_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + Math::LOG_BIQUAD_FILTER_FREQ_SCALE, + Math::LOG_BIQUAD_FILTER_FREQ_INV_SCALE ), filter_2_q( name + "F2Q", diff --git a/src/synth/voice.hpp b/src/synth/voice.hpp index 16647bc7..ed55a5ad 100644 --- a/src/synth/voice.hpp +++ b/src/synth/voice.hpp @@ -84,11 +84,13 @@ class Voice : public SignalProducer FloatParam harmonic_9; typename Filter1::TypeParam filter_1_type; + ToggleParam filter_1_log_scale; FloatParam filter_1_frequency; FloatParam filter_1_q; FloatParam filter_1_gain; typename Filter2::TypeParam filter_2_type; + ToggleParam filter_2_log_scale; FloatParam filter_2_frequency; FloatParam filter_2_q; FloatParam filter_2_gain; diff --git a/tests/performance/perf_math.cpp b/tests/performance/perf_math.cpp index 5842b13e..b2758e14 100644 --- a/tests/performance/perf_math.cpp +++ b/tests/performance/perf_math.cpp @@ -203,6 +203,22 @@ DEFINE_FUNC(MathCombine, "Math::combine(x,0.5,y)", Math::combine(x, 0.5, y), 0.0 DEFINE_FUNC(SimpleCombine, "x*0.5+(1.0-x)*y", x * 0.5 + (1.0 - x) * y, 0.0, 1.0); +NEW_GROUP(); + +DEFINE_FUNC(StdPow24000, "std::pow(24000,x)", std::pow(24000.0, x), 0.0, 1.0); +DEFINE_FUNC( + MathLookupLogBiquadFilterFreq, + "Math::lookup(Math::log_biquad_filter_freq_table(),x)", + Math::lookup( + Math::log_biquad_filter_freq_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + x * Math::LOG_BIQUAD_FILTER_FREQ_SCALE + ), + 0.0, + 1.0 +); + + void usage(char const* name) { char const** next_func_name = &(func_names[0]); diff --git a/tests/test_param.cpp b/tests/test_param.cpp index fc1b2703..618dca3c 100644 --- a/tests/test_param.cpp +++ b/tests/test_param.cpp @@ -1364,6 +1364,86 @@ TEST(when_a_flexible_controller_is_assigned_to_the_leader_of_a_float_param_then_ }) +TEST(a_float_param_may_use_logarithmic_scale, { + constexpr Number min = Constants::BIQUAD_FILTER_FREQUENCY_MIN; + constexpr Number max = Constants::BIQUAD_FILTER_FREQUENCY_MAX; + constexpr Number log2_min = std::log2(min); + constexpr Number log2_max = std::log2(max); + constexpr Integer block_size = 15; + constexpr Frequency sample_rate = 14.0; + constexpr Sample expected_samples_log[] = { + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, + }; + ToggleParam log_scale("log", ToggleParam::OFF); + FloatParam leader( + "freq", + min, + max, + Constants::BIQUAD_FILTER_FREQUENCY_DEFAULT, + 0.0, + &log_scale, + Math::log_biquad_filter_freq_table(), + Math::log_biquad_filter_freq_inv_table(), + Math::LOG_BIQUAD_FILTER_FREQ_TABLE_MAX_INDEX, + Math::LOG_BIQUAD_FILTER_FREQ_SCALE, + Math::LOG_BIQUAD_FILTER_FREQ_INV_SCALE + ); + FloatParam follower(leader); + Envelope envelope("env"); + Sample const* rendered_samples; + Sample rendered_samples_log[block_size]; + + leader.set_sample_rate(sample_rate); + leader.set_value(Constants::BIQUAD_FILTER_FREQUENCY_MIN); + leader.set_envelope(&envelope); + + follower.set_sample_rate(sample_rate); + follower.set_value(Constants::BIQUAD_FILTER_FREQUENCY_MIN); + + envelope.amount.set_value(1.0); + envelope.initial_value.set_value(0.0); + envelope.delay_time.set_value(0.0); + envelope.attack_time.set_value(1.0); + envelope.peak_value.set_value(std::log2(16384.0) / (log2_max - log2_min)); + + assert_eq(min, follower.ratio_to_value(0.0), DOUBLE_DELTA); + assert_eq((min + max) / 2.0, follower.ratio_to_value(0.5), DOUBLE_DELTA); + assert_eq(0.5, follower.value_to_ratio((min + max) / 2.0), DOUBLE_DELTA); + assert_eq(max, follower.ratio_to_value(1.0), DOUBLE_DELTA); + + log_scale.set_value(ToggleParam::ON); + + leader.set_ratio(0.3); + assert_eq(0.3, leader.get_ratio(), 0.001); + + assert_eq(min, follower.ratio_to_value(0.0), DOUBLE_DELTA); + assert_eq( + (log2_min + log2_max) / 2.0, + std::log2(follower.ratio_to_value(0.5)), + 0.02 + ); + assert_eq( + 0.5, + follower.value_to_ratio(std::pow(2.0, (log2_min + log2_max) / 2.0)), + DOUBLE_DELTA + ); + assert_eq(max, follower.ratio_to_value(1.0), DOUBLE_DELTA); + + follower.start_envelope(0.0); + follower.cancel_events(12.0 / sample_rate); + + assert_float_param_changes_during_rendering( + follower, 1, block_size, &rendered_samples + ); + + for (Integer i = 0; i != block_size; ++i) { + rendered_samples_log[i] = std::log2(rendered_samples[i]); + } + + assert_eq(expected_samples_log, rendered_samples_log, block_size, 0.027); +}); + + class Modulator : public SignalProducer { friend class SignalProducer;