diff options
Diffstat (limited to 'Userland/Applications/Piano/Track.cpp')
-rw-r--r-- | Userland/Applications/Piano/Track.cpp | 312 |
1 files changed, 41 insertions, 271 deletions
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; -} |