1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 09:38:11 +00:00
serenity/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp
kleines Filmröllchen 22b836dd7b Userland: Two low-sample rate fixes
1) The Sound Player visualizer couldn't deal with small sample buffers,
   which occur on low sample rates. Now, it simply doesn't update its
buffer, meaning the display is broken on low sample rates. I'm not too
familiar with the visualizer to figure out a proper fix for now, but
this mitigates the issue (and "normal" sample rates still work).
2) Piano wouldn't buffer enough samples for small sample rates, so the
   sample count per buffer is now increased to 2^12, introducing minor
amounts of (acceptable) lag.
2021-08-27 23:35:27 +04:30

130 lines
4.3 KiB
C++

/*
* Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "BarsVisualizationWidget.h"
#include "AudioAlgorithms.h"
#include <AK/Math.h>
#include <LibGUI/Event.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGUI/Window.h>
u32 round_previous_power_of_2(u32 x);
void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event)
{
GUI::Frame::paint_event(event);
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.fill_rect(frame_inner_rect(), Color::Black);
if (m_sample_buffer.is_empty())
return;
fft(m_sample_buffer, false);
double max = AK::sqrt(m_sample_count * 2.);
double freq_bin = m_samplerate / m_sample_count;
constexpr int group_count = 60;
Vector<double, group_count> groups;
groups.resize(group_count);
if (m_gfx_falling_bars.size() != group_count) {
m_gfx_falling_bars.resize(group_count);
for (int& i : m_gfx_falling_bars)
i = 0;
}
for (double& d : groups)
d = 0.;
int bins_per_group = ceil_div((m_sample_count - 1) / 2, group_count) * freq_bin;
for (int i = 1; i < m_sample_count / 2; i++) {
groups[(i * freq_bin) / bins_per_group] += AK::fabs(m_sample_buffer.data()[i].real());
}
for (int i = 0; i < group_count; i++)
groups[i] /= max * freq_bin / (m_adjust_frequencies ? (clamp(AK::pow(AK::E<double>, (double)i / group_count * 3.) - 1.75, 1., 15.)) : 1.);
const int horizontal_margin = 30;
const int top_vertical_margin = 15;
const int pixels_inbetween_groups = frame_inner_rect().width() > 350 ? 5 : 2;
int pixel_per_group_width = (frame_inner_rect().width() - horizontal_margin * 2 - pixels_inbetween_groups * (group_count - 1)) / group_count;
int max_height = frame_inner_rect().height() - top_vertical_margin;
int current_xpos = horizontal_margin;
for (int g = 0; g < group_count; g++) {
m_gfx_falling_bars[g] = AK::min(clamp(max_height - (int)(groups[g] * max_height * 0.8), 0, max_height), m_gfx_falling_bars[g]);
painter.fill_rect(Gfx::Rect(current_xpos, max_height - (int)(groups[g] * max_height * 0.8), pixel_per_group_width, (int)(groups[g] * max_height * 0.8)), Gfx::Color::from_rgb(0x95d437));
painter.fill_rect(Gfx::Rect(current_xpos, m_gfx_falling_bars[g], pixel_per_group_width, 2), Gfx::Color::White);
current_xpos += pixel_per_group_width + pixels_inbetween_groups;
m_gfx_falling_bars[g] += 3;
}
m_is_using_last = false;
}
BarsVisualizationWidget::~BarsVisualizationWidget()
{
}
BarsVisualizationWidget::BarsVisualizationWidget()
: m_last_id(-1)
, m_is_using_last(false)
, m_adjust_frequencies(false)
{
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::Action::create_checkable("Adjust frequency energy (for aesthetics)", [&](GUI::Action& action) {
m_adjust_frequencies = action.is_checked();
}));
}
// 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)
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());
}
void BarsVisualizationWidget::context_menu_event(GUI::ContextMenuEvent& event)
{
m_context_menu->popup(event.screen_position());
}
void BarsVisualizationWidget::set_samplerate(int samplerate)
{
m_samplerate = samplerate;
}