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.
This commit is contained in:
William McPherson 2020-06-15 15:33:53 +10:00 committed by Andreas Kling
parent db5b28b78e
commit ee52572ca1
Notes: sideshowbarker 2024-07-19 05:34:24 +09:00
18 changed files with 371 additions and 247 deletions

View File

@ -1,5 +1,6 @@
set(SOURCES
AudioEngine.cpp
Track.cpp
TrackManager.cpp
KeysWidget.cpp
KnobsWidget.cpp
main.cpp

View File

@ -26,11 +26,11 @@
*/
#include "KeysWidget.h"
#include "AudioEngine.h"
#include "TrackManager.h"
#include <LibGUI/Painter.h>
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;

View File

@ -30,7 +30,7 @@
#include "Music.h"
#include <LibGUI/Frame.h>
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 };

View File

@ -26,14 +26,14 @@
*/
#include "KnobsWidget.h"
#include "AudioEngine.h"
#include "MainWidget.h"
#include "TrackManager.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Label.h>
#include <LibGUI/Slider.h>
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<GUI::VerticalBoxLayout>();
@ -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<GUI::Label>(String::number(m_audio_engine.octave()));
m_wave_value = m_values_container->add<GUI::Label>(wave_strings[m_audio_engine.wave()]);
m_attack_value = m_values_container->add<GUI::Label>(String::number(m_audio_engine.attack()));
m_decay_value = m_values_container->add<GUI::Label>(String::number(m_audio_engine.decay()));
m_sustain_value = m_values_container->add<GUI::Label>(String::number(m_audio_engine.sustain()));
m_release_value = m_values_container->add<GUI::Label>(String::number(m_audio_engine.release()));
m_delay_value = m_values_container->add<GUI::Label>(String::number(m_audio_engine.delay()));
m_octave_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.octave()));
m_wave_value = m_values_container->add<GUI::Label>(wave_strings[m_track_manager.current_track().wave()]);
m_attack_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().attack()));
m_decay_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().decay()));
m_sustain_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().sustain()));
m_release_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().release()));
m_delay_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().delay()));
m_knobs_container = add<GUI::Widget>();
m_knobs_container->set_layout<GUI::HorizontalBoxLayout>();
@ -73,82 +73,82 @@ KnobsWidget::KnobsWidget(AudioEngine& audio_engine, MainWidget& main_widget)
m_octave_knob = m_knobs_container->add<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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<GUI::VerticalSlider>();
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;
}

View File

@ -29,7 +29,7 @@
#include <LibGUI/Frame.h>
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<GUI::Widget> m_labels_container;

View File

