Files
sonus/src/metronome.cpp
T
khodowany 5600f03fc1
publish / publish (push) Successful in 13m13s
Cleanup
2026-09-24 19:44:00 -05:00

117 lines
4.6 KiB
C++

#include "detail/metronome_player.hpp"
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
namespace {
constexpr auto pi = 3.141592653589793238462643383279502884;
auto make_click_samples(std::uint32_t sample_rate, std::uint32_t frame_count, std::float32_t frequency, std::float32_t gain) -> std::vector<std::float32_t> {
auto result = std::vector<std::float32_t>(frame_count);
auto const phase_step = (2.0 * pi * static_cast<double>(frequency)) / static_cast<double>(sample_rate);
auto phase = 0.0;
for (auto index = std::uint32_t{}; index < frame_count; ++index) {
auto const progress = static_cast<double>(index) / static_cast<double>(frame_count);
auto const envelope = (1.0 - progress) * (1.0 - progress);
result[index] = static_cast<std::float32_t>(std::sin(phase) * envelope * static_cast<double>(gain));
phase += phase_step;
}
return result;
}
}
auto sonus::detail::metronome_player::configure(sonus::metronome_config value, std::uint32_t sample_rate, std::bool_t enable) -> void {
auto click_frames = static_cast<std::uint64_t>(sample_rate) * static_cast<std::uint64_t>(value.click_duration.count()) / 1000U;
click_frames = std::clamp<std::uint64_t>(click_frames, 1U, std::numeric_limits<std::uint32_t>::max());
auto frames_per_beat = std::max<std::uint64_t>(1U, static_cast<std::uint64_t>(std::llround((static_cast<double>(sample_rate) * 60.0) / static_cast<double>(value.bpm))));
auto result = std::make_unique<sonus::detail::metronome_render_config>();
result->enabled = enable;
result->beats_per_bar = value.beats_per_bar;
result->frames_per_beat = frames_per_beat;
if (enable) {
result->beat_click = make_click_samples(sample_rate, static_cast<std::uint32_t>(click_frames), value.beat_frequency_hz, value.volume * 0.64F);
result->downbeat_click = make_click_samples(sample_rate, static_cast<std::uint32_t>(click_frames), value.downbeat_frequency_hz, value.volume);
}
publish(std::move(result));
}
auto sonus::detail::metronome_player::stop() -> void {
publish(std::make_unique<sonus::detail::metronome_render_config>());
}
auto sonus::detail::metronome_player::active() const -> std::bool_t {
return metronome_enabled.load(std::memory_order_acquire);
}
auto sonus::detail::metronome_player::collect() -> void {
metronome_configs.collect();
}
auto sonus::detail::metronome_player::publish(std::unique_ptr<sonus::detail::metronome_render_config> next) -> void {
next->generation = next_metronome_generation++;
if (next_metronome_generation == 0U) {
next_metronome_generation = 1U;
}
auto const enabled = next->enabled;
metronome_configs.publish(std::move(next));
metronome_enabled.store(enabled, std::memory_order_release);
}
auto sonus::detail::metronome_player::render(std::float32_t *output, std::uint64_t frame_count, std::uint32_t channels) -> void {
auto guard = metronome_configs.read();
auto const *metro = guard.get();
if (metro == nullptr || !metro->enabled) {
if (metro != nullptr) {
observed_metronome_generation = metro->generation;
}
reset();
return;
}
if (observed_metronome_generation != metro->generation) {
observed_metronome_generation = metro->generation;
reset();
}
for (auto frame = std::uint64_t{}; frame < frame_count; ++frame) {
if (frames_until_next_beat == 0U) {
auto const downbeat = beat_index == 0U;
click_frame_index = 0;
click_is_downbeat = downbeat;
click_frames_remaining = downbeat ? metro->downbeat_click.size() : metro->beat_click.size();
beat_index = (beat_index + 1U) % std::max<std::uint32_t>(1U, metro->beats_per_bar);
frames_until_next_beat = metro->frames_per_beat;
}
auto sample = 0.0F;
if (click_frames_remaining > 0U) {
auto const &click = click_is_downbeat ? metro->downbeat_click : metro->beat_click;
if (click_frame_index < click.size()) {
sample = click[click_frame_index];
++click_frame_index;
--click_frames_remaining;
} else {
click_frames_remaining = 0;
}
}
if (sample != 0.0F) {
auto *frame_out = output + frame * channels;
for (auto channel = std::uint32_t{}; channel < channels; ++channel) {
auto const mixed = static_cast<std::float32_t>(frame_out[channel] + sample);
frame_out[channel] = std::clamp(mixed, static_cast<std::float32_t>(-1.0F), static_cast<std::float32_t>(1.0F));
}
}
--frames_until_next_beat;
}
}
auto sonus::detail::metronome_player::reset() -> void {
beat_index = 0;
frames_until_next_beat = 0;
click_frame_index = 0;
click_frames_remaining = 0;
click_is_downbeat = false;
}