mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 18:22:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			218 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <AK/FixedArray.h>
 | |
| #include <AK/HashMap.h>
 | |
| #include <AK/Noncopyable.h>
 | |
| #include <AK/Types.h>
 | |
| #include <AK/Variant.h>
 | |
| #include <AK/Vector.h>
 | |
| #include <LibAudio/Sample.h>
 | |
| #include <LibDSP/Envelope.h>
 | |
| 
 | |
| 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<i64>(time) - static_cast<i64>(off_sample);
 | |
|         // We're before the end of this note.
 | |
|         if (time_since_end < 0) {
 | |
|             i64 time_since_start = static_cast<i64>(time) - static_cast<i64>(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<double>(time_since_start) / static_cast<double>(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<double>(time_since_start - attack_samples) / static_cast<double>(decay_samples));
 | |
|             }
 | |
|             // This is a note-dependent value!
 | |
|             u32 sustain_samples = length() - attack_samples - decay_samples;
 | |
|             return Envelope::from_sustain(static_cast<double>(time_since_start - attack_samples - decay_samples) / static_cast<double>(sustain_samples));
 | |
|         }
 | |
| 
 | |
|         // Overshot the release time
 | |
|         if (time_since_end > release_samples)
 | |
|             return {};
 | |
|         return Envelope::from_release(static_cast<double>(time_since_end) / static_cast<double>(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<u8> {
 | |
| public:
 | |
|     static constexpr bool equals(u8 const& a, u8 const& b) { return a == b; }
 | |
|     static constexpr unsigned hash(u8 value)
 | |
|     {
 | |
|         return static_cast<unsigned>(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<double, 84> 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<Optional<RollNote>, note_frequencies.size()>;
 | |
| 
 | |
| constexpr size_t const notes_per_octave = 12;
 | |
| constexpr double const middle_c = note_frequencies[36];
 | |
| 
 | |
| struct Signal : public Variant<FixedArray<Sample>, RollNotes> {
 | |
|     using Variant::Variant;
 | |
|     AK_MAKE_NONCOPYABLE(Signal);
 | |
|     AK_MAKE_DEFAULT_MOVABLE(Signal);
 | |
| 
 | |
| public:
 | |
|     ALWAYS_INLINE SignalType type() const
 | |
|     {
 | |
|         if (has<FixedArray<Sample>>())
 | |
|             return SignalType::Sample;
 | |
|         if (has<RollNotes>())
 | |
|             return SignalType::Note;
 | |
|         return SignalType::Invalid;
 | |
|     }
 | |
| };
 | |
| 
 | |
| }
 | 
