1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 12:57:36 +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:
kleines Filmröllchen 2022-07-13 12:44:19 +02:00 committed by Linus Groh
parent 125122a9ab
commit 4941cffdd0
29 changed files with 322 additions and 413 deletions

View file

@ -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);

View file

@ -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;

View file

@ -7,7 +7,6 @@ serenity_component(
set(SOURCES
AudioPlayerLoop.cpp
Track.cpp
TrackManager.cpp
KeysWidget.cpp
KnobsWidget.cpp

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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());
};
});
}

View file

@ -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 };
};

View file

@ -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();
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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 };
};

View file

@ -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

View file

@ -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 };
};

View file

@ -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;