From ee52572ca1bc382e6ccaf8e59e02db2b15157ac8 Mon Sep 17 00:00:00 2001 From: William McPherson Date: Mon, 15 Jun 2020 15:33:53 +1000 Subject: [PATCH] Piano: Allow multiple tracks internally This commit adds multi-track functionality without exposing it to the user. All I really did was rename AudioEngine to Track and allow more than one Track in TrackManager. A lot of the changes are just changing widgets to take a TrackManager and use current_track(). The TrackManager creates Tracks and gives them a read-only reference to the global time value. When the TrackManager wants to fill a sample in the buffer (in fill_buffer()), it calls fill_sample() on each Track. The delay code is slightly different - a Track will fill its m_delay_buffer with the sample it just created rather than the most recent sample in the buffer (which used to be the same thing). TrackManager manages the current octave. Other than those few things, this is a pretty basic separation of concerns. --- Applications/Piano/CMakeLists.txt | 3 +- Applications/Piano/KeysWidget.cpp | 16 +- Applications/Piano/KeysWidget.h | 6 +- Applications/Piano/KnobsWidget.cpp | 66 +++--- Applications/Piano/KnobsWidget.h | 6 +- Applications/Piano/MainWidget.cpp | 20 +- Applications/Piano/MainWidget.h | 6 +- Applications/Piano/RollWidget.cpp | 12 +- Applications/Piano/RollWidget.h | 6 +- Applications/Piano/SamplerWidget.cpp | 16 +- Applications/Piano/SamplerWidget.h | 10 +- .../Piano/{AudioEngine.cpp => Track.cpp} | 216 ++++++++---------- Applications/Piano/{AudioEngine.h => Track.h} | 29 +-- Applications/Piano/TrackManager.cpp | 97 ++++++++ Applications/Piano/TrackManager.h | 71 ++++++ Applications/Piano/WaveWidget.cpp | 12 +- Applications/Piano/WaveWidget.h | 6 +- Applications/Piano/main.cpp | 20 +- 18 files changed, 371 insertions(+), 247 deletions(-) rename Applications/Piano/{AudioEngine.cpp => Track.cpp} (64%) rename Applications/Piano/{AudioEngine.h => Track.h} (80%) create mode 100644 Applications/Piano/TrackManager.cpp create mode 100644 Applications/Piano/TrackManager.h diff --git a/Applications/Piano/CMakeLists.txt b/Applications/Piano/CMakeLists.txt index 0ba5a8ce916..6756afb18c9 100644 --- a/Applications/Piano/CMakeLists.txt +++ b/Applications/Piano/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES - AudioEngine.cpp + Track.cpp + TrackManager.cpp KeysWidget.cpp KnobsWidget.cpp main.cpp diff --git a/Applications/Piano/KeysWidget.cpp b/Applications/Piano/KeysWidget.cpp index c3428120d67..3645c973ab2 100644 --- a/Applications/Piano/KeysWidget.cpp +++ b/Applications/Piano/KeysWidget.cpp @@ -26,11 +26,11 @@ */ #include "KeysWidget.h" -#include "AudioEngine.h" +#include "TrackManager.h" #include -KeysWidget::KeysWidget(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +KeysWidget::KeysWidget(TrackManager& track_manager) + : m_track_manager(track_manager) { set_fill_with_background_color(true); } @@ -41,7 +41,7 @@ KeysWidget::~KeysWidget() int KeysWidget::mouse_note() const { - if (m_mouse_down && m_mouse_note + m_audio_engine.octave_base() < note_count) + if (m_mouse_down && m_mouse_note + m_track_manager.octave_base() < note_count) return m_mouse_note; // Can be -1. else return -1; @@ -49,7 +49,7 @@ int KeysWidget::mouse_note() const void KeysWidget::set_key(int key, Switch switch_key) { - if (key == -1 || key + m_audio_engine.octave_base() >= note_count) + if (key == -1 || key + m_track_manager.octave_base() >= note_count) return; if (switch_key == On) { @@ -60,7 +60,7 @@ void KeysWidget::set_key(int key, Switch switch_key) } ASSERT(m_key_on[key] <= 2); - m_audio_engine.set_note_current_octave(key, switch_key); + m_track_manager.set_note_current_octave(key, switch_key); } int KeysWidget::key_code_to_key(int key_code) const @@ -191,7 +191,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) x += white_key_width; ++i; - if (note + m_audio_engine.octave_base() >= note_count) + if (note + m_track_manager.octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; @@ -213,7 +213,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) x += black_key_offsets[i % black_keys_per_octave]; ++i; - if (note + m_audio_engine.octave_base() >= note_count) + if (note + m_track_manager.octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; diff --git a/Applications/Piano/KeysWidget.h b/Applications/Piano/KeysWidget.h index 59bb6e5a4a9..b5e4a066d47 100644 --- a/Applications/Piano/KeysWidget.h +++ b/Applications/Piano/KeysWidget.h @@ -30,7 +30,7 @@ #include "Music.h" #include -class AudioEngine; +class TrackManager; class KeysWidget final : public GUI::Frame { C_OBJECT(KeysWidget) @@ -43,7 +43,7 @@ public: void set_key(int key, Switch); private: - explicit KeysWidget(AudioEngine&); + explicit KeysWidget(TrackManager&); virtual void paint_event(GUI::PaintEvent&) override; virtual void mousedown_event(GUI::MouseEvent&) override; @@ -52,7 +52,7 @@ private: int note_for_event_position(const Gfx::IntPoint&) const; - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; u8 m_key_on[note_count] { 0 }; diff --git a/Applications/Piano/KnobsWidget.cpp b/Applications/Piano/KnobsWidget.cpp index 7e68687cffc..0296124bf1b 100644 --- a/Applications/Piano/KnobsWidget.cpp +++ b/Applications/Piano/KnobsWidget.cpp @@ -26,14 +26,14 @@ */ #include "KnobsWidget.h" -#include "AudioEngine.h" #include "MainWidget.h" +#include "TrackManager.h" #include #include #include -KnobsWidget::KnobsWidget(AudioEngine& audio_engine, MainWidget& main_widget) - : m_audio_engine(audio_engine) +KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) + : m_track_manager(track_manager) , m_main_widget(main_widget) { set_layout(); @@ -57,13 +57,13 @@ KnobsWidget::KnobsWidget(AudioEngine& audio_engine, MainWidget& main_widget) m_values_container->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); m_values_container->set_preferred_size(0, 10); - m_octave_value = m_values_container->add(String::number(m_audio_engine.octave())); - m_wave_value = m_values_container->add(wave_strings[m_audio_engine.wave()]); - m_attack_value = m_values_container->add(String::number(m_audio_engine.attack())); - m_decay_value = m_values_container->add(String::number(m_audio_engine.decay())); - m_sustain_value = m_values_container->add(String::number(m_audio_engine.sustain())); - m_release_value = m_values_container->add(String::number(m_audio_engine.release())); - m_delay_value = m_values_container->add(String::number(m_audio_engine.delay())); + m_octave_value = m_values_container->add(String::number(m_track_manager.octave())); + m_wave_value = m_values_container->add(wave_strings[m_track_manager.current_track().wave()]); + m_attack_value = m_values_container->add(String::number(m_track_manager.current_track().attack())); + m_decay_value = m_values_container->add(String::number(m_track_manager.current_track().decay())); + m_sustain_value = m_values_container->add(String::number(m_track_manager.current_track().sustain())); + m_release_value = m_values_container->add(String::number(m_track_manager.current_track().release())); + m_delay_value = m_values_container->add(String::number(m_track_manager.current_track().delay())); m_knobs_container = add(); m_knobs_container->set_layout(); @@ -73,82 +73,82 @@ KnobsWidget::KnobsWidget(AudioEngine& audio_engine, MainWidget& main_widget) m_octave_knob = m_knobs_container->add(); m_octave_knob->set_tooltip("Z: octave down, X: octave up"); m_octave_knob->set_range(octave_min - 1, octave_max - 1); - m_octave_knob->set_value((octave_max - 1) - (m_audio_engine.octave() - 1)); + m_octave_knob->set_value((octave_max - 1) - (m_track_manager.octave() - 1)); m_octave_knob->on_value_changed = [this](int value) { int new_octave = octave_max - value; if (m_change_octave) - m_main_widget.set_octave_and_ensure_note_change(new_octave == m_audio_engine.octave() + 1 ? Up : Down); - ASSERT(new_octave == m_audio_engine.octave()); + m_main_widget.set_octave_and_ensure_note_change(new_octave == m_track_manager.octave() + 1 ? Up : Down); + ASSERT(new_octave == m_track_manager.octave()); m_octave_value->set_text(String::number(new_octave)); }; m_wave_knob = m_knobs_container->add(); m_wave_knob->set_tooltip("C: cycle through waveforms"); m_wave_knob->set_range(0, last_wave); - m_wave_knob->set_value(last_wave - m_audio_engine.wave()); + m_wave_knob->set_value(last_wave - m_track_manager.current_track().wave()); m_wave_knob->on_value_changed = [this](int value) { int new_wave = last_wave - value; - m_audio_engine.set_wave(new_wave); - ASSERT(new_wave == m_audio_engine.wave()); + m_track_manager.current_track().set_wave(new_wave); + ASSERT(new_wave == m_track_manager.current_track().wave()); m_wave_value->set_text(wave_strings[new_wave]); }; constexpr int max_attack = 1000; m_attack_knob = m_knobs_container->add(); m_attack_knob->set_range(0, max_attack); - m_attack_knob->set_value(max_attack - m_audio_engine.attack()); + m_attack_knob->set_value(max_attack - m_track_manager.current_track().attack()); m_attack_knob->set_step(100); m_attack_knob->on_value_changed = [this](int value) { int new_attack = max_attack - value; - m_audio_engine.set_attack(new_attack); - ASSERT(new_attack == m_audio_engine.attack()); + m_track_manager.current_track().set_attack(new_attack); + ASSERT(new_attack == m_track_manager.current_track().attack()); m_attack_value->set_text(String::number(new_attack)); }; constexpr int max_decay = 1000; m_decay_knob = m_knobs_container->add(); m_decay_knob->set_range(0, max_decay); - m_decay_knob->set_value(max_decay - m_audio_engine.decay()); + m_decay_knob->set_value(max_decay - m_track_manager.current_track().decay()); m_decay_knob->set_step(100); m_decay_knob->on_value_changed = [this](int value) { int new_decay = max_decay - value; - m_audio_engine.set_decay(new_decay); - ASSERT(new_decay == m_audio_engine.decay()); + m_track_manager.current_track().set_decay(new_decay); + ASSERT(new_decay == m_track_manager.current_track().decay()); m_decay_value->set_text(String::number(new_decay)); }; constexpr int max_sustain = 1000; m_sustain_knob = m_knobs_container->add(); m_sustain_knob->set_range(0, max_sustain); - m_sustain_knob->set_value(max_sustain - m_audio_engine.sustain()); + m_sustain_knob->set_value(max_sustain - m_track_manager.current_track().sustain()); m_sustain_knob->set_step(100); m_sustain_knob->on_value_changed = [this](int value) { int new_sustain = max_sustain - value; - m_audio_engine.set_sustain(new_sustain); - ASSERT(new_sustain == m_audio_engine.sustain()); + m_track_manager.current_track().set_sustain(new_sustain); + ASSERT(new_sustain == m_track_manager.current_track().sustain()); m_sustain_value->set_text(String::number(new_sustain)); }; constexpr int max_release = 1000; m_release_knob = m_knobs_container->add(); m_release_knob->set_range(0, max_release); - m_release_knob->set_value(max_release - m_audio_engine.release()); + m_release_knob->set_value(max_release - m_track_manager.current_track().release()); m_release_knob->set_step(100); m_release_knob->on_value_changed = [this](int value) { int new_release = max_release - value; - m_audio_engine.set_release(new_release); - ASSERT(new_release == m_audio_engine.release()); + m_track_manager.current_track().set_release(new_release); + ASSERT(new_release == m_track_manager.current_track().release()); m_release_value->set_text(String::number(new_release)); }; constexpr int max_delay = 8; m_delay_knob = m_knobs_container->add(); m_delay_knob->set_range(0, max_delay); - m_delay_knob->set_value(max_delay - m_audio_engine.delay()); + m_delay_knob->set_value(max_delay - m_track_manager.current_track().delay()); m_delay_knob->on_value_changed = [this](int value) { int new_delay = max_delay - value; - m_audio_engine.set_delay(new_delay); - ASSERT(new_delay == m_audio_engine.delay()); + m_track_manager.current_track().set_delay(new_delay); + ASSERT(new_delay == m_track_manager.current_track().delay()); m_delay_value->set_text(String::number(new_delay)); }; } @@ -159,12 +159,12 @@ KnobsWidget::~KnobsWidget() void KnobsWidget::update_knobs() { - m_wave_knob->set_value(last_wave - m_audio_engine.wave()); + m_wave_knob->set_value(last_wave - m_track_manager.current_track().wave()); // FIXME: This is needed because when the slider is changed directly, it // needs to change the octave, but if the octave was changed elsewhere, we // need to change the slider without changing the octave. m_change_octave = false; - m_octave_knob->set_value(octave_max - m_audio_engine.octave()); + m_octave_knob->set_value(octave_max - m_track_manager.octave()); m_change_octave = true; } diff --git a/Applications/Piano/KnobsWidget.h b/Applications/Piano/KnobsWidget.h index 315141144e2..f7623df3dcd 100644 --- a/Applications/Piano/KnobsWidget.h +++ b/Applications/Piano/KnobsWidget.h @@ -29,7 +29,7 @@ #include -class AudioEngine; +class TrackManager; class MainWidget; class KnobsWidget final : public GUI::Frame { @@ -40,9 +40,9 @@ public: void update_knobs(); private: - KnobsWidget(AudioEngine&, MainWidget&); + KnobsWidget(TrackManager&, MainWidget&); - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; MainWidget& m_main_widget; RefPtr m_labels_container; diff --git a/Applications/Piano/MainWidget.cpp b/Applications/Piano/MainWidget.cpp index ccfdb35e338..207954738c3 100644 --- a/Applications/Piano/MainWidget.cpp +++ b/Applications/Piano/MainWidget.cpp @@ -26,34 +26,34 @@ */ #include "MainWidget.h" -#include "AudioEngine.h" #include "KeysWidget.h" #include "KnobsWidget.h" #include "RollWidget.h" #include "SamplerWidget.h" +#include "TrackManager.h" #include "WaveWidget.h" #include #include -MainWidget::MainWidget(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +MainWidget::MainWidget(TrackManager& track_manager) + : m_track_manager(track_manager) { set_layout(); layout()->set_spacing(2); layout()->set_margins({ 2, 2, 2, 2 }); set_fill_with_background_color(true); - m_wave_widget = add(audio_engine); + m_wave_widget = add(track_manager); m_wave_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); m_wave_widget->set_preferred_size(0, 100); m_tab_widget = add(); - m_roll_widget = m_tab_widget->add_tab("Piano Roll", audio_engine); + m_roll_widget = m_tab_widget->add_tab("Piano Roll", track_manager); m_roll_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill); m_roll_widget->set_preferred_size(0, 300); - m_tab_widget->add_tab("Sampler", audio_engine); + m_tab_widget->add_tab("Sampler", track_manager); m_keys_and_knobs_container = add(); m_keys_and_knobs_container->set_layout(); @@ -62,9 +62,9 @@ MainWidget::MainWidget(AudioEngine& audio_engine) m_keys_and_knobs_container->set_preferred_size(0, 100); m_keys_and_knobs_container->set_fill_with_background_color(true); - m_keys_widget = m_keys_and_knobs_container->add(audio_engine); + m_keys_widget = m_keys_and_knobs_container->add(track_manager); - m_knobs_widget = m_keys_and_knobs_container->add(audio_engine, *this); + m_knobs_widget = m_keys_and_knobs_container->add(track_manager, *this); m_knobs_widget->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); m_knobs_widget->set_preferred_size(350, 0); } @@ -119,7 +119,7 @@ void MainWidget::special_key_action(int key_code) set_octave_and_ensure_note_change(Up); break; case Key_C: - m_audio_engine.set_wave(Up); + m_track_manager.current_track().set_wave(Up); m_knobs_widget->update_knobs(); break; } @@ -133,7 +133,7 @@ void MainWidget::set_octave_and_ensure_note_change(Direction direction) note_key_action(i, Off); } - m_audio_engine.set_octave(direction); + m_track_manager.set_octave(direction); m_keys_widget->set_key(m_keys_widget->mouse_note(), On); for (int i = 0; i < key_code_count; ++i) { diff --git a/Applications/Piano/MainWidget.h b/Applications/Piano/MainWidget.h index 6d1e80c4c1b..6a2a5436750 100644 --- a/Applications/Piano/MainWidget.h +++ b/Applications/Piano/MainWidget.h @@ -30,7 +30,7 @@ #include "Music.h" #include -class AudioEngine; +class TrackManager; class WaveWidget; class RollWidget; class SamplerWidget; @@ -45,7 +45,7 @@ public: void set_octave_and_ensure_note_change(Direction); private: - explicit MainWidget(AudioEngine&); + explicit MainWidget(TrackManager&); virtual void keydown_event(GUI::KeyEvent&) override; virtual void keyup_event(GUI::KeyEvent&) override; @@ -54,7 +54,7 @@ private: void note_key_action(int key_code, Switch); void special_key_action(int key_code); - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; RefPtr m_wave_widget; RefPtr m_roll_widget; diff --git a/Applications/Piano/RollWidget.cpp b/Applications/Piano/RollWidget.cpp index d9968225d98..738bef8f9e9 100644 --- a/Applications/Piano/RollWidget.cpp +++ b/Applications/Piano/RollWidget.cpp @@ -26,7 +26,7 @@ */ #include "RollWidget.h" -#include "AudioEngine.h" +#include "TrackManager.h" #include #include #include @@ -37,8 +37,8 @@ constexpr int roll_height = note_count * note_height; constexpr int horizontal_scroll_sensitivity = 20; constexpr int max_zoom = 1 << 8; -RollWidget::RollWidget(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +RollWidget::RollWidget(TrackManager& track_manager) + : m_track_manager(track_manager) { set_should_hide_unnecessary_scrollbars(true); set_content_size({ 0, roll_height }); @@ -117,7 +117,7 @@ void RollWidget::paint_event(GUI::PaintEvent& event) painter.translate(horizontal_note_offset_remainder, note_offset_remainder); for (int note = note_count - (note_offset + notes_to_paint); note <= (note_count - 1) - note_offset; ++note) { - for (auto roll_note : m_audio_engine.roll_notes(note)) { + for (auto roll_note : m_track_manager.current_track().roll_notes(note)) { int x = m_roll_width * (static_cast(roll_note.on_sample) / roll_length); int width = m_roll_width * (static_cast(roll_note.length()) / roll_length); if (x + width < x_offset || x > x_offset + widget_inner_rect().width()) @@ -134,7 +134,7 @@ void RollWidget::paint_event(GUI::PaintEvent& event) } } - int x = m_roll_width * (static_cast(m_audio_engine.time()) / roll_length); + int x = m_roll_width * (static_cast(m_track_manager.time()) / roll_length); if (x > x_offset && x <= x_offset + widget_inner_rect().width()) painter.draw_line({ x, 0 }, { x, roll_height }, Gfx::Color::Black); @@ -164,7 +164,7 @@ void RollWidget::mousedown_event(GUI::MouseEvent& event) int note = (note_count - 1) - y; u32 on_sample = roll_length * (static_cast(x) / m_num_notes); u32 off_sample = (roll_length * (static_cast(x + 1) / m_num_notes)) - 1; - m_audio_engine.set_roll_note(note, on_sample, off_sample); + m_track_manager.current_track().set_roll_note(note, on_sample, off_sample); update(); } diff --git a/Applications/Piano/RollWidget.h b/Applications/Piano/RollWidget.h index 235d4244128..43110382180 100644 --- a/Applications/Piano/RollWidget.h +++ b/Applications/Piano/RollWidget.h @@ -30,7 +30,7 @@ #include "Music.h" #include -class AudioEngine; +class TrackManager; class RollWidget final : public GUI::ScrollableWidget { C_OBJECT(RollWidget) @@ -38,13 +38,13 @@ public: virtual ~RollWidget() override; private: - explicit RollWidget(AudioEngine&); + explicit RollWidget(TrackManager&); virtual void paint_event(GUI::PaintEvent&) override; virtual void mousedown_event(GUI::MouseEvent& event) override; virtual void mousewheel_event(GUI::MouseEvent&) override; - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; int m_roll_width; int m_num_notes; diff --git a/Applications/Piano/SamplerWidget.cpp b/Applications/Piano/SamplerWidget.cpp index f3f116ebe4f..e08026453e8 100644 --- a/Applications/Piano/SamplerWidget.cpp +++ b/Applications/Piano/SamplerWidget.cpp @@ -25,7 +25,7 @@ */ #include "SamplerWidget.h" -#include "AudioEngine.h" +#include "TrackManager.h" #include #include #include @@ -33,8 +33,8 @@ #include #include -WaveEditor::WaveEditor(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +WaveEditor::WaveEditor(TrackManager& track_manager) + : m_track_manager(track_manager) { } @@ -56,7 +56,7 @@ void WaveEditor::paint_event(GUI::PaintEvent& event) GUI::Painter painter(*this); painter.fill_rect(frame_inner_rect(), Color::Black); - auto recorded_sample = m_audio_engine.recorded_sample(); + auto recorded_sample = m_track_manager.current_track().recorded_sample(); if (recorded_sample.is_empty()) return; @@ -88,8 +88,8 @@ void WaveEditor::paint_event(GUI::PaintEvent& event) } } -SamplerWidget::SamplerWidget(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +SamplerWidget::SamplerWidget(TrackManager& track_manager) + : m_track_manager(track_manager) { set_layout(); layout()->set_margins({ 10, 10, 10, 10 }); @@ -111,7 +111,7 @@ SamplerWidget::SamplerWidget(AudioEngine& audio_engine) Optional open_path = GUI::FilePicker::get_open_filepath(); if (!open_path.has_value()) return; - String error_string = m_audio_engine.set_recorded_sample(open_path.value()); + String error_string = m_track_manager.current_track().set_recorded_sample(open_path.value()); if (!error_string.is_empty()) { GUI::MessageBox::show(String::format("Failed to load WAV file: %s", error_string.characters()), "Error", GUI::MessageBox::Type::Error); return; @@ -123,7 +123,7 @@ SamplerWidget::SamplerWidget(AudioEngine& audio_engine) m_recorded_sample_name = m_open_button_and_recorded_sample_name_container->add("No sample loaded"); m_recorded_sample_name->set_text_alignment(Gfx::TextAlignment::CenterLeft); - m_wave_editor = add(m_audio_engine); + m_wave_editor = add(m_track_manager); m_wave_editor->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); m_wave_editor->set_preferred_size(0, 100); } diff --git a/Applications/Piano/SamplerWidget.h b/Applications/Piano/SamplerWidget.h index 11c4ec90db7..9091e5fef99 100644 --- a/Applications/Piano/SamplerWidget.h +++ b/Applications/Piano/SamplerWidget.h @@ -28,7 +28,7 @@ #include -class AudioEngine; +class TrackManager; class WaveEditor final : public GUI::Frame { C_OBJECT(WaveEditor) @@ -36,13 +36,13 @@ public: virtual ~WaveEditor() override; private: - explicit WaveEditor(AudioEngine&); + explicit WaveEditor(TrackManager&); virtual void paint_event(GUI::PaintEvent&) override; int sample_to_y(double percentage) const; - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; }; class SamplerWidget final : public GUI::Frame { @@ -51,9 +51,9 @@ public: virtual ~SamplerWidget() override; private: - explicit SamplerWidget(AudioEngine&); + explicit SamplerWidget(TrackManager&); - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; RefPtr m_open_button_and_recorded_sample_name_container; RefPtr m_open_button; diff --git a/Applications/Piano/AudioEngine.cpp b/Applications/Piano/Track.cpp similarity index 64% rename from Applications/Piano/AudioEngine.cpp rename to Applications/Piano/Track.cpp index f4f6bb95602..22cbbe03f89 100644 --- a/Applications/Piano/AudioEngine.cpp +++ b/Applications/Piano/Track.cpp @@ -25,12 +25,13 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "AudioEngine.h" +#include "Track.h" #include #include #include -AudioEngine::AudioEngine() +Track::Track(const u32& time) + : m_time(time) { set_sustain_impl(1000); set_attack(5); @@ -38,119 +39,104 @@ AudioEngine::AudioEngine() set_release(5); } -AudioEngine::~AudioEngine() +Track::~Track() { } -void AudioEngine::fill_buffer(FixedArray& buffer) +void Track::fill_sample(Sample& sample) { - memset(buffer.data(), 0, buffer_size); + Audio::Sample new_sample; - for (size_t i = 0; i < buffer.size(); ++i) { - for (size_t note = 0; note < note_count; ++note) { - if (!m_roll_iters[note].is_end()) { - if (m_roll_iters[note]->on_sample == m_time) { - set_note(note, On); - } else if (m_roll_iters[note]->off_sample == m_time) { - set_note(note, Off); - ++m_roll_iters[note]; - if (m_roll_iters[note].is_end()) - m_roll_iters[note] = m_roll_notes[note].begin(); - } + for (size_t note = 0; note < note_count; ++note) { + if (!m_roll_iters[note].is_end()) { + if (m_roll_iters[note]->on_sample == m_time) { + set_note(note, On); + } else if (m_roll_iters[note]->off_sample == m_time) { + set_note(note, Off); + ++m_roll_iters[note]; + if (m_roll_iters[note].is_end()) + m_roll_iters[note] = m_roll_notes[note].begin(); } + } - switch (m_envelope[note]) { - case Done: + switch (m_envelope[note]) { + case Done: + continue; + case Attack: + m_power[note] += m_attack_step; + if (m_power[note] >= 1) { + m_power[note] = 1; + m_envelope[note] = Decay; + } + break; + case Decay: + m_power[note] -= m_decay_step; + if (m_power[note] < m_sustain_level) + m_power[note] = m_sustain_level; + break; + case Release: + m_power[note] -= m_release_step[note]; + if (m_power[note] <= 0) { + m_power[note] = 0; + m_envelope[note] = Done; continue; - case Attack: - m_power[note] += m_attack_step; - if (m_power[note] >= 1) { - m_power[note] = 1; - m_envelope[note] = Decay; - } - break; - case Decay: - m_power[note] -= m_decay_step; - if (m_power[note] < m_sustain_level) - m_power[note] = m_sustain_level; - break; - case Release: - m_power[note] -= m_release_step[note]; - if (m_power[note] <= 0) { - m_power[note] = 0; - m_envelope[note] = Done; - continue; - } - break; - default: - ASSERT_NOT_REACHED(); } - - Audio::Sample sample; - switch (m_wave) { - case Wave::Sine: - sample = sine(note); - break; - case Wave::Saw: - sample = saw(note); - break; - case Wave::Square: - sample = square(note); - break; - case Wave::Triangle: - sample = triangle(note); - break; - case Wave::Noise: - sample = noise(); - break; - case Wave::RecordedSample: - sample = recorded_sample(note); - break; - default: - ASSERT_NOT_REACHED(); - } - buffer[i].left += sample.left * m_power[note] * volume; - buffer[i].right += sample.right * m_power[note] * volume; + break; + default: + ASSERT_NOT_REACHED(); } - if (m_delay) { - buffer[i].left += m_delay_buffer[m_delay_index].left * 0.333333; - buffer[i].right += m_delay_buffer[m_delay_index].right * 0.333333; - m_delay_buffer[m_delay_index].left = buffer[i].left; - m_delay_buffer[m_delay_index].right = buffer[i].right; - if (++m_delay_index >= m_delay_samples) - m_delay_index = 0; - } - - if (++m_time >= roll_length) { - m_time = 0; - if (!m_should_loop) - break; + Audio::Sample note_sample; + switch (m_wave) { + case Wave::Sine: + note_sample = sine(note); + break; + case Wave::Saw: + note_sample = saw(note); + break; + case Wave::Square: + note_sample = square(note); + break; + case Wave::Triangle: + note_sample = triangle(note); + break; + case Wave::Noise: + note_sample = noise(); + break; + case Wave::RecordedSample: + note_sample = recorded_sample(note); + break; + default: + ASSERT_NOT_REACHED(); } + new_sample.left += note_sample.left * m_power[note] * volume; + new_sample.right += note_sample.right * m_power[note] * volume; } - memcpy(m_back_buffer_ptr->data(), buffer.data(), buffer_size); - swap(m_front_buffer_ptr, m_back_buffer_ptr); + if (m_delay) { + new_sample.left += m_delay_buffer[m_delay_index].left * 0.333333; + new_sample.right += m_delay_buffer[m_delay_index].right * 0.333333; + m_delay_buffer[m_delay_index].left = new_sample.left; + m_delay_buffer[m_delay_index].right = new_sample.right; + if (++m_delay_index >= m_delay_samples) + m_delay_index = 0; + } + + sample.left += new_sample.left; + sample.right += new_sample.right; } -void AudioEngine::reset() +void Track::reset() { - memset(m_front_buffer.data(), 0, buffer_size); - memset(m_back_buffer.data(), 0, buffer_size); - m_front_buffer_ptr = &m_front_buffer; - m_back_buffer_ptr = &m_back_buffer; - memset(m_delay_buffer.data(), 0, m_delay_buffer.size() * sizeof(Sample)); m_delay_index = 0; memset(m_note_on, 0, sizeof(m_note_on)); memset(m_power, 0, sizeof(m_power)); memset(m_envelope, 0, sizeof(m_envelope)); - - m_time = 0; } -String AudioEngine::set_recorded_sample(const StringView& path) +String Track::set_recorded_sample(const StringView& path) { Audio::WavLoader wav_loader(path); if (wav_loader.has_error()) @@ -183,7 +169,7 @@ String AudioEngine::set_recorded_sample(const StringView& path) // All of the information for these waves is on Wikipedia. -Audio::Sample AudioEngine::sine(size_t note) +Audio::Sample Track::sine(size_t note) { double pos = note_frequencies[note] / sample_rate; double sin_step = pos * 2 * M_PI; @@ -192,7 +178,7 @@ Audio::Sample AudioEngine::sine(size_t note) return w; } -Audio::Sample AudioEngine::saw(size_t note) +Audio::Sample Track::saw(size_t note) { double saw_step = note_frequencies[note] / sample_rate; double t = m_pos[note]; @@ -201,7 +187,7 @@ Audio::Sample AudioEngine::saw(size_t note) return w; } -Audio::Sample AudioEngine::square(size_t note) +Audio::Sample Track::square(size_t note) { double pos = note_frequencies[note] / sample_rate; double square_step = pos * 2 * M_PI; @@ -210,7 +196,7 @@ Audio::Sample AudioEngine::square(size_t note) return w; } -Audio::Sample AudioEngine::triangle(size_t note) +Audio::Sample Track::triangle(size_t note) { double triangle_step = note_frequencies[note] / sample_rate; double t = m_pos[note]; @@ -219,14 +205,14 @@ Audio::Sample AudioEngine::triangle(size_t note) return w; } -Audio::Sample AudioEngine::noise() const +Audio::Sample Track::noise() const { double random_percentage = static_cast(rand()) / RAND_MAX; double w = (random_percentage * 2) - 1; return w; } -Audio::Sample AudioEngine::recorded_sample(size_t note) +Audio::Sample Track::recorded_sample(size_t note) { int t = m_pos[note]; if (t >= static_cast(m_recorded_sample.size())) @@ -254,7 +240,7 @@ static inline double calculate_step(double distance, int milliseconds) return step; } -void AudioEngine::set_note(int note, Switch switch_note) +void Track::set_note(int note, Switch switch_note) { ASSERT(note >= 0 && note < note_count); @@ -278,12 +264,7 @@ void AudioEngine::set_note(int note, Switch switch_note) ASSERT(m_power[note] >= 0); } -void AudioEngine::set_note_current_octave(int note, Switch switch_note) -{ - set_note(note + octave_base(), switch_note); -} - -void AudioEngine::sync_roll(int note) +void Track::sync_roll(int note) { auto it = m_roll_notes[note].find([&](auto& roll_note) { return roll_note.off_sample > m_time; }); if (it.is_end()) @@ -292,7 +273,7 @@ void AudioEngine::sync_roll(int note) m_roll_iters[note] = it; } -void AudioEngine::set_roll_note(int note, u32 on_sample, u32 off_sample) +void Track::set_roll_note(int note, u32 on_sample, u32 off_sample) { RollNote new_roll_note = { on_sample, off_sample }; @@ -333,24 +314,13 @@ void AudioEngine::set_roll_note(int note, u32 on_sample, u32 off_sample) sync_roll(note); } -void AudioEngine::set_octave(Direction direction) -{ - if (direction == Up) { - if (m_octave < octave_max) - ++m_octave; - } else { - if (m_octave > octave_min) - --m_octave; - } -} - -void AudioEngine::set_wave(int wave) +void Track::set_wave(int wave) { ASSERT(wave >= first_wave && wave <= last_wave); m_wave = wave; } -void AudioEngine::set_wave(Direction direction) +void Track::set_wave(Direction direction) { if (direction == Up) { if (++m_wave > last_wave) @@ -361,40 +331,40 @@ void AudioEngine::set_wave(Direction direction) } } -void AudioEngine::set_attack(int attack) +void Track::set_attack(int attack) { ASSERT(attack >= 0); m_attack = attack; m_attack_step = calculate_step(1, m_attack); } -void AudioEngine::set_decay(int decay) +void Track::set_decay(int decay) { ASSERT(decay >= 0); m_decay = decay; m_decay_step = calculate_step(1 - m_sustain_level, m_decay); } -void AudioEngine::set_sustain_impl(int sustain) +void Track::set_sustain_impl(int sustain) { ASSERT(sustain >= 0); m_sustain = sustain; m_sustain_level = sustain / 1000.0; } -void AudioEngine::set_sustain(int sustain) +void Track::set_sustain(int sustain) { set_sustain_impl(sustain); set_decay(m_decay); } -void AudioEngine::set_release(int release) +void Track::set_release(int release) { ASSERT(release >= 0); m_release = release; } -void AudioEngine::set_delay(int delay) +void Track::set_delay(int delay) { ASSERT(delay >= 0); m_delay = delay; diff --git a/Applications/Piano/AudioEngine.h b/Applications/Piano/Track.h similarity index 80% rename from Applications/Piano/AudioEngine.h rename to Applications/Piano/Track.h index aae9554520a..99f3e1f3abc 100644 --- a/Applications/Piano/AudioEngine.h +++ b/Applications/Piano/Track.h @@ -35,34 +35,27 @@ typedef AK::SinglyLinkedListIterator, RollNote> RollIter; -class AudioEngine { - AK_MAKE_NONCOPYABLE(AudioEngine) - AK_MAKE_NONMOVABLE(AudioEngine) +class Track { + AK_MAKE_NONCOPYABLE(Track) + AK_MAKE_NONMOVABLE(Track) public: - AudioEngine(); - ~AudioEngine(); + explicit Track(const u32& time); + ~Track(); - const FixedArray& buffer() const { return *m_front_buffer_ptr; } const Vector& recorded_sample() const { return m_recorded_sample; } const SinglyLinkedList& roll_notes(int note) const { return m_roll_notes[note]; } - int octave() const { return m_octave; } - int octave_base() const { return (m_octave - octave_min) * 12; } int wave() const { return m_wave; } int attack() const { return m_attack; } int decay() const { return m_decay; } int sustain() const { return m_sustain; } int release() const { return m_release; } int delay() const { return m_delay; } - int time() const { return m_time; } - void fill_buffer(FixedArray& buffer); + void fill_sample(Sample& sample); void reset(); - void set_should_loop(bool b) { m_should_loop = b; } String set_recorded_sample(const StringView& path); void set_note(int note, Switch); - void set_note_current_octave(int note, Switch); void set_roll_note(int note, u32 on_sample, u32 off_sample); - void set_octave(Direction); void set_wave(int wave); void set_wave(Direction); void set_attack(int attack); @@ -82,11 +75,6 @@ private: void sync_roll(int note); void set_sustain_impl(int sustain); - FixedArray m_front_buffer { sample_count }; - FixedArray m_back_buffer { sample_count }; - FixedArray* m_front_buffer_ptr { &m_front_buffer }; - FixedArray* m_back_buffer_ptr { &m_back_buffer }; - Vector m_delay_buffer; Vector m_recorded_sample; @@ -96,7 +84,6 @@ private: double m_pos[note_count]; // Initialized lazily. Envelope m_envelope[note_count] { Done }; - int m_octave { 4 }; int m_wave { first_wave }; int m_attack; double m_attack_step; @@ -110,9 +97,7 @@ private: size_t m_delay_samples { 0 }; size_t m_delay_index { 0 }; - u32 m_time { 0 }; - - bool m_should_loop { true }; + const u32& m_time; SinglyLinkedList m_roll_notes[note_count]; RollIter m_roll_iters[note_count]; diff --git a/Applications/Piano/TrackManager.cpp b/Applications/Piano/TrackManager.cpp new file mode 100644 index 00000000000..2a9419e3be0 --- /dev/null +++ b/Applications/Piano/TrackManager.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2019-2020, William McPherson + * 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 "TrackManager.h" + +TrackManager::TrackManager() +{ + add_track(); +} + +TrackManager::~TrackManager() +{ +} + +void TrackManager::fill_buffer(FixedArray& buffer) +{ + memset(buffer.data(), 0, buffer_size); + + for (size_t i = 0; i < buffer.size(); ++i) { + for (auto& track : m_tracks) + track->fill_sample(buffer[i]); + + if (++m_time >= roll_length) { + m_time = 0; + if (!m_should_loop) + break; + } + } + + memcpy(m_back_buffer_ptr->data(), buffer.data(), buffer_size); + swap(m_front_buffer_ptr, m_back_buffer_ptr); +} + +void TrackManager::reset() +{ + memset(m_front_buffer.data(), 0, buffer_size); + memset(m_back_buffer.data(), 0, buffer_size); + + m_front_buffer_ptr = &m_front_buffer; + m_back_buffer_ptr = &m_back_buffer; + + m_time = 0; + + for (auto& track : m_tracks) + track->reset(); +} + +void TrackManager::set_note_current_octave(int note, Switch switch_note) +{ + current_track().set_note(note + octave_base(), switch_note); +} + +void TrackManager::set_octave(Direction direction) +{ + if (direction == Up) { + if (m_octave < octave_max) + ++m_octave; + } else { + if (m_octave > octave_min) + --m_octave; + } +} + +void TrackManager::add_track() +{ + m_tracks.append(make(m_time)); +} + +void TrackManager::next_track() +{ + if (++m_current_track >= m_tracks.size()) + m_current_track = 0; +} diff --git a/Applications/Piano/TrackManager.h b/Applications/Piano/TrackManager.h new file mode 100644 index 00000000000..2443adc3435 --- /dev/null +++ b/Applications/Piano/TrackManager.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2019-2020, William McPherson + * 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 "Music.h" +#include "Track.h" +#include +#include +#include + +class TrackManager { + AK_MAKE_NONCOPYABLE(TrackManager) + AK_MAKE_NONMOVABLE(TrackManager) +public: + TrackManager(); + ~TrackManager(); + + Track& current_track() { return *m_tracks[m_current_track]; } + const FixedArray& buffer() const { return *m_front_buffer_ptr; } + int octave() const { return m_octave; } + int octave_base() const { return (m_octave - octave_min) * 12; } + int time() const { return m_time; } + + void fill_buffer(FixedArray& buffer); + void reset(); + void set_should_loop(bool b) { m_should_loop = b; } + void set_note_current_octave(int note, Switch); + void set_octave(Direction); + void add_track(); + void next_track(); + +private: + Vector> m_tracks; + size_t m_current_track { 0 }; + + FixedArray m_front_buffer { sample_count }; + FixedArray m_back_buffer { sample_count }; + FixedArray* m_front_buffer_ptr { &m_front_buffer }; + FixedArray* m_back_buffer_ptr { &m_back_buffer }; + + int m_octave { 4 }; + + u32 m_time { 0 }; + + bool m_should_loop { true }; +}; diff --git a/Applications/Piano/WaveWidget.cpp b/Applications/Piano/WaveWidget.cpp index 9a8cdebc948..9c40b46bded 100644 --- a/Applications/Piano/WaveWidget.cpp +++ b/Applications/Piano/WaveWidget.cpp @@ -26,12 +26,12 @@ */ #include "WaveWidget.h" -#include "AudioEngine.h" +#include "TrackManager.h" #include #include -WaveWidget::WaveWidget(AudioEngine& audio_engine) - : m_audio_engine(audio_engine) +WaveWidget::WaveWidget(TrackManager& track_manager) + : m_track_manager(track_manager) { } @@ -56,9 +56,9 @@ void WaveWidget::paint_event(GUI::PaintEvent& event) painter.fill_rect(frame_inner_rect(), Color::Black); painter.translate(frame_thickness(), frame_thickness()); - Color left_wave_color = left_wave_colors[m_audio_engine.wave()]; - Color right_wave_color = right_wave_colors[m_audio_engine.wave()]; - auto buffer = m_audio_engine.buffer(); + Color left_wave_color = left_wave_colors[m_track_manager.current_track().wave()]; + Color right_wave_color = right_wave_colors[m_track_manager.current_track().wave()]; + auto buffer = m_track_manager.buffer(); double width_scale = static_cast(frame_inner_rect().width()) / buffer.size(); int prev_x = 0; diff --git a/Applications/Piano/WaveWidget.h b/Applications/Piano/WaveWidget.h index 41475eaa118..e8b9cd4d5f5 100644 --- a/Applications/Piano/WaveWidget.h +++ b/Applications/Piano/WaveWidget.h @@ -29,7 +29,7 @@ #include -class AudioEngine; +class TrackManager; class WaveWidget final : public GUI::Frame { C_OBJECT(WaveWidget) @@ -37,11 +37,11 @@ public: virtual ~WaveWidget() override; private: - explicit WaveWidget(AudioEngine&); + explicit WaveWidget(TrackManager&); virtual void paint_event(GUI::PaintEvent&) override; int sample_to_y(int sample) const; - AudioEngine& m_audio_engine; + TrackManager& m_track_manager; }; diff --git a/Applications/Piano/main.cpp b/Applications/Piano/main.cpp index 3fef83134a3..0310a29de33 100644 --- a/Applications/Piano/main.cpp +++ b/Applications/Piano/main.cpp @@ -25,8 +25,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "AudioEngine.h" #include "MainWidget.h" +#include "TrackManager.h" #include #include #include @@ -49,10 +49,10 @@ int main(int argc, char** argv) auto audio_client = Audio::ClientConnection::construct(); audio_client->handshake(); - AudioEngine audio_engine; + TrackManager track_manager; auto window = GUI::Window::construct(); - auto& main_widget = window->set_main_widget(audio_engine); + auto& main_widget = window->set_main_widget(track_manager); window->set_title("Piano"); window->set_rect(90, 90, 840, 600); window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-piano.png")); @@ -71,21 +71,21 @@ int main(int argc, char** argv) FixedArray buffer(sample_count); for (;;) { - audio_engine.fill_buffer(buffer); + track_manager.fill_buffer(buffer); audio->write(reinterpret_cast(buffer.data()), buffer_size); Core::EventLoop::current().post_event(main_widget, make(0)); Core::EventLoop::wake(); if (need_to_write_wav) { need_to_write_wav = false; - audio_engine.reset(); - audio_engine.set_should_loop(false); + track_manager.reset(); + track_manager.set_should_loop(false); do { - audio_engine.fill_buffer(buffer); + track_manager.fill_buffer(buffer); wav_writer.write_samples(reinterpret_cast(buffer.data()), buffer_size); - } while (audio_engine.time()); - audio_engine.reset(); - audio_engine.set_should_loop(true); + } while (track_manager.time()); + track_manager.reset(); + track_manager.set_should_loop(true); wav_writer.finalize(); } }