diff options
author | Arne Elster <arne@elster.li> | 2022-01-09 22:46:06 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-14 22:45:05 +0100 |
commit | 9edaa033e59e40bc8ac8084e52b2801a1d938243 (patch) | |
tree | 59c5dbbd90c6b18973bc288c31fc97f28bad347b /Userland | |
parent | 02462b606842852bdced42d1d7495173f613c352 (diff) | |
download | serenity-9edaa033e59e40bc8ac8084e52b2801a1d938243.zip |
SoundPlayer: Auto refresh visualization widgets
Visualization widgets should only have to tell how many samples they
need per frame and have a render method which receives all data relevant
to draw the next frame.
Diffstat (limited to 'Userland')
7 files changed, 115 insertions, 107 deletions
diff --git a/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.cpp b/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.cpp index 2e69a68b00..888c555257 100644 --- a/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.cpp +++ b/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.cpp @@ -57,7 +57,3 @@ void AlbumCoverVisualizationWidget::start_new_file(StringView filename) else m_album_cover = album_cover_or_error.value(); } - -void AlbumCoverVisualizationWidget::set_buffer(RefPtr<Audio::Buffer>) -{ -} diff --git a/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.h b/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.h index 9cc54c5d0b..29fa5b5c6e 100644 --- a/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.h +++ b/Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.h @@ -16,10 +16,10 @@ class AlbumCoverVisualizationWidget final : public VisualizationWidget { public: ~AlbumCoverVisualizationWidget() override = default; - void set_buffer(RefPtr<Audio::Buffer>) override; void start_new_file(StringView) override; private: + void render(GUI::PaintEvent&, FixedArray<double> const&) override { } void paint_event(GUI::PaintEvent&) override; AlbumCoverVisualizationWidget() = default; ErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_album_cover(StringView const filename); diff --git a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp index 648e141fa1..09356b785b 100644 --- a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp +++ b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp @@ -13,9 +13,7 @@ #include <LibGUI/Painter.h> #include <LibGUI/Window.h> -u32 round_previous_power_of_2(u32 x); - -void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event) +void BarsVisualizationWidget::render(GUI::PaintEvent& event, FixedArray<double> const& samples) { GUI::Frame::paint_event(event); GUI::Painter painter(*this); @@ -23,13 +21,13 @@ void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event) painter.add_clip_rect(event.rect()); painter.fill_rect(frame_inner_rect(), Color::Black); - if (m_sample_buffer.is_empty()) - return; + for (size_t i = 0; i < samples.size(); i++) + m_fft_samples[i] = samples[i]; - LibDSP::fft(m_sample_buffer.span(), false); - double max = AK::sqrt(m_sample_count * 2.); + LibDSP::fft(m_fft_samples.span(), false); + double max = AK::sqrt(samples.size() * 2.); - double freq_bin = m_samplerate / (double)m_sample_count; + double freq_bin = m_samplerate / (double)samples.size(); constexpr int group_count = 60; Vector<double, group_count> groups; @@ -42,9 +40,9 @@ void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event) for (double& d : groups) d = 0.; - int bins_per_group = ceil_div((m_sample_count - 1) / 2, group_count); - for (int i = 1; i < m_sample_count / 2; i++) { - groups[i / bins_per_group] += AK::fabs(m_sample_buffer.data()[i].real()); + int bins_per_group = ceil_div((samples.size() - 1) / 2, static_cast<size_t>(group_count)); + for (size_t i = 1; i < samples.size() / 2; i++) { + groups[i / bins_per_group] += AK::abs(m_fft_samples[i].real()); } for (int i = 0; i < group_count; i++) groups[i] /= max * freq_bin / (m_adjust_frequencies ? (clamp(AK::exp((double)i / group_count * 3.) - 1.75, 1., 15.)) : 1.); @@ -67,7 +65,8 @@ void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event) } BarsVisualizationWidget::BarsVisualizationWidget() - : m_is_using_last(false) + : m_fft_samples(MUST(FixedArray<Complex<double>>::try_create(128))) + , m_is_using_last(false) , m_adjust_frequencies(true) { m_context_menu = GUI::Menu::construct(); @@ -76,54 +75,11 @@ BarsVisualizationWidget::BarsVisualizationWidget() }); frequency_energy_action->set_checked(true); m_context_menu->add_action(frequency_energy_action); -} -// black magic from Hacker's delight -u32 round_previous_power_of_2(u32 x) -{ - x = x | (x >> 1); - x = x | (x >> 2); - x = x | (x >> 4); - x = x | (x >> 8); - x = x | (x >> 16); - return x - (x >> 1); -} - -void BarsVisualizationWidget::set_buffer(RefPtr<Audio::Buffer> buffer, int samples_to_use) -{ - if (m_is_using_last) - return; - m_is_using_last = true; - // FIXME: We should dynamically adapt to the sample count and e.g. perform the fft over multiple buffers. - // For now, the visualizer doesn't work with extremely low global sample rates. - if (buffer->sample_count() < 256) { - m_is_using_last = false; - return; - } - m_sample_count = round_previous_power_of_2(samples_to_use); - m_sample_buffer.resize(m_sample_count); - for (int i = 0; i < m_sample_count; i++) { - m_sample_buffer.data()[i] = (AK::fabs(buffer->samples()[i].left) + AK::fabs(buffer->samples()[i].right)) / 2.; - } - - update(); -} - -void BarsVisualizationWidget::set_buffer(RefPtr<Audio::Buffer> buffer) -{ - if (buffer.is_null()) - return; - if (m_last_id == buffer->id()) - return; - set_buffer(buffer, buffer->sample_count()); + MUST(set_render_sample_count(128)); } void BarsVisualizationWidget::context_menu_event(GUI::ContextMenuEvent& event) { m_context_menu->popup(event.screen_position()); } - -void BarsVisualizationWidget::set_samplerate(int samplerate) -{ - m_samplerate = samplerate; -} diff --git a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h index c0b7aa6062..f6e903b072 100644 --- a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h +++ b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h @@ -9,6 +9,7 @@ #include "VisualizationWidget.h" #include <AK/Complex.h> +#include <AK/FixedArray.h> #include <LibAudio/Buffer.h> #include <LibGUI/Frame.h> @@ -17,21 +18,15 @@ class BarsVisualizationWidget final : public VisualizationWidget { public: ~BarsVisualizationWidget() override = default; - void set_buffer(RefPtr<Audio::Buffer> buffer) override; - void set_samplerate(int samplerate) override; private: BarsVisualizationWidget(); - void set_buffer(RefPtr<Audio::Buffer> buffer, int samples_to_use); - void paint_event(GUI::PaintEvent&) override; + void render(GUI::PaintEvent&, FixedArray<double> const&) override; void context_menu_event(GUI::ContextMenuEvent& event) override; - Vector<Complex<double>> m_sample_buffer; + FixedArray<Complex<double>> m_fft_samples; Vector<int> m_gfx_falling_bars; - int m_last_id; - int m_sample_count; - int m_samplerate; bool m_is_using_last; bool m_adjust_frequencies; RefPtr<GUI::Menu> m_context_menu; diff --git a/Userland/Applications/SoundPlayer/SampleWidget.cpp b/Userland/Applications/SoundPlayer/SampleWidget.cpp index 47d2dfe46c..3abf2eb886 100644 --- a/Userland/Applications/SoundPlayer/SampleWidget.cpp +++ b/Userland/Applications/SoundPlayer/SampleWidget.cpp @@ -10,7 +10,12 @@ #include <LibAudio/Buffer.h> #include <LibGUI/Painter.h> -void SampleWidget::paint_event(GUI::PaintEvent& event) +SampleWidget::SampleWidget() +{ + MUST(set_render_sample_count(512)); +} + +void SampleWidget::render(GUI::PaintEvent& event, FixedArray<double> const& samples) { GUI::Frame::paint_event(event); GUI::Painter painter(*this); @@ -18,38 +23,26 @@ void SampleWidget::paint_event(GUI::PaintEvent& event) painter.add_clip_rect(event.rect()); painter.fill_rect(frame_inner_rect(), Color::Black); - float sample_max = 0; - int count = 0; int x_offset = frame_inner_rect().x(); int x = x_offset; int y_offset = frame_inner_rect().center().y(); - if (m_buffer) { - int samples_per_pixel = m_buffer->sample_count() / frame_inner_rect().width(); - for (int sample_index = 0; sample_index < m_buffer->sample_count() && (x - x_offset) < frame_inner_rect().width(); ++sample_index) { - float sample = AK::fabs((float)m_buffer->samples()[sample_index].left); - - sample_max = max(sample, sample_max); - ++count; + if (samples.size() > 0) { + float samples_per_pixel = samples.size() / static_cast<float>(frame_inner_rect().width()); + float sample_index = 0; - if (count >= samples_per_pixel) { - Gfx::IntPoint min_point = { x, y_offset + static_cast<int>(-sample_max * frame_inner_rect().height() / 2) }; - Gfx::IntPoint max_point = { x++, y_offset + static_cast<int>(sample_max * frame_inner_rect().height() / 2) }; - painter.draw_line(min_point, max_point, Color::Green); + while (sample_index < samples.size()) { + float sample = AK::abs(samples[sample_index]); + for (size_t i = 1; i < static_cast<size_t>(samples_per_pixel + 0.5f); i++) + sample = max(sample, AK::abs(samples[sample_index])); - count = 0; - sample_max = 0; - } + Gfx::IntPoint min_point = { x, y_offset + static_cast<int>(-sample * frame_inner_rect().height() / 2) }; + Gfx::IntPoint max_point = { x, y_offset + static_cast<int>(sample * frame_inner_rect().height() / 2) }; + painter.draw_line(min_point, max_point, Color::Green); + sample_index += samples_per_pixel; + x++; } } else { painter.draw_line({ x, y_offset }, { frame_inner_rect().width(), y_offset }, Color::Green); } } - -void SampleWidget::set_buffer(RefPtr<Audio::Buffer> buffer) -{ - if (m_buffer == buffer) - return; - m_buffer = buffer; - update(); -} diff --git a/Userland/Applications/SoundPlayer/SampleWidget.h b/Userland/Applications/SoundPlayer/SampleWidget.h index e3da71d291..019880b966 100644 --- a/Userland/Applications/SoundPlayer/SampleWidget.h +++ b/Userland/Applications/SoundPlayer/SampleWidget.h @@ -10,20 +10,12 @@ #include "VisualizationWidget.h" #include <LibGUI/Frame.h> -namespace Audio { -class Buffer; -} - class SampleWidget final : public VisualizationWidget { C_OBJECT(SampleWidget) public: virtual ~SampleWidget() override = default; - void set_buffer(RefPtr<Audio::Buffer>) override; - private: - SampleWidget() = default; - virtual void paint_event(GUI::PaintEvent&) override; - - RefPtr<Audio::Buffer> m_buffer; + SampleWidget(); + virtual void render(GUI::PaintEvent&, FixedArray<double> const& samples) override; }; diff --git a/Userland/Applications/SoundPlayer/VisualizationWidget.h b/Userland/Applications/SoundPlayer/VisualizationWidget.h index da28b02a77..e11f233ab9 100644 --- a/Userland/Applications/SoundPlayer/VisualizationWidget.h +++ b/Userland/Applications/SoundPlayer/VisualizationWidget.h @@ -6,18 +6,94 @@ #pragma once +#include <AK/Forward.h> +#include <AK/TypedTransfer.h> #include <LibAudio/Buffer.h> #include <LibGUI/Frame.h> +#include <LibGUI/Painter.h> class VisualizationWidget : public GUI::Frame { C_OBJECT(VisualizationWidget) public: - virtual void set_buffer(RefPtr<Audio::Buffer> buffer) = 0; - virtual void set_samplerate(int) { } + virtual void render(GUI::PaintEvent&, FixedArray<double> const& samples) = 0; + + void set_buffer(RefPtr<Audio::Buffer> buffer) + { + if (buffer.is_null()) + return; + if (buffer->id() == m_last_buffer_id) + return; + m_last_buffer_id = buffer->id(); + + if (m_sample_buffer.size() != static_cast<size_t>(buffer->sample_count())) + m_sample_buffer.resize(buffer->sample_count()); + + for (size_t i = 0; i < static_cast<size_t>(buffer->sample_count()); i++) + m_sample_buffer.data()[i] = (buffer->samples()[i].left + buffer->samples()[i].right) / 2.; + + m_frame_count = 0; + } + + virtual void set_samplerate(int samplerate) + { + m_samplerate = samplerate; + } + + virtual void paint_event(GUI::PaintEvent& event) override + { + if (m_sample_buffer.size() == 0) { + Frame::paint_event(event); + GUI::Painter painter(*this); + painter.add_clip_rect(event.rect()); + painter.fill_rect(frame_inner_rect(), Color::Black); + return; + } + + if (m_render_buffer.size() == 0) + return; + + size_t buffer_position = (m_frame_count * REFRESH_TIME_MILLISECONDS) * m_samplerate / 1000; + if (buffer_position + m_render_buffer.size() >= m_sample_buffer.size()) + buffer_position = m_sample_buffer.size() - m_render_buffer.size(); + + AK::TypedTransfer<double>::copy(m_render_buffer.data(), m_sample_buffer.span().slice(buffer_position).data(), m_render_buffer.size()); + + render(event, m_render_buffer); + } + + virtual void timer_event(Core::TimerEvent&) override + { + update(); + m_frame_count++; + } + + size_t frame_count() const { return m_frame_count; } + + ErrorOr<void> set_render_sample_count(size_t count) + { + auto new_buffer = TRY(FixedArray<double>::try_create(count)); + m_render_buffer.swap(new_buffer); + return {}; + } virtual void start_new_file(StringView) { } protected: - VisualizationWidget() = default; + int m_samplerate; + int m_last_buffer_id; + size_t m_frame_count; + Vector<double> m_sample_buffer; + FixedArray<double> m_render_buffer; + + static constexpr size_t REFRESH_TIME_MILLISECONDS = 30; + + VisualizationWidget() + : m_samplerate(-1) + , m_last_buffer_id(-1) + , m_frame_count(0) + { + start_timer(REFRESH_TIME_MILLISECONDS); + } + virtual ~VisualizationWidget() = default; }; |