From 2a9265db8c2b3044fd5d0df3e745943e314b85c8 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Tue, 19 Nov 2024 18:18:58 +0100 Subject: [PATCH 01/17] WIP LFO implementation --- example/CMakeLists.txt | 1 + example/lfo.cpp | 193 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 example/lfo.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ed36e396a..5e248f73f 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -71,6 +71,7 @@ add_example(storage_tank) add_example(strong_angular_quantities) add_example(total_energy) add_example(unmanned_aerial_vehicle example_utils) +add_example(lfo) add_subdirectory(glide_computer_lib) add_subdirectory(kalman_filter) diff --git a/example/lfo.cpp b/example/lfo.cpp new file mode 100644 index 000000000..8caad32f0 --- /dev/null +++ b/example/lfo.cpp @@ -0,0 +1,193 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!! Before you commit any changes to this file please make sure to check if it !!! +// !!! renders correctly in the documentation "Examples" section. !!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +// #include +// #include +#include +#include +#include +#endif + +namespace third_party { + +//! State of a playback engine for music host application. +struct musical_context { + float sample_rate; //!< samples per second + float tempo; //!< beats per minute (quarter note == one beat) +}; + +//! API provided by music host application to provide global info +//! about the playback engine. +musical_context get_musical_context() +{ + // Example data, this would be variable in a real-world context + return musical_context{.sample_rate = 8000.f, .tempo = 130.f}; +} +} // namespace third_party + +namespace { +using namespace mp_units; + +QUANTITY_SPEC(sample_count, dimensionless, is_kind); +QUANTITY_SPEC(sample_duration, isq::time); +QUANTITY_SPEC(sample_rate, isq::frequency, sample_count / isq::time); + +inline constexpr struct sample final : named_unit<"Smpl", one, kind_of> { +} sample; + +QUANTITY_SPEC(unit_sample_amount, dimensionless, is_kind); + +inline constexpr struct sample_value final : named_unit<"PCM", one, kind_of> { +} sample_value; + +QUANTITY_SPEC(beat_count, dimensionless, is_kind); +QUANTITY_SPEC(beat_duration, isq::time); +QUANTITY_SPEC(tempo, isq::frequency, beat_count / isq::time); + +inline constexpr struct quarter_note final : named_unit<"q", one, kind_of> { +} quarter_note; +static_assert(detail::SymbolicArg); +static_assert(Unit); + +inline constexpr struct whole_note final : named_unit<"w", mag<4> * quarter_note> { +} whole_note; +inline constexpr struct half_note final : named_unit<"h", mag<2> * quarter_note> { +} half_note; +inline constexpr struct dotted_half_note final : named_unit<"h.", mag<3> * quarter_note> { +} dotted_half_note; +inline constexpr struct eigth_note final : named_unit<"8th", mag_ratio<1, 2> * quarter_note> { +} eigth_note; +inline constexpr struct dotted_quarter_note final : named_unit<"q.", mag<3> * eigth_note> { +} dotted_quarter_note; +inline constexpr struct quarter_note_triplet final : named_unit<"qt", mag_ratio<1, 3> * half_note> { +} quarter_note_triplet; +inline constexpr struct sixteenth_note final : named_unit<"16th", mag_ratio<1, 2> * eigth_note> { +} sixteenth_note; +inline constexpr struct dotted_eigth_note final : named_unit<"8th.", mag<3> * sixteenth_note> { +} dotted_eigth_note; + +inline constexpr struct beats_per_minute final : named_unit<"bpm", quarter_note / non_si::minute> { +} beats_per_minute; + +inline constexpr auto smpl = sample; + +inline constexpr auto pcm = sample_value; + +// inline constexpr auto n_wd = 3 * half_note; +inline constexpr auto n_w = whole_note; +// inline constexpr auto n_hd = dotted_half_note; +// inline constexpr auto n_h = half_note; +// inline constexpr auto n_qd = dotted_quarter_note; +// inline constexpr auto n_q = quarter_note; +// inline constexpr auto n_qt = quarter_note_triplet; +// inline constexpr auto n_8thd = dotted_eigth_note; +// inline constexpr auto n_8th = eigth_note; +// inline constexpr auto n_16th = sixteenth_note; + +// inline constexpr auto bpm = beats_per_minute; + +//! Typesafe version of music application playback engine state +struct musical_context { + quantity sample_rate; + quantity tempo; +}; + +//! Typesafe wrapper around API for host application musical context +musical_context get_musical_context() +{ + auto context = third_party::get_musical_context(); + return musical_context{.sample_rate = context.sample_rate * si::hertz, .tempo = context.tempo * beats_per_minute}; +} + +class sine_wave_osc { +public: + sine_wave_osc(const musical_context& context, QuantityOf auto freq) : + m_context{context}, m_frequency{freq} + { + std::cout << std::format("Created LFO with starting frequency {} ({}) for sample rate {} at tempo {}\n", freq, + m_frequency, context.sample_rate, context.tempo); + } + + quantity get_frequency() const { return m_frequency; } + + void set_frequency(QuantityOf auto freq) + { + m_frequency = freq; + std::cout << std::format("Setting frequency to {} ({})\n", freq, m_frequency); + } + + void set_period(QuantityOf auto period) + { + m_frequency = 1.f / period; + std::cout << std::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); + } + + quantity operator()() + { + auto out = sin(m_phase); + m_phase += step; + return out; + } + +private: + void update_step() { m_step = (angular::revolution * m_frequency) / m_context.sample_rate; } + + quantity sin(quantity_point) + { + return std::sin(m_phase.numerical_value_in(angular::radian)) * pcm; + } + + musical_context m_context; + quantity m_frequency; + quantity_point m_phase{0.f}; + quantity m_step; +}; +} // namespace + +int main() +{ + using namespace mp_units::si::unit_symbols; + + const auto context = get_musical_context(); + + auto sin_gen = sine_wave_osc{context, 2 * Hz}; + + // TODO set_frequency and set_period calls for demonstrating use of different units + + + auto buffer = std::vector>(2 * whole_note) +} From 39acafffa0940e8dc7fa206a65f04ccffe821fbd Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 18:32:45 -0500 Subject: [PATCH 02/17] Refactor LFO example and get to compile Split out audio units and musical context functions into individual headers to be used in future examples in addition to the LFO example. --- example/CMakeLists.txt | 2 +- example/audio/1-lfo.cpp | 100 +++++++++ example/audio/CMakeLists.txt | 23 +++ example/audio/audio.h | 94 +++++++++ example/audio/third_party_audio_api.h | 40 ++++ example/audio/wrapped_third_party_audio_api.h | 44 ++++ example/lfo.cpp | 193 ------------------ 7 files changed, 302 insertions(+), 194 deletions(-) create mode 100644 example/audio/1-lfo.cpp create mode 100644 example/audio/CMakeLists.txt create mode 100644 example/audio/audio.h create mode 100644 example/audio/third_party_audio_api.h create mode 100644 example/audio/wrapped_third_party_audio_api.h delete mode 100644 example/lfo.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 5e248f73f..bcbab11f7 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -71,7 +71,7 @@ add_example(storage_tank) add_example(strong_angular_quantities) add_example(total_energy) add_example(unmanned_aerial_vehicle example_utils) -add_example(lfo) +add_subdirectory(audio) add_subdirectory(glide_computer_lib) add_subdirectory(kalman_filter) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp new file mode 100644 index 000000000..651f224c2 --- /dev/null +++ b/example/audio/1-lfo.cpp @@ -0,0 +1,100 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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. + +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#include +#include +#include +#endif + +#include "audio.h" +#include "wrapped_third_party_audio_api.h" + +namespace { +using namespace mp_units; + +class sine_wave_osc { +public: + sine_wave_osc(const audio::musical_context& context, QuantityOf auto freq) : + m_context{context}, m_frequency{freq} + { + std::cout << MP_UNITS_STD_FMT::format( + "Created oscillator with starting frequency {} ({}) for sample rate {} at tempo {}\n", freq, m_frequency, + context.sample_rate, context.tempo); + } + + quantity get_frequency() const { return m_frequency; } + + void set_frequency(QuantityOf auto freq) + { + m_frequency = freq; + std::cout << MP_UNITS_STD_FMT::format("Setting frequency to {} ({})\n", freq, m_frequency); + } + + void set_period(QuantityOf auto period) + { + m_frequency = 1.f / period; + std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); + } + + quantity operator()() + { + auto out = angular::sin(m_phase.quantity_from_zero()); + m_phase += m_step; + return out; + } + +private: + using phase_t = quantity_point; + + void update_step() { m_step = (m_frequency / m_context.sample_rate) * angular::revolution; } + + audio::musical_context m_context; + quantity m_frequency; + phase_t m_phase{0.f * angular::radian}; + quantity m_step; +}; +} // namespace + +int main() +{ + using namespace mp_units::si::unit_symbols; + + const auto context = audio::get_musical_context(); + + + auto sin_gen = sine_wave_osc{context, 1 * Hz}; + + // TODO set_frequency and set_period calls for demonstrating use of different units + + + // auto buffer = std::vector>(2 * audio::whole_note) +} diff --git a/example/audio/CMakeLists.txt b/example/audio/CMakeLists.txt new file mode 100644 index 000000000..0cffaa8a9 --- /dev/null +++ b/example/audio/CMakeLists.txt @@ -0,0 +1,23 @@ +# The MIT License (MIT) +# +# Copyright (c) 2024 Roth Michaels +# +# 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. + +add_example(1-lfo) diff --git a/example/audio/audio.h b/example/audio/audio.h new file mode 100644 index 000000000..dca4eb58a --- /dev/null +++ b/example/audio/audio.h @@ -0,0 +1,94 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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 + +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#include +#include +#include +#endif + +namespace audio { + +QUANTITY_SPEC(sample_count, mp_units::dimensionless, mp_units::is_kind); +QUANTITY_SPEC(sample_duration, mp_units::isq::time); +QUANTITY_SPEC(sample_rate, mp_units::isq::frequency, sample_count / mp_units::isq::time); + +inline constexpr struct sample final : mp_units::named_unit<"Smpl", mp_units::one, mp_units::kind_of> { +} sample; + +QUANTITY_SPEC(unit_sample_amount, mp_units::dimensionless, mp_units::is_kind); + +inline constexpr struct sample_value final : + mp_units::named_unit<"PCM", mp_units::one, mp_units::kind_of> { +} sample_value; + +QUANTITY_SPEC(beat_count, mp_units::dimensionless, mp_units::is_kind); +QUANTITY_SPEC(beat_duration, mp_units::isq::time); +QUANTITY_SPEC(tempo, mp_units::isq::frequency, beat_count / mp_units::isq::time); + +inline constexpr struct quarter_note final : mp_units::named_unit<"q", mp_units::one, mp_units::kind_of> { +} quarter_note; + +inline constexpr struct whole_note final : mp_units::named_unit<"w", mp_units::mag<4> * quarter_note> { +} whole_note; +inline constexpr struct half_note final : mp_units::named_unit<"h", mp_units::mag<2> * quarter_note> { +} half_note; +inline constexpr struct dotted_half_note final : mp_units::named_unit<"h.", mp_units::mag<3> * quarter_note> { +} dotted_half_note; +inline constexpr struct eigth_note final : mp_units::named_unit<"8th", mp_units::mag_ratio<1, 2> * quarter_note> { +} eigth_note; +inline constexpr struct dotted_quarter_note final : mp_units::named_unit<"q.", mp_units::mag<3> * eigth_note> { +} dotted_quarter_note; +inline constexpr struct quarter_note_triplet final : mp_units::named_unit<"qt", mp_units::mag_ratio<1, 3> * half_note> { +} quarter_note_triplet; +inline constexpr struct sixteenth_note final : mp_units::named_unit<"16th", mp_units::mag_ratio<1, 2> * eigth_note> { +} sixteenth_note; +inline constexpr struct dotted_eigth_note final : mp_units::named_unit<"8th.", mp_units::mag<3> * sixteenth_note> { +} dotted_eigth_note; + +inline constexpr struct beats_per_minute final : mp_units::named_unit<"bpm", quarter_note / mp_units::non_si::minute> { +} beats_per_minute; + +namespace unit_symbols { +inline constexpr auto smpl = sample; + +inline constexpr auto pcm = sample_value; + +// inline constexpr auto n_wd = 3 * half_note; +inline constexpr auto n_w = whole_note; +// inline constexpr auto n_hd = dotted_half_note; +// inline constexpr auto n_h = half_note; +// inline constexpr auto n_qd = dotted_quarter_note; +// inline constexpr auto n_q = quarter_note; +// inline constexpr auto n_qt = quarter_note_triplet; +// inline constexpr auto n_8thd = dotted_eigth_note; +// inline constexpr auto n_8th = eigth_note; +// inline constexpr auto n_16th = sixteenth_note; + +// inline constexpr auto bpm = beats_per_minute; +} // namespace unit_symbols +} // namespace audio diff --git a/example/audio/third_party_audio_api.h b/example/audio/third_party_audio_api.h new file mode 100644 index 000000000..c8376efc3 --- /dev/null +++ b/example/audio/third_party_audio_api.h @@ -0,0 +1,40 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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 + +namespace audio_third_party { + +//! State of a playback engine for music host application. +struct musical_context { + float sample_rate; //!< samples per second + float tempo; //!< beats per minute (quarter note == one beat) +}; + +//! API provided by music host application to provide global info +//! about the playback engine. +musical_context get_musical_context() +{ + // Example data, this would be variable in a real-world context + return musical_context{.sample_rate = 8000.f, .tempo = 130.f}; +} +} // namespace audio_third_party diff --git a/example/audio/wrapped_third_party_audio_api.h b/example/audio/wrapped_third_party_audio_api.h new file mode 100644 index 000000000..6dd028728 --- /dev/null +++ b/example/audio/wrapped_third_party_audio_api.h @@ -0,0 +1,44 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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 "audio.h" +#include "third_party_audio_api.h" + +namespace audio { + +//! Typesafe version of music application playback engine state +struct musical_context { + mp_units::quantity sample_rate; + mp_units::quantity tempo; +}; + +//! Typesafe wrapper around API for host application musical context +musical_context get_musical_context() +{ + auto context = audio_third_party::get_musical_context(); + return musical_context{.sample_rate = context.sample_rate * mp_units::si::hertz, + .tempo = context.tempo * beats_per_minute}; +} + +} // namespace audio diff --git a/example/lfo.cpp b/example/lfo.cpp deleted file mode 100644 index 8caad32f0..000000000 --- a/example/lfo.cpp +++ /dev/null @@ -1,193 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2024 Roth Michaels -// -// 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. - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!! Before you commit any changes to this file please make sure to check if it !!! -// !!! renders correctly in the documentation "Examples" section. !!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#include -#endif -#ifdef MP_UNITS_MODULES -import mp_units; -#else -#include -// #include -// #include -#include -#include -#include -#endif - -namespace third_party { - -//! State of a playback engine for music host application. -struct musical_context { - float sample_rate; //!< samples per second - float tempo; //!< beats per minute (quarter note == one beat) -}; - -//! API provided by music host application to provide global info -//! about the playback engine. -musical_context get_musical_context() -{ - // Example data, this would be variable in a real-world context - return musical_context{.sample_rate = 8000.f, .tempo = 130.f}; -} -} // namespace third_party - -namespace { -using namespace mp_units; - -QUANTITY_SPEC(sample_count, dimensionless, is_kind); -QUANTITY_SPEC(sample_duration, isq::time); -QUANTITY_SPEC(sample_rate, isq::frequency, sample_count / isq::time); - -inline constexpr struct sample final : named_unit<"Smpl", one, kind_of> { -} sample; - -QUANTITY_SPEC(unit_sample_amount, dimensionless, is_kind); - -inline constexpr struct sample_value final : named_unit<"PCM", one, kind_of> { -} sample_value; - -QUANTITY_SPEC(beat_count, dimensionless, is_kind); -QUANTITY_SPEC(beat_duration, isq::time); -QUANTITY_SPEC(tempo, isq::frequency, beat_count / isq::time); - -inline constexpr struct quarter_note final : named_unit<"q", one, kind_of> { -} quarter_note; -static_assert(detail::SymbolicArg); -static_assert(Unit); - -inline constexpr struct whole_note final : named_unit<"w", mag<4> * quarter_note> { -} whole_note; -inline constexpr struct half_note final : named_unit<"h", mag<2> * quarter_note> { -} half_note; -inline constexpr struct dotted_half_note final : named_unit<"h.", mag<3> * quarter_note> { -} dotted_half_note; -inline constexpr struct eigth_note final : named_unit<"8th", mag_ratio<1, 2> * quarter_note> { -} eigth_note; -inline constexpr struct dotted_quarter_note final : named_unit<"q.", mag<3> * eigth_note> { -} dotted_quarter_note; -inline constexpr struct quarter_note_triplet final : named_unit<"qt", mag_ratio<1, 3> * half_note> { -} quarter_note_triplet; -inline constexpr struct sixteenth_note final : named_unit<"16th", mag_ratio<1, 2> * eigth_note> { -} sixteenth_note; -inline constexpr struct dotted_eigth_note final : named_unit<"8th.", mag<3> * sixteenth_note> { -} dotted_eigth_note; - -inline constexpr struct beats_per_minute final : named_unit<"bpm", quarter_note / non_si::minute> { -} beats_per_minute; - -inline constexpr auto smpl = sample; - -inline constexpr auto pcm = sample_value; - -// inline constexpr auto n_wd = 3 * half_note; -inline constexpr auto n_w = whole_note; -// inline constexpr auto n_hd = dotted_half_note; -// inline constexpr auto n_h = half_note; -// inline constexpr auto n_qd = dotted_quarter_note; -// inline constexpr auto n_q = quarter_note; -// inline constexpr auto n_qt = quarter_note_triplet; -// inline constexpr auto n_8thd = dotted_eigth_note; -// inline constexpr auto n_8th = eigth_note; -// inline constexpr auto n_16th = sixteenth_note; - -// inline constexpr auto bpm = beats_per_minute; - -//! Typesafe version of music application playback engine state -struct musical_context { - quantity sample_rate; - quantity tempo; -}; - -//! Typesafe wrapper around API for host application musical context -musical_context get_musical_context() -{ - auto context = third_party::get_musical_context(); - return musical_context{.sample_rate = context.sample_rate * si::hertz, .tempo = context.tempo * beats_per_minute}; -} - -class sine_wave_osc { -public: - sine_wave_osc(const musical_context& context, QuantityOf auto freq) : - m_context{context}, m_frequency{freq} - { - std::cout << std::format("Created LFO with starting frequency {} ({}) for sample rate {} at tempo {}\n", freq, - m_frequency, context.sample_rate, context.tempo); - } - - quantity get_frequency() const { return m_frequency; } - - void set_frequency(QuantityOf auto freq) - { - m_frequency = freq; - std::cout << std::format("Setting frequency to {} ({})\n", freq, m_frequency); - } - - void set_period(QuantityOf auto period) - { - m_frequency = 1.f / period; - std::cout << std::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); - } - - quantity operator()() - { - auto out = sin(m_phase); - m_phase += step; - return out; - } - -private: - void update_step() { m_step = (angular::revolution * m_frequency) / m_context.sample_rate; } - - quantity sin(quantity_point) - { - return std::sin(m_phase.numerical_value_in(angular::radian)) * pcm; - } - - musical_context m_context; - quantity m_frequency; - quantity_point m_phase{0.f}; - quantity m_step; -}; -} // namespace - -int main() -{ - using namespace mp_units::si::unit_symbols; - - const auto context = get_musical_context(); - - auto sin_gen = sine_wave_osc{context, 2 * Hz}; - - // TODO set_frequency and set_period calls for demonstrating use of different units - - - auto buffer = std::vector>(2 * whole_note) -} From f4fe2bdd320fe21361f9d638f6419b4271327267 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 18:51:49 -0500 Subject: [PATCH 03/17] Add 0-wrapping_unsafe_apis audio example Add an example of wrapping an unsafe OS or host application API with a type-safe quanitites API. The `audio::get_musical_context` API used in this example will also be used in other audio examples. --- example/audio/0-wrapping_unsafe_apis.cpp | 52 ++++++++++++++++++++++++ example/audio/CMakeLists.txt | 1 + 2 files changed, 53 insertions(+) create mode 100644 example/audio/0-wrapping_unsafe_apis.cpp diff --git a/example/audio/0-wrapping_unsafe_apis.cpp b/example/audio/0-wrapping_unsafe_apis.cpp new file mode 100644 index 000000000..dbbfff6ff --- /dev/null +++ b/example/audio/0-wrapping_unsafe_apis.cpp @@ -0,0 +1,52 @@ +// The MIT License (MIT) +// +// Copyright (c) 2024 Roth Michaels +// +// 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. + +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#endif + +#include "third_party_audio_api.h" +#include "wrapped_third_party_audio_api.h" + +int main() +{ + // Operating system or host applications will provide APIs with various musical context information such as tempo and + // sample rate, but these APIs are not type safe using float or double values; e.g.: + const auto unsafe_context = audio_third_party::get_musical_context(); + + std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n\n", unsafe_context.tempo, + unsafe_context.sample_rate); + + // These unsafe APIs can be wrapped a new API returning type-safe quantities for tempo and sample rate; e.g.: + const auto safe_context = audio::get_musical_context(); + + std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n", safe_context.tempo, + safe_context.sample_rate); +} diff --git a/example/audio/CMakeLists.txt b/example/audio/CMakeLists.txt index 0cffaa8a9..6da4dd84d 100644 --- a/example/audio/CMakeLists.txt +++ b/example/audio/CMakeLists.txt @@ -20,4 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +add_example(0-wrapping_unsafe_apis) add_example(1-lfo) From aec61bcb52f47a0c70d20d9449422ff55ab493f5 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 19:26:56 -0500 Subject: [PATCH 04/17] Finish LFO example --- example/audio/1-lfo.cpp | 55 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp index 651f224c2..251dc4305 100644 --- a/example/audio/1-lfo.cpp +++ b/example/audio/1-lfo.cpp @@ -24,6 +24,7 @@ #ifdef MP_UNITS_IMPORT_STD import std; #else +#include #include #endif #ifdef MP_UNITS_MODULES @@ -41,6 +42,7 @@ import mp_units; namespace { using namespace mp_units; +//! A DSP generator class that generates sample values for a sine wave oscillator. class sine_wave_osc { public: sine_wave_osc(const audio::musical_context& context, QuantityOf auto freq) : @@ -65,6 +67,12 @@ class sine_wave_osc { std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); } + void set_period(QuantityOf auto period) + { + std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); + set_period(period / m_context.tempo); + } + quantity operator()() { auto out = angular::sin(m_phase.quantity_from_zero()); @@ -72,6 +80,8 @@ class sine_wave_osc { return out; } + void reset() { m_phase = phase_t{0.f * angular::radian}; } + private: using phase_t = quantity_point; @@ -90,11 +100,50 @@ int main() const auto context = audio::get_musical_context(); - + // Sine oscillators are sometimes used as a "low-frequency oscillator" + // (LFO) that runs at a frequency below the range of human hearing and + // is used a source of modulation for other paramters in an audio + // algorithm. auto sin_gen = sine_wave_osc{context, 1 * Hz}; - // TODO set_frequency and set_period calls for demonstrating use of different units + // Depending on the use-case sometimes an LFO will be set with a frequency in Hz + sin_gen.set_frequency(13 * Hz); + + // for some use-cases it is more convient for a user to set the period + sin_gen.set_period(42 * s); + + // and in some other use-cases setting the period in musical note duration is more intuitive + sin_gen.set_period(1 * audio::half_note); + + // Our oscillator can be used to generate sample values for a buffer + // of audio samples. In this example we will create a buffer with + // duration equal to 2 measures of 4/4 music (i.e. 2 whole notes at + // the current tempo): + const auto beats = 2 * audio::whole_note; + const auto buffer_duration = beats / context.tempo; + const auto buffer_size = (buffer_duration * context.sample_rate).in(audio::sample); + + std::cout << MP_UNITS_STD_FMT::format("\nCreating buffer with size:\n\t{}\n\t{}\n\t{}\n\n", beats, buffer_duration, + buffer_size); + + using buffer_t = std::vector>; + + auto buffer_1 = buffer_t(std::size_t(buffer_size.numerical_value_in(audio::sample))); + + std::cout << MP_UNITS_STD_FMT::format("Filling buffer with values from LFO @ {}", sin_gen.get_frequency()); + std::generate(begin(buffer_1), end(buffer_1), sin_gen); + + assert(buffer_1.size() > 0u); + std::cout << MP_UNITS_STD_FMT::format("\nLFO Values:\n[{}", buffer_1[0u]); + for (std::size_t i = 1u; i < buffer_1.size(); ++i) { + std::cout << MP_UNITS_STD_FMT::format(", {}", buffer_1[i]); + } + std::cout << "]\n\n"; + // generated values should be the same after resetting oscillator + sin_gen.reset(); + auto buffer_2 = buffer_t(buffer_1.size()); + std::generate(begin(buffer_2), end(buffer_2), sin_gen); - // auto buffer = std::vector>(2 * audio::whole_note) + return buffer_1 == buffer_2; } From fbfecdb37f3e2665d674d1e0d0b91b88d16218ca Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 19:35:34 -0500 Subject: [PATCH 05/17] Uncomment unit symbols --- example/audio/audio.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/example/audio/audio.h b/example/audio/audio.h index dca4eb58a..14f16ba2e 100644 --- a/example/audio/audio.h +++ b/example/audio/audio.h @@ -78,17 +78,17 @@ inline constexpr auto smpl = sample; inline constexpr auto pcm = sample_value; -// inline constexpr auto n_wd = 3 * half_note; +inline constexpr auto n_wd = 3 * half_note; inline constexpr auto n_w = whole_note; -// inline constexpr auto n_hd = dotted_half_note; -// inline constexpr auto n_h = half_note; -// inline constexpr auto n_qd = dotted_quarter_note; -// inline constexpr auto n_q = quarter_note; -// inline constexpr auto n_qt = quarter_note_triplet; -// inline constexpr auto n_8thd = dotted_eigth_note; -// inline constexpr auto n_8th = eigth_note; -// inline constexpr auto n_16th = sixteenth_note; +inline constexpr auto n_hd = dotted_half_note; +inline constexpr auto n_h = half_note; +inline constexpr auto n_qd = dotted_quarter_note; +inline constexpr auto n_q = quarter_note; +inline constexpr auto n_qt = quarter_note_triplet; +inline constexpr auto n_8thd = dotted_eigth_note; +inline constexpr auto n_8th = eigth_note; +inline constexpr auto n_16th = sixteenth_note; -// inline constexpr auto bpm = beats_per_minute; +inline constexpr auto bpm = beats_per_minute; } // namespace unit_symbols } // namespace audio From 8176bdcc41e5b74da95e76006a2c5ae99049a2a2 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 19:48:18 -0500 Subject: [PATCH 06/17] Include in 1-lfo example --- example/audio/1-lfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp index 251dc4305..e2ae80a5a 100644 --- a/example/audio/1-lfo.cpp +++ b/example/audio/1-lfo.cpp @@ -24,6 +24,7 @@ #ifdef MP_UNITS_IMPORT_STD import std; #else +#include #include #include #endif From 50a323b8e2c13c19d08631aeec35a096668e2ed9 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 20:00:28 -0500 Subject: [PATCH 07/17] Try value_cast to fix int->float conversion warning --- example/audio/1-lfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp index e2ae80a5a..1caec4fc2 100644 --- a/example/audio/1-lfo.cpp +++ b/example/audio/1-lfo.cpp @@ -121,7 +121,7 @@ int main() // duration equal to 2 measures of 4/4 music (i.e. 2 whole notes at // the current tempo): const auto beats = 2 * audio::whole_note; - const auto buffer_duration = beats / context.tempo; + const auto buffer_duration = value_cast(beats) / context.tempo; const auto buffer_size = (buffer_duration * context.sample_rate).in(audio::sample); std::cout << MP_UNITS_STD_FMT::format("\nCreating buffer with size:\n\t{}\n\t{}\n\t{}\n\n", beats, buffer_duration, From b0a6ac916dc6e3645a5f31fcc4be9123a0fb3f26 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 20:21:19 -0500 Subject: [PATCH 08/17] Fix reuse of sample_rate name for analysis --- example/audio/0-wrapping_unsafe_apis.cpp | 8 ++++---- example/audio/1-lfo.cpp | 10 +++++----- example/audio/third_party_audio_api.h | 6 +++--- example/audio/wrapped_third_party_audio_api.h | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/audio/0-wrapping_unsafe_apis.cpp b/example/audio/0-wrapping_unsafe_apis.cpp index dbbfff6ff..590638b36 100644 --- a/example/audio/0-wrapping_unsafe_apis.cpp +++ b/example/audio/0-wrapping_unsafe_apis.cpp @@ -41,12 +41,12 @@ int main() // sample rate, but these APIs are not type safe using float or double values; e.g.: const auto unsafe_context = audio_third_party::get_musical_context(); - std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n\n", unsafe_context.tempo, - unsafe_context.sample_rate); + std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n\n", + unsafe_context.current_tempo, unsafe_context.current_sample_rate); // These unsafe APIs can be wrapped a new API returning type-safe quantities for tempo and sample rate; e.g.: const auto safe_context = audio::get_musical_context(); - std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n", safe_context.tempo, - safe_context.sample_rate); + std::cout << MP_UNITS_STD_FMT::format("Musical context:\n\tTempo: {}\n\tSample Rate: {}\n", + safe_context.current_tempo, safe_context.current_sample_rate); } diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp index 1caec4fc2..6d357175c 100644 --- a/example/audio/1-lfo.cpp +++ b/example/audio/1-lfo.cpp @@ -51,7 +51,7 @@ class sine_wave_osc { { std::cout << MP_UNITS_STD_FMT::format( "Created oscillator with starting frequency {} ({}) for sample rate {} at tempo {}\n", freq, m_frequency, - context.sample_rate, context.tempo); + context.current_sample_rate, context.current_tempo); } quantity get_frequency() const { return m_frequency; } @@ -71,7 +71,7 @@ class sine_wave_osc { void set_period(QuantityOf auto period) { std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); - set_period(period / m_context.tempo); + set_period(period / m_context.current_tempo); } quantity operator()() @@ -86,7 +86,7 @@ class sine_wave_osc { private: using phase_t = quantity_point; - void update_step() { m_step = (m_frequency / m_context.sample_rate) * angular::revolution; } + void update_step() { m_step = (m_frequency / m_context.current_sample_rate) * angular::revolution; } audio::musical_context m_context; quantity m_frequency; @@ -121,8 +121,8 @@ int main() // duration equal to 2 measures of 4/4 music (i.e. 2 whole notes at // the current tempo): const auto beats = 2 * audio::whole_note; - const auto buffer_duration = value_cast(beats) / context.tempo; - const auto buffer_size = (buffer_duration * context.sample_rate).in(audio::sample); + const auto buffer_duration = value_cast(beats) / context.current_tempo; + const auto buffer_size = (buffer_duration * context.current_sample_rate).in(audio::sample); std::cout << MP_UNITS_STD_FMT::format("\nCreating buffer with size:\n\t{}\n\t{}\n\t{}\n\n", beats, buffer_duration, buffer_size); diff --git a/example/audio/third_party_audio_api.h b/example/audio/third_party_audio_api.h index c8376efc3..185db80b0 100644 --- a/example/audio/third_party_audio_api.h +++ b/example/audio/third_party_audio_api.h @@ -26,8 +26,8 @@ namespace audio_third_party { //! State of a playback engine for music host application. struct musical_context { - float sample_rate; //!< samples per second - float tempo; //!< beats per minute (quarter note == one beat) + float current_sample_rate; //!< samples per second + float current_tempo; //!< beats per minute (quarter note == one beat) }; //! API provided by music host application to provide global info @@ -35,6 +35,6 @@ struct musical_context { musical_context get_musical_context() { // Example data, this would be variable in a real-world context - return musical_context{.sample_rate = 8000.f, .tempo = 130.f}; + return musical_context{.current_sample_rate = 8000.f, .current_tempo = 130.f}; } } // namespace audio_third_party diff --git a/example/audio/wrapped_third_party_audio_api.h b/example/audio/wrapped_third_party_audio_api.h index 6dd028728..80bebdc38 100644 --- a/example/audio/wrapped_third_party_audio_api.h +++ b/example/audio/wrapped_third_party_audio_api.h @@ -29,16 +29,16 @@ namespace audio { //! Typesafe version of music application playback engine state struct musical_context { - mp_units::quantity sample_rate; - mp_units::quantity tempo; + mp_units::quantity current_sample_rate; + mp_units::quantity current_tempo; }; //! Typesafe wrapper around API for host application musical context musical_context get_musical_context() { auto context = audio_third_party::get_musical_context(); - return musical_context{.sample_rate = context.sample_rate * mp_units::si::hertz, - .tempo = context.tempo * beats_per_minute}; + return musical_context{.current_sample_rate = context.current_sample_rate * mp_units::si::hertz, + .current_tempo = context.current_tempo * beats_per_minute}; } } // namespace audio From 9629004623bca85a4e2bdfc45a452c28634a2702 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 20:27:59 -0500 Subject: [PATCH 09/17] Try adding more value_cast to fix analysis errors --- example/audio/1-lfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-lfo.cpp index 6d357175c..c12d6be74 100644 --- a/example/audio/1-lfo.cpp +++ b/example/audio/1-lfo.cpp @@ -64,14 +64,14 @@ class sine_wave_osc { void set_period(QuantityOf auto period) { - m_frequency = 1.f / period; + m_frequency = 1.f / value_cast(period); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); } void set_period(QuantityOf auto period) { std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); - set_period(period / m_context.current_tempo); + set_period(value_cast(period) / m_context.current_tempo); } quantity operator()() From 8be556fa689e22c8db6bc7634e54753f6e2162c3 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Thu, 19 Dec 2024 20:47:09 -0500 Subject: [PATCH 10/17] Rename 1-lfo -> 1-oscillator --- example/audio/{1-lfo.cpp => 1-oscillator.cpp} | 0 example/audio/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename example/audio/{1-lfo.cpp => 1-oscillator.cpp} (100%) diff --git a/example/audio/1-lfo.cpp b/example/audio/1-oscillator.cpp similarity index 100% rename from example/audio/1-lfo.cpp rename to example/audio/1-oscillator.cpp diff --git a/example/audio/CMakeLists.txt b/example/audio/CMakeLists.txt index 6da4dd84d..196fd1f16 100644 --- a/example/audio/CMakeLists.txt +++ b/example/audio/CMakeLists.txt @@ -21,4 +21,4 @@ # SOFTWARE. add_example(0-wrapping_unsafe_apis) -add_example(1-lfo) +add_example(1-oscillator) From 2e0a824f3dd980f9fb79d35ad469f25bd7a209ed Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Fri, 20 Dec 2024 10:30:54 -0500 Subject: [PATCH 11/17] Update example/audio/third_party_audio_api.h Co-authored-by: Mateusz Pusz --- example/audio/third_party_audio_api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/audio/third_party_audio_api.h b/example/audio/third_party_audio_api.h index 185db80b0..eea6b1415 100644 --- a/example/audio/third_party_audio_api.h +++ b/example/audio/third_party_audio_api.h @@ -32,7 +32,7 @@ struct musical_context { //! API provided by music host application to provide global info //! about the playback engine. -musical_context get_musical_context() +inline musical_context get_musical_context() { // Example data, this would be variable in a real-world context return musical_context{.current_sample_rate = 8000.f, .current_tempo = 130.f}; From 9fbe55693e754d9e808f288cf4ab929a1166b346 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Fri, 20 Dec 2024 10:31:03 -0500 Subject: [PATCH 12/17] Update example/audio/wrapped_third_party_audio_api.h Co-authored-by: Mateusz Pusz --- example/audio/wrapped_third_party_audio_api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/audio/wrapped_third_party_audio_api.h b/example/audio/wrapped_third_party_audio_api.h index 80bebdc38..82ea51b96 100644 --- a/example/audio/wrapped_third_party_audio_api.h +++ b/example/audio/wrapped_third_party_audio_api.h @@ -34,7 +34,7 @@ struct musical_context { }; //! Typesafe wrapper around API for host application musical context -musical_context get_musical_context() +inline musical_context get_musical_context() { auto context = audio_third_party::get_musical_context(); return musical_context{.current_sample_rate = context.current_sample_rate * mp_units::si::hertz, From 03775fd92a42d80a0d44009cfb9d2089c529fa7d Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Wed, 8 Jan 2025 18:52:42 -0500 Subject: [PATCH 13/17] Audio examples 1-oscillator PR feedback - Try including to fix CI error - Use `mp_units::inverse` - Use range-based for-loop - Use CTAG and `quantity` instead of `auto` --- example/audio/1-oscillator.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp index c12d6be74..19862115c 100644 --- a/example/audio/1-oscillator.cpp +++ b/example/audio/1-oscillator.cpp @@ -27,11 +27,14 @@ import std; #include #include #include +#include +#include #endif #ifdef MP_UNITS_MODULES import mp_units; #else #include +#include #include #include #include @@ -64,7 +67,7 @@ class sine_wave_osc { void set_period(QuantityOf auto period) { - m_frequency = 1.f / value_cast(period); + m_frequency = inverse(period); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); } @@ -120,9 +123,9 @@ int main() // of audio samples. In this example we will create a buffer with // duration equal to 2 measures of 4/4 music (i.e. 2 whole notes at // the current tempo): - const auto beats = 2 * audio::whole_note; - const auto buffer_duration = value_cast(beats) / context.current_tempo; - const auto buffer_size = (buffer_duration * context.current_sample_rate).in(audio::sample); + const quantity beats = 2 * audio::whole_note; + const quantity buffer_duration = value_cast(beats) / context.current_tempo; + const quantity buffer_size = (buffer_duration * context.current_sample_rate).in(audio::sample); std::cout << MP_UNITS_STD_FMT::format("\nCreating buffer with size:\n\t{}\n\t{}\n\t{}\n\n", beats, buffer_duration, buffer_size); @@ -134,10 +137,9 @@ int main() std::cout << MP_UNITS_STD_FMT::format("Filling buffer with values from LFO @ {}", sin_gen.get_frequency()); std::generate(begin(buffer_1), end(buffer_1), sin_gen); - assert(buffer_1.size() > 0u); std::cout << MP_UNITS_STD_FMT::format("\nLFO Values:\n[{}", buffer_1[0u]); - for (std::size_t i = 1u; i < buffer_1.size(); ++i) { - std::cout << MP_UNITS_STD_FMT::format(", {}", buffer_1[i]); + for (const auto sampleValue : std::ranges::subrange(begin(buffer_1) + 1, end(buffer_1))) { + std::cout << MP_UNITS_STD_FMT::format(", {}", sampleValue); } std::cout << "]\n\n"; From e5a59483cd47de4fc37f15d8303170a13a2f3d09 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Wed, 8 Jan 2025 19:18:50 -0500 Subject: [PATCH 14/17] Update audio example for dimensionless changes I was suprised by the need to use `.in(one)` here as I would have assumed the result was already in unit `one`. --- example/audio/1-oscillator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp index 19862115c..10cba7f40 100644 --- a/example/audio/1-oscillator.cpp +++ b/example/audio/1-oscillator.cpp @@ -125,7 +125,8 @@ int main() // the current tempo): const quantity beats = 2 * audio::whole_note; const quantity buffer_duration = value_cast(beats) / context.current_tempo; - const quantity buffer_size = (buffer_duration * context.current_sample_rate).in(audio::sample); + const quantity buffer_size = + quantity_cast((buffer_duration * context.current_sample_rate).in(one)); std::cout << MP_UNITS_STD_FMT::format("\nCreating buffer with size:\n\t{}\n\t{}\n\t{}\n\n", beats, buffer_duration, buffer_size); From 7091c1edac230021e2b679063c2b5da55b4b9cac Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Wed, 8 Jan 2025 19:43:07 -0500 Subject: [PATCH 15/17] Remove unused header --- example/audio/1-oscillator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp index 10cba7f40..10a71633c 100644 --- a/example/audio/1-oscillator.cpp +++ b/example/audio/1-oscillator.cpp @@ -25,7 +25,6 @@ import std; #else #include -#include #include #include #include From 607d1ea3249788bba036a53f7116b676ac57d4d4 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Wed, 8 Jan 2025 19:59:12 -0500 Subject: [PATCH 16/17] Add missing call to update_step in 1-oscillator example --- example/audio/1-oscillator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp index 10a71633c..016b0421b 100644 --- a/example/audio/1-oscillator.cpp +++ b/example/audio/1-oscillator.cpp @@ -51,6 +51,7 @@ class sine_wave_osc { sine_wave_osc(const audio::musical_context& context, QuantityOf auto freq) : m_context{context}, m_frequency{freq} { + update_step(); std::cout << MP_UNITS_STD_FMT::format( "Created oscillator with starting frequency {} ({}) for sample rate {} at tempo {}\n", freq, m_frequency, context.current_sample_rate, context.current_tempo); @@ -61,17 +62,20 @@ class sine_wave_osc { void set_frequency(QuantityOf auto freq) { m_frequency = freq; + update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting frequency to {} ({})\n", freq, m_frequency); } void set_period(QuantityOf auto period) { m_frequency = inverse(period); + update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); } void set_period(QuantityOf auto period) { + update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); set_period(value_cast(period) / m_context.current_tempo); } From acf4d8df314375d746889b6666c3c343268b2bf9 Mon Sep 17 00:00:00 2001 From: Roth Michaels Date: Wed, 8 Jan 2025 20:00:02 -0500 Subject: [PATCH 17/17] 1-oscillator example combine set_frequency and set_period into one overload set --- example/audio/1-oscillator.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp index 016b0421b..c476e5abe 100644 --- a/example/audio/1-oscillator.cpp +++ b/example/audio/1-oscillator.cpp @@ -59,25 +59,25 @@ class sine_wave_osc { quantity get_frequency() const { return m_frequency; } - void set_frequency(QuantityOf auto freq) + void set_rate(QuantityOf auto freq) { m_frequency = freq; update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting frequency to {} ({})\n", freq, m_frequency); } - void set_period(QuantityOf auto period) + void set_rate(QuantityOf auto period) { m_frequency = inverse(period); update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} (i.e. frequency to {})\n", period, m_frequency); } - void set_period(QuantityOf auto period) + void set_rate(QuantityOf auto period) { update_step(); std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); - set_period(value_cast(period) / m_context.current_tempo); + set_rate(value_cast(period) / m_context.current_tempo); } quantity operator()() @@ -114,13 +114,13 @@ int main() auto sin_gen = sine_wave_osc{context, 1 * Hz}; // Depending on the use-case sometimes an LFO will be set with a frequency in Hz - sin_gen.set_frequency(13 * Hz); + sin_gen.set_rate(13 * Hz); // for some use-cases it is more convient for a user to set the period - sin_gen.set_period(42 * s); + sin_gen.set_rate(42 * s); // and in some other use-cases setting the period in musical note duration is more intuitive - sin_gen.set_period(1 * audio::half_note); + sin_gen.set_rate(1 * audio::half_note); // Our oscillator can be used to generate sample values for a buffer // of audio samples. In this example we will create a buffer with