summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibAudio/Sample.h
blob: 91ff28f909cfe43745481da95551b4ab5999889e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/Format.h>
#include <AK/Math.h>

namespace Audio {
using AK::Exponentials::exp;
using AK::Exponentials::log;
// Constants for logarithmic volume. See Sample::linear_to_log
// Corresponds to 60dB
constexpr float DYNAMIC_RANGE = 1000;
constexpr float VOLUME_A = 1 / DYNAMIC_RANGE;
float 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 explicit Sample(float left)
        : left(left)
        , right(left)
    {
    }

    // For stereo
    constexpr Sample(float left, float 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 float linear_to_log(float const change) const
    {
        // TODO: Add linear slope around 0
        return VOLUME_A * exp(VOLUME_B * change);
    }

    ALWAYS_INLINE float log_to_linear(float const val) const
    {
        // TODO: Add linear slope around 0
        return log(val / VOLUME_A) / VOLUME_B;
    }

    ALWAYS_INLINE Sample& log_multiply(float const change)
    {
        float factor = linear_to_log(change);
        left *= factor;
        right *= factor;
        return *this;
    }

    ALWAYS_INLINE Sample log_multiplied(float const volume_change) const
    {
        Sample new_frame { left, right };
        new_frame.log_multiply(volume_change);
        return new_frame;
    }

    // Constant power panning
    ALWAYS_INLINE Sample& pan(float const position)
    {
        float const pi_over_2 = AK::Pi<float> * 0.5f;
        float const root_over_2 = AK::sqrt<float>(2.0) * 0.5f;
        float const angle = position * pi_over_2 * 0.5f;
        float s, c;
        AK::sincos<float>(angle, s, c);
        left *= root_over_2 * (c - s);
        right *= root_over_2 * (c + s);
        return *this;
    }

    ALWAYS_INLINE Sample panned(float const position) const
    {
        Sample new_sample { left, right };
        new_sample.pan(position);
        return new_sample;
    }

    constexpr Sample& operator*=(float const mult)
    {
        left *= mult;
        right *= mult;
        return *this;
    }

    constexpr Sample operator*(float const mult) const
    {
        return { left * mult, right * mult };
    }

    constexpr Sample& operator+=(Sample const& other)
    {
        left += other.left;
        right += other.right;
        return *this;
    }
    constexpr Sample& operator+=(float other)
    {
        left += other;
        right += other;
        return *this;
    }

    constexpr Sample operator+(Sample const& other) const
    {
        return { left + other.left, right + other.right };
    }

    float left { 0 };
    float right { 0 };
};

}

namespace AK {

template<>
struct Formatter<Audio::Sample> : Formatter<FormatString> {
    ErrorOr<void> format(FormatBuilder& builder, Audio::Sample const& value)
    {
        return Formatter<FormatString>::format(builder, "[{}, {}]", value.left, value.right);
    }
};

}