summaryrefslogtreecommitdiff
path: root/Userland/Applications/Piano/Track.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Applications/Piano/Track.cpp')
-rw-r--r--Userland/Applications/Piano/Track.cpp312
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;
-}