diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index 9fc671a09fb..0db68bd9a8c 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -8,7 +8,6 @@ add_subdirectory(LibCrypt) add_subdirectory(LibCrypto) add_subdirectory(LibDesktop) add_subdirectory(LibDiff) -add_subdirectory(LibDSP) add_subdirectory(LibFileSystem) add_subdirectory(LibFileSystemAccessClient) add_subdirectory(LibGemini) diff --git a/Userland/Libraries/LibDSP/CMakeLists.txt b/Userland/Libraries/LibDSP/CMakeLists.txt deleted file mode 100644 index c43fe41ee87..00000000000 --- a/Userland/Libraries/LibDSP/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -set(SOURCES - Clip.cpp - Effects.cpp - Synthesizers.cpp - Keyboard.cpp - Track.cpp -) - -serenity_lib(LibDSP dsp) diff --git a/Userland/Libraries/LibDSP/Clip.cpp b/Userland/Libraries/LibDSP/Clip.cpp deleted file mode 100644 index 032b97dc64f..00000000000 --- a/Userland/Libraries/LibDSP/Clip.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Clip.h" - -namespace DSP { - -Sample AudioClip::sample_at(u32 time) -{ - VERIFY(time < m_length); - return m_samples[time]; -} - -Optional NoteClip::note_at(u32 time, u8 pitch) const -{ - for (auto& note : m_notes) { - if (time >= note.on_sample && time <= note.off_sample && pitch == note.pitch) - return note; - } - return {}; -} - -void NoteClip::set_note(RollNote note) -{ - m_notes.remove_all_matching([&](auto const& other) { - return other.pitch == note.pitch && other.overlaps_with(note); - }); - m_notes.append(note); -} - -void NoteClip::remove_note(RollNote note) -{ - // FIXME: See header; this could be much faster with a better datastructure. - m_notes.remove_first_matching([note](auto const& element) { - return element.on_sample == note.on_sample && element.off_sample == note.off_sample && element.pitch == note.pitch; - }); -} - -} diff --git a/Userland/Libraries/LibDSP/Clip.h b/Userland/Libraries/LibDSP/Clip.h deleted file mode 100644 index 32c79947153..00000000000 --- a/Userland/Libraries/LibDSP/Clip.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace DSP { - -// A clip is a self-contained snippet of notes or audio that can freely move inside and in between tracks. -class Clip : public RefCounted { -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: - NoteClip(u32 start, u32 length) - : Clip(start, length) - { - } - - Optional note_at(u32 time, u8 pitch) const; - void set_note(RollNote note); - // May do nothing; that's fine. - void remove_note(RollNote note); - - ReadonlySpan notes() const { return m_notes.span(); } - - RollNote operator[](size_t index) const { return m_notes[index]; } - RollNote operator[](size_t index) { return m_notes[index]; } - bool is_empty() const { return m_notes.is_empty(); } - -private: - // FIXME: Better datastructures to think about here: B-Trees or good ol' RBTrees (not very cache friendly) - Vector m_notes; -}; - -} diff --git a/Userland/Libraries/LibDSP/Effects.cpp b/Userland/Libraries/LibDSP/Effects.cpp deleted file mode 100644 index 33d19f145c6..00000000000 --- a/Userland/Libraries/LibDSP/Effects.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2021, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Effects.h" -#include -#include - -namespace DSP::Effects { - -Delay::Delay(NonnullRefPtr transport) - : EffectProcessor(move(transport)) - , m_delay_decay("Decay"_string, 0.01, 0.99, 0.33, Logarithmic::No) - , m_delay_time("Delay Time"_string, 3, 2000, 900, Logarithmic::Yes) - , m_dry_gain("Dry"_string, 0, 1, 0.9, Logarithmic::No) -{ - - m_parameters.append(m_delay_decay); - m_parameters.append(m_delay_time); - m_parameters.append(m_dry_gain); - - m_delay_time.register_change_listener([this](auto const&) { - this->handle_delay_time_change(); - }); - handle_delay_time_change(); -} - -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); - } -} - -void Delay::process_impl(Signal const& input_signal, Signal& output_signal) -{ - auto const& samples = input_signal.get>(); - auto& output = output_signal.get>(); - for (size_t i = 0; i < output.size(); ++i) { - auto& out = output[i]; - auto const& sample = samples[i]; - out += sample.log_multiplied(static_cast(m_dry_gain)); - out += m_delay_buffer[m_delay_index].log_multiplied(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; - } -} - -Mastering::Mastering(NonnullRefPtr transport) - : EffectProcessor(move(transport)) - , m_pan("Pan"_string, -1, 1, 0, Logarithmic::No) - , m_volume("Volume"_string, 0, 1, 1, Logarithmic::No) - , m_muted("Mute"_string, false) -{ - m_parameters.append(m_muted); - m_parameters.append(m_volume); - m_parameters.append(m_pan); -} - -void Mastering::process_impl(Signal const& input_signal, Signal& output) -{ - process_to_fixed_array(input_signal, output.get>()); -} - -void Mastering::process_to_fixed_array(Signal const& input_signal, FixedArray& output) -{ - if (m_muted) { - output.fill_with({}); - return; - } - - auto const& input = input_signal.get>(); - for (size_t i = 0; i < input.size(); ++i) { - auto sample = input[i]; - sample.log_multiply(static_cast(m_volume)); - sample.pan(static_cast(m_pan)); - output[i] = sample; - } -} - -} diff --git a/Userland/Libraries/LibDSP/Effects.h b/Userland/Libraries/LibDSP/Effects.h deleted file mode 100644 index bcfa6feb327..00000000000 --- a/Userland/Libraries/LibDSP/Effects.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace DSP::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 void process_impl(Signal const&, Signal&) 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 }; -}; - -// 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); - - // The mastering processor can be used by the track and therefore needs to be able to write to a fixed array directly. - // Otherwise, Track needs to do more unnecessary sample data copies. - void process_to_fixed_array(Signal const&, FixedArray&); - -private: - virtual void process_impl(Signal const&, Signal&) override; - - ProcessorRangeParameter m_pan; - ProcessorRangeParameter m_volume; - ProcessorBooleanParameter m_muted; -}; - -} diff --git a/Userland/Libraries/LibDSP/Envelope.h b/Userland/Libraries/LibDSP/Envelope.h deleted file mode 100644 index a456a579efe..00000000000 --- a/Userland/Libraries/LibDSP/Envelope.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021, kleines Filmröllchen . - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace DSP { - -// For now, this cannot be optimal as clang doesn't know underlying type specifications. -enum EnvelopeState { - Off, - Attack, - Decay, - Sustain, - Release, -}; - -struct Envelope { - constexpr Envelope() = default; - constexpr Envelope(double envelope) - : envelope(envelope) - { - } - - constexpr bool is_attack() const { return 0 <= envelope && envelope < 1; } - constexpr double attack() const { return clamp(envelope, 0, 1); } - constexpr void set_attack(double offset) { envelope = offset; } - static constexpr Envelope from_attack(double attack) { return Envelope(attack); } - - constexpr bool is_decay() const { return 1 <= envelope && envelope < 2; } - constexpr double decay() const { return clamp(envelope, 1, 2) - 1; } - constexpr void set_decay(double offset) { envelope = 1 + offset; } - static constexpr Envelope from_decay(double decay) { return Envelope(decay + 1); } - - constexpr bool is_sustain() const { return 2 <= envelope && envelope < 3; } - constexpr double sustain() const { return clamp(envelope, 2, 3) - 2; } - constexpr void set_sustain(double offset) { envelope = 2 + offset; } - static constexpr Envelope from_sustain(double decay) { return Envelope(decay + 2); } - - constexpr bool is_release() const { return 3 <= envelope && envelope < 4; } - constexpr double release() const { return clamp(envelope, 3, 4) - 3; } - constexpr void set_release(double offset) { envelope = 3 + offset; } - static constexpr Envelope from_release(double decay) { return Envelope(decay + 3); } - - constexpr bool is_active() const { return 0 <= envelope && envelope < 4; } - - constexpr void reset() { envelope = -1; } - - constexpr operator EnvelopeState() const - { - if (!is_active()) - return EnvelopeState::Off; - if (is_attack()) - return EnvelopeState::Attack; - if (is_decay()) - return EnvelopeState::Decay; - if (is_sustain()) - return EnvelopeState::Sustain; - if (is_release()) - return EnvelopeState::Release; - VERIFY_NOT_REACHED(); - } - - double envelope { -1 }; -}; - -} diff --git a/Userland/Libraries/LibDSP/FFT.h b/Userland/Libraries/LibDSP/FFT.h deleted file mode 100644 index 0bc35e44d58..00000000000 --- a/Userland/Libraries/LibDSP/FFT.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021, Cesar Torres - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace DSP { - -constexpr void fft(Span> sample_data, bool invert = false) -{ - int n = sample_data.size(); - - for (int i = 1, j = 0; i < n; i++) { - int bit = n >> 1; - for (; j & bit; bit >>= 1) - j ^= bit; - j ^= bit; - - if (i < j) - swap(sample_data[i], sample_data[j]); - } - - for (int len = 2; len <= n; len <<= 1) { - float ang = 2 * AK::Pi / static_cast(len * (invert ? -1 : 1)); - Complex wlen = Complex::from_polar(1.f, ang); - for (int i = 0; i < n; i += len) { - Complex w = { 1., 0. }; - for (int j = 0; j < len / 2; j++) { - Complex u = sample_data[i + j]; - Complex v = sample_data[i + j + len / 2] * w; - sample_data[i + j] = u + v; - sample_data[i + j + len / 2] = u - v; - w *= wlen; - } - } - } - - if (invert) { - for (int i = 0; i < n; i++) - sample_data[i] /= n; - } -} - -} diff --git a/Userland/Libraries/LibDSP/Keyboard.cpp b/Userland/Libraries/LibDSP/Keyboard.cpp deleted file mode 100644 index eaa53e0d5c0..00000000000 --- a/Userland/Libraries/LibDSP/Keyboard.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Keyboard.h" -#include "Music.h" -#include -#include - -namespace DSP { - -void Keyboard::set_keyboard_note(u8 pitch, Keyboard::Switch note_switch) -{ - VERIFY(pitch < note_frequencies.size()); - - if (note_switch == Switch::Off) { - m_pressed_notes[pitch] = {}; - return; - } - - auto fake_note = RollNote { - .on_sample = m_transport->time(), - .off_sample = NumericLimits::max(), - .pitch = pitch, - .velocity = NumericLimits::max(), - }; - - m_pressed_notes[pitch] = fake_note; -} -void Keyboard::set_keyboard_note_in_active_octave(i8 octave_offset, Switch note_switch) -{ - u8 real_note = octave_offset + (m_virtual_keyboard_octave - 1) * notes_per_octave; - set_keyboard_note(real_note, note_switch); -} - -void Keyboard::change_virtual_keyboard_octave(Direction direction) -{ - if (direction == Direction::Up) { - if (m_virtual_keyboard_octave < octave_max) - ++m_virtual_keyboard_octave; - } else { - if (m_virtual_keyboard_octave > octave_min) - --m_virtual_keyboard_octave; - } -} - -ErrorOr Keyboard::set_virtual_keyboard_octave(u8 octave) -{ - if (octave <= octave_max && octave >= octave_min) { - m_virtual_keyboard_octave = octave; - return {}; - } - return Error::from_string_literal("Octave out of range"); -} - -bool Keyboard::is_pressed(u8 pitch) const -{ - return m_pressed_notes[pitch].has_value() && m_pressed_notes[pitch]->is_playing(m_transport->time()); -} - -bool Keyboard::is_pressed_in_active_octave(i8 octave_offset) const -{ - return is_pressed(octave_offset + virtual_keyboard_octave_base()); -} - -} diff --git a/Userland/Libraries/LibDSP/Keyboard.h b/Userland/Libraries/LibDSP/Keyboard.h deleted file mode 100644 index a4f42ed2652..00000000000 --- a/Userland/Libraries/LibDSP/Keyboard.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace DSP { - -class Keyboard : public RefCounted { - -public: - enum class Direction : bool { - Down, - Up, - }; - enum class Switch : bool { - Off, - On, - }; - - Keyboard(NonnullRefPtr transport) - : m_transport(move(transport)) - { - } - - u8 virtual_keyboard_octave() const { return m_virtual_keyboard_octave; } - u8 virtual_keyboard_octave_base() const { return (m_virtual_keyboard_octave - octave_min) * 12; } - // Automatically clips the octave between the minimum and maximum. - void change_virtual_keyboard_octave(Direction); - // Errors out if the set octave is out of range. - ErrorOr set_virtual_keyboard_octave(u8 octave); - - void set_keyboard_note(u8 pitch, Switch note_switch); - void set_keyboard_note_in_active_octave(i8 octave_offset, Switch note_switch); - - RollNotes const& notes() const { return m_pressed_notes; } - Optional note_at(u8 pitch) const { return m_pressed_notes[pitch]; } - bool is_pressed(u8 pitch) const; - bool is_pressed_in_active_octave(i8 octave_offset) const; - -private: - u8 m_virtual_keyboard_octave { 4 }; - RollNotes m_pressed_notes; - - NonnullRefPtr m_transport; - - static constexpr int const octave_min = 1; - static constexpr int const octave_max = 7; -}; - -} diff --git a/Userland/Libraries/LibDSP/Music.h b/Userland/Libraries/LibDSP/Music.h deleted file mode 100644 index 8f443e39a82..00000000000 --- a/Userland/Libraries/LibDSP/Music.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2021, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP { - -using Sample = Audio::Sample; - -constexpr Sample const SAMPLE_OFF = { 0.0, 0.0 }; - -struct RollNote { - constexpr u32 length() const { return (off_sample - on_sample) + 1; } - - u32 on_sample; - u32 off_sample; - u8 pitch; - i8 velocity; - - constexpr Envelope to_envelope(u32 time, u32 attack_samples, u32 decay_samples, u32 release_samples) const - { - i64 time_since_end = static_cast(time) - static_cast(off_sample); - // We're before the end of this note. - if (time_since_end < 0) { - i64 time_since_start = static_cast(time) - static_cast(on_sample); - if (time_since_start < 0) - return {}; - - if (time_since_start < attack_samples) { - if (attack_samples == 0) - return Envelope::from_attack(0); - return Envelope::from_attack(static_cast(time_since_start) / static_cast(attack_samples)); - } - if (time_since_start < attack_samples + decay_samples) { - if (decay_samples == 0) - return Envelope::from_decay(0); - return Envelope::from_decay(static_cast(time_since_start - attack_samples) / static_cast(decay_samples)); - } - // This is a note-dependent value! - u32 sustain_samples = length() - attack_samples - decay_samples; - return Envelope::from_sustain(static_cast(time_since_start - attack_samples - decay_samples) / static_cast(sustain_samples)); - } - - // Overshot the release time - if (time_since_end > release_samples) - return {}; - return Envelope::from_release(static_cast(time_since_end) / static_cast(release_samples)); - } - - constexpr bool is_playing(u32 time) const { return on_sample <= time && time <= off_sample; } - constexpr bool is_playing_during(u32 start_time, u32 end_time) const - { - // There are three scenarios for a playing note. - return - // 1. The note ends within our time frame. - (this->off_sample >= start_time && this->off_sample < end_time) - // 2. The note starts within our time frame. - || (this->on_sample >= start_time && this->on_sample < end_time) - // 3. The note starts before our time frame and ends after it. - || (this->on_sample < start_time && this->off_sample >= end_time); - } - - constexpr bool overlaps_with(RollNote const& other) const - { - // Notes don't overlap if one is completely before or behind the other. - return !((this->on_sample < other.on_sample && this->off_sample < other.on_sample) - || (this->on_sample >= other.off_sample && this->off_sample >= other.off_sample)); - } -}; - -enum class SignalType : u8 { - Invalid, - Sample, - Note -}; - -// Perfect hashing for note (MIDI) values. This just uses the note value as the hash itself. -class PerfectNoteHashTraits : Traits { -public: - static constexpr bool equals(u8 const& a, u8 const& b) { return a == b; } - static constexpr unsigned hash(u8 value) - { - return static_cast(value); - } -}; - -// 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 Array 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, -}; - -using RollNotes = Array, note_frequencies.size()>; - -constexpr size_t const notes_per_octave = 12; -constexpr double const middle_c = note_frequencies[36]; - -struct Signal : public Variant, RollNotes> { - using Variant::Variant; - AK_MAKE_NONCOPYABLE(Signal); - AK_MAKE_DEFAULT_MOVABLE(Signal); - -public: - ALWAYS_INLINE SignalType type() const - { - if (has>()) - return SignalType::Sample; - if (has()) - return SignalType::Note; - return SignalType::Invalid; - } -}; - -} diff --git a/Userland/Libraries/LibDSP/Processor.h b/Userland/Libraries/LibDSP/Processor.h deleted file mode 100644 index d5174b704d5..00000000000 --- a/Userland/Libraries/LibDSP/Processor.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP { - -// A processor processes notes or audio into notes or audio. Processors are e.g. samplers, synthesizers, effects, arpeggiators etc. -class Processor : public RefCounted { - -public: - virtual ~Processor() = default; - void process(Signal const& input_signal, Signal& output_signal) - { - VERIFY(input_signal.type() == m_input_type); - process_impl(input_signal, output_signal); - VERIFY(output_signal.type() == m_output_type); - } - SignalType input_type() const { return m_input_type; } - SignalType output_type() const { return m_output_type; } - Vector& parameters() { return m_parameters; } - Vector const& parameters() const { 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 void process_impl(Signal const& input_signal, Signal& output_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 deleted file mode 100644 index 958420e0937..00000000000 --- a/Userland/Libraries/LibDSP/ProcessorParameter.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP { - -using ParameterFixedPoint = FixedPoint<8, i64>; - -// Identifies the different kinds of parameters. -// Note that achieving parameter type identification is NOT possible with typeid(). -enum class ParameterType : u8 { - Invalid = 0, - Range, - Enum, - Boolean, -}; - -enum class Logarithmic : bool { - No, - Yes -}; - -// 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(ErrorOr name, ParameterType type) - : m_type(type) - { - if (!name.is_error()) - m_name = name.release_value(); - } - - String const& name() const { return m_name; } - ParameterType type() const { return m_type; } - -private: - String m_name {}; - ParameterType const m_type; -}; - -namespace Detail { - -struct ProcessorParameterSetValueTag { - explicit ProcessorParameterSetValueTag() = default; -}; - -template -class ProcessorParameterSingleValue : public ProcessorParameter { - -public: - ProcessorParameterSingleValue(ErrorOr name, ParameterType type, ParameterT initial_value) - : ProcessorParameter(move(name), type) - , 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, DSP::Detail::ProcessorParameterSetValueTag {}); - for (auto const& did_change_value : m_change_value_listeners) - 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; - } - - // FIXME: Devise a good API for unregistering listeners. - void register_change_listener(Function listener) - { - m_change_value_listeners.append(move(listener)); - } - -protected: - ParameterT m_value; - Vector> m_change_value_listeners; -}; -} - -class ProcessorBooleanParameter final : public Detail::ProcessorParameterSingleValue { -public: - ProcessorBooleanParameter(String name, bool initial_value) - : Detail::ProcessorParameterSingleValue(move(name), ParameterType::Boolean, move(initial_value)) - { - } -}; - -class ProcessorRangeParameter final : public Detail::ProcessorParameterSingleValue { -public: - ProcessorRangeParameter(ErrorOr name, ParameterFixedPoint min_value, ParameterFixedPoint max_value, ParameterFixedPoint initial_value, Logarithmic logarithmic) - : Detail::ProcessorParameterSingleValue(move(name), ParameterType::Range, move(initial_value)) - , m_min_value(move(min_value)) - , m_max_value(move(max_value)) - , m_default_value(move(initial_value)) - , m_logarithmic(logarithmic) - { - 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(), to_copy.is_logarithmic()) - { - } - - ParameterFixedPoint min_value() const { return m_min_value; } - ParameterFixedPoint max_value() const { return m_max_value; } - ParameterFixedPoint range() const { return m_max_value - m_min_value; } - constexpr Logarithmic is_logarithmic() const { return m_logarithmic; } - ParameterFixedPoint default_value() const { return m_default_value; } - void set_value(ParameterFixedPoint value) - { - Detail::ProcessorParameterSingleValue::set_value(value.clamp(min_value(), max_value())); - } - -private: - double const m_min_value; - double const m_max_value; - double const m_default_value; - Logarithmic const m_logarithmic; -}; - -template -class ProcessorEnumParameter final : public Detail::ProcessorParameterSingleValue { -public: - ProcessorEnumParameter(ErrorOr name, EnumT initial_value) - : Detail::ProcessorParameterSingleValue(move(name), ParameterType::Enum, initial_value) - { - } -}; - -} -template<> -struct AK::Formatter : AK::StandardFormatter { - - Formatter() = default; - explicit Formatter(StandardFormatter formatter) - : StandardFormatter(formatter) - { - } - ErrorOr format(FormatBuilder& builder, DSP::ProcessorRangeParameter value) - { - if (m_mode == Mode::Pointer) { - Formatter formatter { *this }; - return formatter.format(builder, reinterpret_cast(&value)); - } - - 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()); - - TRY(builder.put_literal(TRY(String::formatted("[{} - {}]: {}", value.min_value(), value.max_value(), value.value())))); - return {}; - } -}; diff --git a/Userland/Libraries/LibDSP/Synthesizers.cpp b/Userland/Libraries/LibDSP/Synthesizers.cpp deleted file mode 100644 index 387747ad5a7..00000000000 --- a/Userland/Libraries/LibDSP/Synthesizers.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP::Synthesizers { - -Classic::Classic(NonnullRefPtr transport) - : DSP::SynthesizerProcessor(move(transport)) - , m_waveform("Waveform"_string, Waveform::Saw) - , m_attack("Attack"_string, 0.01, 2000, 5, Logarithmic::Yes) - , m_decay("Decay"_string, 0.01, 20'000, 80, Logarithmic::Yes) - , m_sustain("Sustain"_string, 0.001, 1, 0.725, Logarithmic::No) - , m_release("Release"_string, 0.01, 6'000, 120, Logarithmic::Yes) -{ - m_parameters.append(m_waveform); - m_parameters.append(m_attack); - m_parameters.append(m_decay); - m_parameters.append(m_sustain); - m_parameters.append(m_release); -} - -void Classic::process_impl(Signal const& input_signal, [[maybe_unused]] Signal& output_signal) -{ - auto const& in = input_signal.get(); - auto& output_samples = output_signal.get>(); - - // Do this for every time step and set the signal accordingly. - for (size_t sample_index = 0; sample_index < output_samples.size(); ++sample_index) { - Sample& out = output_samples[sample_index]; - out = {}; - u32 sample_time = m_transport->time() + sample_index; - - Array, note_frequencies.size()> playing_envelopes; - - // "Press" the necessary notes in the internal representation, - // and "release" all of the others - for (u8 i = 0; i < note_frequencies.size(); ++i) { - if (auto maybe_note = in[i]; maybe_note.has_value()) - m_playing_notes[i] = maybe_note; - - if (m_playing_notes[i].has_value()) { - Envelope note_envelope = m_playing_notes[i]->to_envelope(sample_time, m_attack * m_transport->ms_sample_rate(), m_decay * m_transport->ms_sample_rate(), m_release * m_transport->ms_sample_rate()); - // There are two conditions for removing notes: - // 1. The envelope has expired, regardless of whether the note was still given to us in the input. - if (!note_envelope.is_active()) { - m_playing_notes[i] = {}; - continue; - } - // 2. The envelope has not expired, but the note was not given to us. - // This means that the note abruptly stopped playing; i.e. the audio infrastructure didn't know the length of the notes initially. - // That basically means we're dealing with a keyboard note. Chop its end time to end now. - if (!note_envelope.is_release() && !in[i].has_value()) { - // dbgln("note {} not released, setting release phase, envelope={}", i, note_envelope.envelope); - note_envelope.set_release(0); - auto real_note = *m_playing_notes[i]; - real_note.off_sample = sample_time; - m_playing_notes[i] = real_note; - } - - playing_envelopes[i] = PitchedEnvelope { note_envelope, i }; - } - } - - for (auto envelope : playing_envelopes) { - if (!envelope.has_value()) - continue; - double volume = volume_from_envelope(*envelope); - double wave = wave_position(sample_time, envelope->note); - out += volume * wave; - } - } -} - -// Linear ADSR envelope with no peak adjustment. -double Classic::volume_from_envelope(Envelope const& envelope) const -{ - switch (static_cast(envelope)) { - case EnvelopeState::Off: - return 0; - case EnvelopeState::Attack: - return envelope.attack(); - case EnvelopeState::Decay: - // As we fade from high (1) to low (headroom above the sustain level) here, use 1-decay as the interpolation. - return (1. - envelope.decay()) * (1. - m_sustain) + m_sustain; - case EnvelopeState::Sustain: - return m_sustain; - case EnvelopeState::Release: - // Same goes for the release fade from high to low. - return (1. - envelope.release()) * m_sustain; - } - VERIFY_NOT_REACHED(); -} - -double Classic::wave_position(u32 sample_time, u8 note) -{ - switch (m_waveform) { - case Sine: - return sin_position(sample_time, note); - case Triangle: - return triangle_position(sample_time, note); - case Square: - return square_position(sample_time, note); - case Saw: - return saw_position(sample_time, note); - case Noise: - return noise_position(sample_time, note); - } - VERIFY_NOT_REACHED(); -} - -double Classic::samples_per_cycle(u8 note) const -{ - return m_transport->sample_rate() / note_frequencies[note]; -} - -double Classic::sin_position(u32 sample_time, u8 note) const -{ - double spc = samples_per_cycle(note); - double cycle_pos = sample_time / spc; - return AK::sin(cycle_pos * 2 * AK::Pi); -} - -// Absolute value of the saw wave "flips" the negative portion into the positive, creating a ramp up and down. -double Classic::triangle_position(u32 sample_time, u8 note) const -{ - double saw = saw_position(sample_time, note); - return AK::fabs(saw) * 2 - 1; -} - -// The first half of the cycle period is 1, the other half -1. -double Classic::square_position(u32 sample_time, u8 note) const -{ - double spc = samples_per_cycle(note); - double progress = AK::fmod(static_cast(sample_time), spc) / spc; - return progress >= 0.5 ? -1 : 1; -} - -// Modulus creates inverse saw, which we need to flip and scale. -double Classic::saw_position(u32 sample_time, u8 note) const -{ - double spc = samples_per_cycle(note); - double unscaled = spc - AK::fmod(static_cast(sample_time), spc); - return unscaled / (samples_per_cycle(note) / 2.) - 1; -} - -// We resample the noise twenty times per cycle. -double Classic::noise_position(u32 sample_time, u8 note) -{ - double spc = samples_per_cycle(note); - u32 getrandom_interval = max(static_cast(spc / 2), 1); - // Note that this code only works well if the processor is called for every increment of time. - if (sample_time % getrandom_interval == 0) - last_random[note] = (get_random() / static_cast(NumericLimits::max()) - .5) * 2; - return last_random[note]; -} - -} diff --git a/Userland/Libraries/LibDSP/Synthesizers.h b/Userland/Libraries/LibDSP/Synthesizers.h deleted file mode 100644 index 45b3a4cdab8..00000000000 --- a/Userland/Libraries/LibDSP/Synthesizers.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace DSP::Synthesizers { - -enum Waveform : u8 { - Sine, - Triangle, - Square, - Saw, - Noise, -}; - -struct PitchedEnvelope : Envelope { - constexpr PitchedEnvelope() = default; - constexpr PitchedEnvelope(double envelope, u8 note) - : Envelope(envelope) - , note(note) - { - } - constexpr PitchedEnvelope(Envelope envelope, u8 note) - : Envelope(envelope) - , note(note) - { - } - - u8 note; -}; - -class Classic : public SynthesizerProcessor { -public: - Classic(NonnullRefPtr); - - Waveform wave() const { return m_waveform.value(); } - -private: - virtual void process_impl(Signal const&, Signal&) override; - - double volume_from_envelope(Envelope const&) const; - double wave_position(u32 sample_time, u8 note); - double samples_per_cycle(u8 note) const; - double sin_position(u32 sample_time, u8 note) const; - double triangle_position(u32 sample_time, u8 note) const; - double square_position(u32 sample_time, u8 note) const; - double saw_position(u32 sample_time, u8 note) const; - double noise_position(u32 sample_time, u8 note); - - ProcessorEnumParameter m_waveform; - ProcessorRangeParameter m_attack; - ProcessorRangeParameter m_decay; - ProcessorRangeParameter m_sustain; - ProcessorRangeParameter m_release; - - RollNotes m_playing_notes; - Array last_random; -}; - -} diff --git a/Userland/Libraries/LibDSP/Track.cpp b/Userland/Libraries/LibDSP/Track.cpp deleted file mode 100644 index 4e0deb64467..00000000000 --- a/Userland/Libraries/LibDSP/Track.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2021, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP { - -bool Track::add_processor(NonnullRefPtr new_processor) -{ - m_processor_chain.append(move(new_processor)); - if (!check_processor_chain_valid()) { - (void)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.ptr(); - } - return true; -} - -NonnullRefPtr Track::synth() -{ - return static_ptr_cast(m_processor_chain[0]); -} -NonnullRefPtr Track::delay() -{ - return static_ptr_cast(m_processor_chain[1]); -} - -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); -} - -ErrorOr Track::resize_internal_buffers_to(size_t buffer_size) -{ - m_secondary_sample_buffer = TRY(FixedArray::create(buffer_size)); - FixedArray cache = TRY(FixedArray::create(buffer_size)); - bool false_variable = false; - while (!m_sample_lock.compare_exchange_strong(false_variable, true)) - usleep(1); - m_cached_sample_buffer.swap(cache); - m_sample_lock.store(false); - return {}; -} - -void Track::current_signal(FixedArray& output_signal) -{ - // This is real-time code. We must NEVER EVER EVER allocate. - NoAllocationGuard guard; - VERIFY(m_secondary_sample_buffer.type() == SignalType::Sample); - VERIFY(output_signal.size() == m_secondary_sample_buffer.get>().size()); - - compute_current_clips_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) { - // 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(source_signal->type() == SignalType::Sample); - VERIFY(output_signal.size() == source_signal->get>().size()); - // The last processor is the fixed mastering processor. This can write directly to the output data. We also just trust this processor that it does the right thing :^) - m_track_mastering->process_to_fixed_array(*source_signal, output_signal); - - bool false_variable = false; - if (m_sample_lock.compare_exchange_strong(false_variable, true)) { - AK::TypedTransfer::copy(m_cached_sample_buffer.data(), output_signal.data(), m_cached_sample_buffer.size()); - m_sample_lock.store(false); - } -} - -void Track::write_cached_signal_to(Span output_signal) -{ - bool false_variable = false; - while (!m_sample_lock.compare_exchange_strong(false_variable, true)) { - usleep(1); - } - VERIFY(output_signal.size() == m_cached_sample_buffer.size()); - - AK::TypedTransfer::copy(output_signal.data(), m_cached_sample_buffer.data(), m_cached_sample_buffer.size()); - - m_sample_lock.store(false); -} - -void NoteTrack::compute_current_clips_signal() -{ - // FIXME: Handle looping properly - u32 start_time = m_transport->time(); - VERIFY(m_secondary_sample_buffer.type() == SignalType::Sample); - size_t sample_count = m_secondary_sample_buffer.get>().size(); - u32 end_time = start_time + static_cast(sample_count); - - // Find the currently playing clips. - // We can't handle more than 32 playing clips at a time, but that is a ridiculous number. - Array, 32> playing_clips; - size_t playing_clips_index = 0; - for (auto& clip : m_clips) { - // A clip is playing if its start time or end time fall in the current time range. - // Or, if they both enclose the current time range. - if ((clip->start() <= start_time && clip->end() >= end_time) - || (clip->start() >= start_time && clip->start() < end_time) - || (clip->end() > start_time && clip->end() <= end_time)) { - VERIFY(playing_clips_index < playing_clips.size()); - playing_clips[playing_clips_index++] = clip; - } - } - - auto& current_notes = m_current_signal.get(); - m_current_signal.get().fill({}); - - if (playing_clips_index == 0) - return; - - for (auto const& playing_clip : playing_clips) { - if (playing_clip.is_null()) - break; - for (auto const& note : playing_clip->notes()) { - if (note.is_playing_during(start_time, end_time)) - current_notes[note.pitch] = note; - } - } - - for (auto const& keyboard_note : m_keyboard->notes()) { - if (!keyboard_note.has_value() || !keyboard_note->is_playing_during(start_time, end_time)) - continue; - // Always overwrite roll notes with keyboard notes. - current_notes[keyboard_note->pitch] = keyboard_note; - } -} - -void AudioTrack::compute_current_clips_signal() -{ - // This is quite involved as we need to look at multiple clips and take looping into account. - TODO(); -} - -Optional NoteTrack::note_at(u32 time, u8 pitch) const -{ - for (auto& clip : m_clips) { - if (time >= clip->start() && time <= clip->end()) - return clip->note_at(time, pitch); - } - - return {}; -} - -void NoteTrack::set_note(RollNote note) -{ - for (auto& clip : m_clips) { - if (clip->start() <= note.on_sample && clip->end() >= note.on_sample) - clip->set_note(note); - } -} - -void NoteTrack::remove_note(RollNote note) -{ - for (auto& clip : m_clips) - clip->remove_note(note); -} - -void NoteTrack::add_clip(u32 start_time, u32 end_time) -{ - m_clips.append(AK::make_ref_counted(start_time, end_time)); -} - -} diff --git a/Userland/Libraries/LibDSP/Track.h b/Userland/Libraries/LibDSP/Track.h deleted file mode 100644 index 372aef0819f..00000000000 --- a/Userland/Libraries/LibDSP/Track.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DSP { - -// A track is also known as a channel and serves as a container for the audio pipeline: clips -> processors -> mixing & output -class Track : public RefCounted - , public Weakable { -public: - virtual ~Track() = 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. - void current_signal(FixedArray& output_signal); - - void write_cached_signal_to(Span output_signal); - - // We are informed of an audio buffer size change. This happens off-audio-thread so we can allocate. - ErrorOr resize_internal_buffers_to(size_t buffer_size); - - Vector> const& processor_chain() const { return m_processor_chain; } - NonnullRefPtr transport() const { return m_transport; } - NonnullRefPtr track_mastering() { return m_track_mastering; } - - // FIXME: These two getters are temporary until we have dynamic processor UI - NonnullRefPtr synth(); - NonnullRefPtr delay(); - -protected: - Track(NonnullRefPtr transport, NonnullRefPtr keyboard) - : m_transport(move(transport)) - , m_track_mastering(make_ref_counted(m_transport)) - , m_keyboard(move(keyboard)) - { - } - bool check_processor_chain_valid_with_initial_type(SignalType initial_type) const; - - // Subclasses override to provide the base signal to the processing chain - virtual void compute_current_clips_signal() = 0; - - Vector> m_processor_chain; - NonnullRefPtr m_transport; - NonnullRefPtr m_track_mastering; - NonnullRefPtr m_keyboard; - // The current signal is stored here, to prevent unnecessary reallocation. - Signal m_current_signal { FixedArray {} }; - - // These are so that we don't have to allocate a secondary buffer in current_signal(). - // A sample buffer possibly used by the processor chain. - Signal m_secondary_sample_buffer { FixedArray {} }; - // A note buffer possibly used by the processor chain. - Signal m_secondary_note_buffer { RollNotes {} }; - -private: - Atomic m_sample_lock; - - FixedArray m_cached_sample_buffer = {}; -}; - -class NoteTrack final : public Track { -public: - virtual ~NoteTrack() override = default; - - NoteTrack(NonnullRefPtr transport, NonnullRefPtr keyboard) - : Track(move(transport), move(keyboard)) - { - m_current_signal = RollNotes {}; - } - - bool check_processor_chain_valid() const override; - ReadonlySpan> notes() const { return m_clips.span(); } - - Optional note_at(u32 time, u8 pitch) const; - void set_note(RollNote note); - void remove_note(RollNote note); - - void add_clip(u32 start_time, u32 end_time); - -protected: - void compute_current_clips_signal() override; - -private: - Vector> m_clips; -}; - -class AudioTrack final : public Track { -public: - virtual ~AudioTrack() override = default; - - AudioTrack(NonnullRefPtr transport, NonnullRefPtr keyboard) - : Track(move(transport), move(keyboard)) - { - } - - bool check_processor_chain_valid() const override; - Vector> const& clips() const { return m_clips; } - -protected: - void compute_current_clips_signal() override; - -private: - Vector> m_clips; -}; - -} diff --git a/Userland/Libraries/LibDSP/Transport.h b/Userland/Libraries/LibDSP/Transport.h deleted file mode 100644 index 05df46435e4..00000000000 --- a/Userland/Libraries/LibDSP/Transport.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021-2022, kleines Filmröllchen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace DSP { - -// The DAW-wide timekeeper and synchronizer -class Transport final : public RefCounted { -public: - constexpr u32 time() const { return m_time; } - constexpr u16 beats_per_minute() const { return m_beats_per_minute; } - constexpr double current_second() const { return static_cast(m_time) / m_sample_rate; } - constexpr double samples_per_measure() const { return (1.0 / m_beats_per_minute) * 60.0 * m_sample_rate; } - constexpr double sample_rate() const { return m_sample_rate; } - constexpr double ms_sample_rate() const { return m_sample_rate / 1000.; } - constexpr double current_measure() const { return m_time / samples_per_measure(); } - - void set_time(u32 time) { m_time = time; } - - 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) - { - } - -private: - // FIXME: You can't make more than 24h of (48kHz) music with this. - // But do you want to, really? :^) - u32 m_time { 0 }; - u16 const m_beats_per_minute { 0 }; - u8 const m_beats_per_measure { 0 }; - u32 const m_sample_rate; -}; - -} diff --git a/Userland/Libraries/LibDSP/Window.h b/Userland/Libraries/LibDSP/Window.h deleted file mode 100644 index 2adf3d421e7..00000000000 --- a/Userland/Libraries/LibDSP/Window.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021, Arne Elster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace DSP { - -template -class Window final { -public: - template - constexpr static Array hamming() { return make_window(calculate_hamming); } - constexpr static FixedArray hamming(size_t size) { return make_window(size, calculate_hamming); } - - template - constexpr static Array hann() { return make_window(calculate_hann); } - constexpr static FixedArray hann(size_t size) { return make_window(size, calculate_hann); } - - template - constexpr static Array blackman_harris() { return make_window(calculate_blackman_harris); } - constexpr static FixedArray blackman_harris(size_t size) { return make_window(size, calculate_blackman_harris); } - -private: - constexpr static float calculate_hann(size_t index, size_t size) - { - return 0.5f * (1 - AK::cos((2 * AK::Pi * index) / (size - 1))); - } - - constexpr static float calculate_hamming(size_t index, size_t size) - { - return 0.54f - 0.46f * AK::cos((2 * AK::Pi * index) / (size - 1)); - } - - constexpr static float calculate_blackman_harris(size_t index, size_t size) - { - T const a0 = 0.35875; - T const a1 = 0.48829; - T const a2 = 0.14128; - T const a3 = 0.01168; - return a0 - a1 * AK::cos(2 * AK::Pi * index / size) + a2 * AK::cos(4 * AK::Pi * index / size) - a3 * AK::cos(6 * AK::Pi * index / size); - } - - template - constexpr static Array make_window(auto window_function) - { - Array result; - for (size_t i = 0; i < size; i++) { - result[i] = window_function(i, size); - } - return result; - } - - constexpr static FixedArray make_window(size_t size, auto window_function) - { - FixedArray result; - result.resize(size); - for (size_t i = 0; i < size; i++) { - result[i] = window_function(i, size); - } - return result; - } -}; - -}