mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:47: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:
parent
54c005754a
commit
74f1f2b5e2
10 changed files with 234 additions and 69 deletions
62
Userland/Applications/Piano/AudioPlayerLoop.cpp
Normal file
62
Userland/Applications/Piano/AudioPlayerLoop.cpp
Normal 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);
|
||||
}
|
38
Userland/Applications/Piano/AudioPlayerLoop.h
Normal file
38
Userland/Applications/Piano/AudioPlayerLoop.h
Normal 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;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
63
Userland/Applications/Piano/PlayerWidget.cpp
Normal file
63
Userland/Applications/Piano/PlayerWidget.cpp
Normal 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()
|
||||
{
|
||||
}
|
33
Userland/Applications/Piano/PlayerWidget.h
Normal file
33
Userland/Applications/Piano/PlayerWidget.h
Normal 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;
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue