diff --git a/Userland/Applications/Piano/AudioPlayerLoop.cpp b/Userland/Applications/Piano/AudioPlayerLoop.cpp index 9a0861a0ed..dc19c69542 100644 --- a/Userland/Applications/Piano/AudioPlayerLoop.cpp +++ b/Userland/Applications/Piano/AudioPlayerLoop.cpp @@ -12,10 +12,10 @@ // Converts Piano-internal data to an Audio::Buffer that AudioServer receives static NonnullRefPtr music_samples_to_buffer(Array samples) { - Vector frames; + Vector frames; frames.ensure_capacity(sample_count); for (auto sample : samples) { - Audio::Frame frame = { sample.left / (double)NumericLimits::max(), sample.right / (double)NumericLimits::max() }; + Audio::Sample frame = { sample.left / (double)NumericLimits::max(), sample.right / (double)NumericLimits::max() }; frames.unchecked_append(frame); } return Audio::Buffer::create_with_samples(frames); diff --git a/Userland/Applications/Piano/Track.cpp b/Userland/Applications/Piano/Track.cpp index 1cd6b7be71..4fcba80f5a 100644 --- a/Userland/Applications/Piano/Track.cpp +++ b/Userland/Applications/Piano/Track.cpp @@ -31,7 +31,7 @@ Track::~Track() void Track::fill_sample(Sample& sample) { - Audio::Frame new_sample; + Audio::Sample new_sample; for (size_t note = 0; note < note_count; ++note) { if (!m_roll_iterators[note].is_end()) { @@ -72,7 +72,7 @@ void Track::fill_sample(Sample& sample) VERIFY_NOT_REACHED(); } - Audio::Frame note_sample; + Audio::Sample note_sample; switch (m_wave) { case Wave::Sine: note_sample = sine(note); @@ -161,7 +161,7 @@ String Track::set_recorded_sample(const StringView& path) // All of the information for these waves is on Wikipedia. -Audio::Frame Track::sine(size_t note) +Audio::Sample Track::sine(size_t note) { double pos = note_frequencies[note] / sample_rate; double sin_step = pos * 2 * M_PI; @@ -170,7 +170,7 @@ Audio::Frame Track::sine(size_t note) return w; } -Audio::Frame Track::saw(size_t note) +Audio::Sample Track::saw(size_t note) { double saw_step = note_frequencies[note] / sample_rate; double t = m_pos[note]; @@ -179,7 +179,7 @@ Audio::Frame Track::saw(size_t note) return w; } -Audio::Frame Track::square(size_t note) +Audio::Sample Track::square(size_t note) { double pos = note_frequencies[note] / sample_rate; double square_step = pos * 2 * M_PI; @@ -188,7 +188,7 @@ Audio::Frame Track::square(size_t note) return w; } -Audio::Frame Track::triangle(size_t note) +Audio::Sample Track::triangle(size_t note) { double triangle_step = note_frequencies[note] / sample_rate; double t = m_pos[note]; @@ -197,7 +197,7 @@ Audio::Frame Track::triangle(size_t note) return w; } -Audio::Frame Track::noise(size_t note) +Audio::Sample Track::noise(size_t note) { double step = note_frequencies[note] / sample_rate; // m_pos keeps track of the time since the last random sample @@ -210,7 +210,7 @@ Audio::Frame Track::noise(size_t note) return m_last_w[note]; } -Audio::Frame Track::recorded_sample(size_t note) +Audio::Sample Track::recorded_sample(size_t note) { int t = m_pos[note]; if (t >= static_cast(m_recorded_sample.size())) diff --git a/Userland/Applications/Piano/Track.h b/Userland/Applications/Piano/Track.h index 42cf32114d..2397cd776f 100644 --- a/Userland/Applications/Piano/Track.h +++ b/Userland/Applications/Piano/Track.h @@ -24,7 +24,7 @@ public: explicit Track(const u32& time); ~Track(); - const Vector& recorded_sample() const { return m_recorded_sample; } + const Vector& recorded_sample() const { return m_recorded_sample; } const SinglyLinkedList& roll_notes(int note) const { return m_roll_notes[note]; } int wave() const { return m_wave; } int volume() const { return m_volume; } @@ -48,17 +48,17 @@ public: void set_release(int release); private: - Audio::Frame sine(size_t note); - Audio::Frame saw(size_t note); - Audio::Frame square(size_t note); - Audio::Frame triangle(size_t note); - Audio::Frame noise(size_t note); - Audio::Frame recorded_sample(size_t note); + Audio::Sample sine(size_t note); + Audio::Sample saw(size_t note); + Audio::Sample square(size_t note); + Audio::Sample triangle(size_t note); + Audio::Sample noise(size_t note); + Audio::Sample recorded_sample(size_t note); void sync_roll(int note); void set_sustain_impl(int sustain); - Vector m_recorded_sample; + Vector m_recorded_sample; u8 m_note_on[note_count] { 0 }; double m_power[note_count] { 0 }; diff --git a/Userland/Libraries/LibAudio/Buffer.cpp b/Userland/Libraries/LibAudio/Buffer.cpp index 4f401b4f28..b53fc9d5bf 100644 --- a/Userland/Libraries/LibAudio/Buffer.cpp +++ b/Userland/Libraries/LibAudio/Buffer.cpp @@ -45,7 +45,7 @@ i32 Buffer::allocate_id() } template -static void read_samples_from_stream(InputMemoryStream& stream, SampleReader read_sample, Vector& samples, int num_channels) +static void read_samples_from_stream(InputMemoryStream& stream, SampleReader read_sample, Vector& samples, int num_channels) { double norm_l = 0; double norm_r = 0; @@ -54,7 +54,7 @@ static void read_samples_from_stream(InputMemoryStream& stream, SampleReader rea case 1: for (;;) { norm_l = read_sample(stream); - samples.append(Frame(norm_l)); + samples.append(Sample(norm_l)); if (stream.handle_any_error()) { break; @@ -65,7 +65,7 @@ static void read_samples_from_stream(InputMemoryStream& stream, SampleReader rea for (;;) { norm_l = read_sample(stream); norm_r = read_sample(stream); - samples.append(Frame(norm_l, norm_r)); + samples.append(Sample(norm_l, norm_r)); if (stream.handle_any_error()) { break; @@ -130,7 +130,7 @@ RefPtr Buffer::from_pcm_data(ReadonlyBytes data, int num_channels, PcmSa RefPtr Buffer::from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples) { - Vector fdata; + Vector fdata; fdata.ensure_capacity(num_samples); switch (sample_format) { @@ -189,7 +189,7 @@ template Vector ResampleHelper::resample(Vector); NonnullRefPtr resample_buffer(ResampleHelper& resampler, Buffer const& to_resample) { - Vector resampled; + Vector resampled; resampled.ensure_capacity(to_resample.sample_count() * ceil_div(resampler.source(), resampler.target())); for (size_t i = 0; i < static_cast(to_resample.sample_count()); ++i) { auto sample = to_resample.samples()[i]; diff --git a/Userland/Libraries/LibAudio/Buffer.h b/Userland/Libraries/LibAudio/Buffer.h index 6020a0f6a5..dbc08814e6 100644 --- a/Userland/Libraries/LibAudio/Buffer.h +++ b/Userland/Libraries/LibAudio/Buffer.h @@ -8,144 +8,17 @@ #pragma once #include -#include #include #include #include #include +#include #include #include namespace Audio { using namespace AK::Exponentials; -// Constants for logarithmic volume. See Frame::operator* -// Corresponds to 60dB -constexpr double DYNAMIC_RANGE = 1000; -constexpr double VOLUME_A = 1 / DYNAMIC_RANGE; -double const VOLUME_B = log(DYNAMIC_RANGE); - -// A single sample in an audio buffer. -// Values are floating point, and should range from -1.0 to +1.0 -struct Frame { - constexpr Frame() = default; - - // For mono - constexpr Frame(double left) - : left(left) - , right(left) - { - } - - // For stereo - constexpr Frame(double left, double right) - : left(left) - , right(right) - { - } - - void clip() - { - if (left > 1) - left = 1; - else if (left < -1) - left = -1; - - if (right > 1) - right = 1; - else if (right < -1) - right = -1; - } - - // Logarithmic scaling, as audio should ALWAYS do. - // Reference: https://www.dr-lex.be/info-stuff/volumecontrols.html - // We use the curve `factor = a * exp(b * change)`, - // where change is the input fraction we want to change by, - // a = 1/1000, b = ln(1000) = 6.908 and factor is the multiplier used. - // The value 1000 represents the dynamic range in sound pressure, which corresponds to 60 dB(A). - // This is a good dynamic range because it can represent all loudness values from - // 30 dB(A) (barely hearable with background noise) - // to 90 dB(A) (almost too loud to hear and about the reasonable limit of actual sound equipment). - // - // Format ranges: - // - Linear: 0.0 to 1.0 - // - Logarithmic: 0.0 to 1.0 - - ALWAYS_INLINE double linear_to_log(double const change) - { - // TODO: Add linear slope around 0 - return VOLUME_A * exp(VOLUME_B * change); - } - - ALWAYS_INLINE double log_to_linear(double const val) - { - // TODO: Add linear slope around 0 - return log(val / VOLUME_A) / VOLUME_B; - } - - ALWAYS_INLINE Frame& log_multiply(double const change) - { - double factor = linear_to_log(change); - left *= factor; - right *= factor; - return *this; - } - - ALWAYS_INLINE Frame log_multiplied(double const volume_change) const - { - Frame new_frame { left, right }; - new_frame.log_multiply(volume_change); - return new_frame; - } - - ALWAYS_INLINE Frame& log_pan(double const pan) - { - left *= linear_to_log(min(pan * -1 + 1.0, 1.0)); - right *= linear_to_log(min(pan + 1.0, 1.0)); - return *this; - } - - ALWAYS_INLINE Frame log_pan(double const pan) const - { - Frame new_frame { left, right }; - new_frame.log_pan(pan); - return new_frame; - } - - constexpr Frame& operator*=(double const mult) - { - left *= mult; - right *= mult; - return *this; - } - - constexpr Frame operator*(double const mult) - { - return { left * mult, right * mult }; - } - - constexpr Frame& operator+=(Frame const& other) - { - left += other.left; - right += other.right; - return *this; - } - constexpr Frame& operator+=(double other) - { - left += other; - right += other; - return *this; - } - - constexpr Frame operator+(Frame const& other) - { - return { left + other.left, right + other.right }; - } - - double left { 0 }; - double right { 0 }; -}; - // Supported PCM sample formats. enum PcmSampleFormat : u8 { Uint8, @@ -196,7 +69,7 @@ class Buffer : public RefCounted { public: static RefPtr from_pcm_data(ReadonlyBytes data, int num_channels, PcmSampleFormat sample_format); static RefPtr from_pcm_stream(InputMemoryStream& stream, int num_channels, PcmSampleFormat sample_format, int num_samples); - static NonnullRefPtr create_with_samples(Vector&& samples) + static NonnullRefPtr create_with_samples(Vector&& samples) { return adopt_ref(*new Buffer(move(samples))); } @@ -205,20 +78,20 @@ public: return adopt_ref(*new Buffer(move(buffer), buffer_id, sample_count)); } - const Frame* samples() const { return (const Frame*)data(); } + const Sample* samples() const { return (const Sample*)data(); } int sample_count() const { return m_sample_count; } const void* data() const { return m_buffer.data(); } - int size_in_bytes() const { return m_sample_count * (int)sizeof(Frame); } + int size_in_bytes() const { return m_sample_count * (int)sizeof(Sample); } int id() const { return m_id; } const Core::AnonymousBuffer& anonymous_buffer() const { return m_buffer; } private: - explicit Buffer(const Vector samples) - : m_buffer(Core::AnonymousBuffer::create_with_size(samples.size() * sizeof(Frame)).release_value()) + explicit Buffer(const Vector samples) + : m_buffer(Core::AnonymousBuffer::create_with_size(samples.size() * sizeof(Sample)).release_value()) , m_id(allocate_id()) , m_sample_count(samples.size()) { - memcpy(m_buffer.data(), samples.data(), samples.size() * sizeof(Frame)); + memcpy(m_buffer.data(), samples.data(), samples.size() * sizeof(Sample)); } explicit Buffer(Core::AnonymousBuffer buffer, i32 buffer_id, int sample_count) diff --git a/Userland/Libraries/LibAudio/FlacLoader.cpp b/Userland/Libraries/LibAudio/FlacLoader.cpp index f3c061ec94..02aa809b24 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.cpp +++ b/Userland/Libraries/LibAudio/FlacLoader.cpp @@ -231,7 +231,7 @@ void FlacLoaderPlugin::seek(const int position) RefPtr FlacLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input) { - Vector samples; + Vector samples; ssize_t remaining_samples = m_total_samples - m_loaded_samples; if (remaining_samples <= 0) { return nullptr; @@ -417,7 +417,7 @@ void FlacLoaderPlugin::next_frame() m_current_frame_data.ensure_capacity(left.size()); // zip together channels for (size_t i = 0; i < left.size(); ++i) { - Frame frame = { left[i] / sample_rescale, right[i] / sample_rescale }; + Sample frame = { left[i] / sample_rescale, right[i] / sample_rescale }; m_current_frame_data.unchecked_append(frame); } diff --git a/Userland/Libraries/LibAudio/FlacLoader.h b/Userland/Libraries/LibAudio/FlacLoader.h index 915bd44b5c..f80fc57f65 100644 --- a/Userland/Libraries/LibAudio/FlacLoader.h +++ b/Userland/Libraries/LibAudio/FlacLoader.h @@ -143,7 +143,7 @@ private: u64 m_data_start_location { 0 }; OwnPtr m_stream; Optional m_current_frame; - Vector m_current_frame_data; + Vector m_current_frame_data; u64 m_current_sample_or_frame { 0 }; }; diff --git a/Userland/Libraries/LibAudio/Sample.h b/Userland/Libraries/LibAudio/Sample.h new file mode 100644 index 0000000000..23faf6244a --- /dev/null +++ b/Userland/Libraries/LibAudio/Sample.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Audio { +using namespace AK::Exponentials; +// Constants for logarithmic volume. See Sample::linear_to_log +// Corresponds to 60dB +constexpr double DYNAMIC_RANGE = 1000; +constexpr double VOLUME_A = 1 / DYNAMIC_RANGE; +double const VOLUME_B = log(DYNAMIC_RANGE); + +// A single sample in an audio buffer. +// Values are floating point, and should range from -1.0 to +1.0 +struct Sample { + constexpr Sample() = default; + + // For mono + constexpr Sample(double left) + : left(left) + , right(left) + { + } + + // For stereo + constexpr Sample(double left, double right) + : left(left) + , right(right) + { + } + + void clip() + { + if (left > 1) + left = 1; + else if (left < -1) + left = -1; + + if (right > 1) + right = 1; + else if (right < -1) + right = -1; + } + + // Logarithmic scaling, as audio should ALWAYS do. + // Reference: https://www.dr-lex.be/info-stuff/volumecontrols.html + // We use the curve `factor = a * exp(b * change)`, + // where change is the input fraction we want to change by, + // a = 1/1000, b = ln(1000) = 6.908 and factor is the multiplier used. + // The value 1000 represents the dynamic range in sound pressure, which corresponds to 60 dB(A). + // This is a good dynamic range because it can represent all loudness values from + // 30 dB(A) (barely hearable with background noise) + // to 90 dB(A) (almost too loud to hear and about the reasonable limit of actual sound equipment). + // + // Format ranges: + // - Linear: 0.0 to 1.0 + // - Logarithmic: 0.0 to 1.0 + + ALWAYS_INLINE double linear_to_log(double const change) + { + // TODO: Add linear slope around 0 + return VOLUME_A * exp(VOLUME_B * change); + } + + ALWAYS_INLINE double log_to_linear(double const val) + { + // TODO: Add linear slope around 0 + return log(val / VOLUME_A) / VOLUME_B; + } + + ALWAYS_INLINE Sample& log_multiply(double const change) + { + double factor = linear_to_log(change); + left *= factor; + right *= factor; + return *this; + } + + ALWAYS_INLINE Sample log_multiplied(double const volume_change) const + { + Sample new_frame { left, right }; + new_frame.log_multiply(volume_change); + return new_frame; + } + + ALWAYS_INLINE Sample& log_pan(double const pan) + { + left *= linear_to_log(min(pan * -1 + 1.0, 1.0)); + right *= linear_to_log(min(pan + 1.0, 1.0)); + return *this; + } + + ALWAYS_INLINE Sample log_pan(double const pan) const + { + Sample new_frame { left, right }; + new_frame.log_pan(pan); + return new_frame; + } + + constexpr Sample& operator*=(double const mult) + { + left *= mult; + right *= mult; + return *this; + } + + constexpr Sample operator*(double const mult) + { + return { left * mult, right * mult }; + } + + constexpr Sample& operator+=(Sample const& other) + { + left += other.left; + right += other.right; + return *this; + } + constexpr Sample& operator+=(double other) + { + left += other; + right += other; + return *this; + } + + constexpr Sample operator+(Sample const& other) + { + return { left + other.left, right + other.right }; + } + + double left { 0 }; + double right { 0 }; +}; + +} diff --git a/Userland/Libraries/LibDSP/Music.h b/Userland/Libraries/LibDSP/Music.h index 5a9c434a6b..306b3c6193 100644 --- a/Userland/Libraries/LibDSP/Music.h +++ b/Userland/Libraries/LibDSP/Music.h @@ -14,7 +14,7 @@ namespace LibDSP { // FIXME: Audio::Frame is 64-bit float, which is quite large for long clips. -using Sample = Audio::Frame; +using Sample = Audio::Sample; Sample const SAMPLE_OFF = { 0.0, 0.0 }; diff --git a/Userland/Services/AudioServer/Mixer.cpp b/Userland/Services/AudioServer/Mixer.cpp index bc57b25eda..99d6038930 100644 --- a/Userland/Services/AudioServer/Mixer.cpp +++ b/Userland/Services/AudioServer/Mixer.cpp @@ -77,8 +77,8 @@ void Mixer::mix() active_mix_queues.remove_all_matching([&](auto& entry) { return !entry->client(); }); - Audio::Frame mixed_buffer[1024]; - auto mixed_buffer_length = (int)(sizeof(mixed_buffer) / sizeof(Audio::Frame)); + Audio::Sample mixed_buffer[1024]; + auto mixed_buffer_length = (int)(sizeof(mixed_buffer) / sizeof(Audio::Sample)); m_main_volume.advance_time(); @@ -94,7 +94,7 @@ void Mixer::mix() for (int i = 0; i < mixed_buffer_length; ++i) { auto& mixed_sample = mixed_buffer[i]; - Audio::Frame sample; + Audio::Sample sample; if (!queue->get_next_sample(sample)) break; sample.log_multiply(SAMPLE_HEADROOM); diff --git a/Userland/Services/AudioServer/Mixer.h b/Userland/Services/AudioServer/Mixer.h index a9dbde4cc8..0cd48c8500 100644 --- a/Userland/Services/AudioServer/Mixer.h +++ b/Userland/Services/AudioServer/Mixer.h @@ -40,7 +40,7 @@ public: bool is_full() const { return m_queue.size() >= 3; } void enqueue(NonnullRefPtr&&); - bool get_next_sample(Audio::Frame& sample) + bool get_next_sample(Audio::Sample& sample) { if (m_paused) return false;