1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 02:37:36 +00:00

LibDSP+Piano: Convert DSP APIs to accept entire sample ranges

This has mainly performance benefits, so that we only need to call into
all processors once for every audio buffer segment. It requires
adjusting quite some logic in most processors and in Track, as we have
to consider a larger collection of notes and samples at each step.

There's some cautionary TODOs in the currently unused LibDSP tracks
because they don't do things properly yet.
This commit is contained in:
kleines Filmröllchen 2022-05-11 23:24:46 +02:00 committed by Linus Groh
parent 4d65607649
commit 9035d9e845
9 changed files with 123 additions and 75 deletions

View file

@ -4,13 +4,16 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/FixedArray.h>
#include <AK/NoAllocationGuard.h>
#include <AK/Optional.h>
#include <AK/StdLibExtras.h>
#include <AK/TypedTransfer.h>
#include <AK/Types.h>
#include <LibDSP/Music.h>
#include <LibDSP/Processor.h>
#include <LibDSP/Track.h>
using namespace std;
namespace LibDSP {
bool Track::add_processor(NonnullRefPtr<Processor> new_processor)
@ -48,20 +51,43 @@ bool NoteTrack::check_processor_chain_valid() const
return check_processor_chain_valid_with_initial_type(SignalType::Note);
}
Sample Track::current_signal()
ErrorOr<void> Track::resize_internal_buffers_to(size_t buffer_size)
{
m_secondary_sample_buffer = TRY(FixedArray<Sample>::try_create(buffer_size));
return {};
}
void Track::current_signal(FixedArray<Sample>& output_signal)
{
// This is real-time code. We must NEVER EVER EVER allocate.
NoAllocationGuard guard;
VERIFY(output_signal.size() == m_secondary_sample_buffer.get<FixedArray<Sample>>().size());
compute_current_clips_signal();
Optional<Signal> the_signal;
Signal* source_signal = &m_current_signal;
// This provides an audio buffer of the right size. It is not allocated here, but whenever we are informed about a buffer size change.
Signal* target_signal = &m_secondary_sample_buffer;
for (auto& processor : m_processor_chain) {
the_signal = processor.process(the_signal.value_or(m_current_signal));
// Depending on what the processor needs to have as output, we need to place either a pre-allocated note hash map or a pre-allocated sample buffer in the target signal.
if (processor.output_type() == SignalType::Note)
target_signal = &m_secondary_note_buffer;
else
target_signal = &m_secondary_sample_buffer;
processor.process(*source_signal, *target_signal);
swap(source_signal, target_signal);
}
VERIFY(the_signal.has_value() && the_signal->type() == SignalType::Sample);
return the_signal->get<Sample>();
VERIFY(source_signal->type() == SignalType::Sample);
VERIFY(output_signal.size() == source_signal->get<FixedArray<Sample>>().size());
// This is one final unavoidable memcopy. Otherwise we need to special-case the last processor or
AK::TypedTransfer<Sample>::copy(output_signal.data(), source_signal->get<FixedArray<Sample>>().data(), output_signal.size());
}
void NoteTrack::compute_current_clips_signal()
{
// Consider the entire time duration.
TODO();
u32 time = m_transport->time();
// Find the currently playing clip.
NoteClip* playing_clip = nullptr;
@ -91,22 +117,8 @@ void NoteTrack::compute_current_clips_signal()
void AudioTrack::compute_current_clips_signal()
{
// Find the currently playing clip.
u32 time = m_transport->time();
AudioClip* playing_clip = nullptr;
for (auto& clip : m_clips) {
if (clip.start() <= time && clip.end() >= time) {
playing_clip = &clip;
break;
}
}
if (playing_clip == nullptr) {
m_current_signal = Signal(static_cast<Sample const&>(SAMPLE_OFF));
}
// Index into the clip's samples.
u32 effective_sample = time - playing_clip->start();
m_current_signal = Signal(playing_clip->sample_at(effective_sample));
// This is quite involved as we need to look at multiple clips and take looping into account.
TODO();
}
}