From 413e212ea83dca0dbd1f75554d9e677a90758ed0 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 12 Mar 2023 21:03:56 +0100 Subject: [PATCH] Piano: Cache buffers in Track and WaveWidget The Track itself caches the Samples after each processing step which allows it to be queried without the need to process it every time. This result is queried by the WaveWidget which then caches the result to prevent unnecessary heap allocations every paint event. --- Userland/Applications/Piano/MainWidget.cpp | 1 + Userland/Applications/Piano/WaveWidget.cpp | 17 +++++++------- Userland/Applications/Piano/WaveWidget.h | 8 +++++++ Userland/Libraries/LibDSP/Track.cpp | 26 ++++++++++++++++++++++ Userland/Libraries/LibDSP/Track.h | 8 +++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Userland/Applications/Piano/MainWidget.cpp b/Userland/Applications/Piano/MainWidget.cpp index a2ade38d62..8b36f9997e 100644 --- a/Userland/Applications/Piano/MainWidget.cpp +++ b/Userland/Applications/Piano/MainWidget.cpp @@ -41,6 +41,7 @@ ErrorOr MainWidget::initialize() m_wave_widget = TRY(try_add(m_track_manager)); m_wave_widget->set_fixed_height(100); + TRY(m_wave_widget->set_sample_size(sample_count)); m_tab_widget = TRY(try_add()); m_roll_widget = TRY(m_tab_widget->try_add_tab(TRY("Piano Roll"_string), m_track_manager)); diff --git a/Userland/Applications/Piano/WaveWidget.cpp b/Userland/Applications/Piano/WaveWidget.cpp index cdb6b1942c..cf8542a090 100644 --- a/Userland/Applications/Piano/WaveWidget.cpp +++ b/Userland/Applications/Piano/WaveWidget.cpp @@ -36,20 +36,19 @@ void WaveWidget::paint_event(GUI::PaintEvent& event) Color left_wave_color = left_wave_colors[m_track_manager.current_track()->synth()->wave()]; Color right_wave_color = right_wave_colors[m_track_manager.current_track()->synth()->wave()]; - // FIXME: We can't get the last buffer from the track manager anymore - auto buffer = FixedArray::must_create_but_fixme_should_propagate_errors(sample_count); - double width_scale = static_cast(frame_inner_rect().width()) / buffer.size(); + m_track_manager.current_track()->write_cached_signal_to(m_samples.span()); + double width_scale = static_cast(frame_inner_rect().width()) / m_samples.size(); - auto const maximum = Audio::Sample::max_range(buffer.span()); + auto const maximum = Audio::Sample::max_range(m_samples.span()); int prev_x = 0; - int prev_y_left = sample_to_y(buffer[0].left, maximum.left); - int prev_y_right = sample_to_y(buffer[0].right, maximum.right); + int prev_y_left = sample_to_y(m_samples[0].left, maximum.left); + int prev_y_right = sample_to_y(m_samples[0].right, maximum.right); painter.set_pixel({ prev_x, prev_y_left }, left_wave_color); painter.set_pixel({ prev_x, prev_y_right }, right_wave_color); - for (size_t x = 1; x < buffer.size(); ++x) { - int y_left = sample_to_y(buffer[x].left, maximum.left); - int y_right = sample_to_y(buffer[x].right, maximum.right); + for (size_t x = 1; x < m_samples.size(); ++x) { + int y_left = sample_to_y(m_samples[x].left, maximum.left); + int y_right = sample_to_y(m_samples[x].right, maximum.right); Gfx::IntPoint point1_left(prev_x * width_scale, prev_y_left); Gfx::IntPoint point2_left(x * width_scale, y_left); diff --git a/Userland/Applications/Piano/WaveWidget.h b/Userland/Applications/Piano/WaveWidget.h index 3440f5be89..3b56d5de3e 100644 --- a/Userland/Applications/Piano/WaveWidget.h +++ b/Userland/Applications/Piano/WaveWidget.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include @@ -18,6 +19,12 @@ class WaveWidget final : public GUI::Frame { public: virtual ~WaveWidget() override = default; + ErrorOr set_sample_size(size_t sample_size) + { + TRY(m_samples.try_resize(sample_size)); + return {}; + } + private: // Scales the sample-y value down by a bit, so that it doesn't look like it is clipping. static constexpr float rescale_factor = 1.2f; @@ -29,4 +36,5 @@ private: int sample_to_y(float sample, float sample_max) const; TrackManager& m_track_manager; + Vector m_samples; }; diff --git a/Userland/Libraries/LibDSP/Track.cpp b/Userland/Libraries/LibDSP/Track.cpp index 2e2d784efe..4e0deb6446 100644 --- a/Userland/Libraries/LibDSP/Track.cpp +++ b/Userland/Libraries/LibDSP/Track.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace DSP { @@ -64,6 +65,12 @@ bool NoteTrack::check_processor_chain_valid() const ErrorOr Track::resize_internal_buffers_to(size_t buffer_size) { m_secondary_sample_buffer = TRY(FixedArray::create(buffer_size)); + FixedArray cache = TRY(FixedArray::create(buffer_size)); + bool false_variable = false; + while (!m_sample_lock.compare_exchange_strong(false_variable, true)) + usleep(1); + m_cached_sample_buffer.swap(cache); + m_sample_lock.store(false); return {}; } @@ -92,6 +99,25 @@ void Track::current_signal(FixedArray& output_signal) VERIFY(output_signal.size() == source_signal->get>().size()); // The last processor is the fixed mastering processor. This can write directly to the output data. We also just trust this processor that it does the right thing :^) m_track_mastering->process_to_fixed_array(*source_signal, output_signal); + + bool false_variable = false; + if (m_sample_lock.compare_exchange_strong(false_variable, true)) { + AK::TypedTransfer::copy(m_cached_sample_buffer.data(), output_signal.data(), m_cached_sample_buffer.size()); + m_sample_lock.store(false); + } +} + +void Track::write_cached_signal_to(Span output_signal) +{ + bool false_variable = false; + while (!m_sample_lock.compare_exchange_strong(false_variable, true)) { + usleep(1); + } + VERIFY(output_signal.size() == m_cached_sample_buffer.size()); + + AK::TypedTransfer::copy(output_signal.data(), m_cached_sample_buffer.data(), m_cached_sample_buffer.size()); + + m_sample_lock.store(false); } void NoteTrack::compute_current_clips_signal() diff --git a/Userland/Libraries/LibDSP/Track.h b/Userland/Libraries/LibDSP/Track.h index b75d59dba5..372aef0819 100644 --- a/Userland/Libraries/LibDSP/Track.h +++ b/Userland/Libraries/LibDSP/Track.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -31,6 +32,8 @@ public: // Creates the current signal of the track by processing current note or audio data through the processing chain. void current_signal(FixedArray& output_signal); + void write_cached_signal_to(Span output_signal); + // We are informed of an audio buffer size change. This happens off-audio-thread so we can allocate. ErrorOr resize_internal_buffers_to(size_t buffer_size); @@ -66,6 +69,11 @@ protected: Signal m_secondary_sample_buffer { FixedArray {} }; // A note buffer possibly used by the processor chain. Signal m_secondary_note_buffer { RollNotes {} }; + +private: + Atomic m_sample_lock; + + FixedArray m_cached_sample_buffer = {}; }; class NoteTrack final : public Track {