Serenity Operating System
at master 161 lines 4.2 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#pragma once 9 10#include <AK/Format.h> 11#include <AK/Math.h> 12 13namespace Audio { 14using AK::Exponentials::exp; 15using AK::Exponentials::log; 16// Constants for logarithmic volume. See Sample::linear_to_log 17// Corresponds to 60dB 18constexpr float DYNAMIC_RANGE = 1000; 19constexpr float VOLUME_A = 1 / DYNAMIC_RANGE; 20float const VOLUME_B = log(DYNAMIC_RANGE); 21 22// A single sample in an audio buffer. 23// Values are floating point, and should range from -1.0 to +1.0 24struct Sample { 25 constexpr Sample() = default; 26 27 // For mono 28 constexpr explicit Sample(float left) 29 : left(left) 30 , right(left) 31 { 32 } 33 34 // For stereo 35 constexpr Sample(float left, float right) 36 : left(left) 37 , right(right) 38 { 39 } 40 41 void clip() 42 { 43 if (left > 1) 44 left = 1; 45 else if (left < -1) 46 left = -1; 47 48 if (right > 1) 49 right = 1; 50 else if (right < -1) 51 right = -1; 52 } 53 54 // Logarithmic scaling, as audio should ALWAYS do. 55 // Reference: https://www.dr-lex.be/info-stuff/volumecontrols.html 56 // We use the curve `factor = a * exp(b * change)`, 57 // where change is the input fraction we want to change by, 58 // a = 1/1000, b = ln(1000) = 6.908 and factor is the multiplier used. 59 // The value 1000 represents the dynamic range in sound pressure, which corresponds to 60 dB(A). 60 // This is a good dynamic range because it can represent all loudness values from 61 // 30 dB(A) (barely hearable with background noise) 62 // to 90 dB(A) (almost too loud to hear and about the reasonable limit of actual sound equipment). 63 // 64 // Format ranges: 65 // - Linear: 0.0 to 1.0 66 // - Logarithmic: 0.0 to 1.0 67 68 ALWAYS_INLINE float linear_to_log(float const change) const 69 { 70 // TODO: Add linear slope around 0 71 return VOLUME_A * exp(VOLUME_B * change); 72 } 73 74 ALWAYS_INLINE float log_to_linear(float const val) const 75 { 76 // TODO: Add linear slope around 0 77 return log(val / VOLUME_A) / VOLUME_B; 78 } 79 80 ALWAYS_INLINE Sample& log_multiply(float const change) 81 { 82 float factor = linear_to_log(change); 83 left *= factor; 84 right *= factor; 85 return *this; 86 } 87 88 ALWAYS_INLINE Sample log_multiplied(float const volume_change) const 89 { 90 Sample new_frame { left, right }; 91 new_frame.log_multiply(volume_change); 92 return new_frame; 93 } 94 95 // Constant power panning 96 ALWAYS_INLINE Sample& pan(float const position) 97 { 98 float const pi_over_2 = AK::Pi<float> * 0.5f; 99 float const root_over_2 = AK::sqrt<float>(2.0) * 0.5f; 100 float const angle = position * pi_over_2 * 0.5f; 101 float s, c; 102 AK::sincos<float>(angle, s, c); 103 left *= root_over_2 * (c - s); 104 right *= root_over_2 * (c + s); 105 return *this; 106 } 107 108 ALWAYS_INLINE Sample panned(float const position) const 109 { 110 Sample new_sample { left, right }; 111 new_sample.pan(position); 112 return new_sample; 113 } 114 115 constexpr Sample& operator*=(float const mult) 116 { 117 left *= mult; 118 right *= mult; 119 return *this; 120 } 121 122 constexpr Sample operator*(float const mult) const 123 { 124 return { left * mult, right * mult }; 125 } 126 127 constexpr Sample& operator+=(Sample const& other) 128 { 129 left += other.left; 130 right += other.right; 131 return *this; 132 } 133 constexpr Sample& operator+=(float other) 134 { 135 left += other; 136 right += other; 137 return *this; 138 } 139 140 constexpr Sample operator+(Sample const& other) const 141 { 142 return { left + other.left, right + other.right }; 143 } 144 145 float left { 0 }; 146 float right { 0 }; 147}; 148 149} 150 151namespace AK { 152 153template<> 154struct Formatter<Audio::Sample> : Formatter<FormatString> { 155 ErrorOr<void> format(FormatBuilder& builder, Audio::Sample const& value) 156 { 157 return Formatter<FormatString>::format(builder, "[{}, {}]"sv, value.left, value.right); 158 } 159}; 160 161}