Implement stereo audio volume control
This commit is contained in:
parent
acc2fd486e
commit
bd16679b97
5 changed files with 84 additions and 28 deletions
|
|
@ -63,12 +63,8 @@ struct audio_app
|
||||||
: app::app("Audio example")
|
: app::app("Audio example")
|
||||||
{
|
{
|
||||||
mixer_ = audio::make_mixer();
|
mixer_ = audio::make_mixer();
|
||||||
|
volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
|
||||||
auto [ dup1, dup2 ] = audio::duplicate(mixer_);
|
engine_.output()->stream(volume_control_);
|
||||||
left_volume_ = audio::volume(dup1, 0.5f, 0.1f);
|
|
||||||
right_volume_ = audio::volume(dup2, 0.5f, 0.1f);
|
|
||||||
auto result = audio::stereo(left_volume_, right_volume_);
|
|
||||||
engine_.output()->stream(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_key_down(SDL_Keycode key) override
|
void on_key_down(SDL_Keycode key) override
|
||||||
|
|
@ -101,14 +97,14 @@ struct audio_app
|
||||||
float const time = clock_.count();
|
float const time = clock_.count();
|
||||||
|
|
||||||
float volume = std::sin(time);
|
float volume = std::sin(time);
|
||||||
left_volume_->gain(0.5f + 0.5f * volume);
|
volume_control_->gain_left(0.5f + 0.5f * volume);
|
||||||
right_volume_->gain(0.5f - 0.5f * volume);
|
volume_control_->gain_right(0.5f - 0.5f * volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
audio::engine engine_;
|
audio::engine engine_;
|
||||||
audio::mixer_ptr mixer_;
|
audio::mixer_ptr mixer_;
|
||||||
std::shared_ptr<audio::volume_control> left_volume_, right_volume_;
|
std::shared_ptr<audio::volume_control_stereo> volume_control_;
|
||||||
std::map<SDL_Keycode, audio::channel_ptr> channels_;
|
std::map<SDL_Keycode, audio::channel_ptr> channels_;
|
||||||
|
|
||||||
util::clock<> clock_;
|
util::clock<> clock_;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,19 @@ namespace psemek::audio
|
||||||
virtual float smoothness(float value) = 0;
|
virtual float smoothness(float value) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct volume_control_stereo
|
||||||
|
: stream
|
||||||
|
{
|
||||||
|
virtual float gain_left() const = 0;
|
||||||
|
virtual float gain_right() const = 0;
|
||||||
|
virtual float gain_left(float value) = 0;
|
||||||
|
virtual float gain_right(float value) = 0;
|
||||||
|
|
||||||
|
virtual float smoothness() const = 0;
|
||||||
|
virtual float smoothness(float value) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<volume_control> volume(stream_ptr stream, float gain = 1.f, float smoothness = 0.f);
|
std::shared_ptr<volume_control> volume(stream_ptr stream, float gain = 1.f, float smoothness = 0.f);
|
||||||
|
std::shared_ptr<volume_control_stereo> volume_stereo(stream_ptr stream, float gain_left = 1.f, float gain_right = 1.f, float smoothness = 0.f);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ namespace psemek::audio
|
||||||
|
|
||||||
struct volume_base
|
struct volume_base
|
||||||
{
|
{
|
||||||
volume_base(float gain, float smoothness);
|
volume_base(float gain_left, float gain_right, float smoothness);
|
||||||
|
|
||||||
float gain() const { return gain_.load(); }
|
float gain_left() const { return gain_[0].load(); }
|
||||||
float gain(float value) { return gain_.exchange(value); }
|
float gain_right() const { return gain_[1].load(); }
|
||||||
|
|
||||||
|
float gain_left(float value) { return gain_[0].exchange(value); }
|
||||||
|
float gain_right(float value) { return gain_[1].exchange(value); }
|
||||||
|
|
||||||
float smoothness() const;
|
float smoothness() const;
|
||||||
float smoothness(float value);
|
float smoothness(float value);
|
||||||
|
|
@ -20,9 +23,9 @@ namespace psemek::audio
|
||||||
void apply(float * data, std::size_t sample_count);
|
void apply(float * data, std::size_t sample_count);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<float> gain_;
|
std::atomic<float> gain_[2];
|
||||||
std::atomic<float> smoothness_multiplier_;
|
std::atomic<float> smoothness_multiplier_;
|
||||||
float real_gain_;
|
float real_gain_[2];
|
||||||
float last_sample_src_[2];
|
float last_sample_src_[2];
|
||||||
float last_sample_tgt_[2];
|
float last_sample_tgt_[2];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,50 @@ namespace psemek::audio
|
||||||
: volume_control
|
: volume_control
|
||||||
{
|
{
|
||||||
volume_control_impl(stream_ptr stream, float gain, float smoothness)
|
volume_control_impl(stream_ptr stream, float gain, float smoothness)
|
||||||
: base_(gain, smoothness)
|
: base_(gain, gain, smoothness)
|
||||||
, stream_(std::move(stream))
|
, stream_(std::move(stream))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
float gain() const override { return base_.gain(); }
|
float gain() const override { return base_.gain_left(); }
|
||||||
float gain(float value) override { return base_.gain(value); }
|
float gain(float value) override { base_.gain_left(value); return base_.gain_right(value); }
|
||||||
|
|
||||||
|
float smoothness() const override { return base_.smoothness(); }
|
||||||
|
float smoothness(float value) override { return base_.smoothness(value); }
|
||||||
|
|
||||||
|
std::optional<std::size_t> length() const override
|
||||||
|
{
|
||||||
|
return stream_->length();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t played() const override
|
||||||
|
{
|
||||||
|
return stream_->played();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct volume_control_stereo_impl
|
||||||
|
: volume_control_stereo
|
||||||
|
{
|
||||||
|
volume_control_stereo_impl(stream_ptr stream, float gain_left, float gain_right, float smoothness)
|
||||||
|
: base_(gain_left, gain_right, smoothness)
|
||||||
|
, stream_(std::move(stream))
|
||||||
|
{}
|
||||||
|
|
||||||
|
float gain_left() const override { return base_.gain_left(); }
|
||||||
|
float gain_right() const override { return base_.gain_right(); }
|
||||||
|
float gain_left(float value) override { return base_.gain_left(value); }
|
||||||
|
float gain_right(float value) override { return base_.gain_right(value); }
|
||||||
|
|
||||||
float smoothness() const override { return base_.smoothness(); }
|
float smoothness() const override { return base_.smoothness(); }
|
||||||
float smoothness(float value) override { return base_.smoothness(value); }
|
float smoothness(float value) override { return base_.smoothness(value); }
|
||||||
|
|
@ -50,4 +88,9 @@ namespace psemek::audio
|
||||||
return std::make_shared<volume_control_impl>(std::move(stream), gain, smoothness);
|
return std::make_shared<volume_control_impl>(std::move(stream), gain, smoothness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<volume_control_stereo> volume_stereo(stream_ptr stream, float gain_left, float gain_right, float smoothness)
|
||||||
|
{
|
||||||
|
return std::make_shared<volume_control_stereo_impl>(std::move(stream), gain_left, gain_right, smoothness);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
namespace psemek::audio
|
namespace psemek::audio
|
||||||
{
|
{
|
||||||
|
|
||||||
volume_base::volume_base(float gain, float smoothness)
|
volume_base::volume_base(float gain_left, float gain_right, float smoothness)
|
||||||
: gain_{gain}
|
: gain_{gain_left, gain_right}
|
||||||
, smoothness_multiplier_{smoothness_to_multiplier(smoothness)}
|
, smoothness_multiplier_{smoothness_to_multiplier(smoothness)}
|
||||||
, real_gain_{gain}
|
, real_gain_{gain_left, gain_right}
|
||||||
, last_sample_src_{0.f, 0.f}
|
, last_sample_src_{0.f, 0.f}
|
||||||
, last_sample_tgt_{0.f, 0.f}
|
, last_sample_tgt_{0.f, 0.f}
|
||||||
{}
|
{}
|
||||||
|
|
@ -28,24 +28,25 @@ namespace psemek::audio
|
||||||
|
|
||||||
void volume_base::apply(float * data, std::size_t sample_count)
|
void volume_base::apply(float * data, std::size_t sample_count)
|
||||||
{
|
{
|
||||||
float gain = gain_.load();
|
float gain[2] = {gain_[0].load(), gain_[1].load()};
|
||||||
float smoothness_multiplier = smoothness_multiplier_.load();
|
float smoothness_multiplier = smoothness_multiplier_.load();
|
||||||
|
|
||||||
auto apply_impl = [this](float & target, float & last_src, float & last_tgt)
|
auto apply_impl = [this](float & target, int i)
|
||||||
{
|
{
|
||||||
auto old = target;
|
auto old = target;
|
||||||
target = last_tgt + (old - last_src) * real_gain_;
|
target = last_sample_tgt_[i] + (old - last_sample_src_[i]) * real_gain_[i];
|
||||||
last_src = old;
|
last_sample_src_[i] = old;
|
||||||
last_tgt = target;
|
last_sample_tgt_[i] = target;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto end = data + sample_count;
|
auto end = data + sample_count;
|
||||||
for (auto p = data; p < end;)
|
for (auto p = data; p < end;)
|
||||||
{
|
{
|
||||||
apply_impl(*p++, last_sample_src_[0], last_sample_tgt_[0]);
|
apply_impl(*p++, 0);
|
||||||
apply_impl(*p++, last_sample_src_[1], last_sample_tgt_[1]);
|
apply_impl(*p++, 1);
|
||||||
|
|
||||||
smooth_update(real_gain_, gain, smoothness_multiplier);
|
smooth_update(real_gain_[0], gain[0], smoothness_multiplier);
|
||||||
|
smooth_update(real_gain_[1], gain[1], smoothness_multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue