diff options
author | Cesar Torres <shortanemoia@protonmail.com> | 2021-03-21 13:01:33 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-27 10:20:55 +0100 |
commit | 7aa52978359c721cb283bab786a36943dc5b3e20 (patch) | |
tree | f5ef8cc2ae35d0957bea660d15c96c893bf20a45 /Userland/Applications/SoundPlayer | |
parent | 45e928bfb294e43cd54bb75567c8ce3bf0645662 (diff) | |
download | serenity-7aa52978359c721cb283bab786a36943dc5b3e20.zip |
SoundPlayer: Update the SoundPlayer interface
Also fix rebase conflict
Diffstat (limited to 'Userland/Applications/SoundPlayer')
5 files changed, 395 insertions, 59 deletions
diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidget.cpp b/Userland/Applications/SoundPlayer/SoundPlayerWidget.cpp index f2066aca89..59c1932952 100644 --- a/Userland/Applications/SoundPlayer/SoundPlayerWidget.cpp +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidget.cpp @@ -25,19 +25,22 @@ */ #include "SoundPlayerWidget.h" +#include "Common.h" #include <AK/StringBuilder.h> #include <LibCore/MimeData.h> #include <LibGUI/BoxLayout.h> #include <LibGUI/Button.h> #include <LibGUI/Label.h> #include <LibGUI/MessageBox.h> -#include <math.h> -SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, NonnullRefPtr<Audio::ClientConnection> connection) +SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager) : m_window(window) , m_connection(connection) - , m_manager(connection) + , m_manager(manager) { + window.set_resizable(false); + window.resize(350, 140); + set_fill_with_background_color(true); set_layout<GUI::VerticalBoxLayout>(); layout()->set_margins({ 2, 2, 2, 2 }); @@ -104,16 +107,7 @@ SoundPlayerWidget::~SoundPlayerWidget() { } -SoundPlayerWidget::Slider::~Slider() -{ -} - -void SoundPlayerWidget::hide_scope(bool hide) -{ - m_sample_widget->set_visible(!hide); -} - -void SoundPlayerWidget::open_file(String path) +void SoundPlayerWidget::open_file(StringView path) { NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); if (loader->has_error() || !loader->sample_rate()) { @@ -192,3 +186,8 @@ void SoundPlayerWidget::update_position(const int position) m_slider->set_value(total_norm_samples); } + +void SoundPlayerWidget::hide_scope(bool hide) +{ + m_sample_widget->set_visible(!hide); +} diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidget.h b/Userland/Applications/SoundPlayer/SoundPlayerWidget.h index ab7bc60aea..9f01a552ee 100644 --- a/Userland/Applications/SoundPlayer/SoundPlayerWidget.h +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidget.h @@ -26,24 +26,28 @@ #pragma once +#include "Common.h" #include "PlaybackManager.h" +#include "Player.h" #include "SampleWidget.h" +#include <AK/NonnullRefPtr.h> #include <LibGUI/Button.h> #include <LibGUI/Label.h> -#include <LibGUI/Slider.h> #include <LibGUI/Widget.h> #include <LibGUI/Window.h> -class SoundPlayerWidget final : public GUI::Widget { +class SoundPlayerWidget final : public GUI::Widget + , public Player { C_OBJECT(SoundPlayerWidget) public: virtual ~SoundPlayerWidget() override; - void open_file(String path); + void open_file(StringView path) override; void hide_scope(bool); - PlaybackManager& manager() { return m_manager; } + Audio::ClientConnection& client_connection() override { return m_connection; } + PlaybackManager& playback_manager() override { return m_manager; } private: - explicit SoundPlayerWidget(GUI::Window&, NonnullRefPtr<Audio::ClientConnection>); + explicit SoundPlayerWidget(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager); virtual void drop_event(GUI::DropEvent&) override; @@ -52,35 +56,10 @@ private: int normalize_rate(int) const; int denormalize_rate(int) const; - class Slider final : public GUI::Slider { - C_OBJECT(Slider) - public: - virtual ~Slider() override; - Function<void(int)> on_knob_released; - void set_value(int value) - { - if (!knob_dragging()) - GUI::Slider::set_value(value); - } - - protected: - Slider(Orientation orientation) - : GUI::Slider(orientation) - { - } - - virtual void mouseup_event(GUI::MouseEvent& event) override - { - if (on_knob_released && is_enabled()) - on_knob_released(value()); - - GUI::Slider::mouseup_event(event); - } - }; - GUI::Window& m_window; - NonnullRefPtr<Audio::ClientConnection> m_connection; - PlaybackManager m_manager; + Audio::ClientConnection& m_connection; + PlaybackManager& m_manager; + float m_sample_ratio { 1.0 }; RefPtr<GUI::Label> m_status; RefPtr<GUI::Label> m_elapsed; diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp new file mode 100644 index 0000000000..c7eb311188 --- /dev/null +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp @@ -0,0 +1,200 @@ +/* + * 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 "SoundPlayerWidgetAdvancedView.h" +#include "BarsVisualizationWidget.h" +#include "PlaybackManager.h" +#include "SoundPlayerWidget.h" +#include <AK/SIMD.h> +#include <LibGUI/Action.h> +#include <LibGUI/BoxLayout.h> +#include <LibGUI/Button.h> +#include <LibGUI/DragOperation.h> +#include <LibGUI/Label.h> +#include <LibGUI/MessageBox.h> +#include <LibGUI/Slider.h> +#include <LibGUI/ToolBar.h> +#include <LibGUI/ToolBarContainer.h> +#include <LibGUI/Window.h> +#include <LibGfx/Bitmap.h> + +SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager) + : m_window(window) + , m_connection(connection) + , m_manager(manager) +{ + + window.resize(455, 350); + window.set_minimum_size(440, 130); + window.set_resizable(true); + + set_fill_with_background_color(true); + set_layout<GUI::VerticalBoxLayout>(); + + m_play_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"); + m_pause_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/pause.png"); + m_stop_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png"); + m_back_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"); + m_next_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"); + + m_visualization = add<BarsVisualizationWidget>(); + + m_playback_progress_slider = add<Slider>(Orientation::Horizontal); + m_playback_progress_slider->set_fixed_height(20); + m_playback_progress_slider->set_min(0); + m_playback_progress_slider->set_max(m_manager.total_length() * 44100); //this value should be set when we load a new file + m_playback_progress_slider->on_knob_released = [&](int value) { + m_manager.seek(value); + }; + + auto& toolbar_container = add<GUI::ToolBarContainer>(); + + auto& menubar = toolbar_container.add<GUI::ToolBar>(); + + m_play_button = menubar.add<GUI::Button>(); + m_play_button->set_icon(*m_play_icon); + m_play_button->set_fixed_width(50); + + m_play_button->on_click = [&](unsigned) { + bool paused = m_manager.toggle_pause(); + m_play_button->set_icon(paused ? *m_play_icon : *m_pause_icon); + m_stop_button->set_enabled(!paused); + }; + + m_stop_button = menubar.add<GUI::Button>(); + m_stop_button->set_icon(*m_stop_icon); + m_stop_button->set_fixed_width(50); + m_stop_button->on_click = [&](unsigned) { + m_manager.stop(); + m_play_button->set_icon(*m_play_icon); + m_stop_button->set_enabled(false); + }; + + auto& timestamp_label = menubar.add<GUI::Label>(); + timestamp_label.set_fixed_width(110); + timestamp_label.set_text("Elapsed: 00:00:00"); + + // filler_label + menubar.add<GUI::Label>(); + + auto& back_button = menubar.add<GUI::Button>(); + back_button.set_fixed_width(50); + back_button.set_icon(*m_back_icon); + + auto& next_button = menubar.add<GUI::Button>(); + next_button.set_fixed_width(50); + next_button.set_icon(*m_next_icon); + + m_volume_label = &menubar.add<GUI::Label>(); + m_volume_label->set_fixed_width(30); + m_volume_label->set_text("100%"); + + auto& volume_slider = menubar.add<GUI::HorizontalSlider>(); + volume_slider.set_fixed_width(95); + volume_slider.set_min(0); + volume_slider.set_max(150); + volume_slider.set_value(100); + + volume_slider.on_change = [&](int value) { + double volume = m_nonlinear_volume_slider ? (double)(value * value) / (100 * 100) : value / 100.; + m_volume_label->set_text(String::formatted("{}%", (int)(volume * 100))); + set_volume(volume); + }; + + set_volume(1.); + set_nonlinear_volume_slider(false); + + m_manager.on_update = [&]() { + //TODO: make this program support other sample rates + int samples_played = m_connection.get_played_samples() + m_manager.last_seek(); + int current_second = samples_played / 44100; + timestamp_label.set_text(String::formatted("Elapsed: {:02}:{:02}:{:02}", current_second / 3600, current_second / 60, current_second % 60)); + m_playback_progress_slider->set_value(samples_played); + + dynamic_cast<Visualization*>(m_visualization.ptr())->set_buffer(m_manager.current_buffer()); + }; + + m_manager.on_load_sample_buffer = [&](Audio::Buffer& buffer) { + if (m_volume == 1.) + return; + auto sample_count = buffer.sample_count(); + if (sample_count % 4 == 0) { + const int total_iter = sample_count / (sizeof(AK::SIMD::f64x4) / sizeof(double) / 2); + AK::SIMD::f64x4* sample_ptr = const_cast<AK::SIMD::f64x4*>(reinterpret_cast<const AK::SIMD::f64x4*>((buffer.data()))); + for (int i = 0; i < total_iter; ++i) { + sample_ptr[i] = sample_ptr[i] * m_volume; + } + } else { + const int total_iter = sample_count / (sizeof(AK::SIMD::f64x2) / sizeof(double) / 2); + AK::SIMD::f64x2* sample_ptr = const_cast<AK::SIMD::f64x2*>(reinterpret_cast<const AK::SIMD::f64x2*>((buffer.data()))); + for (int i = 0; i < total_iter; ++i) { + sample_ptr[i] = sample_ptr[i] * m_volume; + } + } + }; +} + +void SoundPlayerWidgetAdvancedView::set_volume(double value) +{ + m_volume = value; +} + +void SoundPlayerWidgetAdvancedView::open_file(StringView path) +{ + NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); + if (loader->has_error() || !loader->sample_rate()) { + const String error_string = loader->error_string(); + GUI::MessageBox::show(&m_window, String::formatted("Failed to load audio file: {} ({})", path, error_string.is_null() ? "Unknown error" : error_string), + "Filetype error", GUI::MessageBox::Type::Error); + return; + } + m_window.set_title(String::formatted("{} - SoundPlayer", loader->file()->filename())); + m_playback_progress_slider->set_max(loader->total_samples()); + m_manager.set_loader(move(loader)); +} + +void SoundPlayerWidgetAdvancedView::set_nonlinear_volume_slider(bool nonlinear) +{ + m_nonlinear_volume_slider = nonlinear; +} + +void SoundPlayerWidgetAdvancedView::drop_event(GUI::DropEvent& event) +{ + event.accept(); + window()->move_to_front(); + + if (event.mime_data().has_urls()) { + auto urls = event.mime_data().urls(); + if (urls.is_empty()) + return; + open_file(urls.first().path()); + } +} + +SoundPlayerWidgetAdvancedView::~SoundPlayerWidgetAdvancedView() +{ + m_manager.on_load_sample_buffer = nullptr; +} diff --git a/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h new file mode 100644 index 0000000000..dfdf71a892 --- /dev/null +++ b/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.h @@ -0,0 +1,85 @@ +/* + * 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 "BarsVisualizationWidget.h" +#include "PlaybackManager.h" +#include "Player.h" +#include "SoundPlayerWidget.h" +#include <AK/NonnullRefPtr.h> +#include <LibAudio/ClientConnection.h> +#include <LibGUI/Widget.h> + +class SoundPlayerWidgetAdvancedView final : public GUI::Widget + , public Player { + C_OBJECT(SoundPlayerWidgetAdvancedView) + +public: + explicit SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager); + ~SoundPlayerWidgetAdvancedView() override; + + void open_file(StringView path) override; + Audio::ClientConnection& client_connection() override { return m_connection; } + PlaybackManager& playback_manager() override { return m_manager; } + + template<typename T> + void set_visualization() + { + m_visualization->remove_from_parent(); + update(); + auto new_visualization = T::construct(); + insert_child_before(new_visualization, *static_cast<Core::Object*>(m_playback_progress_slider.ptr())); + m_visualization = new_visualization; + } + + void set_nonlinear_volume_slider(bool nonlinear); + + void set_volume(double value); + +private: + void drop_event(GUI::DropEvent& event) override; + + GUI::Window& m_window; + Audio::ClientConnection& m_connection; + PlaybackManager& m_manager; + + RefPtr<GUI::Widget> m_visualization; + + RefPtr<Gfx::Bitmap> m_play_icon; + RefPtr<Gfx::Bitmap> m_pause_icon; + RefPtr<Gfx::Bitmap> m_stop_icon; + RefPtr<Gfx::Bitmap> m_back_icon; + RefPtr<Gfx::Bitmap> m_next_icon; + + RefPtr<GUI::Button> m_play_button; + RefPtr<GUI::Button> m_stop_button; + RefPtr<Slider> m_playback_progress_slider; + RefPtr<GUI::Label> m_volume_label; + + double m_volume; + bool m_nonlinear_volume_slider; +}; diff --git a/Userland/Applications/SoundPlayer/main.cpp b/Userland/Applications/SoundPlayer/main.cpp index 7a1946e6fc..a41ec64bec 100644 --- a/Userland/Applications/SoundPlayer/main.cpp +++ b/Userland/Applications/SoundPlayer/main.cpp @@ -24,7 +24,10 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "NoVisualizationWidget.h" +#include "Player.h" #include "SoundPlayerWidget.h" +#include "SoundPlayerWidgetAdvancedView.h" #include <LibAudio/ClientConnection.h> #include <LibGUI/Action.h> #include <LibGUI/Application.h> @@ -52,6 +55,8 @@ int main(int argc, char** argv) auto audio_client = Audio::ClientConnection::construct(); audio_client->handshake(); + PlaybackManager playback_manager(audio_client); + if (pledge("stdio recvfd sendfd accept rpath thread", nullptr) < 0) { perror("pledge"); return 1; @@ -61,31 +66,55 @@ int main(int argc, char** argv) auto window = GUI::Window::construct(); window->set_title("Sound Player"); - window->set_resizable(false); - window->resize(350, 140); window->set_icon(app_icon.bitmap_for_size(16)); auto menubar = GUI::MenuBar::construct(); - auto& app_menu = menubar->add_menu("File"); - auto& player = window->set_main_widget<SoundPlayerWidget>(window, audio_client); + auto& app_menu = menubar->add_menu("File"); + // start in simple view by default + Player* player = &window->set_main_widget<SoundPlayerWidget>(window, audio_client, playback_manager); if (argc > 1) { String path = argv[1]; - player.open_file(path); - player.manager().play(); + player->open_file(path); + player->playback_manager().play(); } - auto hide_scope = GUI::Action::create_checkable("Hide scope", { Mod_Ctrl, Key_H }, [&](auto& action) { - player.hide_scope(action.is_checked()); - }); - app_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) { Optional<String> path = GUI::FilePicker::get_open_filepath(window, "Open sound file..."); if (path.has_value()) { - player.open_file(path.value()); + player->open_file(path.value()); } })); - app_menu.add_action(move(hide_scope)); + + RefPtr<GUI::Action> hide_scope; + + auto advanced_view_check = GUI::Action::create_checkable("Advanced view", { Mod_Ctrl, Key_A }, [&](auto& action) { + window->close(); + if (action.is_checked()) { + player = &window->set_main_widget<SoundPlayerWidgetAdvancedView>(window, audio_client, playback_manager); + hide_scope->set_checkable(false); + } else { + player = &window->set_main_widget<SoundPlayerWidget>(window, audio_client, playback_manager); + hide_scope->set_checkable(true); + } + window->show(); + }); + app_menu.add_action(advanced_view_check); + + hide_scope = GUI::Action::create_checkable("Hide visualization (legacy view)", { Mod_Ctrl, Key_H }, [&](auto& action) { + if (!advanced_view_check->is_checked()) + static_cast<SoundPlayerWidget*>(player)->hide_scope(action.is_checked()); + }); + + auto linear_volume_slider = GUI::Action::create_checkable("Nonlinear volume slider", [&](auto& action) { + if (advanced_view_check->is_checked()) + static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_nonlinear_volume_slider(action.is_checked()); + }); + app_menu.add_action(linear_volume_slider); + + auto ptr_copy = hide_scope; + + app_menu.add_action(ptr_copy.release_nonnull()); app_menu.add_separator(); app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); @@ -94,11 +123,55 @@ int main(int argc, char** argv) auto& playback_menu = menubar->add_menu("Playback"); auto loop = GUI::Action::create_checkable("Loop", { Mod_Ctrl, Key_R }, [&](auto& action) { - player.manager().loop(action.is_checked()); + player->playback_manager().loop(action.is_checked()); }); playback_menu.add_action(move(loop)); + auto& visualization_menu = menubar->add_menu("Visualization"); + Vector<NonnullRefPtr<GUI::Action>> visualization_checkmarks; + GUI::Action* checked_vis = nullptr; + auto uncheck_all_but = [&](GUI::Action& one) {for (auto& a : visualization_checkmarks) if (a != &one) a->set_checked(false); }; + + auto bars = GUI::Action::create_checkable("Bars", [&](auto& action) { + uncheck_all_but(action); + if (checked_vis == &action) { + action.set_checked(true); + return; + } + checked_vis = &action; + static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<BarsVisualizationWidget>(); + }); + bars->set_checked(true); + + visualization_menu.add_action(bars); + visualization_checkmarks.append(bars); + + auto samples = GUI::Action::create_checkable("Samples", [&](auto& action) { + uncheck_all_but(action); + if (checked_vis == &action) { + action.set_checked(true); + return; + } + checked_vis = &action; + static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<SampleWidget>(); + }); + + visualization_menu.add_action(samples); + visualization_checkmarks.append(samples); + + auto none = GUI::Action::create_checkable("None", [&](auto& action) { + uncheck_all_but(action); + if (checked_vis == &action) { + action.set_checked(true); + return; + } + static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<NoVisualizationWidget>(); + }); + + visualization_menu.add_action(none); + visualization_checkmarks.append(none); + auto& help_menu = menubar->add_menu("Help"); help_menu.add_action(GUI::CommonActions::make_about_action("Sound Player", app_icon, window)); |