1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 20:17:44 +00:00

Piano: Add Play/Pause, Forward and Back buttons

Piano now has a toolbar allowing the playback to be paused,
or to be stepped forward or back a note.
This commit is contained in:
JJ Roberts-White 2021-07-11 12:58:17 +10:00 committed by Ali Mohammad Pur
parent 54c005754a
commit 74f1f2b5e2
10 changed files with 234 additions and 69 deletions

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "AudioPlayerLoop.h"
#include "TrackManager.h"
// Converts Piano-internal data to an Audio::Buffer that AudioServer receives
static NonnullRefPtr<Audio::Buffer> music_samples_to_buffer(Array<Sample, sample_count> samples)
{
Vector<Audio::Frame, sample_count> frames;
frames.ensure_capacity(sample_count);
for (auto sample : samples) {
Audio::Frame frame = { sample.left / (double)NumericLimits<i16>::max(), sample.right / (double)NumericLimits<i16>::max() };
frames.unchecked_append(frame);
}
return Audio::Buffer::create_with_samples(frames);
}
AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer)
: m_track_manager(track_manager)
, m_need_to_write_wav(need_to_write_wav)
, m_wav_writer(wav_writer)
{
m_audio_client = Audio::ClientConnection::construct();
m_audio_client->on_finish_playing_buffer = [this](int buffer_id) {
(void)buffer_id;
enqueue_audio();
};
}
void AudioPlayerLoop::enqueue_audio()
{
m_track_manager.fill_buffer(m_buffer);
NonnullRefPtr<Audio::Buffer> audio_buffer = music_samples_to_buffer(m_buffer);
m_audio_client->async_enqueue(audio_buffer);
// FIXME: This should be done somewhere else.
if (m_need_to_write_wav) {
m_need_to_write_wav = false;
m_track_manager.reset();
m_track_manager.set_should_loop(false);
do {
m_track_manager.fill_buffer(m_buffer);
m_wav_writer.write_samples(reinterpret_cast<u8*>(m_buffer.data()), buffer_size);
} while (m_track_manager.time());
m_track_manager.reset();
m_track_manager.set_should_loop(true);
m_wav_writer.finalize();
}
}
void AudioPlayerLoop::toggle_paused()
{
m_should_play_audio = !m_should_play_audio;
m_audio_client->set_paused(!m_should_play_audio);
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Music.h"
#include <LibAudio/ClientConnection.h>
#include <LibAudio/WavWriter.h>
#include <LibCore/Object.h>
class TrackManager;
// Wrapper class accepting custom events to advance the track playing and forward audio data to the system.
// This does not run on a separate thread, preventing IPC multithreading madness.
class AudioPlayerLoop : public Core::Object {
C_OBJECT(AudioPlayerLoop)
public:
AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer);
void enqueue_audio();
void toggle_paused();
bool is_playing() { return m_should_play_audio; }
private:
TrackManager& m_track_manager;
Array<Sample, sample_count> m_buffer;
RefPtr<Audio::ClientConnection> m_audio_client;
bool m_should_play_audio = true;
bool& m_need_to_write_wav;
Audio::WavWriter& m_wav_writer;
};

View file