@ -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 <LibGUI/BoxLayout.h>
#include <LibGUI/TabWidget.h>
MainWidget::MainWidget(AudioEngine& audio_engine)
: m_audio_engine(audio_engine)
MainWidget::MainWidget(TrackManager& track_manager)
: m_track_manager(track_manager)
{
set_layout<GUI::VerticalBoxLayout>();
layout()->set_spacing(2);
layout()->set_margins({ 2, 2, 2, 2 });
set_fill_with_background_color(true);
m_wave_widget = add<WaveWidget>(audio_engine);
m_wave_widget = add<WaveWidget>(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<GUI::TabWidget>();
m_roll_widget = m_tab_widget->add_tab<RollWidget>("Piano Roll", audio_engine);
m_roll_widget = m_tab_widget->add_tab<RollWidget>("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<SamplerWidget>("Sampler", audio_engine);
m_tab_widget->add_tab<SamplerWidget>("Sampler", track_manager);
m_keys_and_knobs_container = add<GUI::Widget>();
m_keys_and_knobs_container->set_layout<GUI::HorizontalBoxLayout>();
@ -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<KeysWidget>(audio_engine);
m_keys_widget = m_keys_and_knobs_container->add<KeysWidget>(track_manager);
m_knobs_widget = m_keys_and_knobs_container->add<KnobsWidget>(audio_engine, *this);
m_knobs_widget = m_keys_and_knobs_container->add<KnobsWidget>(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) {

View File

@ -30,7 +30,7 @@
#include "Music.h"
#include <LibGUI/Widget.h>
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<WaveWidget> m_wave_widget;
RefPtr<RollWidget> m_roll_widget;

View File

@ -26,7 +26,7 @@
*/
#include "RollWidget.h"
#include "AudioEngine.h"
#include "TrackManager.h"
#include <LibGUI/Painter.h>
#include <LibGUI/ScrollBar.h>
#include <math.h>
@ -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<double>(roll_note.on_sample) / roll_length);
int width = m_roll_width * (static_cast<double>(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<double>(m_audio_engine.time()) / roll_length);
int x = m_roll_width * (static_cast<double>(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<double>(x) / m_num_notes);
u32 off_sample = (roll_length * (static_cast<double>(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();
}

View File

@ -30,7 +30,7 @@
#include "Music.h"
#include <LibGUI/ScrollableWidget.h>
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;

View File

@ -25,7 +25,7 @@
*/
#include "SamplerWidget.h"
#include "AudioEngine.h"
#include "TrackManager.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/FilePicker.h>
@ -33,8 +33,8 @@
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h>
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<GUI::VerticalBoxLayout>();
layout()->set_margins({ 10, 10, 10, 10 });
@ -111,7 +111,7 @@ SamplerWidget::SamplerWidget(AudioEngine& audio_engine)
Optional<String> 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<GUI::Label>("No sample loaded");
m_recorded_sample_name->set_text_alignment(Gfx::TextAlignment::CenterLeft);
m_wave_editor = add<WaveEditor>(m_audio_engine);
m_wave_editor = add<WaveEditor>(m_track_manager);
m_wave_editor->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
m_wave_editor->set_preferred_size(0, 100);
}

View File

@ -28,7 +28,7 @@
#include <LibGUI/Frame.h>
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<GUI::Widget> m_open_button_and_recorded_sample_name_container;
RefPtr<GUI::Button> m_open_button;

View File

@ -25,12 +25,13 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "AudioEngine.h"
#include "Track.h"
#include <AK/NumericLimits.h>
#include <LibAudio/WavLoader.h>
#include <math.h>
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<Sample>& 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<double>(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<int>(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;

View File

@ -35,34 +35,27 @@
typedef AK::SinglyLinkedListIterator<SinglyLinkedList<RollNote>, 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<Sample>& buffer() const { return *m_front_buffer_ptr; }
const Vector<Audio::Sample>& recorded_sample() const { return m_recorded_sample; }
const SinglyLinkedList<RollNote>& 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<Sample>& 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<Sample> m_front_buffer { sample_count };
FixedArray<Sample> m_back_buffer { sample_count };
FixedArray<Sample>* m_front_buffer_ptr { &m_front_buffer };
FixedArray<Sample>* m_back_buffer_ptr { &m_back_buffer };
Vector<Sample> m_delay_buffer;
Vector<Audio::Sample> 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<RollNote> m_roll_notes[note_count];
RollIter m_roll_iters[note_count];

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* 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<Sample>& 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<Track>(m_time));
}
void TrackManager::next_track()
{
if (++m_current_track >= m_tracks.size())
m_current_track = 0;
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* 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 <AK/Noncopyable.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Vector.h>
class TrackManager {
AK_MAKE_NONCOPYABLE(TrackManager)
AK_MAKE_NONMOVABLE(TrackManager)
public:
TrackManager();
~TrackManager();
Track& current_track() { return *m_tracks[m_current_track]; }
const FixedArray<Sample>& 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<Sample>& 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<NonnullOwnPtr<Track>> m_tracks;
size_t m_current_track { 0 };
FixedArray<Sample> m_front_buffer { sample_count };
FixedArray<Sample> m_back_buffer { sample_count };
FixedArray<Sample>* m_front_buffer_ptr { &m_front_buffer };
FixedArray<Sample>* m_back_buffer_ptr { &m_back_buffer };
int m_octave { 4 };
u32 m_time { 0 };
bool m_should_loop { true };
};

View File

@ -26,12 +26,12 @@
*/
#include "WaveWidget.h"
#include "AudioEngine.h"
#include "TrackManager.h"
#include <AK/NumericLimits.h>
#include <LibGUI/Painter.h>
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<double>(frame_inner_rect().width()) / buffer.size();
int prev_x = 0;

View File

@ -29,7 +29,7 @@
#include <LibGUI/Frame.h>
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;
};

View File

@ -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 <LibAudio/ClientConnection.h>
#include <LibAudio/WavWriter.h>
#include <LibCore/EventLoop.h>
@ -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<MainWidget>(audio_engine);
auto& main_widget = window->set_main_widget<MainWidget>(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<Sample> buffer(sample_count);
for (;;) {
audio_engine.fill_buffer(buffer);
track_manager.fill_buffer(buffer);
audio->write(reinterpret_cast<u8*>(buffer.data()), buffer_size);
Core::EventLoop::current().post_event(main_widget, make<Core::CustomEvent>(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<u8*>(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();
}
}