mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 11:47:34 +00:00
Piano+LibDSP: Move Track to LibDSP
This is a tangly commit and it fixes all the bugs that a plain move would have caused (i.e. we need to touch other logic which had wrong assumptions).
This commit is contained in:
parent
125122a9ab
commit
4941cffdd0
29 changed files with 322 additions and 413 deletions
|
@ -15,17 +15,9 @@
|
|||
#include <LibAudio/Sample.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
|
||||
static FixedArray<Audio::Sample> music_samples_to_buffer(Vector<Music::Sample>& music_samples)
|
||||
{
|
||||
FixedArray<Audio::Sample> samples = MUST(FixedArray<Audio::Sample>::try_create(music_samples.size()));
|
||||
for (size_t i = 0; i < music_samples.size(); ++i)
|
||||
samples[i] = { static_cast<float>(music_samples[i].left) / AK::NumericLimits<i16>::max(), static_cast<float>(music_samples[i].right) / AK::NumericLimits<i16>::max() };
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer)
|
||||
: m_track_manager(track_manager)
|
||||
, m_buffer(FixedArray<DSP::Sample>::must_create_but_fixme_should_propagate_errors(sample_count))
|
||||
, m_need_to_write_wav(need_to_write_wav)
|
||||
, m_wav_writer(wav_writer)
|
||||
{
|
||||
|
@ -34,7 +26,7 @@ AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_writ
|
|||
auto target_sample_rate = m_audio_client->get_sample_rate();
|
||||
if (target_sample_rate == 0)
|
||||
target_sample_rate = Music::sample_rate;
|
||||
m_resampler = Audio::ResampleHelper<Sample>(Music::sample_rate, target_sample_rate);
|
||||
m_resampler = Audio::ResampleHelper<DSP::Sample>(Music::sample_rate, target_sample_rate);
|
||||
|
||||
// FIXME: I said I would never write such a hack again, but here we are.
|
||||
// This code should die as soon as possible anyways, so it doesn't matter.
|
||||
|
@ -44,7 +36,7 @@ AudioPlayerLoop::AudioPlayerLoop(TrackManager& track_manager, bool& need_to_writ
|
|||
|
||||
void AudioPlayerLoop::timer_event(Core::TimerEvent&)
|
||||
{
|
||||
if (m_audio_client->remaining_samples() < buffer_size)
|
||||
if (m_audio_client->remaining_samples() < sample_count)
|
||||
enqueue_audio();
|
||||
}
|
||||
|
||||
|
@ -53,8 +45,7 @@ void AudioPlayerLoop::enqueue_audio()
|
|||
m_track_manager.fill_buffer(m_buffer);
|
||||
// FIXME: Handle OOM better.
|
||||
auto audio_buffer = m_resampler->resample(m_buffer);
|
||||
auto real_buffer = music_samples_to_buffer(audio_buffer);
|
||||
(void)m_audio_client->async_enqueue(real_buffer);
|
||||
(void)m_audio_client->async_enqueue(audio_buffer);
|
||||
|
||||
// FIXME: This should be done somewhere else.
|
||||
if (m_need_to_write_wav) {
|
||||
|
@ -63,7 +54,7 @@ void AudioPlayerLoop::enqueue_audio()
|
|||
m_track_manager.set_should_loop(false);
|
||||
do {
|
||||
m_track_manager.fill_buffer(m_buffer);
|
||||
m_wav_writer.write_samples(reinterpret_cast<u8*>(m_buffer.data()), buffer_size);
|
||||
m_wav_writer.write_samples(m_buffer.span());
|
||||
} while (m_track_manager.transport()->time());
|
||||
m_track_manager.reset();
|
||||
m_track_manager.set_should_loop(true);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibAudio/WavWriter.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibDSP/Music.h>
|
||||
|
||||
class TrackManager;
|
||||
|
||||
|
@ -33,8 +34,8 @@ private:
|
|||
virtual void timer_event(Core::TimerEvent&) override;
|
||||
|
||||
TrackManager& m_track_manager;
|
||||
Array<Sample, sample_count> m_buffer;
|
||||
Optional<Audio::ResampleHelper<Sample>> m_resampler;
|
||||
FixedArray<DSP::Sample> m_buffer;
|
||||
Optional<Audio::ResampleHelper<DSP::Sample>> m_resampler;
|
||||
RefPtr<Audio::ConnectionToServer> m_audio_client;
|
||||
|
||||
bool m_should_play_audio = true;
|
||||
|
|
|
@ -7,7 +7,6 @@ serenity_component(
|
|||
|
||||
set(SOURCES
|
||||
AudioPlayerLoop.cpp
|
||||
Track.cpp
|
||||
TrackManager.cpp
|
||||
KeysWidget.cpp
|
||||
KnobsWidget.cpp
|
||||
|
|
|
@ -34,7 +34,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget)
|
|||
m_values_container->set_layout<GUI::HorizontalBoxLayout>();
|
||||
m_values_container->set_fixed_height(10);
|
||||
|
||||
m_volume_value = m_values_container->add<GUI::Label>(String::number(m_track_manager.current_track().volume()));
|
||||
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.keyboard()->virtual_keyboard_octave()));
|
||||
|
||||
m_knobs_container = add<GUI::Widget>();
|
||||
|
@ -44,13 +44,13 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget)
|
|||
|
||||
m_volume_knob = m_knobs_container->add<GUI::VerticalSlider>();
|
||||
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_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_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));
|
||||
};
|
||||
|
||||
|
@ -67,7 +67,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget)
|
|||
m_octave_value->set_text(String::number(new_octave));
|
||||
};
|
||||
|
||||
for (auto& raw_parameter : m_track_manager.current_track().synth()->parameters()) {
|
||||
for (auto& raw_parameter : m_track_manager.current_track()->synth()->parameters()) {
|
||||
// The synth has range and enum parameters
|
||||
switch (raw_parameter.type()) {
|
||||
case DSP::ParameterType::Range: {
|
||||
|
@ -94,7 +94,7 @@ KnobsWidget::KnobsWidget(TrackManager& track_manager, MainWidget& main_widget)
|
|||
}
|
||||
}
|
||||
|
||||
for (auto& raw_parameter : m_track_manager.current_track().delay()->parameters()) {
|
||||
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.
|
||||
auto& parameter = static_cast<DSP::ProcessorRangeParameter&>(raw_parameter);
|
||||
m_delay_values.append(m_values_container->add<GUI::Label>(String::number(static_cast<double>(parameter.value()))));
|
||||
|
@ -116,7 +116,7 @@ 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_volume_knob->set_value(volume_max - m_track_manager.current_track()->volume());
|
||||
m_octave_knob->set_value(octave_max - m_track_manager.keyboard()->virtual_keyboard_octave());
|
||||
|
||||
m_change_underlying = true;
|
||||
|
|
|
@ -26,8 +26,6 @@ struct Sample {
|
|||
// HACK: needs to increase with device sample rate, but all of the sample_count stuff is static for now
|
||||
constexpr int sample_count = 1 << 10;
|
||||
|
||||
constexpr int buffer_size = sample_count * sizeof(Sample);
|
||||
|
||||
constexpr double sample_rate = 44100;
|
||||
|
||||
// Headroom for the synth
|
||||
|
|
|
@ -35,9 +35,9 @@ public:
|
|||
auto value = static_cast<EnumT>(model_index.row());
|
||||
m_parameter.set_value_sneaky(value, DSP::Detail::ProcessorParameterSetValueTag {});
|
||||
};
|
||||
m_parameter.did_change_value = [this](auto new_value) {
|
||||
m_parameter.register_change_listener([this](auto new_value) {
|
||||
set_selected_index(static_cast<int>(new_value));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Release focus when escape is pressed
|
||||
|
|
|
@ -30,13 +30,16 @@ ProcessorParameterSlider::ProcessorParameterSlider(Orientation orientation, DSP:
|
|||
m_value_label->set_text(String::formatted("{:.2f}", static_cast<double>(m_parameter)));
|
||||
|
||||
on_change = [this](auto value) {
|
||||
if (m_currently_setting_from_ui)
|
||||
return;
|
||||
m_currently_setting_from_ui = true;
|
||||
DSP::ParameterFixedPoint real_value;
|
||||
real_value.raw() = value;
|
||||
if (is_logarithmic())
|
||||
// FIXME: Implement exponential for fixed point
|
||||
real_value = exp(static_cast<double>(real_value));
|
||||
real_value = exp2(static_cast<double>(real_value));
|
||||
|
||||
m_parameter.set_value_sneaky(real_value, DSP::Detail::ProcessorParameterSetValueTag {});
|
||||
m_parameter.set_value(real_value);
|
||||
if (m_value_label) {
|
||||
double value = static_cast<double>(m_parameter);
|
||||
String label_text = String::formatted("{:.2f}", value);
|
||||
|
@ -47,11 +50,12 @@ ProcessorParameterSlider::ProcessorParameterSlider(Orientation orientation, DSP:
|
|||
else
|
||||
m_value_label->set_text(label_text);
|
||||
}
|
||||
m_currently_setting_from_ui = false;
|
||||
};
|
||||
m_parameter.did_change_value = [this](auto value) {
|
||||
m_parameter.register_change_listener([this](auto value) {
|
||||
if (!is_logarithmic())
|
||||
set_value(value.raw());
|
||||
else
|
||||
set_value(value.log2().raw());
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,4 +29,6 @@ protected:
|
|||
private:
|
||||
// Converts based on processor parameter boundaries.
|
||||
int linear_to_logarithmic(int linear_value);
|
||||
|
||||
bool m_currently_setting_from_ui { false };
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include "RollWidget.h"
|
||||
#include "LibGUI/Event.h"
|
||||
#include "TrackManager.h"
|
||||
#include <AK/IntegralMath.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
@ -134,9 +135,9 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
|
|||
painter.translate(-x_offset, -y_offset);
|
||||
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) {
|
||||
int y = ((note_count - 1) - note) * note_height;
|
||||
for (auto roll_note : m_track_manager.current_track().roll_notes(note)) {
|
||||
for (auto const& clip : m_track_manager.current_track()->notes()) {
|
||||
for (auto const& roll_note : clip->notes()) {
|
||||
int y = ((note_count - 1) - roll_note.pitch) * note_height;
|
||||
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())
|
||||
|
@ -150,7 +151,10 @@ void RollWidget::paint_event(GUI::PaintEvent& event)
|
|||
painter.fill_rect(rect, note_pressed_color);
|
||||
painter.draw_rect(rect, Color::Black);
|
||||
}
|
||||
}
|
||||
|
||||
for (int note = note_count - (note_offset + notes_to_paint); note <= (note_count - 1) - note_offset; ++note) {
|
||||
int y = ((note_count - 1) - note) * note_height;
|
||||
Gfx::IntRect note_name_rect(3, y, 1, note_height);
|
||||
auto note_name = note_names[note % notes_per_octave];
|
||||
|
||||
|
@ -197,7 +201,13 @@ void RollWidget::mousemove_event(GUI::MouseEvent& event)
|
|||
|
||||
if (m_note_drag_location.has_value()) {
|
||||
// Clear previous note
|
||||
m_track_manager.current_track().set_roll_note(m_drag_note, m_note_drag_location.value().on_sample, m_note_drag_location.value().off_sample);
|
||||
m_track_manager.current_track()->remove_note(m_note_drag_location.value());
|
||||
}
|
||||
|
||||
// Right-Click deletes notes
|
||||
if (event.button() == GUI::MouseButton::Secondary) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
auto get_note_x = [&](int x0) {
|
||||
|
@ -220,8 +230,8 @@ void RollWidget::mousemove_event(GUI::MouseEvent& event)
|
|||
|
||||
u32 on_sample = roll_length * (static_cast<double>(min(x0, x1)) / m_num_notes);
|
||||
u32 off_sample = (roll_length * (static_cast<double>(max(x0, x1) + 1) / m_num_notes)) - 1;
|
||||
m_track_manager.current_track().set_roll_note(m_drag_note, on_sample, off_sample);
|
||||
m_note_drag_location = RollNote { on_sample, off_sample, (u8)m_drag_note, 0 };
|
||||
m_note_drag_location = RollNote { on_sample, off_sample, (u8)m_drag_note, 127 };
|
||||
m_track_manager.current_track()->set_note(m_note_drag_location.value());
|
||||
|
||||
update();
|
||||
}
|
||||
|
|
|
@ -33,36 +33,8 @@ void WaveEditor::paint_event(GUI::PaintEvent& event)
|
|||
GUI::Painter painter(*this);
|
||||
painter.fill_rect(frame_inner_rect(), Color::Black);
|
||||
|
||||
auto recorded_sample = m_track_manager.current_track().recorded_sample();
|
||||
if (recorded_sample.is_empty())
|
||||
return;
|
||||
|
||||
double width_scale = static_cast<double>(frame_inner_rect().width()) / recorded_sample.size();
|
||||
|
||||
painter.translate(frame_thickness(), frame_thickness());
|
||||
|
||||
int prev_x = 0;
|
||||
int left_prev_y = sample_to_y(recorded_sample[0].left);
|
||||
int right_prev_y = sample_to_y(recorded_sample[0].right);
|
||||
painter.set_pixel({ prev_x, left_prev_y }, left_wave_colors[RecordedSample]);
|
||||
painter.set_pixel({ prev_x, right_prev_y }, right_wave_colors[RecordedSample]);
|
||||
|
||||
for (size_t x = 1; x < recorded_sample.size(); ++x) {
|
||||
int left_y = sample_to_y(recorded_sample[x].left);
|
||||
int right_y = sample_to_y(recorded_sample[x].right);
|
||||
|
||||
Gfx::IntPoint left_point1(prev_x * width_scale, left_prev_y);
|
||||
Gfx::IntPoint left_point2(x * width_scale, left_y);
|
||||
painter.draw_line(left_point1, left_point2, left_wave_colors[RecordedSample]);
|
||||
|
||||
Gfx::IntPoint right_point1(prev_x * width_scale, right_prev_y);
|
||||
Gfx::IntPoint right_point2(x * width_scale, right_y);
|
||||
painter.draw_line(right_point1, right_point2, right_wave_colors[RecordedSample]);
|
||||
|
||||
prev_x = x;
|
||||
left_prev_y = left_y;
|
||||
right_prev_y = right_y;
|
||||
}
|
||||
// FIXME: This widget currently can't display anything.
|
||||
return;
|
||||
}
|
||||
|
||||
SamplerWidget::SamplerWidget(TrackManager& track_manager)
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
|
||||
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Track.h"
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <LibAudio/Loader.h>
|
||||
#include <LibDSP/Music.h>
|
||||
#include <math.h>
|
||||
|
||||
Track::Track(NonnullRefPtr<DSP::Transport> transport, NonnullRefPtr<DSP::Keyboard> keyboard)
|
||||
: m_transport(move(transport))
|
||||
, m_delay(make_ref_counted<DSP::Effects::Delay>(m_transport))
|
||||
, m_synth(make_ref_counted<DSP::Synthesizers::Classic>(m_transport))
|
||||
, m_keyboard(move(keyboard))
|
||||
{
|
||||
set_volume(volume_max);
|
||||
}
|
||||
|
||||
void Track::fill_sample(Sample& sample)
|
||||
{
|
||||
auto playing_notes = DSP::RollNotes {};
|
||||
|
||||
for (size_t i = 0; i < note_count; ++i) {
|
||||
bool has_roll_notes = false;
|
||||
auto& notes_at_pitch = m_roll_notes[i];
|
||||
for (auto& note : notes_at_pitch) {
|
||||
if (note.is_playing(m_transport->time())) {
|
||||
has_roll_notes = true;
|
||||
playing_notes.set(i, note);
|
||||
}
|
||||
}
|
||||
if (m_is_active_track) {
|
||||
auto key_at_pitch = m_keyboard->note_at(i);
|
||||
if (key_at_pitch.has_value() && key_at_pitch.value().is_playing(m_transport->time()))
|
||||
playing_notes.set(i, key_at_pitch.release_value());
|
||||
// If there are roll notes playing, don't stop them when we lift a keyboard key.
|
||||
else if (!has_roll_notes)
|
||||
playing_notes.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
auto synthesized_sample = DSP::Signal { FixedArray<Audio::Sample>::must_create_but_fixme_should_propagate_errors(1) };
|
||||
m_synth->process(playing_notes, synthesized_sample);
|
||||
auto delayed_signal = DSP::Signal { FixedArray<Audio::Sample>::must_create_but_fixme_should_propagate_errors(1) };
|
||||
m_delay->process(synthesized_sample, delayed_signal);
|
||||
auto delayed_sample = delayed_signal.get<FixedArray<Audio::Sample>>()[0];
|
||||
|
||||
// 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 += delayed_sample.left;
|
||||
sample.right += delayed_sample.right;
|
||||
}
|
||||
|
||||
void Track::reset()
|
||||
{
|
||||
for (size_t note = 0; note < note_count; ++note)
|
||||
m_roll_iterators[note] = m_roll_notes[note].begin();
|
||||
}
|
||||
|
||||
void Track::sync_roll(int note)
|
||||
{
|
||||
auto it = m_roll_notes[note].find_if([&](auto& roll_note) { return roll_note.off_sample > m_transport->time(); });
|
||||
if (it.is_end())
|
||||
m_roll_iterators[note] = m_roll_notes[note].begin();
|
||||
else
|
||||
m_roll_iterators[note] = it;
|
||||
}
|
||||
|
||||
void Track::set_roll_note(int note, u32 on_sample, u32 off_sample)
|
||||
{
|
||||
RollNote new_roll_note = { on_sample, off_sample, (u8)note, 0 };
|
||||
|
||||
VERIFY(note >= 0 && note < note_count);
|
||||
VERIFY(new_roll_note.off_sample < roll_length);
|
||||
VERIFY(new_roll_note.length() >= 2);
|
||||
|
||||
for (auto it = m_roll_notes[note].begin(); !it.is_end();) {
|
||||
if (it->on_sample > new_roll_note.off_sample) {
|
||||
m_roll_notes[note].insert_before(it, new_roll_note);
|
||||
sync_roll(note);
|
||||
return;
|
||||
}
|
||||
if (it->on_sample <= new_roll_note.on_sample && it->off_sample >= new_roll_note.on_sample) {
|
||||
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) {
|
||||
it.remove(m_roll_notes[note]);
|
||||
it = m_roll_notes[note].begin();
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
m_roll_notes[note].append(new_roll_note);
|
||||
sync_roll(note);
|
||||
}
|
||||
|
||||
void Track::set_volume(int volume)
|
||||
{
|
||||
VERIFY(volume >= 0);
|
||||
m_volume = volume;
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
|
||||
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Music.h"
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/SinglyLinkedList.h>
|
||||
#include <LibDSP/Effects.h>
|
||||
#include <LibDSP/Keyboard.h>
|
||||
#include <LibDSP/Music.h>
|
||||
#include <LibDSP/Synthesizers.h>
|
||||
#include <LibDSP/Transport.h>
|
||||
|
||||
using DSP::RollNote;
|
||||
using RollIter = AK::SinglyLinkedListIterator<SinglyLinkedList<RollNote>, RollNote>;
|
||||
|
||||
class Track {
|
||||
AK_MAKE_NONCOPYABLE(Track);
|
||||
AK_MAKE_NONMOVABLE(Track);
|
||||
|
||||
public:
|
||||
Track(NonnullRefPtr<DSP::Transport>, NonnullRefPtr<DSP::Keyboard>);
|
||||
~Track() = default;
|
||||
|
||||
Vector<Audio::Sample> const& recorded_sample() const { return m_recorded_sample; }
|
||||
SinglyLinkedList<RollNote> const& roll_notes(int note) const { return m_roll_notes[note]; }
|
||||
int volume() const { return m_volume; }
|
||||
NonnullRefPtr<DSP::Synthesizers::Classic> synth() { return m_synth; }
|
||||
NonnullRefPtr<DSP::Effects::Delay> delay() { return m_delay; }
|
||||
|
||||
void fill_sample(Sample& sample);
|
||||
void reset();
|
||||
String set_recorded_sample(StringView path);
|
||||
void set_roll_note(int note, u32 on_sample, u32 off_sample);
|
||||
void set_volume(int volume);
|
||||
void set_active(bool active) { m_is_active_track = active; }
|
||||
|
||||
private:
|
||||
Audio::Sample recorded_sample(size_t note);
|
||||
|
||||
void sync_roll(int note);
|
||||
|
||||
Vector<Audio::Sample> m_recorded_sample;
|
||||
|
||||
int m_volume;
|
||||
|
||||
NonnullRefPtr<DSP::Transport> m_transport;
|
||||
NonnullRefPtr<DSP::Effects::Delay> m_delay;
|
||||
NonnullRefPtr<DSP::Synthesizers::Classic> m_synth;
|
||||
|
||||
SinglyLinkedList<RollNote> m_roll_notes[note_count];
|
||||
RollIter m_roll_iterators[note_count];
|
||||
NonnullRefPtr<DSP::Keyboard> m_keyboard;
|
||||
bool m_is_active_track { false };
|
||||
};
|
|
@ -8,15 +8,18 @@
|
|||
*/
|
||||
|
||||
#include "TrackManager.h"
|
||||
#include "Applications/Piano/Music.h"
|
||||
#include "Music.h"
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/TypedTransfer.h>
|
||||
#include <LibDSP/Effects.h>
|
||||
#include <LibDSP/Synthesizers.h>
|
||||
|
||||
TrackManager::TrackManager()
|
||||
: m_transport(make_ref_counted<DSP::Transport>(120, 4))
|
||||
, m_keyboard(make_ref_counted<DSP::Keyboard>(m_transport))
|
||||
, m_temporary_track_buffer(FixedArray<DSP::Sample>::must_create_but_fixme_should_propagate_errors(sample_count))
|
||||
{
|
||||
add_track();
|
||||
m_tracks[m_current_track]->set_active(true);
|
||||
}
|
||||
|
||||
void TrackManager::time_forward(int amount)
|
||||
|
@ -30,47 +33,38 @@ void TrackManager::time_forward(int amount)
|
|||
}
|
||||
}
|
||||
|
||||
void TrackManager::fill_buffer(Span<Sample> buffer)
|
||||
void TrackManager::fill_buffer(FixedArray<DSP::Sample>& buffer)
|
||||
{
|
||||
memset(buffer.data(), 0, buffer_size);
|
||||
VERIFY(buffer.size() == m_temporary_track_buffer.size());
|
||||
size_t sample_count = buffer.size();
|
||||
// No need to zero the temp buffer as the track overwrites it anyways.
|
||||
buffer.fill_with({});
|
||||
|
||||
for (size_t i = 0; i < buffer.size(); ++i) {
|
||||
for (auto& track : m_tracks)
|
||||
track->fill_sample(buffer[i]);
|
||||
|
||||
m_transport->set_time(m_transport->time() + 1);
|
||||
// FIXME: This should be handled automatically by Transport.
|
||||
if (m_transport->time() >= roll_length) {
|
||||
m_transport->set_time(0);
|
||||
if (!m_should_loop)
|
||||
break;
|
||||
}
|
||||
for (auto& track : m_tracks) {
|
||||
track->current_signal(m_temporary_track_buffer);
|
||||
for (size_t i = 0; i < sample_count; ++i)
|
||||
buffer[i] += m_temporary_track_buffer[i];
|
||||
}
|
||||
|
||||
memcpy(m_current_back_buffer.data(), buffer.data(), buffer_size);
|
||||
swap(m_current_front_buffer, m_current_back_buffer);
|
||||
m_transport->set_time(m_transport->time() + sample_count);
|
||||
// FIXME: This should be handled automatically by Transport. It will also advance slightly past the loop point if we're unlucky.
|
||||
if (m_transport->time() >= roll_length)
|
||||
m_transport->set_time(0);
|
||||
}
|
||||
|
||||
void TrackManager::reset()
|
||||
{
|
||||
memset(m_front_buffer.data(), 0, buffer_size);
|
||||
memset(m_back_buffer.data(), 0, buffer_size);
|
||||
|
||||
m_current_front_buffer = m_front_buffer.span();
|
||||
m_current_back_buffer = m_back_buffer.span();
|
||||
|
||||
m_transport->set_time(0);
|
||||
|
||||
for (auto& track : m_tracks) {
|
||||
track->reset();
|
||||
track->set_active(false);
|
||||
}
|
||||
m_tracks[m_current_track]->set_active(true);
|
||||
}
|
||||
|
||||
void TrackManager::add_track()
|
||||
{
|
||||
m_tracks.append(make<Track>(m_transport, m_keyboard));
|
||||
auto new_track = make_ref_counted<DSP::NoteTrack>(m_transport, m_keyboard);
|
||||
MUST(new_track->resize_internal_buffers_to(m_temporary_track_buffer.size()));
|
||||
new_track->add_processor(make_ref_counted<DSP::Synthesizers::Classic>(m_transport));
|
||||
new_track->add_processor(make_ref_counted<DSP::Effects::Delay>(m_transport));
|
||||
new_track->add_clip(0, roll_length);
|
||||
m_tracks.append(move(new_track));
|
||||
}
|
||||
|
||||
int TrackManager::next_track_index() const
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "Music.h"
|
||||
#include "Track.h"
|
||||
#include <AK/Array.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibDSP/Keyboard.h>
|
||||
#include <LibDSP/Track.h>
|
||||
|
||||
class TrackManager {
|
||||
AK_MAKE_NONCOPYABLE(TrackManager);
|
||||
|
@ -26,16 +27,12 @@ public:
|
|||
TrackManager();
|
||||
~TrackManager() = default;
|
||||
|
||||
Track& current_track() { return *m_tracks[m_current_track]; }
|
||||
Span<const Sample> buffer() const { return m_current_front_buffer; }
|
||||
NonnullRefPtr<DSP::NoteTrack> current_track() { return *m_tracks[m_current_track]; }
|
||||
int track_count() { return m_tracks.size(); };
|
||||
void set_current_track(size_t track_index)
|
||||
{
|
||||
VERIFY((int)track_index < track_count());
|
||||
auto old_track = m_current_track;
|
||||
m_current_track = track_index;
|
||||
m_tracks[old_track]->set_active(false);
|
||||
m_tracks[m_current_track]->set_active(true);
|
||||
}
|
||||
|
||||
NonnullRefPtr<DSP::Transport> transport() const { return m_transport; }
|
||||
|
@ -43,7 +40,7 @@ public:
|
|||
// Legacy API, do not add new users.
|
||||
void time_forward(int amount);
|
||||
|
||||
void fill_buffer(Span<Sample>);
|
||||
void fill_buffer(FixedArray<DSP::Sample>&);
|
||||
void reset();
|
||||
void set_keyboard_note(int note, DSP::Keyboard::Switch note_switch);
|
||||
void set_should_loop(bool b) { m_should_loop = b; }
|
||||
|
@ -51,15 +48,12 @@ public:
|
|||
int next_track_index() const;
|
||||
|
||||
private:
|
||||
Vector<NonnullOwnPtr<Track>> m_tracks;
|
||||
Vector<NonnullRefPtr<DSP::NoteTrack>> m_tracks;
|
||||
NonnullRefPtr<DSP::Transport> m_transport;
|
||||
NonnullRefPtr<DSP::Keyboard> m_keyboard;
|
||||
size_t m_current_track { 0 };
|
||||
|
||||
Array<Sample, sample_count> m_front_buffer;
|
||||
Array<Sample, sample_count> m_back_buffer;
|
||||
Span<Sample> m_current_front_buffer { m_front_buffer.span() };
|
||||
Span<Sample> m_current_back_buffer { m_back_buffer.span() };
|
||||
FixedArray<DSP::Sample> m_temporary_track_buffer;
|
||||
|
||||
bool m_should_loop { true };
|
||||
};
|
||||
|
|
|
@ -33,9 +33,10 @@ 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().synth()->wave()];
|
||||
Color right_wave_color = right_wave_colors[m_track_manager.current_track().synth()->wave()];
|
||||
auto buffer = m_track_manager.buffer();
|
||||
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()];
|
||||
// FIXME: We can't get the last buffer from the track manager anymore
|
||||
auto buffer = FixedArray<Music::Sample>::must_create_but_fixme_should_propagate_errors(sample_count);
|
||||
double width_scale = static_cast<double>(frame_inner_rect().width()) / buffer.size();
|
||||
|
||||
int prev_x = 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue