mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03:00
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:
parent
8f4b577405
commit
a749b16674
Notes:
sideshowbarker
2024-07-18 08:59:31 +09:00
Author: https://github.com/kleinesfilmroellchen Commit: https://github.com/SerenityOS/serenity/commit/a749b166748 Pull-request: https://github.com/SerenityOS/serenity/pull/9638 Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/alimpfard
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
8
Userland/Libraries/LibDSP/CMakeLists.txt
Normal file
8
Userland/Libraries/LibDSP/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
Clip.cpp
|
||||
Track.cpp
|
||||
Effects.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibDSP dsp)
|
||||
target_link_libraries(LibDSP LibCore)
|
45
Userland/Libraries/LibDSP/Clip.cpp
Normal file
45
Userland/Libraries/LibDSP/Clip.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
56
Userland/Libraries/LibDSP/Clip.h
Normal file
56
Userland/Libraries/LibDSP/Clip.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
66
Userland/Libraries/LibDSP/Effects.cpp
Normal file
66
Userland/Libraries/LibDSP/Effects.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
45
Userland/Libraries/LibDSP/Effects.h
Normal file
45
Userland/Libraries/LibDSP/Effects.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
146
Userland/Libraries/LibDSP/Music.h
Normal file
146
Userland/Libraries/LibDSP/Music.h
Normal 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];
|
||||
|
||||
}
|
73
Userland/Libraries/LibDSP/Processor.h
Normal file
73
Userland/Libraries/LibDSP/Processor.h
Normal 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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
155
Userland/Libraries/LibDSP/ProcessorParameter.h
Normal file
155
Userland/Libraries/LibDSP/ProcessorParameter.h
Normal 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()));
|
||||
}
|
||||
};
|
110
Userland/Libraries/LibDSP/Track.cpp
Normal file
110
Userland/Libraries/LibDSP/Track.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
73
Userland/Libraries/LibDSP/Track.h
Normal file
73
Userland/Libraries/LibDSP/Track.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
44
Userland/Libraries/LibDSP/Transport.h
Normal file
44
Userland/Libraries/LibDSP/Transport.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user