From 94216ce6386d4f9854742839598816a83c874495 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Wed, 5 Oct 2022 13:54:10 +0300 Subject: [PATCH] Add volume control effect --- examples/audio.cpp | 15 +++++- libs/audio/include/psemek/audio/attack.hpp | 30 +++++++++++ .../include/psemek/audio/effect/volume.hpp | 20 +++++++ .../psemek/audio/effect/volume_base.hpp | 30 +++++++++++ libs/audio/source/effect/volume.cpp | 43 +++++++++++++++ libs/audio/source/effect/volume_base.cpp | 52 +++++++++++++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 libs/audio/include/psemek/audio/attack.hpp create mode 100644 libs/audio/include/psemek/audio/effect/volume.hpp create mode 100644 libs/audio/include/psemek/audio/effect/volume_base.hpp create mode 100644 libs/audio/source/effect/volume.cpp create mode 100644 libs/audio/source/effect/volume_base.cpp diff --git a/examples/audio.cpp b/examples/audio.cpp index 932b783e..8d95f3ae 100644 --- a/examples/audio.cpp +++ b/examples/audio.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,17 @@ struct audio_app { mixer_ = audio::make_mixer(); engine_.output(mixer_); + + auto volume = audio::volume(audio::sine_wave(440.f), 1.f, 0.01f); + auto channel = mixer_->add(volume); + std::this_thread::sleep_for(std::chrono::seconds{1}); + volume->gain(0.5f); + std::this_thread::sleep_for(std::chrono::seconds{1}); + volume->gain(0.25f); + std::this_thread::sleep_for(std::chrono::seconds{1}); + volume->gain(0.125f); + std::this_thread::sleep_for(std::chrono::seconds{1}); + channel->stop(); } void on_key_down(SDL_Keycode key) override @@ -43,7 +55,8 @@ struct audio_app if (key_to_midi.contains(key) && !channels_.contains(key)) { int midi = key_to_midi.at(key); - channels_[key] = mixer_->add(audio::sine_wave(440.f * std::pow(2.f, (midi - 69) / 12.f))); + auto tone = audio::sine_wave(440.f * std::pow(2.f, (midi - 69) / 12.f)); + channels_[key] = mixer_->add(audio::volume(tone, 0.5f)); } } diff --git a/libs/audio/include/psemek/audio/attack.hpp b/libs/audio/include/psemek/audio/attack.hpp new file mode 100644 index 00000000..22a0f79b --- /dev/null +++ b/libs/audio/include/psemek/audio/attack.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +namespace psemek::audio +{ + + inline float attack_to_multiplier(float attack) + { + if (attack == 0.f) + return 1.f; + return - std::expm1(- inv_frequency / attack); + } + + inline float multiplier_to_attack(float multiplier) + { + if (multiplier == 1.f) + return 0.f; + + return - inv_frequency / std::log1p(- multiplier); + } + + inline void attack_update(float & value, float target_value, float multiplier) + { + value += (target_value - value) * multiplier; + } + +} diff --git a/libs/audio/include/psemek/audio/effect/volume.hpp b/libs/audio/include/psemek/audio/effect/volume.hpp new file mode 100644 index 00000000..ad097111 --- /dev/null +++ b/libs/audio/include/psemek/audio/effect/volume.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace psemek::audio +{ + + struct volume_control + : stream + { + virtual float gain() const = 0; + virtual float gain(float value) = 0; + + virtual float attack() const = 0; + virtual float attack(float value) = 0; + }; + + std::shared_ptr volume(stream_ptr stream, float gain = 1.f, float attack = 0.f); + +} diff --git a/libs/audio/include/psemek/audio/effect/volume_base.hpp b/libs/audio/include/psemek/audio/effect/volume_base.hpp new file mode 100644 index 00000000..6077d35a --- /dev/null +++ b/libs/audio/include/psemek/audio/effect/volume_base.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +namespace psemek::audio +{ + + struct volume_base + { + volume_base(float gain, float attack); + + float gain() const { return gain_.load(); } + float gain(float value) { return gain_.exchange(value); } + + float attack() const; + float attack(float value); + + void apply(float * data, std::size_t sample_count); + + private: + std::atomic gain_; + std::atomic attack_multiplier_; + float real_gain_; + float last_sample_src_[2]; + float last_sample_tgt_[2]; + }; + +} diff --git a/libs/audio/source/effect/volume.cpp b/libs/audio/source/effect/volume.cpp new file mode 100644 index 00000000..74047721 --- /dev/null +++ b/libs/audio/source/effect/volume.cpp @@ -0,0 +1,43 @@ +#include +#include + +namespace psemek::audio +{ + + namespace + { + + struct volume_control_impl + : volume_control + { + volume_control_impl(stream_ptr stream, float gain, float attack) + : base_(gain, attack) + , stream_(std::move(stream)) + {} + + float gain() const override { return base_.gain(); } + float gain(float value) override { return base_.gain(value); } + + float attack() const override { return base_.attack(); } + float attack(float value) override { return base_.attack(value); } + + std::size_t read(float * data, std::size_t sample_count) override + { + auto result = stream_->read(data, sample_count); + base_.apply(data, result); + return result; + } + + private: + volume_base base_; + stream_ptr stream_; + }; + + } + + std::shared_ptr volume(stream_ptr stream, float gain, float attack) + { + return std::make_shared(std::move(stream), gain, attack); + } + +} diff --git a/libs/audio/source/effect/volume_base.cpp b/libs/audio/source/effect/volume_base.cpp new file mode 100644 index 00000000..39a08fa5 --- /dev/null +++ b/libs/audio/source/effect/volume_base.cpp @@ -0,0 +1,52 @@ +#include +#include +#include + +#include + +namespace psemek::audio +{ + + volume_base::volume_base(float gain, float attack) + : gain_{gain} + , attack_multiplier_{attack_to_multiplier(attack)} + , real_gain_{gain} + , last_sample_src_{0.f, 0.f} + , last_sample_tgt_{0.f, 0.f} + {} + + float volume_base::attack() const + { + return multiplier_to_attack(attack_multiplier_.load()); + } + + float volume_base::attack(float value) + { + auto old = attack_multiplier_.exchange(attack_to_multiplier(value)); + return multiplier_to_attack(old); + } + + void volume_base::apply(float * data, std::size_t sample_count) + { + float gain = gain_.load(); + float attack_multiplier = attack_multiplier_.load(); + + auto apply_impl = [this](float & target, float & last_src, float & last_tgt) + { + auto old = target; + target = last_tgt + (old - last_src) * real_gain_; + last_src = old; + last_tgt = target; + }; + + auto end = data + sample_count; + for (auto p = data; p < end;) + { + apply_impl(*p++, last_sample_src_[0], last_sample_tgt_[0]); + apply_impl(*p++, last_sample_src_[1], last_sample_tgt_[1]); + + attack_update(real_gain_, gain, attack_multiplier); + } + } + +}