diff options
author | Cesar Torres <shortanemoia@protonmail.com> | 2021-03-21 12:50:58 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-27 10:20:55 +0100 |
commit | f6f2f67c56bbdf9aa8e15e7345d199e293becd8d (patch) | |
tree | 2288f107bf8576a9965851d55f6f632aadf980b0 /Userland/Applications | |
parent | b02f01843ae8c14647d2037c8c4bc4c975ca0162 (diff) | |
download | serenity-f6f2f67c56bbdf9aa8e15e7345d199e293becd8d.zip |
SoundPlayer: Add a 'Bars' audio visualization
Diffstat (limited to 'Userland/Applications')
-rw-r--r-- | Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp | 148 | ||||
-rw-r--r-- | Userland/Applications/SoundPlayer/BarsVisualizationWidget.h | 55 |
2 files changed, 203 insertions, 0 deletions
diff --git a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp new file mode 100644 index 0000000000..bb19713241 --- /dev/null +++ b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "BarsVisualizationWidget.h" +#include "AudioAlgorithms.h" +#include <AK/Complex.h> +#include <LibGUI/Event.h> +#include <LibGUI/Menu.h> +#include <LibGUI/Painter.h> +#include <LibGUI/Window.h> +#include <math.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_null() || m_sample_buffer.is_empty()) + return; + + fft(m_sample_buffer, false); + double max = sqrt(m_sample_count * 2); + + //TODO: don't hardcode this! + double freq_bin = 44100 / 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] += fabs(m_sample_buffer.data()[i].real()); + } + for (int i = 0; i < group_count; i++) + groups[i] /= max * freq_bin / (m_adjust_frequencies ? (clamp(pow(M_E, (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 = 5; + 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; + VERIFY(buffer->sample_count() >= 256); + 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] = (fabs(buffer->samples()[i].left) + 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::mousedown_event(GUI::MouseEvent& event) +{ + Widget::mousedown_event(event); + if (event.button() == GUI::Right) { + m_context_menu->popup(event.position()); + } +} + diff --git a/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h new file mode 100644 index 0000000000..914c2a115c --- /dev/null +++ b/Userland/Applications/SoundPlayer/BarsVisualizationWidget.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "VisualizationBase.h" +#include <AK/Complex.h> +#include <LibAudio/Buffer.h> +#include <LibGUI/Frame.h> + +class BarsVisualizationWidget final : public GUI::Frame + , public Visualization { + C_OBJECT(BarsVisualizationWidget) + +public: + ~BarsVisualizationWidget() override; + void set_buffer(RefPtr<Audio::Buffer> buffer) override; + +private: + void set_buffer(RefPtr<Audio::Buffer> buffer, int samples_to_use); + BarsVisualizationWidget(); + void paint_event(GUI::PaintEvent&) override; + void mousedown_event(GUI::MouseEvent& event) override; + + Vector<Complex<double>> m_sample_buffer; + Vector<int> m_gfx_falling_bars; + int m_last_id; + int m_sample_count; + bool m_is_using_last; + bool m_adjust_frequencies; + RefPtr<GUI::Menu> m_context_menu; +}; |