From e7504e46f6adba080da883139b6d651fb0aafa52 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 27 May 2024 20:32:20 -0400 Subject: [PATCH] Concepts Usage, Exploration One (#101) Add some code concepts for types and strategies we've used throughout the codebase for common APIs like sample rate, ote to pitch, db to linear. rather than using this commit to consilidate all the names of all the clients, use this to enumerate all of them into a concept and start using that concept. Once we've propagated that, we can quickly and easily see where we need to consolidate names with the compiler. --- include/sst/basic-blocks/concepts/concepts.h | 9 + .../concepts/envelope_modulator_rate.h | 58 +++++++ .../sst/basic-blocks/concepts/sample_rate.h | 155 +++++++++++++++++ .../basic-blocks/concepts/unit_conversions.h | 157 ++++++++++++++++++ .../sst/basic-blocks/modulators/SimpleLFO.h | 15 +- 5 files changed, 388 insertions(+), 6 deletions(-) create mode 100644 include/sst/basic-blocks/concepts/envelope_modulator_rate.h create mode 100644 include/sst/basic-blocks/concepts/sample_rate.h create mode 100644 include/sst/basic-blocks/concepts/unit_conversions.h diff --git a/include/sst/basic-blocks/concepts/concepts.h b/include/sst/basic-blocks/concepts/concepts.h index 91e1e51..d7c37f2 100644 --- a/include/sst/basic-blocks/concepts/concepts.h +++ b/include/sst/basic-blocks/concepts/concepts.h @@ -27,17 +27,26 @@ #ifndef INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_CONCEPTS_H #define INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_CONCEPTS_H +#include #include // Why this form? gcc10 works but provides -std=c++2a in some cases static_assert(__cplusplus > 201703L, "sst-basic-blocks requires C++20; please update your build"); +#include "sample_rate.h" +#include "envelope_modulator_rate.h" +#include "unit_conversions.h" + namespace sst::basic_blocks::concepts { template constexpr bool is_positive_power_of_two(T x) noexcept { return (x > 0) && ((x & (x - 1)) == 0) && std::is_integral_v; } +template constexpr bool is_power_of_two_ge(T x, T above) noexcept +{ + return (x > 0) && ((x & (x - 1)) == 0) && std::is_integral_v; +} } // namespace sst::basic_blocks::concepts #endif // SURGE_CONCEPTS_H diff --git a/include/sst/basic-blocks/concepts/envelope_modulator_rate.h b/include/sst/basic-blocks/concepts/envelope_modulator_rate.h new file mode 100644 index 0000000..d26e990 --- /dev/null +++ b/include/sst/basic-blocks/concepts/envelope_modulator_rate.h @@ -0,0 +1,58 @@ +/* + * sst-basic-blocks - an open source library of core audio utilities + * built by Surge Synth Team. + * + * Provides a collection of tools useful on the audio thread for blocks, + * modulation, etc... or useful for adapting code to multiple environments. + * + * Copyright 2023, various authors, as described in the GitHub + * transaction log. Parts of this code are derived from similar + * functions original in Surge or ShortCircuit. + * + * sst-basic-blocks is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * A very small number of explicitly chosen header files can also be + * used in an MIT/BSD context. Please see the README.md file in this + * repo or the comments in the individual files. Only headers with an + * explicit mention that they are dual licensed may be copied and reused + * outside the GPL3 terms. + * + * All source in sst-basic-blocks available at + * https://github.com/surge-synthesizer/sst-basic-blocks + */ + +#ifndef INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_ENVELOPE_MODULATOR_RATE_H +#define INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_ENVELOPE_MODULATOR_RATE_H + +#include +#include + +namespace sst::basic_blocks::concepts +{ + +/* + * Envelope Rate Linear No-Wrap is defined as + * block size * 2^f / sample rate + */ +template +concept has_envelope_rate_linear_nowrap = requires(T *t, float f) { + { + t->envelope_rate_linear_nowrap(f) + } -> std::convertible_to; +}; + +template +concept providesModulatorDPhase = has_envelope_rate_linear_nowrap; + +template + requires(providesModulatorDPhase) +inline double getModulatorDPhase(T *t, float f) +{ + return t->envelope_rate_linear_nowrap(f); +} +} // namespace sst::basic_blocks::concepts + +#endif // SHORTCIRCUITXT_ENVELOPE_MODULATOR_RATE_H diff --git a/include/sst/basic-blocks/concepts/sample_rate.h b/include/sst/basic-blocks/concepts/sample_rate.h new file mode 100644 index 0000000..b074d9f --- /dev/null +++ b/include/sst/basic-blocks/concepts/sample_rate.h @@ -0,0 +1,155 @@ +/* + * sst-basic-blocks - an open source library of core audio utilities + * built by Surge Synth Team. + * + * Provides a collection of tools useful on the audio thread for blocks, + * modulation, etc... or useful for adapting code to multiple environments. + * + * Copyright 2023, various authors, as described in the GitHub + * transaction log. Parts of this code are derived from similar + * functions original in Surge or ShortCircuit. + * + * sst-basic-blocks is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * A very small number of explicitly chosen header files can also be + * used in an MIT/BSD context. Please see the README.md file in this + * repo or the comments in the individual files. Only headers with an + * explicit mention that they are dual licensed may be copied and reused + * outside the GPL3 terms. + * + * All source in sst-basic-blocks available at + * https://github.com/surge-synthesizer/sst-basic-blocks + */ + +#ifndef INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_SAMPLE_RATE_H +#define INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_SAMPLE_RATE_H + +#include +#include + +namespace sst::basic_blocks::concepts +{ +template +concept has_samplerate_member = requires(T *t) { + { + t->samplerate + } -> std::convertible_to; +}; + +template +concept has_sampleRate_member = requires(T *t) { + { + t->sampleRate + } -> std::convertible_to; +}; + +template +concept has_getSampleRate_method = requires(T *t) { + { + t->getSampleRate() + } -> std::convertible_to; +}; + +template +concept has_staticGetSampleRate_method = requires(T *t) { + { + T::getSampleRate(t) + } -> std::convertible_to; +}; + +template +concept has_dsamplerate_member = requires(T *t) { + { + t->dsamplerate + } -> std::same_as; +}; + +template +concept has_samplerate_inv_member = requires(T *t) { + { + t->samplerate_inv + } -> std::convertible_to; +}; + +template +concept has_sampleRateInv_member = requires(T *t) { + { + t->sampleRateInv + } -> std::convertible_to; +}; + +template +concept has_getSampleRateInv_method = requires(T *t) { + { + t->getSampleRateInv() + } -> std::convertible_to; +}; + +template +concept has_dsamplerate_inv_member = requires(T *t) { + { + t->dsamplerate_inv + } -> std::same_as; +}; + +template +concept has_staticGetSampleRateInv_method = requires(T *s) { + { + T::getSampleRateInv(s) + } -> std::floating_point; +}; + +template +concept has_staticSampleRateInv_method = requires(T *s) { + { + T::sampleRateInv(s) + } -> std::floating_point; +}; +template +concept supportsSampleRate = has_samplerate_member || has_sampleRate_member || + has_getSampleRate_method || has_staticGetSampleRate_method; + +template +concept supportsSampleRateInv = + has_samplerate_inv_member || has_sampleRateInv_member || has_getSampleRateInv_method || + has_staticGetSampleRateInv_method || has_staticSampleRateInv_method; + +template +concept supportsDoubleSampleRate = has_dsamplerate_member && has_dsamplerate_inv_member; + +template +inline float getSampleRate(T *t) + requires(supportsSampleRate) +{ + if constexpr (has_samplerate_member) + return t->samplerate; + else if constexpr (has_sampleRate_member) + return t->sampleRate; + else if constexpr (has_getSampleRate_method) + return t->getSampleRate(); + else if constexpr (has_staticGetSampleRate_method) + return T::getSampleRate(t); +} + +template +inline float getSampleRateInv(T *t) + requires(supportsSampleRateInv) +{ + if constexpr (has_samplerate_inv_member) + return t->samplerate_inv; + else if constexpr (has_sampleRateInv_member) + return t->sampleRateInv; + else if constexpr (has_getSampleRateInv_method) + return t->getSampleRateInv(); + else if constexpr (has_staticGetSampleRateInv_method) + return T::getSampleRateInv(t); + else if constexpr (has_staticSampleRateInv_method) + return T::sampleRateInv(t); +} + +} // namespace sst::basic_blocks::concepts + +#endif // SHORTCIRCUITXT_SAMPLE_RATE_H diff --git a/include/sst/basic-blocks/concepts/unit_conversions.h b/include/sst/basic-blocks/concepts/unit_conversions.h new file mode 100644 index 0000000..1edac59 --- /dev/null +++ b/include/sst/basic-blocks/concepts/unit_conversions.h @@ -0,0 +1,157 @@ +/* + * sst-basic-blocks - an open source library of core audio utilities + * built by Surge Synth Team. + * + * Provides a collection of tools useful on the audio thread for blocks, + * modulation, etc... or useful for adapting code to multiple environments. + * + * Copyright 2023, various authors, as described in the GitHub + * transaction log. Parts of this code are derived from similar + * functions original in Surge or ShortCircuit. + * + * sst-basic-blocks is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * A very small number of explicitly chosen header files can also be + * used in an MIT/BSD context. Please see the README.md file in this + * repo or the comments in the individual files. Only headers with an + * explicit mention that they are dual licensed may be copied and reused + * outside the GPL3 terms. + * + * All source in sst-basic-blocks available at + * https://github.com/surge-synthesizer/sst-basic-blocks + */ + +#ifndef INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_UNIT_CONVERSIONS_H +#define INCLUDE_SST_BASIC_BLOCKS_CONCEPTS_UNIT_CONVERSIONS_H + +#include +#include +#include + +/* + * All the names and flavorts we evern given these are here. + * We obviously need to trim these down once we have everything + * going. That's tedious but doable because we can just + * remove the constraint from the concept and the if once we + * are there. + */ +namespace sst::basic_blocks::concepts +{ +template +concept has_note_to_pitch_ignoring_tuning = requires(T *s, float f) { + { + s->note_to_pitch_ignoring_tuning(f) + } -> std::floating_point; +}; + +template +concept has_noteToPitchIgnoringTuning = requires(T *s, float f) { + { + s->noteToPitchIgnoringTuning(f) + } -> std::floating_point; +}; + +template +concept has_noteToPitch = requires(T *s, float f) { + { + s->noteToPitch(f) + } -> std::floating_point; +}; + +template +concept has_staticNoteToPitchIgnoringTuning = requires(T *s, float f) { + { + T::noteToPitchIgnoringTuning(s, f) + } -> std::floating_point; +}; + +template +concept has_staticEqualNoteToPitch = requires(T *s, float f) { + { + T::equalNoteToPitch(s, f) + } -> std::floating_point; +}; + +template +concept providesNoteToPitch = + has_noteToPitchIgnoringTuning || has_note_to_pitch_ignoring_tuning || + has_noteToPitch || has_staticNoteToPitchIgnoringTuning || has_staticEqualNoteToPitch; + +template + requires(providesNoteToPitch) +inline float convertNoteToPitch(T *t, float n) +{ + if constexpr (has_note_to_pitch_ignoring_tuning) + { + return t->note_to_pitch_ignoring_tuning(n); + } + else if constexpr (has_noteToPitchIgnoringTuning) + { + return t->noteToPitchIgnoringTuning(n); + } + else if constexpr (has_noteToPitch) + { + return t->noteToPitch(n); + } + else if constexpr (has_staticNoteToPitchIgnoringTuning) + { + return T::noteToPitchIgnoringTuning(t, n); + } + else if constexpr (has_staticEqualNoteToPitch) + { + return T::equalNoteToPitch(t, n); + } + + assert(false); + return 0; +} + +template +concept has_db_to_linear = requires(T *s, float f) { + { + s->db_to_linear(f) + } -> std::floating_point; +}; + +template +concept has_dbToLinear = requires(T *s, float f) { + { + s->dbToLinear(f) + } -> std::floating_point; +}; + +template +concept has_staticDbToLinear = requires(T *s, float f) { + { + T::dbToLinear(s, f) + } -> std::floating_point; +}; + +template +concept providesDbToLinear = has_dbToLinear || has_db_to_linear || has_staticDbToLinear; + +template + requires(providesDbToLinear) +inline float convertDbToLinear(T *t, float n) +{ + if constexpr (has_db_to_linear) + { + return t->db_to_linear(n); + } + else if constexpr (has_dbToLinear) + { + return t->dbToLinear(n); + } + else if constexpr (has_staticDbToLinear) + { + return T::dbToLinear(t, n); + } + assert(false); + return 0; +} + +} // namespace sst::basic_blocks::concepts +#endif // SHORTCIRCUITXT_UNIT_CONVERSIONS_H diff --git a/include/sst/basic-blocks/modulators/SimpleLFO.h b/include/sst/basic-blocks/modulators/SimpleLFO.h index 374aa4f..3df8543 100644 --- a/include/sst/basic-blocks/modulators/SimpleLFO.h +++ b/include/sst/basic-blocks/modulators/SimpleLFO.h @@ -33,20 +33,23 @@ #include #include #include +#include "../concepts/concepts.h" namespace sst::basic_blocks::modulators { // For context on SRProvider see the ADSRDAHD Envelope -template struct SimpleLFO +template + requires(concepts::is_power_of_two_ge(BLOCK_SIZE, 8) && + concepts::supportsSampleRate && + concepts::providesModulatorDPhase) +struct SimpleLFO { SRProvider *srProvider{nullptr}; std::default_random_engine gen; std::uniform_real_distribution distro; std::function urng = []() { return 0; }; - static_assert((BLOCK_SIZE >= 8) & !(BLOCK_SIZE & (BLOCK_SIZE - 1)), - "Block size must be power of 2 8 or above."); static constexpr float BLOCK_SIZE_INV{1.f / BLOCK_SIZE}; float rngState[2]{0, 0}; @@ -156,7 +159,7 @@ template struct SimpleLFO { float target{0.f}; - auto frate = srProvider->envelope_rate_linear_nowrap(-r); + auto frate = concepts::getModulatorDPhase(srProvider, -r); phase += frate * (reverse ? -1 : 1); int phaseMidpoint{0}; bool phaseTurned{false}; @@ -225,8 +228,8 @@ template struct SimpleLFO if (urng() > (-d)) { // 10 ms triggers according to spec so thats 1% of sample rate - rndTrigCountdown = - (int)std::round(0.01 * srProvider->samplerate * BLOCK_SIZE_INV); + rndTrigCountdown = (int)std::round(0.01 * concepts::getSampleRate(srProvider) * + BLOCK_SIZE_INV); } } if (rndTrigCountdown > 0)