diff --git a/Userland/Applications/Piano/KnobsWidget.cpp b/Userland/Applications/Piano/KnobsWidget.cpp index 17f5c37e64..ca9b74b4f3 100644 --- a/Userland/Applications/Piano/KnobsWidget.cpp +++ b/Userland/Applications/Piano/KnobsWidget.cpp @@ -12,12 +12,6 @@ #include #include -constexpr int max_attack = 1000; -constexpr int max_decay = 1000; -constexpr int max_sustain = 1000; -constexpr int max_release = 1000; -constexpr int max_delay = 8; - KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) : m_track_manager(track_manager) , m_main_widget(main_widget) @@ -29,6 +23,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) m_labels_container->set_layout(); m_labels_container->set_fixed_height(20); + m_volume_label = m_labels_container->add("Volume"); m_octave_label = m_labels_container->add("Octave"); m_wave_label = m_labels_container->add("Wave"); m_attack_label = m_labels_container->add("Attack"); @@ -41,6 +36,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) m_values_container->set_layout(); m_values_container->set_fixed_height(10); + m_volume_value = m_values_container->add(String::number(m_track_manager.current_track().volume())); m_octave_value = m_values_container->add(String::number(m_track_manager.octave())); m_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())); @@ -54,10 +50,23 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) // FIXME: Implement vertical flipping in GUI::Slider, not here. + m_volume_knob = m_knobs_container->add(); + m_volume_knob->set_range(0, volume_max); + m_volume_knob->set_value(volume_max - m_track_manager.current_track().volume()); + m_volume_knob->set_step(10); + m_volume_knob->on_change = [this](int value) { + int new_volume = volume_max - value; + if (m_change_underlying) + m_track_manager.current_track().set_volume(new_volume); + VERIFY(new_volume == m_track_manager.current_track().volume()); + m_volume_value->set_text(String::number(new_volume)); + }; + m_octave_knob = m_knobs_container->add(); m_octave_knob->set_tooltip("Z: octave down, X: octave up"); m_octave_knob->set_range(octave_min - 1, octave_max - 1); m_octave_knob->set_value((octave_max - 1) - (m_track_manager.octave() - 1)); + m_octave_knob->set_step(1); m_octave_knob->on_change = [this](int value) { int new_octave = octave_max - value; if (m_change_underlying) @@ -70,6 +79,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) 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) @@ -79,11 +89,12 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) }; m_attack_knob = m_knobs_container->add(); - m_attack_knob->set_range(0, max_attack); - m_attack_knob->set_value(max_attack - m_track_manager.current_track().attack()); - m_attack_knob->set_step(100); + 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 = max_attack - 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()); @@ -91,11 +102,12 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) }; m_decay_knob = m_knobs_container->add(); - m_decay_knob->set_range(0, max_decay); - m_decay_knob->set_value(max_decay - m_track_manager.current_track().decay()); - m_decay_knob->set_step(100); + 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 = max_decay - 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()); @@ -103,11 +115,12 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) }; m_sustain_knob = m_knobs_container->add(); - m_sustain_knob->set_range(0, max_sustain); - m_sustain_knob->set_value(max_sustain - m_track_manager.current_track().sustain()); - m_sustain_knob->set_step(100); + 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 = max_sustain - 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()); @@ -115,11 +128,12 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) }; m_release_knob = m_knobs_container->add(); - m_release_knob->set_range(0, max_release); - m_release_knob->set_value(max_release - m_track_manager.current_track().release()); - m_release_knob->set_step(100); + 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 = max_release - 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()); @@ -127,10 +141,12 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget) }; m_delay_knob = m_knobs_container->add(); - m_delay_knob->set_range(0, max_delay); - m_delay_knob->set_value(max_delay - m_track_manager.current_track().delay()); + m_delay_knob->set_tooltip("Delay speed, 0 = off"); + m_delay_knob->set_range(0, delay_max); + m_delay_knob->set_value(delay_max - m_track_manager.current_track().delay()); + m_release_knob->set_step(1); m_delay_knob->on_change = [this](int value) { - int new_delay = max_delay - value; + int new_delay = delay_max - value; if (m_change_underlying) m_track_manager.current_track().set_delay(new_delay); VERIFY(new_delay == m_track_manager.current_track().delay()); @@ -151,13 +167,14 @@ void KnobsWidget::update_knobs() // need to change the slider without changing the underlying value. m_change_underlying = false; + m_volume_knob->set_value(volume_max - m_track_manager.current_track().volume()); m_octave_knob->set_value(octave_max - m_track_manager.octave()); m_wave_knob->set_value(last_wave - m_track_manager.current_track().wave()); - m_attack_knob->set_value(max_attack - m_track_manager.current_track().attack()); - m_decay_knob->set_value(max_decay - m_track_manager.current_track().decay()); - m_sustain_knob->set_value(max_sustain - m_track_manager.current_track().sustain()); - m_release_knob->set_value(max_release - m_track_manager.current_track().release()); - m_delay_knob->set_value(max_delay - m_track_manager.current_track().delay()); + 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_delay_knob->set_value(delay_max - m_track_manager.current_track().delay()); m_change_underlying = true; } diff --git a/Userland/Applications/Piano/KnobsWidget.h b/Userland/Applications/Piano/KnobsWidget.h index c1760608f2..aa3710d8d0 100644 --- a/Userland/Applications/Piano/KnobsWidget.h +++ b/Userland/Applications/Piano/KnobsWidget.h @@ -26,6 +26,7 @@ private: MainWidget& m_main_widget; RefPtr m_labels_container; + RefPtr m_volume_label; RefPtr m_octave_label; RefPtr m_wave_label; RefPtr m_attack_label; @@ -35,6 +36,7 @@ private: RefPtr m_delay_label; RefPtr m_values_container; + RefPtr m_volume_value; RefPtr m_octave_value; RefPtr m_wave_value; RefPtr m_attack_value; @@ -44,6 +46,7 @@ private: RefPtr m_delay_value; RefPtr m_knobs_container; + RefPtr m_volume_knob; RefPtr m_octave_knob; RefPtr m_wave_knob; RefPtr m_attack_knob; diff --git a/Userland/Applications/Piano/Music.h b/Userland/Applications/Piano/Music.h index 7ee4f2be02..b80f4b759e 100644 --- a/Userland/Applications/Piano/Music.h +++ b/Userland/Applications/Piano/Music.h @@ -29,7 +29,7 @@ constexpr int buffer_size = sample_count * sizeof(Sample); constexpr double sample_rate = 44100; -constexpr double volume = 1800; +constexpr double volume_factor = 1800; enum Switch { Off, @@ -183,6 +183,14 @@ 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; constexpr int notes_per_beat = 4; diff --git a/Userland/Applications/Piano/Track.cpp b/Userland/Applications/Piano/Track.cpp index e62e76dddf..5dde39da04 100644 --- a/Userland/Applications/Piano/Track.cpp +++ b/Userland/Applications/Piano/Track.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2019-2020, William McPherson + * Copyright (c) 2021, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -13,6 +14,7 @@ Track::Track(const u32& time) : m_time(time) { + set_volume(volume_max); set_sustain_impl(1000); set_attack(5); set_decay(1000); @@ -81,7 +83,7 @@ void Track::fill_sample(Sample& sample) note_sample = triangle(note); break; case Wave::Noise: - note_sample = noise(); + note_sample = noise(note); break; case Wave::RecordedSample: note_sample = recorded_sample(note); @@ -89,8 +91,8 @@ void Track::fill_sample(Sample& sample) default: VERIFY_NOT_REACHED(); } - new_sample.left += note_sample.left * m_power[note] * volume; - new_sample.right += note_sample.right * m_power[note] * volume; + new_sample.left += note_sample.left * m_power[note] * volume_factor * (static_cast(volume()) / volume_max); + new_sample.right += note_sample.right * m_power[note] * volume_factor * (static_cast(volume()) / volume_max); } if (m_delay) { @@ -188,11 +190,17 @@ Audio::Frame Track::triangle(size_t note) return w; } -Audio::Frame Track::noise() const +Audio::Frame Track::noise(size_t note) { - double random_percentage = static_cast(rand()) / RAND_MAX; - double w = (random_percentage * 2) - 1; - return w; + 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(rand()) / RAND_MAX; + m_last_w[note] = (random_percentage * 2) - 1; + m_pos[note] = 0; + } + return m_last_w[note]; } Audio::Frame Track::recorded_sample(size_t note) @@ -308,6 +316,12 @@ void Track::set_wave(Direction direction) } } +void Track::set_volume(int volume) +{ + VERIFY(volume >= 0); + m_volume = volume; +} + void Track::set_attack(int attack) { VERIFY(attack >= 0); diff --git a/Userland/Applications/Piano/Track.h b/Userland/Applications/Piano/Track.h index a76af003d2..6c660e37fc 100644 --- a/Userland/Applications/Piano/Track.h +++ b/Userland/Applications/Piano/Track.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2019-2020, William McPherson + * Copyright (c) 2021, kleines Filmröllchen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -25,6 +26,7 @@ public: const Vector& recorded_sample() const { return m_recorded_sample; } const SinglyLinkedList& 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; } @@ -38,6 +40,7 @@ public: void set_roll_note(int note, u32 on_sample, u32 off_sample); void set_wave(int wave); void set_wave(Direction); + void set_volume(int volume); void set_attack(int attack); void set_decay(int decay); void set_sustain(int sustain); @@ -49,7 +52,7 @@ private: Audio::Frame saw(size_t note); Audio::Frame square(size_t note); Audio::Frame triangle(size_t note); - Audio::Frame noise() const; + Audio::Frame noise(size_t note); Audio::Frame recorded_sample(size_t note); void sync_roll(int note); @@ -62,9 +65,12 @@ private: 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;