diff --git a/Userland/Applications/Piano/KeysWidget.cpp b/Userland/Applications/Piano/KeysWidget.cpp index 21f7d2a6af..e43f07fc8c 100644 --- a/Userland/Applications/Piano/KeysWidget.cpp +++ b/Userland/Applications/Piano/KeysWidget.cpp @@ -7,53 +7,32 @@ */ #include "KeysWidget.h" +#include "LibDSP/Keyboard.h" #include "TrackManager.h" #include #include #include -KeysWidget::KeysWidget(TrackManager& track_manager) - : m_track_manager(track_manager) +KeysWidget::KeysWidget(NonnullRefPtr keyboard) + : m_keyboard(move(keyboard)) { set_fill_with_background_color(true); } int KeysWidget::mouse_note() const { - if (m_mouse_down && m_mouse_note + m_track_manager.octave_base() < note_count) + if (m_mouse_down && m_mouse_note + m_keyboard->virtual_keyboard_octave_base() < note_count) return m_mouse_note; // Can be -1. else return -1; } -void KeysWidget::set_key(int key, Switch switch_key) +void KeysWidget::set_key(i8 key, LibDSP::Keyboard::Switch switch_note) { - if (key == -1 || key + m_track_manager.octave_base() >= note_count) - return; - - if (switch_key == On) { - ++m_key_on[key]; - } else { - if (m_key_on[key] >= 1) - --m_key_on[key]; - } - VERIFY(m_key_on[key] <= 2); - - m_track_manager.set_keyboard_note(key + m_track_manager.octave_base(), switch_key); + m_keyboard->set_keyboard_note_in_active_octave(key, switch_note); } -bool KeysWidget::note_is_set(int note) const -{ - if (note < m_track_manager.octave_base()) - return false; - - if (note >= m_track_manager.octave_base() + note_count) - return false; - - return m_key_on[note - m_track_manager.octave_base()] != 0; -} - -int KeysWidget::key_code_to_key(int key_code) const +i8 KeysWidget::key_code_to_key(int key_code) { switch (key_code) { case Key_A: @@ -170,7 +149,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) int i = 0; for (;;) { Gfx::IntRect rect(x, 0, white_key_width, frame_inner_rect().height()); - painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::White); + painter.fill_rect(rect, m_keyboard->is_pressed_in_active_octave(note) ? note_pressed_color : Color::White); painter.draw_rect(rect, Color::Black); if (i < white_key_labels_count) { rect.set_height(rect.height() * 1.5); @@ -181,7 +160,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) x += white_key_width; ++i; - if (note + m_track_manager.octave_base() >= note_count) + if (note + m_keyboard->virtual_keyboard_octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; @@ -192,7 +171,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) i = 0; for (;;) { Gfx::IntRect rect(x, 0, black_key_width, black_key_height); - painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::Black); + painter.fill_rect(rect, m_keyboard->is_pressed_in_active_octave(note) ? note_pressed_color : Color::Black); painter.draw_rect(rect, Color::Black); if (i < black_key_labels_count) { rect.set_height(rect.height() * 1.5); @@ -203,7 +182,7 @@ void KeysWidget::paint_event(GUI::PaintEvent& event) x += black_key_offsets[i % black_keys_per_octave]; ++i; - if (note + m_track_manager.octave_base() >= note_count) + if (note + m_keyboard->virtual_keyboard_octave_base() >= note_count) break; if (x >= frame_inner_rect().width()) break; @@ -274,7 +253,7 @@ void KeysWidget::mousedown_event(GUI::MouseEvent& event) m_mouse_note = note_for_event_position(event.position()); - set_key(m_mouse_note, On); + set_key(m_mouse_note, LibDSP::Keyboard::Switch::On); update(); } @@ -285,7 +264,7 @@ void KeysWidget::mouseup_event(GUI::MouseEvent& event) m_mouse_down = false; - set_key(m_mouse_note, Off); + set_key(m_mouse_note, LibDSP::Keyboard::Switch::Off); update(); } @@ -299,8 +278,8 @@ void KeysWidget::mousemove_event(GUI::MouseEvent& event) if (m_mouse_note == new_mouse_note) return; - set_key(m_mouse_note, Off); - set_key(new_mouse_note, On); + set_key(m_mouse_note, LibDSP::Keyboard::Switch::Off); + set_key(new_mouse_note, LibDSP::Keyboard::Switch::On); update(); m_mouse_note = new_mouse_note; diff --git a/Userland/Applications/Piano/KeysWidget.h b/Userland/Applications/Piano/KeysWidget.h index d21aba7fa0..96c4743ddf 100644 --- a/Userland/Applications/Piano/KeysWidget.h +++ b/Userland/Applications/Piano/KeysWidget.h @@ -9,6 +9,8 @@ #pragma once #include "Music.h" +#include +#include #include class TrackManager; @@ -18,14 +20,11 @@ class KeysWidget final : public GUI::Frame { public: virtual ~KeysWidget() override = default; - int key_code_to_key(int key_code) const; + static i8 key_code_to_key(int key_code); int mouse_note() const; - void set_key(int key, Switch); - bool note_is_set(int note) const; - private: - explicit KeysWidget(TrackManager&); + KeysWidget(NonnullRefPtr); virtual void paint_event(GUI::PaintEvent&) override; virtual void mousedown_event(GUI::MouseEvent&) override; @@ -34,9 +33,9 @@ private: int note_for_event_position(Gfx::IntPoint const&) const; - TrackManager& m_track_manager; + void set_key(i8 key, LibDSP::Keyboard::Switch); - u8 m_key_on[note_count] { 0 }; + NonnullRefPtr m_keyboard; bool m_mouse_down { false }; int m_mouse_note { -1 }; diff --git a/Userland/Applications/Piano/KnobsWidget.cpp b/Userland/Applications/Piano/KnobsWidget.cpp index 17fe3486fa..74281af578 100644 --- a/Userland/Applications/Piano/KnobsWidget.cpp +++ b/Userland/Applications/Piano/KnobsWidget.cpp @@ -35,7 +35,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) m_values_container->set_fixed_height(10); m_volume_value = m_values_container->add(String::number(m_track_manager.current_track().volume())); - m_octave_value = m_values_container->add(String::number(m_track_manager.octave())); + m_octave_value = m_values_container->add(String::number(m_track_manager.keyboard()->virtual_keyboard_octave())); m_knobs_container = add(); m_knobs_container->set_layout(); @@ -57,13 +57,13 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, 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_track_manager.octave() - 1)); + m_octave_knob->set_value((octave_max - 1) - (m_track_manager.keyboard()->virtual_keyboard_octave() - 1)); m_octave_knob->set_step(1); m_octave_knob->on_change = [this](int value) { int new_octave = octave_max - value; if (m_change_underlying) m_main_widget.set_octave_and_ensure_note_change(new_octave); - VERIFY(new_octave == m_track_manager.octave()); + VERIFY(new_octave == m_track_manager.keyboard()->virtual_keyboard_octave()); m_octave_value->set_text(String::number(new_octave)); }; @@ -117,7 +117,7 @@ void KnobsWidget::update_knobs() m_change_underlying = false; m_volume_knob->set_value(volume_max - m_track_manager.current_track().volume()); - m_octave_knob->set_value(octave_max - m_track_manager.octave()); + m_octave_knob->set_value(octave_max - m_track_manager.keyboard()->virtual_keyboard_octave()); m_change_underlying = true; } diff --git a/Userland/Applications/Piano/MainWidget.cpp b/Userland/Applications/Piano/MainWidget.cpp index 70ecfde1b3..a5ff250dba 100644 --- a/Userland/Applications/Piano/MainWidget.cpp +++ b/Userland/Applications/Piano/MainWidget.cpp @@ -46,7 +46,7 @@ MainWidget::MainWidget(TrackManager& track_manager, AudioPlayerLoop& loop) m_keys_and_knobs_container->set_fixed_height(130); m_keys_and_knobs_container->set_fill_with_background_color(true); - m_keys_widget = m_keys_and_knobs_container->add(track_manager); + m_keys_widget = m_keys_and_knobs_container->add(track_manager.keyboard()); m_knobs_widget = m_keys_and_knobs_container->add(track_manager, *this); @@ -85,7 +85,7 @@ void MainWidget::keydown_event(GUI::KeyEvent& event) else m_keys_pressed[event.key()] = true; - note_key_action(event.key(), On); + note_key_action(event.key(), LibDSP::Keyboard::Switch::On); special_key_action(event.key()); m_keys_widget->update(); } @@ -94,24 +94,26 @@ void MainWidget::keyup_event(GUI::KeyEvent& event) { m_keys_pressed[event.key()] = false; - note_key_action(event.key(), Off); + note_key_action(event.key(), LibDSP::Keyboard::Switch::Off); m_keys_widget->update(); } -void MainWidget::note_key_action(int key_code, Switch switch_note) +void MainWidget::note_key_action(int key_code, LibDSP::Keyboard::Switch switch_note) { - int key = m_keys_widget->key_code_to_key(key_code); - m_keys_widget->set_key(key, switch_note); + auto key = m_keys_widget->key_code_to_key(key_code); + if (key == -1) + return; + m_track_manager.keyboard()->set_keyboard_note_in_active_octave(key, switch_note); } void MainWidget::special_key_action(int key_code) { switch (key_code) { case Key_Z: - set_octave_and_ensure_note_change(Down); + set_octave_and_ensure_note_change(LibDSP::Keyboard::Direction::Down); break; case Key_X: - set_octave_and_ensure_note_change(Up); + set_octave_and_ensure_note_change(LibDSP::Keyboard::Direction::Up); break; case Key_C: m_knobs_widget->cycle_waveform(); @@ -124,36 +126,38 @@ void MainWidget::special_key_action(int key_code) void MainWidget::turn_off_pressed_keys() { - m_keys_widget->set_key(m_keys_widget->mouse_note(), Off); + if (m_keys_widget->mouse_note() != -1) + m_track_manager.keyboard()->set_keyboard_note_in_active_octave(m_keys_widget->mouse_note(), LibDSP::Keyboard::Switch::Off); for (int i = 0; i < key_code_count; ++i) { if (m_keys_pressed[i]) - note_key_action(i, Off); + note_key_action(i, LibDSP::Keyboard::Switch::Off); } } void MainWidget::turn_on_pressed_keys() { - m_keys_widget->set_key(m_keys_widget->mouse_note(), On); + if (m_keys_widget->mouse_note() != -1) + m_track_manager.keyboard()->set_keyboard_note_in_active_octave(m_keys_widget->mouse_note(), LibDSP::Keyboard::Switch::On); for (int i = 0; i < key_code_count; ++i) { if (m_keys_pressed[i]) - note_key_action(i, On); + note_key_action(i, LibDSP::Keyboard::Switch::On); } } void MainWidget::set_octave_and_ensure_note_change(int octave) { turn_off_pressed_keys(); - m_track_manager.set_octave(octave); + MUST(m_track_manager.keyboard()->set_virtual_keyboard_octave(octave)); turn_on_pressed_keys(); m_knobs_widget->update_knobs(); m_keys_widget->update(); } -void MainWidget::set_octave_and_ensure_note_change(Direction direction) +void MainWidget::set_octave_and_ensure_note_change(LibDSP::Keyboard::Direction direction) { turn_off_pressed_keys(); - m_track_manager.set_octave(direction); + m_track_manager.keyboard()->change_virtual_keyboard_octave(direction); turn_on_pressed_keys(); m_knobs_widget->update_knobs(); diff --git a/Userland/Applications/Piano/MainWidget.h b/Userland/Applications/Piano/MainWidget.h index 845465b1e5..2e2b98d76e 100644 --- a/Userland/Applications/Piano/MainWidget.h +++ b/Userland/Applications/Piano/MainWidget.h @@ -10,6 +10,7 @@ #pragma once #include "Music.h" +#include #include class AudioPlayerLoop; @@ -28,7 +29,7 @@ public: void add_track_actions(GUI::Menu&); - void set_octave_and_ensure_note_change(Direction); + void set_octave_and_ensure_note_change(LibDSP::Keyboard::Direction); void set_octave_and_ensure_note_change(int); private: @@ -38,7 +39,7 @@ private: virtual void keyup_event(GUI::KeyEvent&) override; virtual void custom_event(Core::CustomEvent&) override; - void note_key_action(int key_code, Switch); + void note_key_action(int key_code, LibDSP::Keyboard::Switch); void special_key_action(int key_code); void turn_off_pressed_keys(); @@ -56,5 +57,6 @@ private: RefPtr m_knobs_widget; RefPtr m_player_widget; + // Not the piano keys, but the computer keyboard keys! bool m_keys_pressed[key_code_count] { false }; }; diff --git a/Userland/Applications/Piano/Music.h b/Userland/Applications/Piano/Music.h index 7cb23a5286..75dc9b9ada 100644 --- a/Userland/Applications/Piano/Music.h +++ b/Userland/Applications/Piano/Music.h @@ -33,11 +33,6 @@ constexpr double sample_rate = 44100; // Headroom for the synth constexpr double volume_factor = 0.8; -enum Switch { - Off, - On, -}; - enum Direction { Down, Up, diff --git a/Userland/Applications/Piano/RollWidget.cpp b/Userland/Applications/Piano/RollWidget.cpp index 6132b5cb61..16a20d8cab 100644 --- a/Userland/Applications/Piano/RollWidget.cpp +++ b/Userland/Applications/Piano/RollWidget.cpp @@ -126,7 +126,7 @@ void RollWidget::paint_event(GUI::PaintEvent& event) int distance_to_next_x = next_x_pos - x_pos; Gfx::IntRect rect(x_pos, y_pos, distance_to_next_x, note_height); - if (keys_widget() && keys_widget()->note_is_set(note)) + if (m_track_manager.keyboard()->is_pressed(note)) painter.fill_rect(rect, note_pressed_color.with_alpha(128)); } } diff --git a/Userland/Applications/Piano/Track.cpp b/Userland/Applications/Piano/Track.cpp index 7953b634e4..117d7b350d 100644 --- a/Userland/Applications/Piano/Track.cpp +++ b/Userland/Applications/Piano/Track.cpp @@ -16,10 +16,11 @@ #include #include -Track::Track(NonnullRefPtr transport) +Track::Track(NonnullRefPtr transport, NonnullRefPtr keyboard) : m_transport(move(transport)) , m_delay(make_ref_counted(m_transport)) , m_synth(make_ref_counted(m_transport)) + , m_keyboard(move(keyboard)) { set_volume(volume_max); } @@ -29,17 +30,22 @@ void Track::fill_sample(Sample& sample) auto playing_notes = LibDSP::RollNotes {}; for (size_t i = 0; i < note_count; ++i) { + bool has_roll_notes = false; auto& notes_at_pitch = m_roll_notes[i]; for (auto& note : notes_at_pitch) { - if (note.is_playing(m_transport->time())) + if (note.is_playing(m_transport->time())) { + has_roll_notes = true; playing_notes.set(i, note); + } + } + if (m_is_active_track) { + auto key_at_pitch = m_keyboard->note_at(i); + if (key_at_pitch.has_value() && key_at_pitch.value().is_playing(m_transport->time())) + playing_notes.set(i, key_at_pitch.release_value()); + // If there are roll notes playing, don't stop them when we lift a keyboard key. + else if (!has_roll_notes) + playing_notes.remove(i); } - auto& key_at_pitch = m_keyboard_notes[i]; - if (key_at_pitch.has_value() && key_at_pitch.value().is_playing(m_transport->time())) - playing_notes.set(i, key_at_pitch.value()); - // No need to keep non-playing keyboard notes around. - else - m_keyboard_notes[i] = {}; } auto synthesized_sample = LibDSP::Signal { FixedArray::must_create_but_fixme_should_propagate_errors(1) }; @@ -105,24 +111,6 @@ void Track::set_roll_note(int note, u32 on_sample, u32 off_sample) sync_roll(note); } -void Track::set_keyboard_note(int note, Switch state) -{ - VERIFY(note >= 0 && note < note_count); - if (state == Switch::Off) { - // If the note is playing, we need to start releasing it, otherwise just delete - if (auto& maybe_roll_note = m_keyboard_notes[note]; maybe_roll_note.has_value()) { - auto& roll_note = maybe_roll_note.value(); - if (roll_note.is_playing(m_transport->time())) - roll_note.off_sample = m_transport->time(); - else - m_keyboard_notes[note] = {}; - } - } else - // FIXME: The end time needs to be far in the future. - m_keyboard_notes[note] - = RollNote { m_transport->time(), m_transport->time() + static_cast(sample_rate) * 10'000, static_cast(note), 0 }; -} - void Track::set_volume(int volume) { VERIFY(volume >= 0); diff --git a/Userland/Applications/Piano/Track.h b/Userland/Applications/Piano/Track.h index a78e6fb37e..bd0800acf4 100644 --- a/Userland/Applications/Piano/Track.h +++ b/Userland/Applications/Piano/Track.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,7 @@ class Track { AK_MAKE_NONMOVABLE(Track); public: - Track(NonnullRefPtr); + Track(NonnullRefPtr, NonnullRefPtr); ~Track() = default; Vector const& recorded_sample() const { return m_recorded_sample; } @@ -39,8 +40,8 @@ public: void reset(); String set_recorded_sample(StringView path); void set_roll_note(int note, u32 on_sample, u32 off_sample); - void set_keyboard_note(int note, Switch state); void set_volume(int volume); + void set_active(bool active) { m_is_active_track = active; } private: Audio::Sample recorded_sample(size_t note); @@ -57,5 +58,6 @@ private: SinglyLinkedList m_roll_notes[note_count]; RollIter m_roll_iterators[note_count]; - Array, note_count> m_keyboard_notes; + NonnullRefPtr m_keyboard; + bool m_is_active_track { false }; }; diff --git a/Userland/Applications/Piano/TrackManager.cpp b/Userland/Applications/Piano/TrackManager.cpp index 408a64d277..d4fc774905 100644 --- a/Userland/Applications/Piano/TrackManager.cpp +++ b/Userland/Applications/Piano/TrackManager.cpp @@ -13,8 +13,10 @@ TrackManager::TrackManager() : m_transport(make_ref_counted(120, 4)) + , m_keyboard(make_ref_counted(m_transport)) { add_track(); + m_tracks[m_current_track]->set_active(true); } void TrackManager::time_forward(int amount) @@ -59,36 +61,16 @@ void TrackManager::reset() m_transport->set_time(0); - for (auto& track : m_tracks) + for (auto& track : m_tracks) { track->reset(); -} - -void TrackManager::set_keyboard_note(int note, Switch note_switch) -{ - m_tracks[m_current_track]->set_keyboard_note(note, note_switch); -} - -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::set_octave(int octave) -{ - if (octave <= octave_max && octave >= octave_min) { - m_octave = octave; + track->set_active(false); } + m_tracks[m_current_track]->set_active(true); } void TrackManager::add_track() { - m_tracks.append(make(m_transport)); + m_tracks.append(make(m_transport, m_keyboard)); } int TrackManager::next_track_index() const diff --git a/Userland/Applications/Piano/TrackManager.h b/Userland/Applications/Piano/TrackManager.h index f1985971f2..7e96d5544a 100644 --- a/Userland/Applications/Piano/TrackManager.h +++ b/Userland/Applications/Piano/TrackManager.h @@ -9,6 +9,8 @@ #pragma once +#include "AK/NonnullRefPtr.h" +#include "LibDSP/Keyboard.h" #include "Music.h" #include "Track.h" #include @@ -26,30 +28,32 @@ public: Track& current_track() { return *m_tracks[m_current_track]; } Span buffer() const { return m_current_front_buffer; } - int octave() const { return m_octave; } - int octave_base() const { return (m_octave - octave_min) * 12; } int track_count() { return m_tracks.size(); }; void set_current_track(size_t track_index) { VERIFY((int)track_index < track_count()); + auto old_track = m_current_track; m_current_track = track_index; + m_tracks[old_track]->set_active(false); + m_tracks[m_current_track]->set_active(true); } NonnullRefPtr transport() const { return m_transport; } + NonnullRefPtr keyboard() const { return m_keyboard; } // Legacy API, do not add new users. void time_forward(int amount); void fill_buffer(Span); void reset(); - void set_keyboard_note(int note, Switch note_switch); + void set_keyboard_note(int note, LibDSP::Keyboard::Switch note_switch); void set_should_loop(bool b) { m_should_loop = b; } - void set_octave(Direction); - void set_octave(int octave); void add_track(); int next_track_index() const; private: Vector> m_tracks; + NonnullRefPtr m_transport; + NonnullRefPtr m_keyboard; size_t m_current_track { 0 }; Array m_front_buffer; @@ -57,9 +61,5 @@ private: Span m_current_front_buffer { m_front_buffer.span() }; Span m_current_back_buffer { m_back_buffer.span() }; - int m_octave { 4 }; - - NonnullRefPtr m_transport; - bool m_should_loop { true }; };