Add volume control effect

This commit is contained in:
Nikita Lisitsa 2022-10-05 13:54:10 +03:00
parent b3e583283a
commit 94216ce638
6 changed files with 189 additions and 1 deletions

View file

@ -3,6 +3,7 @@
#include <psemek/audio/wave/sawtooth.hpp> #include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/square.hpp> #include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/triangle.hpp> #include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/effect/volume.hpp>
#include <psemek/audio/mixer.hpp> #include <psemek/audio/mixer.hpp>
#include <psemek/app/app.hpp> #include <psemek/app/app.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/main.hpp>
@ -34,6 +35,17 @@ struct audio_app
{ {
mixer_ = audio::make_mixer(); mixer_ = audio::make_mixer();
engine_.output(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 void on_key_down(SDL_Keycode key) override
@ -43,7 +55,8 @@ struct audio_app
if (key_to_midi.contains(key) && !channels_.contains(key)) if (key_to_midi.contains(key) && !channels_.contains(key))
{ {
int midi = key_to_midi.at(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));
} }
} }

View file

@ -0,0 +1,30 @@
#pragma once
#include <psemek/audio/constants.hpp>
#include <cmath>
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;
}
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <psemek/audio/stream.hpp>
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_control> volume(stream_ptr stream, float gain = 1.f, float attack = 0.f);
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <atomic>
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<float> gain_;
std::atomic<float> attack_multiplier_;
float real_gain_;
float last_sample_src_[2];
float last_sample_tgt_[2];
};
}

View file

@ -0,0 +1,43 @@
#include <psemek/audio/effect/volume.hpp>
#include <psemek/audio/effect/volume_base.hpp>
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_control> volume(stream_ptr stream, float gain, float attack)
{
return std::make_shared<volume_control_impl>(std::move(stream), gain, attack);
}
}

View file

@ -0,0 +1,52 @@
#include <psemek/audio/effect/volume_base.hpp>
#include <psemek/audio/constants.hpp>
#include <psemek/audio/attack.hpp>
#include <cmath>
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);
}
}
}