/* * 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); public: Signal& operator=(Signal&&) = default; Signal(Signal&&) = default; ALWAYS_INLINE SignalType type() const { if (has>()) return SignalType::Sample; if (has()) return SignalType::Note; return SignalType::Invalid; } }; }