diff --git a/Applications/Piano/CMakeLists.txt b/Applications/Piano/CMakeLists.txt index 0ba5a8ce91..6756afb18c 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 c3428120d6..3645c973ab 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 59bb6e5a4a..b5e4a066d4 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 7e68687cff..0296124bf1 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 315141144e..f7623df3dc 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 ccfdb35e33..207954738c 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 6d1e80c4c1..6a2a543675 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 d9968225d9..738bef8f9e 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 235d424412..4311038218 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 f3f116ebe4..e08026453e 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 11c4ec90db..9091e5fef9 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 f4f6bb9560..22cbbe03f8 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 aae9554520..99f3e1f3ab 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 0000000000..2a9419e3be --- /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 0000000000..2443adc343 --- /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 9a8cdebc94..9c40b46bde 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 41475eaa11..e8b9cd4d5f 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 3fef83134a..0310a29de3 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(); } }