@ -6,12 +6,14 @@ serenity_component(
)
set(SOURCES
AudioPlayerLoop.cpp
Track.cpp
TrackManager.cpp
KeysWidget.cpp
KnobsWidget.cpp
main.cpp
MainWidget.cpp
PlayerWidget.cpp
RollWidget.cpp
SamplerWidget.cpp
WaveWidget.cpp

View file

@ -8,6 +8,7 @@
#include "MainWidget.h"
#include "KeysWidget.h"
#include "KnobsWidget.h"
#include "PlayerWidget.h"
#include "RollWidget.h"
#include "SamplerWidget.h"
#include "TrackManager.h"
@ -17,8 +18,9 @@
#include <LibGUI/Menu.h>
#include <LibGUI/TabWidget.h>
MainWidget::MainWidget(TrackManager& track_manager)
MainWidget::MainWidget(TrackManager& track_manager, AudioPlayerLoop& loop)
: m_track_manager(track_manager)
, m_audio_loop(loop)
{
set_layout<GUI::VerticalBoxLayout>();
layout()->set_spacing(2);
@ -35,6 +37,8 @@ MainWidget::MainWidget(TrackManager& track_manager)
m_tab_widget->add_tab<SamplerWidget>("Sampler", track_manager);
m_player_widget = add<PlayerWidget>(track_manager, loop);
m_keys_and_knobs_container = add<GUI::Widget>();
m_keys_and_knobs_container->set_layout<GUI::HorizontalBoxLayout>();
m_keys_and_knobs_container->layout()->set_spacing(2);

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -10,12 +11,14 @@
#include "Music.h"
#include <LibGUI/Widget.h>
class AudioPlayerLoop;
class TrackManager;
class WaveWidget;
class RollWidget;
class SamplerWidget;
class KeysWidget;
class KnobsWidget;
class PlayerWidget;
class MainWidget final : public GUI::Widget {
C_OBJECT(MainWidget)
@ -28,7 +31,7 @@ public:
void set_octave_and_ensure_note_change(int);
private:
explicit MainWidget(TrackManager&);
explicit MainWidget(TrackManager&, AudioPlayerLoop&);
virtual void keydown_event(GUI::KeyEvent&) override;
virtual void keyup_event(GUI::KeyEvent&) override;
@ -41,6 +44,7 @@ private:
void turn_on_pressed_keys();
TrackManager& m_track_manager;
AudioPlayerLoop& m_audio_loop;
RefPtr<WaveWidget> m_wave_widget;
RefPtr<RollWidget> m_roll_widget;
@ -49,6 +53,7 @@ private:
RefPtr<GUI::Widget> m_keys_and_knobs_container;
RefPtr<KeysWidget> m_keys_widget;
RefPtr<KnobsWidget> m_knobs_widget;
RefPtr<PlayerWidget> m_player_widget;
bool m_keys_pressed[key_code_count] { false };
};

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "PlayerWidget.h"
#include "AudioPlayerLoop.h"
#include "Music.h"
#include "TrackManager.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
PlayerWidget::PlayerWidget(TrackManager& manager, AudioPlayerLoop& loop)
: m_track_manager(manager)
, m_audio_loop(loop)
{
set_layout<GUI::HorizontalBoxLayout>();
set_fill_with_background_color(true);
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_back_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"); // Go back a note
m_next_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"); // Advance a note
m_play_button = add<GUI::Button>();
m_play_button->set_icon(*m_pause_icon);
m_play_button->set_fixed_width(30);
m_play_button->set_tooltip("Play/Pause playback");
m_play_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
m_play_button->on_click = [this](unsigned) {
m_audio_loop.toggle_paused();
if (m_audio_loop.is_playing()) {
m_play_button->set_icon(*m_pause_icon);
} else {
m_play_button->set_icon(*m_play_icon);
}
};
m_back_button = add<GUI::Button>();
m_back_button->set_icon(*m_back_icon);
m_back_button->set_fixed_width(30);
m_back_button->set_tooltip("Previous Note");
m_back_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
m_back_button->on_click = [this](unsigned) {
m_track_manager.time_forward(-(sample_rate / (beats_per_minute / 60) / notes_per_beat));
};
m_next_button = add<GUI::Button>();
m_next_button->set_icon(*m_next_icon);
m_next_button->set_fixed_width(30);
m_next_button->set_tooltip("Next Note");
m_next_button->set_focus_policy(GUI::FocusPolicy::NoFocus);
m_next_button->on_click = [this](unsigned) {
m_track_manager.time_forward((sample_rate / (beats_per_minute / 60) / notes_per_beat));
};
}
PlayerWidget::~PlayerWidget()
{
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/Toolbar.h>
class AudioPlayerLoop;
class TrackManager;
class PlayerWidget final : public GUI::Toolbar {
C_OBJECT(PlayerWidget)
public:
virtual ~PlayerWidget() override;
private:
explicit PlayerWidget(TrackManager&, AudioPlayerLoop&);
TrackManager& m_track_manager;
AudioPlayerLoop& m_audio_loop;
RefPtr<Gfx::Bitmap> m_play_icon;
RefPtr<Gfx::Bitmap> m_pause_icon;
RefPtr<Gfx::Bitmap> m_back_icon;
RefPtr<Gfx::Bitmap> m_next_icon;
RefPtr<GUI::Button> m_play_button;
RefPtr<GUI::Button> m_back_button;
RefPtr<GUI::Button> m_next_button;
};

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* Copyright (c) 2021 JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -16,6 +17,17 @@ TrackManager::~TrackManager()
{
}
void TrackManager::time_forward(int amount)
{
int new_value = (static_cast<int>(m_time) + amount) % roll_length;
if (new_value < 0) { // If the new time value is negaive add roll_length to wrap around
m_time = roll_length + new_value;
} else {
m_time = new_value;
}
}
void TrackManager::fill_buffer(Span<Sample> buffer)
{
memset(buffer.data(), 0, buffer_size);

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -26,7 +27,9 @@ public:
Span<const Sample> buffer() const { return m_current_front_buffer; }
int octave() const { return m_octave; }
int octave_base() const { return (m_octave - octave_min) * 12; }
int time() const { return m_time; }
void time_forward(int amount);
void fill_buffer(Span<Sample>);
void reset();

View file

@ -2,10 +2,12 @@
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2021, JJ Roberts-White <computerfido@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "AudioPlayerLoop.h"
#include "MainWidget.h"
#include "TrackManager.h"
#include <AK/Array.h>
@ -26,65 +28,6 @@
#include <LibGfx/Bitmap.h>
#include <LibThreading/Thread.h>
// Converts Piano-internal data to an Audio::Buffer that AudioServer receives
static NonnullRefPtr<Audio::Buffer> music_samples_to_buffer(Array<Sample, sample_count> samples)
{
Vector<Audio::Frame, sample_count> frames;
frames.ensure_capacity(sample_count);
for (auto sample : samples) {
Audio::Frame frame = { sample.left / (double)NumericLimits<i16>::max(), sample.right / (double)NumericLimits<i16>::max() };
frames.unchecked_append(frame);
}
return Audio::Buffer::create_with_samples(frames);
}
// Wrapper class accepting custom events to advance the track playing and forward audio data to the system.
// This does not run on a separate thread, preventing IPC multithreading madness.
class AudioPlayerLoop : public Core::Object {
C_OBJECT(AudioPlayerLoop)
public:
AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer)
: m_track_manager(track_manager)
, m_need_to_write_wav(need_to_write_wav)
, m_wav_writer(wav_writer)
{
m_audio_client = Audio::ClientConnection::construct();
m_audio_client->on_finish_playing_buffer = [this](int buffer_id) {
(void)buffer_id;
enqueue_audio();
};
}
void enqueue_audio()
{
m_track_manager.fill_buffer(m_buffer);
NonnullRefPtr<Audio::Buffer> audio_buffer = music_samples_to_buffer(m_buffer);
m_audio_client->async_enqueue(audio_buffer);
// FIXME: This should be done somewhere else.
if (m_need_to_write_wav) {
m_need_to_write_wav = false;
m_track_manager.reset();
m_track_manager.set_should_loop(false);
do {
m_track_manager.fill_buffer(m_buffer);
m_wav_writer.write_samples(reinterpret_cast<u8*>(m_buffer.data()), buffer_size);
} while (m_track_manager.time());
m_track_manager.reset();
m_track_manager.set_should_loop(true);
m_wav_writer.finalize();
}
}
private:
TrackManager& m_track_manager;
Array<Sample, sample_count> m_buffer;
RefPtr<Audio::ClientConnection> m_audio_client;
bool& m_need_to_write_wav;
Audio::WavWriter& m_wav_writer;
};
int main(int argc, char** argv)
{
if (pledge("stdio thread rpath cpath wpath recvfd sendfd unix", nullptr) < 0) {
@ -96,14 +39,6 @@ int main(int argc, char** argv)
TrackManager track_manager;
auto app_icon = GUI::Icon::default_icon("app-piano");
auto window = GUI::Window::construct();
auto& main_widget = window->set_main_widget<MainWidget>(track_manager);
window->set_title("Piano");
window->resize(840, 600);
window->set_icon(app_icon.bitmap_for_size(16));
window->show();
Audio::WavWriter wav_writer;
Optional<String> save_path;
bool need_to_write_wav = false;
@ -112,6 +47,14 @@ int main(int argc, char** argv)
audio_loop->enqueue_audio();
audio_loop->enqueue_audio();
auto app_icon = GUI::Icon::default_icon("app-piano");
auto window = GUI::Window::construct();
auto& main_widget = window->set_main_widget<MainWidget>(track_manager, audio_loop);
window->set_title("Piano");
window->resize(840, 600);
window->set_icon(app_icon.bitmap_for_size(16));
window->show();
auto main_widget_updater = Core::Timer::construct(static_cast<int>((1 / 60.0) * 1000), [&] {
Core::EventLoop::current().post_event(main_widget, make<Core::CustomEvent>(0));
});