Libraries: Add LibDSP

LibDSP is a library for digital signal processing, and is primarily
intended to support the future DAW version of Piano.
This commit is contained in:
kleines Filmröllchen 2021-08-27 16:18:11 +02:00 committed by Ali Mohammad Pur
parent 8f4b577405
commit a749b16674
Notes: sideshowbarker 2024-07-18 08:59:31 +09:00
14 changed files with 829 additions and 0 deletions

View File

@ -63,6 +63,7 @@ I'm also on [Patreon](https://www.patreon.com/serenityos) and [GitHub Sponsors](
* JavaScript engine (LibJS)
* Markdown (LibMarkdown)
* Audio (LibAudio)
* Digital Signal Processing/Synthesizer Chains (LibDSP)
* PCI database (LibPCIDB)
* Terminal emulation (LibVT)
* Out-of-process network protocol I/O (LibProtocol)

View File

@ -14,6 +14,7 @@ add_subdirectory(LibDebug)
add_subdirectory(LibDesktop)
add_subdirectory(LibDiff)
add_subdirectory(LibDl)
add_subdirectory(LibDSP)
add_subdirectory(LibELF)
add_subdirectory(LibFileSystemAccessClient)
add_subdirectory(LibGemini)

View File

@ -60,6 +60,12 @@ struct Frame {
right *= pct;
}
// FIXME: This is temporary until we have log scaling
Frame scaled(double fraction) const
{
return Frame { left * fraction, right * fraction };
}
Frame& operator+=(const Frame& other)
{
left += other.left;

View File

@ -0,0 +1,8 @@
set(SOURCES
Clip.cpp
Track.cpp
Effects.cpp
)
serenity_lib(LibDSP dsp)
target_link_libraries(LibDSP LibCore)

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Clip.h"
namespace LibDSP {
Sample AudioClip::sample_at(u32 time)
{
VERIFY(time < m_length);
return m_samples[time];
}
void NoteClip::set_note(RollNote note)
{
VERIFY(note.pitch >= 0 && note.pitch < note_count);
VERIFY(note.off_sample < m_length);
VERIFY(note.length() >= 2);
auto& notes = m_notes[note.pitch];
for (auto it = notes.begin(); !it.is_end();) {
auto iterated_note = *it;
if (iterated_note.on_sample > note.off_sample) {
notes.insert_before(it, note);
return;
}
if (iterated_note.on_sample <= note.on_sample && iterated_note.off_sample >= note.on_sample) {
notes.remove(it);
return;
}
if ((note.on_sample == 0 || iterated_note.on_sample >= note.on_sample - 1) && iterated_note.on_sample <= note.off_sample) {
notes.remove(it);
it = notes.begin();
continue;
}
++it;
}
notes.append(note);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Music.h"
#include <AK/SinglyLinkedList.h>
#include <AK/Types.h>
#include <LibCore/Object.h>
namespace LibDSP {
// A clip is a self-contained snippet of notes or audio that can freely move inside and in between tracks.
class Clip : public Core::Object {
C_OBJECT_ABSTRACT(Clip)
public:
Clip(u32 start, u32 length)
: m_start(start)
, m_length(length)
{
}
virtual ~Clip() = default;
u32 start() const { return m_start; }
u32 length() const { return m_length; }
u32 end() const { return m_start + m_length; }
protected:
u32 m_start;
u32 m_length;
};
class AudioClip final : public Clip {
public:
Sample sample_at(u32 time);
Vector<Sample> const& samples() const { return m_samples; }
private:
Vector<Sample> m_samples;
};
class NoteClip final : public Clip {
public:
void set_note(RollNote note);
Array<SinglyLinkedList<RollNote>, note_count>& notes() { return m_notes; }
private:
Array<SinglyLinkedList<RollNote>, note_count> m_notes;
};
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Effects.h"
#include <math.h>
namespace LibDSP::Effects {
Delay::Delay(NonnullRefPtr<Transport> transport)
: EffectProcessor(move(transport))
, m_delay_decay("Decay"sv, 0.01, 0.99, 0.33)
, m_delay_time("Delay Time"sv, 3, 2000, 900)
, m_dry_gain("Dry"sv, 0, 1, 0.9)
{
m_parameters.append(m_delay_decay);
m_parameters.append(m_delay_time);
m_parameters.append(m_dry_gain);
}
void Delay::handle_delay_time_change()
{
// We want a delay buffer that can hold samples filling the specified number of milliseconds.
double seconds = static_cast<double>(m_delay_time) / 1000.0;
size_t sample_count = ceil(seconds * m_transport->sample_rate());
if (sample_count != m_delay_buffer.size()) {
m_delay_buffer.resize(sample_count, true);
m_delay_index %= max(m_delay_buffer.size(), 1);
m_old_delay_size = m_delay_buffer.size();
}
}
Signal Delay::process_impl(Signal const& input_signal)
{
handle_delay_time_change();
Sample const& in = input_signal.get<Sample>();
Sample out;
// FIXME: Once we have log scaling, change these to use it instead
out += in.scaled(static_cast<double>(m_dry_gain));
out += m_delay_buffer[m_delay_index].scaled(m_delay_decay);
// This is also convenient for disabling the delay effect by setting the buffer size to 0
if (m_delay_buffer.size() >= 1)
m_delay_buffer[m_delay_index++] = out;
if (m_delay_index >= m_delay_buffer.size())
m_delay_index = 0;
return Signal(out);
}
Mastering::Mastering(NonnullRefPtr<Transport> transport)
: EffectProcessor(move(transport))
{
}
Signal Mastering::process_impl([[maybe_unused]] Signal const& input_signal)
{
TODO();
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Processor.h"
#include "ProcessorParameter.h"
#include "Transport.h"
#include <AK/Types.h>
namespace LibDSP::Effects {
// A simple digital delay effect using a delay buffer.
// This is based on Piano's old built-in delay.
class Delay : public EffectProcessor {
public:
Delay(NonnullRefPtr<Transport>);
private:
virtual Signal process_impl(Signal const&) override;
void handle_delay_time_change();
ProcessorRangeParameter m_delay_decay;
ProcessorRangeParameter m_delay_time;
ProcessorRangeParameter m_dry_gain;
Vector<Sample> m_delay_buffer;
size_t m_delay_index { 0 };
size_t m_old_delay_size = m_delay_buffer.size();
};
// A simple effect that applies volume, mute and pan to its input signal.
// Convenient for attenuating signals in the middle of long chains.
class Mastering : public EffectProcessor {
public:
Mastering(NonnullRefPtr<Transport>);
private:
virtual Signal process_impl(Signal const&) override;
};
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibAudio/Buffer.h>
namespace LibDSP {
// FIXME: Audio::Frame is 64-bit float, which is quite large for long clips.
using Sample = Audio::Frame;
Sample const SAMPLE_OFF = { 0.0, 0.0 };
struct RollNote {
u32 length() const { return (off_sample - on_sample) + 1; }
u32 on_sample;
u32 off_sample;
u8 pitch;
i8 velocity;
};
enum class SignalType : u8 {
Invalid,
Sample,
Note
};
struct Signal : public Variant<Sample, Vector<RollNote>> {
using Variant::Variant;
ALWAYS_INLINE SignalType type() const
{
return has<Sample>() ? SignalType::Sample : has<Vector<RollNote>>() ? SignalType::Note
: SignalType::Invalid;
}
};
// Equal temperament, A = 440Hz
// We calculate note frequencies relative to A4:
// 440.0 * pow(pow(2.0, 1.0 / 12.0), N)
// Where N is the note distance from A.
constexpr double note_frequencies[] = {
// Octave 1
32.703195662574764,
34.647828872108946,
36.708095989675876,
38.890872965260044,
41.203444614108669,
43.653528929125407,
46.249302838954222,
48.99942949771858,
51.913087197493056,
54.999999999999915,
58.270470189761156,
61.735412657015416,
// Octave 2
65.406391325149571,
69.295657744217934,
73.416191979351794,
77.781745930520117,
82.406889228217381,
87.307057858250872,
92.4986056779085,
97.998858995437217,
103.82617439498618,
109.99999999999989,
116.54094037952237,
123.4708253140309,
// Octave 3
130.8127826502992,
138.59131548843592,
146.83238395870364,
155.56349186104035,
164.81377845643485,
174.61411571650183,
184.99721135581709,
195.99771799087452,
207.65234878997245,
219.99999999999989,
233.08188075904488,
246.94165062806198,
// Octave 4
261.62556530059851,
277.18263097687202,
293.66476791740746,
311.12698372208081,
329.62755691286986,
349.22823143300383,
369.99442271163434,
391.99543598174927,
415.30469757994513,
440,
466.16376151808993,
493.88330125612413,
// Octave 5
523.25113060119736,
554.36526195374427,
587.32953583481526,
622.25396744416196,
659.25511382574007,
698.456462866008,
739.98884542326903,
783.99087196349899,
830.60939515989071,
880.00000000000034,
932.32752303618031,
987.76660251224882,
// Octave 6
1046.5022612023952,
1108.7305239074892,
1174.659071669631,
1244.5079348883246,
1318.5102276514808,
1396.9129257320169,
1479.977690846539,
1567.9817439269987,
1661.2187903197821,
1760.000000000002,
1864.6550460723618,
1975.5332050244986,
// Octave 7
2093.0045224047913,
2217.4610478149793,
2349.3181433392633,
2489.0158697766506,
2637.020455302963,
2793.8258514640347,
2959.9553816930793,
3135.9634878539991,
3322.437580639566,
3520.0000000000055,
3729.3100921447249,
3951.0664100489994,
};
constexpr size_t note_count = array_size(note_frequencies);
constexpr double middle_c = note_frequencies[36];
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/StdLibExtras.h>
#include <AK/Types.h>
#include <LibCore/Object.h>
#include <LibDSP/Music.h>
#include <LibDSP/ProcessorParameter.h>
#include <LibDSP/Transport.h>
namespace LibDSP {
// A processor processes notes or audio into notes or audio. Processors are e.g. samplers, synthesizers, effects, arpeggiators etc.
class Processor : public Core::Object {
C_OBJECT_ABSTRACT(Processor);
public:
virtual ~Processor()
{
}
Signal process(Signal const& input_signal)
{
VERIFY(input_signal.type() == m_input_type);
auto processed = process_impl(input_signal);
VERIFY(processed.type() == m_output_type);
return processed;
}
SignalType input_type() const { return m_input_type; }
SignalType output_type() const { return m_output_type; }
Vector<ProcessorParameter&>& parameters() { return m_parameters; }
private:
SignalType const m_input_type;
SignalType const m_output_type;
protected:
Processor(NonnullRefPtr<Transport> transport, SignalType input_type, SignalType output_type)
: m_input_type(input_type)
, m_output_type(output_type)
, m_transport(move(transport))
{
}
virtual Signal process_impl(Signal const& input_signal) = 0;
NonnullRefPtr<Transport> m_transport;
Vector<ProcessorParameter&> m_parameters;
};
// A common type of processor that changes audio data, i.e. applies an effect to it.
class EffectProcessor : public Processor {
protected:
EffectProcessor(NonnullRefPtr<Transport> transport)
: Processor(transport, SignalType::Sample, SignalType::Sample)
{
}
};
// A common type of processor that synthesizes audio from note data.
class SynthesizerProcessor : public Processor {
protected:
SynthesizerProcessor(NonnullRefPtr<Transport> transport)
: Processor(transport, SignalType::Note, SignalType::Sample)
{
}
};
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "AK/Forward.h"
#include "LibGUI/Label.h"
#include "Music.h"
#include <AK/FixedPoint.h>
#include <AK/Format.h>
#include <AK/Types.h>
#include <LibCore/Object.h>
namespace LibDSP {
using ParameterFixedPoint = FixedPoint<8, i64>;
// Processors have modifiable parameters that should be presented to the UI in a uniform way without requiring the processor itself to implement custom interfaces.
class ProcessorParameter {
public:
ProcessorParameter(String name)
: m_name(move(name))
{
}
String const& name() const { return m_name; }
private:
String const m_name;
};
namespace Detail {
struct ProcessorParameterSetValueTag {
explicit ProcessorParameterSetValueTag() = default;
};
template<typename ParameterT>
class ProcessorParameterSingleValue : public ProcessorParameter {
public:
ProcessorParameterSingleValue(String name, ParameterT initial_value)
: ProcessorParameter(move(name))
, m_value(move(initial_value))
{
}
operator ParameterT() const
{
return value();
}
operator double() const requires(IsSame<ParameterT, ParameterFixedPoint>)
{
return static_cast<double>(value());
}
ParameterT value() const { return m_value; };
void set_value(ParameterT value)
{
set_value_sneaky(value, LibDSP::Detail::ProcessorParameterSetValueTag {});
if (did_change_value)
did_change_value(value);
}
// Use of this function is discouraged. It doesn't notify the value listener.
void set_value_sneaky(ParameterT value, [[maybe_unused]] Detail::ProcessorParameterSetValueTag)
{
if (value != m_value)
m_value = value;
}
Function<void(ParameterT const&)> did_change_value;
protected:
ParameterT m_value;
};
}
class ProcessorBooleanParameter final : public Detail::ProcessorParameterSingleValue<bool> {
public:
ProcessorBooleanParameter(String name, bool initial_value)
: Detail::ProcessorParameterSingleValue<bool>(move(name), move(initial_value))
{
}
};
class ProcessorRangeParameter final : public Detail::ProcessorParameterSingleValue<ParameterFixedPoint> {
public:
ProcessorRangeParameter(String name, ParameterFixedPoint min_value, ParameterFixedPoint max_value, ParameterFixedPoint initial_value)
: Detail::ProcessorParameterSingleValue<ParameterFixedPoint>(move(name), move(initial_value))
, m_min_value(move(min_value))
, m_max_value(move(max_value))
, m_default_value(move(initial_value))
{
VERIFY(initial_value <= max_value && initial_value >= min_value);
}
ProcessorRangeParameter(ProcessorRangeParameter const& to_copy)
: ProcessorRangeParameter(to_copy.name(), to_copy.min_value(), to_copy.max_value(), to_copy.value())
{
}
ParameterFixedPoint min_value() const { return m_min_value; }
ParameterFixedPoint max_value() const { return m_max_value; }
ParameterFixedPoint default_value() const { return m_default_value; }
void set_value(ParameterFixedPoint value)
{
VERIFY(value <= m_max_value && value >= m_min_value);
Detail::ProcessorParameterSingleValue<ParameterFixedPoint>::set_value(value);
}
private:
double const m_min_value;
double const m_max_value;
double const m_default_value;
};
}
template<>
struct AK::Formatter<LibDSP::ProcessorRangeParameter> : AK::StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
void format(FormatBuilder& builder, LibDSP::ProcessorRangeParameter value)
{
if (m_mode == Mode::Pointer) {
Formatter<FlatPtr> formatter { *this };
formatter.format(builder, reinterpret_cast<FlatPtr>(&value));
return;
}
if (m_sign_mode != FormatBuilder::SignMode::Default)
VERIFY_NOT_REACHED();
if (m_alternative_form)
VERIFY_NOT_REACHED();
if (m_zero_pad)
VERIFY_NOT_REACHED();
if (m_mode != Mode::Default)
VERIFY_NOT_REACHED();
if (m_width.has_value() && m_precision.has_value())
VERIFY_NOT_REACHED();
m_width = m_width.value_or(0);
m_precision = m_precision.value_or(NumericLimits<size_t>::max());
builder.put_literal(String::formatted("[{} - {}]: {}", value.min_value(), value.max_value(), value.value()));
}
};

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Track.h"
#include "Processor.h"
#include <AK/Types.h>
using namespace std;
namespace LibDSP {
bool Track::add_processor(NonnullRefPtr<Processor> new_processor)
{
m_processor_chain.append(move(new_processor));
if (!check_processor_chain_valid()) {
m_processor_chain.take_last();
return false;
}
return true;
}
bool Track::check_processor_chain_valid_with_initial_type(SignalType initial_type) const
{
Processor const* previous_processor = nullptr;
for (auto& processor : m_processor_chain) {
// The first processor must have the given initial signal type as input.
if (previous_processor == nullptr) {
if (processor.input_type() != initial_type)
return false;
} else if (previous_processor->output_type() != processor.input_type())
return false;
previous_processor = &processor;
}
return true;
}
bool AudioTrack::check_processor_chain_valid() const
{
return check_processor_chain_valid_with_initial_type(SignalType::Sample);
}
bool NoteTrack::check_processor_chain_valid() const
{
return check_processor_chain_valid_with_initial_type(SignalType::Note);
}
Sample Track::current_signal()
{
Signal the_signal = current_clips_signal();
for (auto& processor : m_processor_chain) {
the_signal = processor.process(the_signal);
}
VERIFY(the_signal.type() == SignalType::Sample);
return the_signal.get<Sample>();
}
Signal NoteTrack::current_clips_signal()
{
u32 time = m_transport->time();
// Find the currently playing clip.
NoteClip* playing_clip = nullptr;
for (auto& clip : m_clips) {
if (clip.start() <= time && clip.end() >= time) {
playing_clip = &clip;
break;
}
}
if (playing_clip == nullptr) {
return Signal(Vector<RollNote>());
}
// Find the playing notes inside the clip.
Vector<RollNote> playing_notes;
// FIXME: performance?
for (auto& note_list : playing_clip->notes()) {
for (auto& note : note_list) {
if (note.on_sample >= time && note.off_sample >= time)
break;
if (note.on_sample <= time && note.off_sample >= time)
// FIXME: This copies the note, but we don't rely on playing_clip to keep its notes around.
playing_notes.append(note);
}
}
return Signal(playing_notes);
}
Signal AudioTrack::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) {
return Signal(static_cast<Sample const&>(SAMPLE_OFF));
}
// Index into the clip's samples.
u32 effective_sample = time - playing_clip->start();
return Signal(playing_clip->sample_at(effective_sample));
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Clip.h"
#include "Music.h"
#include "Processor.h"
#include <LibCore/Object.h>
namespace LibDSP {
// A track is also known as a channel and serves as a container for the audio pipeline: clips -> processors -> mixing & output
class Track : public Core::Object {
C_OBJECT_ABSTRACT(Track)
public:
Track(NonnullRefPtr<Transport> transport)
: m_transport(move(transport))
{
}
virtual ~Track() override = default;
virtual bool check_processor_chain_valid() const = 0;
bool add_processor(NonnullRefPtr<Processor> new_processor);
// Creates the current signal of the track by processing current note or audio data through the processing chain
Sample current_signal();
NonnullRefPtrVector<Processor> const& processor_chain() const { return m_processor_chain; }
NonnullRefPtr<Transport> const transport() const { return m_transport; }
protected:
bool check_processor_chain_valid_with_initial_type(SignalType initial_type) const;
// Subclasses override to provide the base signal to the processing chain
virtual Signal current_clips_signal() = 0;
NonnullRefPtrVector<Processor> m_processor_chain;
NonnullRefPtr<Transport> const m_transport;
};
class NoteTrack final : public Track {
public:
virtual ~NoteTrack() override = default;
bool check_processor_chain_valid() const override;
NonnullRefPtrVector<NoteClip> const& clips() const { return m_clips; }
protected:
virtual Signal current_clips_signal() override;
private:
NonnullRefPtrVector<NoteClip> m_clips;
};
class AudioTrack final : public Track {
public:
virtual ~AudioTrack() override = default;
bool check_processor_chain_valid() const override;
NonnullRefPtrVector<AudioClip> const& clips() const { return m_clips; }
protected:
virtual Signal current_clips_signal() override;
private:
NonnullRefPtrVector<AudioClip> m_clips;
};
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Music.h"
#include <AK/Types.h>
#include <LibCore/Object.h>
namespace LibDSP {
// The DAW-wide timekeeper and synchronizer
class Transport final : public Core::Object {
C_OBJECT(Transport)
public:
Transport(u16 beats_per_minute, u8 beats_per_measure, u32 sample_rate)
: m_beats_per_minute(beats_per_minute)
, m_beats_per_measure(beats_per_measure)
, m_sample_rate(sample_rate)
{
}
Transport(u16 beats_per_minute, u8 beats_per_measure)
: Transport(beats_per_minute, beats_per_measure, 44100)
{
}
u32 const& time() const { return m_time; }
u16 beats_per_minute() const { return m_beats_per_minute; }
double current_second() const { return m_time / m_sample_rate; }
double samples_per_measure() const { return (1.0 / m_beats_per_minute) * 60.0 * m_sample_rate; }
double sample_rate() const { return m_sample_rate; }
double current_measure() const { return m_time / samples_per_measure(); }
private:
u32 m_time { 0 };
u16 const m_beats_per_minute { 0 };
u8 const m_beats_per_measure { 0 };
u32 const m_sample_rate;
};
}