summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorArne Elster <arne@elster.li>2022-01-09 22:46:06 +0100
committerAndreas Kling <kling@serenityos.org>2022-03-14 22:45:05 +0100
commit9edaa033e59e40bc8ac8084e52b2801a1d938243 (patch)
tree59c5dbbd90c6b18973bc288c31fc97f28bad347b /Userland
parent02462b606842852bdced42d1d7495173f613c352 (diff)
downloadserenity-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')
-rw-r--r--Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.cpp4
-rw-r--r--Userland/Applications/SoundPlayer/AlbumCoverVisualizationWidget.h2
-rw-r--r--Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp68
-rw-r--r--Userland/Applications/SoundPlayer/BarsVisualizationWidget.h11
-rw-r--r--Userland/Applications/SoundPlayer/SampleWidget.cpp43
-rw-r--r--Userland/Applications/SoundPlayer/SampleWidget.h12
-rw-r--r--Userland/Applications/SoundPlayer/VisualizationWidget.h82
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;
};