diff options
author | kleines Filmröllchen <malu.bertsch@gmail.com> | 2021-09-28 18:01:39 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-11-22 22:26:17 +0100 |
commit | b14a64b3c8a2b3a004e1b7bdc5bae435f7b48219 (patch) | |
tree | 339e5ba4d2f102db8b5f7168b30bd8f18523cc6a | |
parent | 3ca059da2ddb170014781b77484a12acd3c1914e (diff) | |
download | serenity-b14a64b3c8a2b3a004e1b7bdc5bae435f7b48219.zip |
Piano: Move to LibDSP's Classic synthesizer
Almost all synthesizer code in Piano is removed in favor of the LibDSP
reimplementation.
This causes some issues that mainly have to do with the way Piano
currently handles talking to LibDSP. Additionally, the sampler is gone
for now and will be reintroduced with future work.
-rw-r--r-- | Userland/Applications/Piano/KeysWidget.cpp | 2 | ||||
-rw-r--r-- | Userland/Applications/Piano/KnobsWidget.cpp | 114 | ||||
-rw-r--r-- | Userland/Applications/Piano/KnobsWidget.h | 25 | ||||
-rw-r--r-- | Userland/Applications/Piano/MainWidget.cpp | 3 | ||||
-rw-r--r-- | Userland/Applications/Piano/Music.h | 46 | ||||
-rw-r--r-- | Userland/Applications/Piano/RollWidget.h | 2 | ||||
-rw-r--r-- | Userland/Applications/Piano/SamplerWidget.cpp | 6 | ||||
-rw-r--r-- | Userland/Applications/Piano/Track.cpp | 312 | ||||
-rw-r--r-- | Userland/Applications/Piano/Track.h | 42 | ||||
-rw-r--r-- | Userland/Applications/Piano/TrackManager.cpp | 5 | ||||
-rw-r--r-- | Userland/Applications/Piano/TrackManager.h | 2 | ||||
-rw-r--r-- | Userland/Applications/Piano/WaveWidget.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibDSP/ProcessorParameter.h | 24 | ||||
-rw-r--r-- | Userland/Libraries/LibDSP/Synthesizers.cpp | 5 |
14 files changed, 128 insertions, 464 deletions
diff --git a/Userland/Applications/Piano/KeysWidget.cpp b/Userland/Applications/Piano/KeysWidget.cpp index 08203a58d2..ae3124154a 100644 --- a/Userland/Applications/Piano/KeysWidget.cpp +++ b/Userland/Applications/Piano/KeysWidget.cpp @@ -42,7 +42,7 @@ void KeysWidget::set_key(int key, Switch switch_key) } VERIFY(m_key_on[key] <= 2); - m_track_manager.set_note_current_octave(key, switch_key); + m_track_manager.set_keyboard_note(key + m_track_manager.octave_base(), switch_key); } bool KeysWidget::note_is_set(int note) const diff --git a/Userland/Applications/Piano/KnobsWidget.cpp b/Userland/Applications/Piano/KnobsWidget.cpp index ed13dfe851..2e84504cd7 100644 --- a/Userland/Applications/Piano/KnobsWidget.cpp +++ b/Userland/Applications/Piano/KnobsWidget.cpp @@ -7,7 +7,9 @@ #include "KnobsWidget.h" #include "MainWidget.h" +#include "ProcessorParameterWidget/Slider.h" #include "TrackManager.h" +#include <LibDSP/ProcessorParameter.h> #include <LibGUI/BoxLayout.h> #include <LibGUI/Label.h> #include <LibGUI/Slider.h> @@ -26,11 +28,6 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) m_volume_label = m_labels_container->add<GUI::Label>("Volume"); m_octave_label = m_labels_container->add<GUI::Label>("Octave"); - m_wave_label = m_labels_container->add<GUI::Label>("Wave"); - m_attack_label = m_labels_container->add<GUI::Label>("Attack"); - m_decay_label = m_labels_container->add<GUI::Label>("Decay"); - m_sustain_label = m_labels_container->add<GUI::Label>("Sustain"); - m_release_label = m_labels_container->add<GUI::Label>("Release"); m_values_container = add<GUI::Widget>(); m_values_container->set_layout<GUI::HorizontalBoxLayout>(); @@ -38,11 +35,6 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) m_volume_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().volume())); 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_knobs_container = add<GUI::Widget>(); m_knobs_container->set_layout<GUI::HorizontalBoxLayout>(); @@ -74,70 +66,32 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) 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_track_manager.current_track().wave()); - m_wave_knob->set_step(1); - m_wave_knob->on_change = [this](int value) { - int new_wave = last_wave - value; - if (m_change_underlying) - m_track_manager.current_track().set_wave(new_wave); - VERIFY(new_wave == m_track_manager.current_track().wave()); - m_wave_value->set_text(wave_strings[new_wave]); - }; - - m_attack_knob = m_knobs_container->add<GUI::VerticalSlider>(); - m_attack_knob->set_tooltip("Envelope attack in milliseconds"); - m_attack_knob->set_range(0, attack_max); - m_attack_knob->set_value(attack_max - m_track_manager.current_track().attack()); - m_attack_knob->set_step(25); - m_attack_knob->on_change = [this](int value) { - int new_attack = attack_max - value; - if (m_change_underlying) - m_track_manager.current_track().set_attack(new_attack); - VERIFY(new_attack == m_track_manager.current_track().attack()); - m_attack_value->set_text(String::number(new_attack)); - }; - - m_decay_knob = m_knobs_container->add<GUI::VerticalSlider>(); - m_decay_knob->set_tooltip("Envelope decay in milliseconds"); - m_decay_knob->set_range(0, decay_max); - m_decay_knob->set_value(decay_max - m_track_manager.current_track().decay()); - m_decay_knob->set_step(25); - m_decay_knob->on_change = [this](int value) { - int new_decay = decay_max - value; - if (m_change_underlying) - m_track_manager.current_track().set_decay(new_decay); - VERIFY(new_decay == m_track_manager.current_track().decay()); - m_decay_value->set_text(String::number(new_decay)); - }; - - m_sustain_knob = m_knobs_container->add<GUI::VerticalSlider>(); - m_sustain_knob->set_tooltip("Envelope sustain level percent"); - m_sustain_knob->set_range(0, sustain_max); - m_sustain_knob->set_value(sustain_max - m_track_manager.current_track().sustain()); - m_sustain_knob->set_step(25); - m_sustain_knob->on_change = [this](int value) { - int new_sustain = sustain_max - value; - if (m_change_underlying) - m_track_manager.current_track().set_sustain(new_sustain); - VERIFY(new_sustain == m_track_manager.current_track().sustain()); - m_sustain_value->set_text(String::number(new_sustain)); - }; - - m_release_knob = m_knobs_container->add<GUI::VerticalSlider>(); - m_release_knob->set_tooltip("Envelope release in milliseconds"); - m_release_knob->set_range(0, release_max); - m_release_knob->set_value(release_max - m_track_manager.current_track().release()); - m_release_knob->set_step(25); - m_release_knob->on_change = [this](int value) { - int new_release = release_max - value; - if (m_change_underlying) - m_track_manager.current_track().set_release(new_release); - VERIFY(new_release == m_track_manager.current_track().release()); - m_release_value->set_text(String::number(new_release)); - }; + for (auto& raw_parameter : m_track_manager.current_track().synth()->parameters()) { + // The synth has range and enum parameters + switch (raw_parameter.type()) { + case LibDSP::ParameterType::Range: { + auto& parameter = static_cast<LibDSP::ProcessorRangeParameter&>(raw_parameter); + m_synth_values.append(m_values_container->add<GUI::Label>(String::number(static_cast<double>(parameter.value())))); + auto& parameter_knob_value = m_synth_values.last(); + m_synth_labels.append(m_labels_container->add<GUI::Label>(String::formatted("Synth: {}", parameter.name()))); + m_synth_knobs.append(m_knobs_container->add<ProcessorParameterSlider>(Orientation::Vertical, parameter, parameter_knob_value)); + break; + } + case LibDSP::ParameterType::Enum: { + // FIXME: We shouldn't do that, but we know the synth and it is nice + auto& parameter = static_cast<LibDSP::ProcessorEnumParameter<LibDSP::Synthesizers::Waveform>&>(raw_parameter); + // The value is empty for enum parameters + m_synth_values.append(m_values_container->add<GUI::Label>(String::empty())); + m_synth_labels.append(m_labels_container->add<GUI::Label>(String::formatted("Synth: {}", parameter.name()))); + auto enum_strings = Vector<String> { "Sine", "Triangle", "Square", "Saw", "Noise" }; + m_synth_knobs.append(m_knobs_container->add<ProcessorParameterDropdown<LibDSP::Synthesizers::Waveform>>(parameter, move(enum_strings))); + m_synth_waveform = static_cast<ProcessorParameterDropdown<LibDSP::Synthesizers::Waveform>&>(m_synth_knobs.last()); + break; + } + default: + VERIFY_NOT_REACHED(); + } + } for (auto& raw_parameter : m_track_manager.current_track().delay()->parameters()) { // FIXME: We shouldn't do that, but we know the effect and it's nice. @@ -153,10 +107,13 @@ KnobsWidget::~KnobsWidget() { } -void KnobsWidget::update_knobs() +void KnobsWidget::cycle_waveform() { - m_wave_knob->set_value(last_wave - m_track_manager.current_track().wave()); + m_synth_waveform->set_selected_index((m_synth_waveform->selected_index() + 1) % m_synth_waveform->model()->row_count()); +} +void KnobsWidget::update_knobs() +{ // FIXME: This is needed because when the slider is changed normally, we // need to change the underlying value, but if the keyboard was used, we // need to change the slider without changing the underlying value. @@ -164,11 +121,6 @@ void KnobsWidget::update_knobs() 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_wave_knob->set_value(last_wave - m_track_manager.current_track().wave()); - m_attack_knob->set_value(attack_max - m_track_manager.current_track().attack()); - m_decay_knob->set_value(decay_max - m_track_manager.current_track().decay()); - m_sustain_knob->set_value(sustain_max - m_track_manager.current_track().sustain()); - m_release_knob->set_value(release_max - m_track_manager.current_track().release()); m_change_underlying = true; } diff --git a/Userland/Applications/Piano/KnobsWidget.h b/Userland/Applications/Piano/KnobsWidget.h index 48c4c5f364..25c6add299 100644 --- a/Userland/Applications/Piano/KnobsWidget.h +++ b/Userland/Applications/Piano/KnobsWidget.h @@ -7,9 +7,14 @@ #pragma once +#include "ProcessorParameterWidget/Dropdown.h" #include "ProcessorParameterWidget/Slider.h" #include <AK/NonnullRefPtrVector.h> +#include <LibDSP/ProcessorParameter.h> +#include <LibDSP/Synthesizers.h> #include <LibGUI/Frame.h> +#include <LibGUI/Label.h> +#include <LibGUI/Widget.h> class TrackManager; class MainWidget; @@ -20,6 +25,7 @@ public: virtual ~KnobsWidget() override; void update_knobs(); + void cycle_waveform(); private: KnobsWidget(TrackManager&, MainWidget&); @@ -30,31 +36,20 @@ private: RefPtr<GUI::Widget> m_labels_container; RefPtr<GUI::Label> m_volume_label; RefPtr<GUI::Label> m_octave_label; - RefPtr<GUI::Label> m_wave_label; - RefPtr<GUI::Label> m_attack_label; - RefPtr<GUI::Label> m_decay_label; - RefPtr<GUI::Label> m_sustain_label; - RefPtr<GUI::Label> m_release_label; + NonnullRefPtrVector<GUI::Label> m_synth_labels; NonnullRefPtrVector<GUI::Label> m_delay_labels; RefPtr<GUI::Widget> m_values_container; RefPtr<GUI::Label> m_volume_value; RefPtr<GUI::Label> m_octave_value; - RefPtr<GUI::Label> m_wave_value; - RefPtr<GUI::Label> m_attack_value; - RefPtr<GUI::Label> m_decay_value; - RefPtr<GUI::Label> m_sustain_value; - RefPtr<GUI::Label> m_release_value; + NonnullRefPtrVector<GUI::Label> m_synth_values; NonnullRefPtrVector<GUI::Label> m_delay_values; RefPtr<GUI::Widget> m_knobs_container; RefPtr<GUI::Slider> m_volume_knob; RefPtr<GUI::Slider> m_octave_knob; - RefPtr<GUI::Slider> m_wave_knob; - RefPtr<GUI::Slider> m_attack_knob; - RefPtr<GUI::Slider> m_decay_knob; - RefPtr<GUI::Slider> m_sustain_knob; - RefPtr<GUI::Slider> m_release_knob; + RefPtr<ProcessorParameterDropdown<LibDSP::Synthesizers::Waveform>> m_synth_waveform; + NonnullRefPtrVector<GUI::Widget> m_synth_knobs; NonnullRefPtrVector<ProcessorParameterSlider> m_delay_knobs; bool m_change_underlying { true }; diff --git a/Userland/Applications/Piano/MainWidget.cpp b/Userland/Applications/Piano/MainWidget.cpp index c789e2fad0..8e7a1a4bd2 100644 --- a/Userland/Applications/Piano/MainWidget.cpp +++ b/Userland/Applications/Piano/MainWidget.cpp @@ -117,8 +117,7 @@ void MainWidget::special_key_action(int key_code) set_octave_and_ensure_note_change(Up); break; case Key_C: - m_track_manager.current_track().set_wave(Up); - m_knobs_widget->update_knobs(); + m_knobs_widget->cycle_waveform(); break; } } diff --git a/Userland/Applications/Piano/Music.h b/Userland/Applications/Piano/Music.h index ce2c814735..c4b662db8b 100644 --- a/Userland/Applications/Piano/Music.h +++ b/Userland/Applications/Piano/Music.h @@ -38,48 +38,11 @@ enum Switch { On, }; -struct RollNote { - u32 length() const { return (off_sample - on_sample) + 1; } - - u32 on_sample; - u32 off_sample; - u8 pitch; - i8 velocity; -}; - enum Direction { Down, Up, }; -enum Wave { - Sine, - Triangle, - Square, - Saw, - Noise, - RecordedSample, -}; - -constexpr const char* wave_strings[] = { - "Sine", - "Triangle", - "Square", - "Saw", - "Noise", - "Frame", -}; - -constexpr int first_wave = Sine; -constexpr int last_wave = RecordedSample; - -enum Envelope { - Done, - Attack, - Decay, - Release, -}; - enum KeyColor { White, Black, @@ -142,6 +105,9 @@ const Color left_wave_colors[] = { }, }; +// HACK: make the display code shut up for now +constexpr int RecordedSample = 5; + const Color right_wave_colors[] = { // Sine { @@ -187,13 +153,7 @@ constexpr int black_keys_per_octave = 5; constexpr int octave_min = 1; constexpr int octave_max = 7; -// These values represent the user-side bounds, the application may use a different scale. -constexpr int attack_max = 1000; -constexpr int decay_max = 1000; -constexpr int sustain_max = 1000; -constexpr int release_max = 1000; constexpr int volume_max = 1000; -constexpr int delay_max = 8; constexpr double beats_per_minute = 60; constexpr int beats_per_bar = 4; diff --git a/Userland/Applications/Piano/RollWidget.h b/Userland/Applications/Piano/RollWidget.h index db33f2aa62..d3ce7e5375 100644 --- a/Userland/Applications/Piano/RollWidget.h +++ b/Userland/Applications/Piano/RollWidget.h @@ -10,9 +10,11 @@ #include "KeysWidget.h" #include "Music.h" +#include <LibDSP/Music.h> #include <LibGUI/AbstractScrollableWidget.h> class TrackManager; +using LibDSP::RollNote; class RollWidget final : public GUI::AbstractScrollableWidget { C_OBJECT(RollWidget) diff --git a/Userland/Applications/Piano/SamplerWidget.cpp b/Userland/Applications/Piano/SamplerWidget.cpp index 14051a4538..afa910c3f9 100644 --- a/Userland/Applications/Piano/SamplerWidget.cpp +++ b/Userland/Applications/Piano/SamplerWidget.cpp @@ -89,11 +89,7 @@ SamplerWidget::SamplerWidget(TrackManager& track_manager) Optional<String> open_path = GUI::FilePicker::get_open_filepath(window()); if (!open_path.has_value()) return; - String error_string = m_track_manager.current_track().set_recorded_sample(open_path.value()); - if (!error_string.is_empty()) { - GUI::MessageBox::show(window(), String::formatted("Failed to load WAV file: {}", error_string.characters()), "Error", GUI::MessageBox::Type::Error); - return; - } + // TODO: We don't actually load the sample. m_recorded_sample_name->set_text(open_path.value()); m_wave_editor->update(); }; diff --git a/Userland/Applications/Piano/Track.cpp b/Userland/Applications/Piano/Track.cpp index 40899e4568..0a3f176cdf 100644 --- a/Userland/Applications/Piano/Track.cpp +++ b/Userland/Applications/Piano/Track.cpp @@ -7,7 +7,9 @@ */ #include "Track.h" +#include "Music.h" #include <AK/Math.h> +#include <AK/NonnullRefPtr.h> #include <AK/NumericLimits.h> #include <LibAudio/Loader.h> #include <LibDSP/Music.h> @@ -17,12 +19,9 @@ Track::Track(const u32& time) : m_time(time) , m_temporary_transport(LibDSP::Transport::construct(120, 4)) , m_delay(make_ref_counted<LibDSP::Effects::Delay>(m_temporary_transport)) + , m_synth(make_ref_counted<LibDSP::Synthesizers::Classic>(m_temporary_transport)) { set_volume(volume_max); - set_sustain_impl(1000); - set_attack(5); - set_decay(1000); - set_release(5); } Track::~Track() @@ -31,237 +30,44 @@ Track::~Track() void Track::fill_sample(Sample& sample) { - Audio::Sample new_sample; + m_temporary_transport->time() = m_time; - for (size_t note = 0; note < note_count; ++note) { - if (!m_roll_iterators[note].is_end()) { - if (m_roll_iterators[note]->on_sample == m_time) { - set_note(note, On); - } else if (m_roll_iterators[note]->off_sample == m_time) { - set_note(note, Off); - ++m_roll_iterators[note]; - if (m_roll_iterators[note].is_end()) - m_roll_iterators[note] = m_roll_notes[note].begin(); - } - } + auto playing_notes = LibDSP::RollNotes {}; - 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; - } - break; - default: - VERIFY_NOT_REACHED(); + for (size_t i = 0; i < note_count; ++i) { + auto& notes_at_pitch = m_roll_notes[i]; + for (auto& note : notes_at_pitch) { + if (note.is_playing(m_time)) + playing_notes.set(i, note); } - - 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(note); - break; - case Wave::RecordedSample: - note_sample = recorded_sample(note); - break; - default: - VERIFY_NOT_REACHED(); - } - new_sample.left += note_sample.left * m_power[note] * NumericLimits<i16>::max() * volume_factor * (static_cast<double>(volume()) / volume_max); - new_sample.right += note_sample.right * m_power[note] * NumericLimits<i16>::max() * volume_factor * (static_cast<double>(volume()) / volume_max); + auto& key_at_pitch = m_keyboard_notes[i]; + if (key_at_pitch.has_value() && key_at_pitch.value().is_playing(m_time)) + playing_notes.set(i, key_at_pitch.value()); + // No need to keep non-playing keyboard notes around. + else + m_keyboard_notes[i] = {}; } - auto new_sample_dsp = LibDSP::Signal(LibDSP::Sample { new_sample.left / NumericLimits<i16>::max(), new_sample.right / NumericLimits<i16>::max() }); - auto delayed_sample = m_delay->process(new_sample_dsp).get<LibDSP::Sample>(); - - new_sample.left = delayed_sample.left * NumericLimits<i16>::max(); - new_sample.right = delayed_sample.right * NumericLimits<i16>::max(); + auto synthesized_sample = m_synth->process(playing_notes).get<LibDSP::Sample>(); + auto delayed_sample = m_delay->process(synthesized_sample).get<LibDSP::Sample>(); - new_sample.left = clamp(new_sample.left, NumericLimits<i16>::min(), NumericLimits<i16>::max()); - new_sample.right = clamp(new_sample.right, NumericLimits<i16>::min(), NumericLimits<i16>::max()); + // HACK: Convert to old Piano range: 16-bit int + delayed_sample *= NumericLimits<i16>::max(); + delayed_sample.left = clamp(delayed_sample.left, NumericLimits<i16>::min(), NumericLimits<i16>::max()); + delayed_sample.right = clamp(delayed_sample.right, NumericLimits<i16>::min(), NumericLimits<i16>::max()); + // TODO: Use the master processor + delayed_sample *= static_cast<double>(m_volume) / static_cast<double>(volume_max) * volume_factor; - sample.left += new_sample.left; - sample.right += new_sample.right; + sample.left += delayed_sample.left; + sample.right += delayed_sample.right; } void Track::reset() { - - memset(m_note_on, 0, sizeof(m_note_on)); - memset(m_power, 0, sizeof(m_power)); - memset(m_envelope, 0, sizeof(m_envelope)); - for (size_t note = 0; note < note_count; ++note) m_roll_iterators[note] = m_roll_notes[note].begin(); } -String Track::set_recorded_sample(StringView path) -{ - NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); - if (loader->has_error()) - return String(loader->error_string()); - auto buffer = loader->get_more_samples(60 * loader->sample_rate()); // 1 minute maximum - if (loader->has_error()) - return String(loader->error_string()); - // Resample to Piano's internal sample rate - auto resampler = Audio::ResampleHelper<double>(loader->sample_rate(), sample_rate); - buffer = Audio::resample_buffer(resampler, *buffer); - - if (!m_recorded_sample.is_empty()) - m_recorded_sample.clear(); - m_recorded_sample.resize(buffer->sample_count()); - - double peak = 0; - for (int i = 0; i < buffer->sample_count(); ++i) { - double left_abs = fabs(buffer->samples()[i].left); - double right_abs = fabs(buffer->samples()[i].right); - if (left_abs > peak) - peak = left_abs; - if (right_abs > peak) - peak = right_abs; - } - - if (peak) { - for (int i = 0; i < buffer->sample_count(); ++i) { - m_recorded_sample[i].left = buffer->samples()[i].left / peak; - m_recorded_sample[i].right = buffer->samples()[i].right / peak; - } - } - - return String::empty(); -} - -// All of the information for these waves is on Wikipedia. - -Audio::Sample Track::sine(size_t note) -{ - double pos = note_frequencies[note] / sample_rate; - double sin_step = pos * 2 * M_PI; - double w = sin(m_pos[note]); - m_pos[note] += sin_step; - return Audio::Sample { w }; -} - -Audio::Sample Track::saw(size_t note) -{ - double saw_step = note_frequencies[note] / sample_rate; - double t = m_pos[note]; - double w = (0.5 - (t - floor(t))) * 2; - m_pos[note] += saw_step; - return Audio::Sample { w }; -} - -Audio::Sample Track::square(size_t note) -{ - double pos = note_frequencies[note] / sample_rate; - double square_step = pos * 2 * M_PI; - double w = AK::sin(m_pos[note]) >= 0 ? 1 : -1; - m_pos[note] += square_step; - return Audio::Sample { w }; -} - -Audio::Sample Track::triangle(size_t note) -{ - double triangle_step = note_frequencies[note] / sample_rate; - double t = m_pos[note]; - double w = AK::fabs(AK::fmod((4 * t) + 1, 4.) - 2) - 1.; - m_pos[note] += triangle_step; - return Audio::Sample { w }; -} - -Audio::Sample Track::noise(size_t note) -{ - double step = note_frequencies[note] / sample_rate; - // m_pos keeps track of the time since the last random sample - m_pos[note] += step; - if (m_pos[note] > 0.05) { - double random_percentage = static_cast<double>(rand()) / RAND_MAX; - m_last_w[note] = (random_percentage * 2) - 1; - m_pos[note] = 0; - } - return Audio::Sample { m_last_w[note] }; -} - -Audio::Sample Track::recorded_sample(size_t note) -{ - int t = m_pos[note]; - if (t >= static_cast<int>(m_recorded_sample.size())) - return {}; - double w_left = m_recorded_sample[t].left; - double w_right = m_recorded_sample[t].right; - if (t + 1 < static_cast<int>(m_recorded_sample.size())) { - double t_fraction = m_pos[note] - t; - w_left += (m_recorded_sample[t + 1].left - m_recorded_sample[t].left) * t_fraction; - w_right += (m_recorded_sample[t + 1].right - m_recorded_sample[t].right) * t_fraction; - } - double recorded_sample_step = note_frequencies[note] / middle_c; - m_pos[note] += recorded_sample_step; - return { w_left, w_right }; -} - -static inline double calculate_step(double distance, int milliseconds) -{ - if (milliseconds == 0) - return distance; - - constexpr double samples_per_millisecond = sample_rate / 1000.0; - double samples = milliseconds * samples_per_millisecond; - double step = distance / samples; - return step; -} - -void Track::set_note(int note, Switch switch_note) -{ - VERIFY(note >= 0 && note < note_count); - - if (switch_note == On) { - if (m_note_on[note] == 0) { - m_pos[note] = 0; - m_envelope[note] = Attack; - } - ++m_note_on[note]; - } else { - if (m_note_on[note] >= 1) { - if (m_note_on[note] == 1) { - m_release_step[note] = calculate_step(m_power[note], m_release); - m_envelope[note] = Release; - } - --m_note_on[note]; - } - } - - VERIFY(m_note_on[note] != NumericLimits<u8>::max()); - VERIFY(m_power[note] >= 0); -} - void Track::sync_roll(int note) { auto it = m_roll_notes[note].find_if([&](auto& roll_note) { return roll_note.off_sample > m_time; }); @@ -286,15 +92,11 @@ void Track::set_roll_note(int note, u32 on_sample, u32 off_sample) return; } if (it->on_sample <= new_roll_note.on_sample && it->off_sample >= new_roll_note.on_sample) { - if (m_time >= it->on_sample && m_time <= it->off_sample) - set_note(note, Off); it.remove(m_roll_notes[note]); sync_roll(note); return; } if ((new_roll_note.on_sample == 0 || it->on_sample >= new_roll_note.on_sample - 1) && it->on_sample <= new_roll_note.off_sample) { - if (m_time >= new_roll_note.off_sample && m_time <= it->off_sample) - set_note(note, Off); it.remove(m_roll_notes[note]); it = m_roll_notes[note].begin(); continue; @@ -306,21 +108,22 @@ void Track::set_roll_note(int note, u32 on_sample, u32 off_sample) sync_roll(note); } -void Track::set_wave(int wave) -{ - VERIFY(wave >= first_wave && wave <= last_wave); - m_wave = wave; -} - -void Track::set_wave(Direction direction) +void Track::set_keyboard_note(int note, Switch state) { - if (direction == Up) { - if (++m_wave > last_wave) - m_wave = first_wave; - } else { - if (--m_wave < first_wave) - m_wave = last_wave; - } + 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_time)) + roll_note.off_sample = m_time; + else + m_keyboard_notes[note] = {}; + } + } else + // FIXME: The end time needs to be far in the future. + m_keyboard_notes[note] + = RollNote { m_time, m_time + static_cast<u32>(sample_rate) * 10'000, static_cast<u8>(note), 0 }; } void Track::set_volume(int volume) @@ -328,36 +131,3 @@ void Track::set_volume(int volume) VERIFY(volume >= 0); m_volume = volume; } - -void Track::set_attack(int attack) -{ - VERIFY(attack >= 0); - m_attack = attack; - m_attack_step = calculate_step(1, m_attack); -} - -void Track::set_decay(int decay) -{ - VERIFY(decay >= 0); - m_decay = decay; - m_decay_step = calculate_step(1 - m_sustain_level, m_decay); -} - -void Track::set_sustain_impl(int sustain) -{ - VERIFY(sustain >= 0); - m_sustain = sustain; - m_sustain_level = sustain / 1000.0; -} - -void Track::set_sustain(int sustain) -{ - set_sustain_impl(sustain); - set_decay(m_decay); -} - -void Track::set_release(int release) -{ - VERIFY(release >= 0); - m_release = release; -} diff --git a/Userland/Applications/Piano/Track.h b/Userland/Applications/Piano/Track.h index f21fd2a460..b7e48efcae 100644 --- a/Userland/Applications/Piano/Track.h +++ b/Userland/Applications/Piano/Track.h @@ -10,10 +10,14 @@ #include "Music.h" #include <AK/Noncopyable.h> +#include <AK/NonnullRefPtr.h> #include <AK/SinglyLinkedList.h> #include <LibAudio/Buffer.h> #include <LibDSP/Effects.h> +#include <LibDSP/Music.h> +#include <LibDSP/Synthesizers.h> +using LibDSP::RollNote; using RollIter = AK::SinglyLinkedListIterator<SinglyLinkedList<RollNote>, RollNote>; class Track { @@ -26,63 +30,33 @@ public: 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 wave() const { return m_wave; } int volume() const { return m_volume; } - int attack() const { return m_attack; } - int decay() const { return m_decay; } - int sustain() const { return m_sustain; } - int release() const { return m_release; } + NonnullRefPtr<LibDSP::Synthesizers::Classic> synth() { return m_synth; } NonnullRefPtr<LibDSP::Effects::Delay> delay() { return m_delay; } void fill_sample(Sample& sample); void reset(); String set_recorded_sample(StringView path); - void set_note(int note, Switch); void set_roll_note(int note, u32 on_sample, u32 off_sample); - void set_wave(int wave); - void set_wave(Direction); + void set_keyboard_note(int note, Switch state); void set_volume(int volume); - void set_attack(int attack); - void set_decay(int decay); - void set_sustain(int sustain); - void set_release(int release); private: - Audio::Sample sine(size_t note); - Audio::Sample saw(size_t note); - Audio::Sample square(size_t note); - Audio::Sample triangle(size_t note); - Audio::Sample noise(size_t note); Audio::Sample recorded_sample(size_t note); void sync_roll(int note); - void set_sustain_impl(int sustain); Vector<Audio::Sample> m_recorded_sample; - u8 m_note_on[note_count] { 0 }; - double m_power[note_count] { 0 }; - double m_pos[note_count]; // Initialized lazily. - // Synths may use this to keep track of the last wave position - double m_last_w[note_count] { 0 }; - Envelope m_envelope[note_count] { Done }; - - int m_wave { first_wave }; int m_volume; - int m_attack; - double m_attack_step; - int m_decay; - double m_decay_step; - int m_sustain; - double m_sustain_level; - int m_release; - double m_release_step[note_count]; const u32& m_time; NonnullRefPtr<LibDSP::Transport> m_temporary_transport; NonnullRefPtr<LibDSP::Effects::Delay> m_delay; + NonnullRefPtr<LibDSP::Synthesizers::Classic> m_synth; SinglyLinkedList<RollNote> m_roll_notes[note_count]; RollIter m_roll_iterators[note_count]; + Array<Optional<RollNote>, note_count> m_keyboard_notes; }; diff --git a/Userland/Applications/Piano/TrackManager.cpp b/Userland/Applications/Piano/TrackManager.cpp index d0f69edf1a..5e2537c77f 100644 --- a/Userland/Applications/Piano/TrackManager.cpp +++ b/Userland/Applications/Piano/TrackManager.cpp @@ -7,6 +7,7 @@ */ #include "TrackManager.h" +#include "Applications/Piano/Music.h" TrackManager::TrackManager() { @@ -61,9 +62,9 @@ void TrackManager::reset() track->reset(); } -void TrackManager::set_note_current_octave(int note, Switch switch_note) +void TrackManager::set_keyboard_note(int note, Switch note_switch) { - current_track().set_note(note + octave_base(), switch_note); + m_tracks[m_current_track]->set_keyboard_note(note, note_switch); } void TrackManager::set_octave(Direction direction) diff --git a/Userland/Applications/Piano/TrackManager.h b/Userland/Applications/Piano/TrackManager.h index 9986085f63..08893cc22f 100644 --- a/Userland/Applications/Piano/TrackManager.h +++ b/Userland/Applications/Piano/TrackManager.h @@ -33,8 +33,8 @@ public: void fill_buffer(Span<Sample>); void reset(); + void set_keyboard_note(int note, Switch note_switch); void set_should_loop(bool b) { m_should_loop = b; } - void set_note_current_octave(int note, Switch); void set_octave(Direction); void set_octave(int octave); void add_track(); diff --git a/Userland/Applications/Piano/WaveWidget.cpp b/Userland/Applications/Piano/WaveWidget.cpp index 064d05029e..c91fefcfd9 100644 --- a/Userland/Applications/Piano/WaveWidget.cpp +++ b/Userland/Applications/Piano/WaveWidget.cpp @@ -36,8 +36,8 @@ 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_track_manager.current_track().wave()]; - Color right_wave_color = right_wave_colors[m_track_manager.current_track().wave()]; + Color left_wave_color = left_wave_colors[m_track_manager.current_track().synth()->wave()]; + Color right_wave_color = right_wave_colors[m_track_manager.current_track().synth()->wave()]; auto buffer = m_track_manager.buffer(); double width_scale = static_cast<double>(frame_inner_rect().width()) / buffer.size(); diff --git a/Userland/Libraries/LibDSP/ProcessorParameter.h b/Userland/Libraries/LibDSP/ProcessorParameter.h index 022b24ba38..d72b74978c 100644 --- a/Userland/Libraries/LibDSP/ProcessorParameter.h +++ b/Userland/Libraries/LibDSP/ProcessorParameter.h @@ -17,18 +17,30 @@ namespace LibDSP { using ParameterFixedPoint = FixedPoint<8, i64>; +// Identifies the different kinds of parameters. +// Note that achieving parameter type identification is NOT possible with typeid(). +enum class ParameterType : u8 { + Invalid = 0, + Range, + Enum, + Boolean, +}; + // Processors have modifiable parameters that should be presented to the UI in a uniform way without requiring the processor itself to implement custom interfaces. class ProcessorParameter { public: - ProcessorParameter(String name) + ProcessorParameter(String name, ParameterType type) : m_name(move(name)) + , m_type(type) { } String const& name() const { return m_name; } + ParameterType type() const { return m_type; } private: String const m_name; + ParameterType const m_type; }; namespace Detail { @@ -41,8 +53,8 @@ template<typename ParameterT> class ProcessorParameterSingleValue : public ProcessorParameter { public: - ProcessorParameterSingleValue(String name, ParameterT initial_value) - : ProcessorParameter(move(name)) + ProcessorParameterSingleValue(String name, ParameterType type, ParameterT initial_value) + : ProcessorParameter(move(name), type) , m_value(move(initial_value)) { } @@ -82,7 +94,7 @@ protected: class ProcessorBooleanParameter final : public Detail::ProcessorParameterSingleValue<bool> { public: ProcessorBooleanParameter(String name, bool initial_value) - : Detail::ProcessorParameterSingleValue<bool>(move(name), move(initial_value)) + : Detail::ProcessorParameterSingleValue<bool>(move(name), ParameterType::Boolean, move(initial_value)) { } }; @@ -90,7 +102,7 @@ public: class ProcessorRangeParameter final : public Detail::ProcessorParameterSingleValue<ParameterFixedPoint> { public: ProcessorRangeParameter(String name, ParameterFixedPoint min_value, ParameterFixedPoint max_value, ParameterFixedPoint initial_value) - : Detail::ProcessorParameterSingleValue<ParameterFixedPoint>(move(name), move(initial_value)) + : Detail::ProcessorParameterSingleValue<ParameterFixedPoint>(move(name), ParameterType::Range, move(initial_value)) , m_min_value(move(min_value)) , m_max_value(move(max_value)) , m_default_value(move(initial_value)) @@ -122,7 +134,7 @@ template<typename EnumT> requires(IsEnum<EnumT>) class ProcessorEnumParameter final : public Detail::ProcessorParameterSingleValue<EnumT> { public: ProcessorEnumParameter(String name, EnumT initial_value) - : Detail::ProcessorParameterSingleValue<EnumT>(move(name), initial_value, ParameterType::Enum) + : Detail::ProcessorParameterSingleValue<EnumT>(move(name), ParameterType::Enum, initial_value) { } }; diff --git a/Userland/Libraries/LibDSP/Synthesizers.cpp b/Userland/Libraries/LibDSP/Synthesizers.cpp index 98e97402a1..4a3988d067 100644 --- a/Userland/Libraries/LibDSP/Synthesizers.cpp +++ b/Userland/Libraries/LibDSP/Synthesizers.cpp @@ -63,6 +63,7 @@ Signal Classic::process_impl(Signal const& input_signal) return out; } +// Linear ADSR envelope with no peak adjustment. double Classic::volume_from_envelope(Envelope envelope) { switch (static_cast<EnvelopeState>(envelope)) { @@ -71,10 +72,12 @@ double Classic::volume_from_envelope(Envelope envelope) case EnvelopeState::Attack: return envelope.attack(); case EnvelopeState::Decay: - return (1. - envelope.decay()) * m_sustain + m_sustain; + // As we fade from high (1) to low (headroom above the sustain level) here, use 1-decay as the interpolation. + return (1. - envelope.decay()) * (1. - m_sustain) + m_sustain; case EnvelopeState::Sustain: return m_sustain; case EnvelopeState::Release: + // Same goes for the release fade from high to low. return (1. - envelope.release()) * m_sustain; } VERIFY_NOT_REACHED(); |