diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ed36e396a..bcbab11f7 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -72,5 +72,6 @@ add_example(strong_angular_quantities) add_example(total_energy) add_example(unmanned_aerial_vehicle example_utils) +add_subdirectory(audio) add_subdirectory(glide_computer_lib) add_subdirectory(kalman_filter) diff --git a/example/audio/0-wrapping_unsafe_apis.cpp b/example/audio/0-wrapping_unsafe_apis.cpp new file mode 100644 index 000000000..590638b36 --- /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.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.current_tempo, safe_context.current_sample_rate); +} diff --git a/example/audio/1-oscillator.cpp b/example/audio/1-oscillator.cpp new file mode 100644 index 000000000..c476e5abe --- /dev/null +++ b/example/audio/1-oscillator.cpp @@ -0,0 +1,156 @@ +// 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 +#include +#include +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#include +#include +#include +#include +#endif + +#include "audio.h" +#include "wrapped_third_party_audio_api.h" + +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) : + 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); + } + + quantity get_frequency() const { return m_frequency; } + + 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_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_rate(QuantityOf auto period) + { + update_step(); + std::cout << MP_UNITS_STD_FMT::format("Setting period to {} -- ", period); + set_rate(value_cast(period) / m_context.current_tempo); + } + + quantity operator()() + { + auto out = angular::sin(m_phase.quantity_from_zero()); + m_phase += m_step; + return out; + } + + void reset() { m_phase = phase_t{0.f * angular::radian}; } + +private: + using phase_t = quantity_point; + + void update_step() { m_step = (m_frequency / m_context.current_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(); + + // 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}; + + // Depending on the use-case sometimes an LFO will be set with a frequency in 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_rate(42 * s); + + // and in some other use-cases setting the period in musical note duration is more intuitive + 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 + // duration equal to 2 measures of 4/4 music (i.e. 2 whole notes at + // the current tempo): + const quantity beats = 2 * audio::whole_note; + const quantity buffer_duration = value_cast(beats) / context.current_tempo; + 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); + + 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); + + std::cout << MP_UNITS_STD_FMT::format("\nLFO Values:\n[{}", buffer_1[0u]); + 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"; + + // 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); + + return buffer_1 == buffer_2; +} diff --git a/example/audio/CMakeLists.txt b/example/audio/CMakeLists.txt new file mode 100644 index 000000000..196fd1f16 --- /dev/null +++ b/example/audio/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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(0-wrapping_unsafe_apis) +add_example(1-oscillator) diff --git a/example/audio/audio.h b/example/audio/audio.h new file mode 100644 index 000000000..14f16ba2e --- /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..eea6b1415 --- /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 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 +//! about the playback engine. +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}; +} +} // 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..82ea51b96 --- /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 current_sample_rate; + mp_units::quantity current_tempo; +}; + +//! Typesafe wrapper around API for host application 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, + .current_tempo = context.current_tempo * beats_per_minute}; +} + +} // namespace audio