From a749b166748f46aa562b1f46eb400351f5a9a87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kleines=20Filmr=C3=B6llchen?= Date: Fri, 27 Aug 2021 16:18:11 +0200 Subject: [PATCH] Libraries: Add LibDSP LibDSP is a library for digital signal processing, and is primarily intended to support the future DAW version of Piano. --- README.md | 1 + Userland/Libraries/CMakeLists.txt | 1 + Userland/Libraries/LibAudio/Buffer.h | 6 + Userland/Libraries/LibDSP/CMakeLists.txt | 8 + Userland/Libraries/LibDSP/Clip.cpp | 45 +++++ Userland/Libraries/LibDSP/Clip.h | 56 +++++++ Userland/Libraries/LibDSP/Effects.cpp | 66 ++++++++ Userland/Libraries/LibDSP/Effects.h | 45 +++++ Userland/Libraries/LibDSP/Music.h | 146 +++++++++++++++++ Userland/Libraries/LibDSP/Processor.h | 73 +++++++++ .../Libraries/LibDSP/ProcessorParameter.h | 155 ++++++++++++++++++ Userland/Libraries/LibDSP/Track.cpp | 110 +++++++++++++ Userland/Libraries/LibDSP/Track.h | 73 +++++++++ Userland/Libraries/LibDSP/Transport.h | 44 +++++ 14 files changed, 829 insertions(+) create mode 100644 Userland/Libraries/LibDSP/CMakeLists.txt create mode 100644 Userland/Libraries/LibDSP/Clip.cpp create mode 100644 Userland/Libraries/LibDSP/Clip.h create mode 100644 Userland/Libraries/LibDSP/Effects.cpp create mode 100644 Userland/Libraries/LibDSP/Effects.h create mode 100644 Userland/Libraries/LibDSP/Music.h create mode 100644 Userland/Libraries/LibDSP/Processor.h create mode 100644 Userland/Libraries/LibDSP/ProcessorParameter.h create mode 100644 Userland/Libraries/LibDSP/Track.cpp create mode 100644 Userland/Libraries/LibDSP/Track.h create mode 100644 Userland/Libraries/LibDSP/Transport.h diff --git a/README.md b/README.md index 417ce3d748..4de68d292b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index 8c0ec81ef2..140ae6e3ee 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -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) diff --git a/Userland/Libraries/LibAudio/Buffer.h b/Userland/Libraries/LibAudio/Buffer.h index b00a533dc3..ebf40af0bd 100644 --- a/Userland/Libraries/LibAudio/Buffer.h +++ b/Userland/Libraries/LibAudio/Buffer.h @@ -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; diff --git a/Userland/Libraries/LibDSP/CMakeLists.txt b/Userland/Libraries/LibDSP/CMakeLists.txt new file mode 100644 index 0000000000..baa422c63d --- /dev/null +++ b/Userland/Libraries/LibDSP/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES + Clip.cpp + Track.cpp + Effects.cpp +) + +serenity_lib(LibDSP dsp) +target_link_libraries(LibDSP LibCore) diff --git a/Userland/Libraries/LibDSP/Clip.cpp b/Userland/Libraries/LibDSP/Clip.cpp new file mode 100644 index 0000000000..38335d64a9 --- /dev/null +++ b/Userland/Libraries/LibDSP/Clip.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * 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); +} + +} diff --git a/Userland/Libraries/LibDSP/Clip.h b/Userland/Libraries/LibDSP/Clip.h new file mode 100644 index 0000000000..95bc204249 --- /dev/null +++ b/Userland/Libraries/LibDSP/Clip.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Music.h" +#include +#include +#include + +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 const& samples() const { return m_samples; } + +private: + Vector m_samples; +}; + +class NoteClip final : public Clip { +public: + void set_note(RollNote note); + + Array, note_count>& notes() { return m_notes; } + +private: + Array, note_count> m_notes; +}; + +} diff --git a/Userland/Libraries/LibDSP/Effects.cpp b/Userland/Libraries/LibDSP/Effects.cpp new file mode 100644 index 0000000000..38c0232c34 --- /dev/null +++ b/Userland/Libraries/LibDSP/Effects.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Effects.h" +#include + +namespace LibDSP::Effects { + +Delay::Delay(NonnullRefPtr 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(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 out; + // FIXME: Once we have log scaling, change these to use it instead + out += in.scaled(static_cast(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) + : EffectProcessor(move(transport)) +{ +} + +Signal Mastering::process_impl([[maybe_unused]] Signal const& input_signal) +{ + TODO(); +} + +} diff --git a/Userland/Libraries/LibDSP/Effects.h b/Userland/Libraries/LibDSP/Effects.h new file mode 100644 index 0000000000..848589ca72 --- /dev/null +++ b/Userland/Libraries/LibDSP/Effects.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Processor.h" +#include "ProcessorParameter.h" +#include "Transport.h" +#include + +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); + +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 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); + +private: + virtual Signal process_impl(Signal const&) override; +}; + +} diff --git a/Userland/Libraries/LibDSP/Music.h b/Userland/Libraries/LibDSP/Music.h new file mode 100644 index 0000000000..5a9c434a6b --- /dev/null +++ b/Userland/Libraries/LibDSP/Music.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +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> { + using Variant::Variant; + ALWAYS_INLINE SignalType type() const + { + return has() ? SignalType::Sample : has>() ? 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]; + +} diff --git a/Userland/Libraries/LibDSP/Processor.h b/Userland/Libraries/LibDSP/Processor.h new file mode 100644 index 0000000000..b2deee3f04 --- /dev/null +++ b/Userland/Libraries/LibDSP/Processor.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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& parameters() { return m_parameters; } + +private: + SignalType const m_input_type; + SignalType const m_output_type; + +protected: + Processor(NonnullRefPtr 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 m_transport; + Vector 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) + : 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) + : Processor(transport, SignalType::Note, SignalType::Sample) + { + } +}; + +} diff --git a/Userland/Libraries/LibDSP/ProcessorParameter.h b/Userland/Libraries/LibDSP/ProcessorParameter.h new file mode 100644 index 0000000000..aa545397d4 --- /dev/null +++ b/Userland/Libraries/LibDSP/ProcessorParameter.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "AK/Forward.h" +#include "LibGUI/Label.h" +#include "Music.h" +#include +#include +#include +#include + +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 +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) + { + return static_cast(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 did_change_value; + +protected: + ParameterT m_value; +}; +} + +class ProcessorBooleanParameter final : public Detail::ProcessorParameterSingleValue { +public: + ProcessorBooleanParameter(String name, bool initial_value) + : Detail::ProcessorParameterSingleValue(move(name), move(initial_value)) + { + } +}; + +class ProcessorRangeParameter final : public Detail::ProcessorParameterSingleValue { +public: + ProcessorRangeParameter(String name, ParameterFixedPoint min_value, ParameterFixedPoint max_value, ParameterFixedPoint initial_value) + : Detail::ProcessorParameterSingleValue(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::set_value(value); + } + +private: + double const m_min_value; + double const m_max_value; + double const m_default_value; +}; + +} +template<> +struct AK::Formatter : AK::StandardFormatter { + + Formatter() = default; + explicit Formatter(StandardFormatter formatter) + : StandardFormatter(formatter) + { + } + void format(FormatBuilder& builder, LibDSP::ProcessorRangeParameter value) + { + if (m_mode == Mode::Pointer) { + Formatter formatter { *this }; + formatter.format(builder, reinterpret_cast(&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::max()); + + builder.put_literal(String::formatted("[{} - {}]: {}", value.min_value(), value.max_value(), value.value())); + } +}; diff --git a/Userland/Libraries/LibDSP/Track.cpp b/Userland/Libraries/LibDSP/Track.cpp new file mode 100644 index 0000000000..5369201d07 --- /dev/null +++ b/Userland/Libraries/LibDSP/Track.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Track.h" +#include "Processor.h" +#include + +using namespace std; + +namespace LibDSP { + +bool Track::add_processor(NonnullRefPtr 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(); +} + +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()); + } + + // Find the playing notes inside the clip. + Vector 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_OFF)); + } + + // Index into the clip's samples. + u32 effective_sample = time - playing_clip->start(); + return Signal(playing_clip->sample_at(effective_sample)); +} + +} diff --git a/Userland/Libraries/LibDSP/Track.h b/Userland/Libraries/LibDSP/Track.h new file mode 100644 index 0000000000..95f075b3f8 --- /dev/null +++ b/Userland/Libraries/LibDSP/Track.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Clip.h" +#include "Music.h" +#include "Processor.h" +#include + +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) + : m_transport(move(transport)) + { + } + virtual ~Track() override = default; + + virtual bool check_processor_chain_valid() const = 0; + bool add_processor(NonnullRefPtr new_processor); + + // Creates the current signal of the track by processing current note or audio data through the processing chain + Sample current_signal(); + + NonnullRefPtrVector const& processor_chain() const { return m_processor_chain; } + NonnullRefPtr 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 m_processor_chain; + NonnullRefPtr const m_transport; +}; + +class NoteTrack final : public Track { +public: + virtual ~NoteTrack() override = default; + + bool check_processor_chain_valid() const override; + NonnullRefPtrVector const& clips() const { return m_clips; } + +protected: + virtual Signal current_clips_signal() override; + +private: + NonnullRefPtrVector m_clips; +}; + +class AudioTrack final : public Track { +public: + virtual ~AudioTrack() override = default; + + bool check_processor_chain_valid() const override; + NonnullRefPtrVector const& clips() const { return m_clips; } + +protected: + virtual Signal current_clips_signal() override; + +private: + NonnullRefPtrVector m_clips; +}; + +} diff --git a/Userland/Libraries/LibDSP/Transport.h b/Userland/Libraries/LibDSP/Transport.h new file mode 100644 index 0000000000..d588ca0334 --- /dev/null +++ b/Userland/Libraries/LibDSP/Transport.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Music.h" +#include +#include + +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; +}; + +}