Serenity Operating System
at hosted 193 lines 7.3 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "SoundPlayerWidget.h" 28#include <AK/StringBuilder.h> 29#include <LibGUI/BoxLayout.h> 30#include <LibGUI/Button.h> 31#include <LibGUI/Label.h> 32#include <LibGUI/MessageBox.h> 33#include <LibM/math.h> 34 35SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, NonnullRefPtr<Audio::ClientConnection> connection) 36 : m_window(window) 37 , m_connection(connection) 38 , m_manager(connection) 39{ 40 set_fill_with_background_color(true); 41 set_layout<GUI::VerticalBoxLayout>(); 42 layout()->set_margins({ 2, 2, 2, 2 }); 43 44 auto& status_widget = add<GUI::Widget>(); 45 status_widget.set_fill_with_background_color(true); 46 status_widget.set_layout<GUI::HorizontalBoxLayout>(); 47 48 m_elapsed = status_widget.add<GUI::Label>(); 49 m_elapsed->set_frame_shape(Gfx::FrameShape::Container); 50 m_elapsed->set_frame_shadow(Gfx::FrameShadow::Sunken); 51 m_elapsed->set_frame_thickness(2); 52 m_elapsed->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 53 m_elapsed->set_preferred_size(80, 0); 54 55 auto& sample_widget_container = status_widget.add<GUI::Widget>(); 56 sample_widget_container.set_layout<GUI::HorizontalBoxLayout>(); 57 sample_widget_container.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill); 58 59 m_sample_widget = sample_widget_container.add<SampleWidget>(); 60 61 m_remaining = status_widget.add<GUI::Label>(); 62 m_remaining->set_frame_shape(Gfx::FrameShape::Container); 63 m_remaining->set_frame_shadow(Gfx::FrameShadow::Sunken); 64 m_remaining->set_frame_thickness(2); 65 m_remaining->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 66 m_remaining->set_preferred_size(80, 0); 67 68 m_slider = add<Slider>(Orientation::Horizontal); 69 m_slider->set_min(0); 70 m_slider->set_enabled(false); 71 m_slider->on_knob_released = [&](int value) { m_manager.seek(denormalize_rate(value)); }; 72 73 auto& control_widget = add<GUI::Widget>(); 74 control_widget.set_fill_with_background_color(true); 75 control_widget.set_layout<GUI::HorizontalBoxLayout>(); 76 control_widget.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 77 control_widget.set_preferred_size(0, 30); 78 control_widget.layout()->set_margins({ 10, 2, 10, 2 }); 79 control_widget.layout()->set_spacing(10); 80 81 m_play = control_widget.add<GUI::Button>(); 82 m_play->set_icon(*m_pause_icon); 83 m_play->set_enabled(false); 84 m_play->on_click = [this] { 85 m_play->set_icon(m_manager.toggle_pause() ? *m_play_icon : *m_pause_icon); 86 }; 87 88 m_stop = control_widget.add<GUI::Button>(); 89 m_stop->set_enabled(false); 90 m_stop->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png")); 91 m_stop->on_click = [this] { m_manager.stop(); }; 92 93 m_status = add<GUI::Label>(); 94 m_status->set_frame_shape(Gfx::FrameShape::Box); 95 m_status->set_frame_shadow(Gfx::FrameShadow::Raised); 96 m_status->set_frame_thickness(4); 97 m_status->set_text_alignment(Gfx::TextAlignment::CenterLeft); 98 m_status->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 99 m_status->set_preferred_size(0, 18); 100 m_status->set_text("No file open!"); 101 102 update_position(0); 103 104 m_manager.on_update = [&]() { update_ui(); }; 105} 106 107SoundPlayerWidget::~SoundPlayerWidget() 108{ 109} 110 111SoundPlayerWidget::Slider::~Slider() 112{ 113} 114 115void SoundPlayerWidget::hide_scope(bool hide) 116{ 117 m_sample_widget->set_visible(!hide); 118} 119 120void SoundPlayerWidget::open_file(String path) 121{ 122 if (!path.ends_with(".wav")) { 123 GUI::MessageBox::show("Selected file is not a \".wav\" file!", "Filetype error", GUI::MessageBox::Type::Error); 124 return; 125 } 126 127 OwnPtr<Audio::WavLoader> loader = make<Audio::WavLoader>(path); 128 if (loader->has_error()) { 129 GUI::MessageBox::show( 130 String::format( 131 "Failed to load WAV file: %s (%s)", 132 path.characters(), 133 loader->error_string()), 134 "Filetype error", GUI::MessageBox::Type::Error); 135 return; 136 } 137 138 m_sample_ratio = PLAYBACK_MANAGER_RATE / static_cast<float>(loader->sample_rate()); 139 140 m_slider->set_max(normalize_rate(static_cast<int>(loader->total_samples()))); 141 m_slider->set_enabled(true); 142 m_play->set_enabled(true); 143 m_stop->set_enabled(true); 144 145 m_window.set_title(String::format("%s - SoundPlayer", loader->file()->filename().characters())); 146 m_status->set_text(String::format( 147 "Sample rate %uHz, %u %s, %u bits per sample", 148 loader->sample_rate(), 149 loader->num_channels(), 150 (loader->num_channels() == 1) ? "channel" : "channels", 151 loader->bits_per_sample())); 152 153 m_manager.set_loader(move(loader)); 154 update_position(0); 155} 156 157int SoundPlayerWidget::normalize_rate(int rate) const 158{ 159 return static_cast<int>(rate * m_sample_ratio); 160} 161 162int SoundPlayerWidget::denormalize_rate(int rate) const 163{ 164 return static_cast<int>(rate / m_sample_ratio); 165} 166 167void SoundPlayerWidget::update_ui() 168{ 169 m_sample_widget->set_buffer(m_manager.current_buffer()); 170 m_play->set_icon(m_manager.is_paused() ? *m_play_icon : *m_pause_icon); 171 update_position(m_manager.connection()->get_played_samples()); 172} 173 174void SoundPlayerWidget::update_position(const int position) 175{ 176 int total_norm_samples = position + normalize_rate(m_manager.last_seek()); 177 float seconds = (total_norm_samples / static_cast<float>(PLAYBACK_MANAGER_RATE)); 178 float remaining_seconds = m_manager.total_length() - seconds; 179 180 m_elapsed->set_text(String::format( 181 "Elapsed:\n%u:%02u.%02u", 182 static_cast<int>(seconds / 60), 183 static_cast<int>(seconds) % 60, 184 static_cast<int>(seconds * 100) % 100)); 185 186 m_remaining->set_text(String::format( 187 "Remaining:\n%u:%02u.%02u", 188 static_cast<int>(remaining_seconds / 60), 189 static_cast<int>(remaining_seconds) % 60, 190 static_cast<int>(remaining_seconds * 100) % 100)); 191 192 m_slider->set_value(total_norm_samples); 193}