openMSX
BlipBuffer.cc
Go to the documentation of this file.
1#include "BlipBuffer.hh"
2#include "cstd.hh"
3#include "Math.hh"
4#include "ranges.hh"
5#include "xrange.hh"
6#include <algorithm>
7#include <array>
8#include <cstring>
9#include <cassert>
10#include <iostream>
11
12namespace openmsx {
13
14// Number of samples in a (pre-calculated) impulse-response wave-form.
15constexpr int BLIP_IMPULSE_WIDTH = 16;
16
17// Derived constants
19
20
21// Precalculated impulse table.
22constexpr auto impulses = [] {
23 constexpr int HALF_SIZE = BLIP_RES / 2 * (BLIP_IMPULSE_WIDTH - 1);
24 double fImpulse[HALF_SIZE + 2 * BLIP_RES] = {};
25 double* out = &fImpulse[BLIP_RES];
26
27 // generate sinc, apply hamming window
28 double oversample = ((4.5 / (BLIP_IMPULSE_WIDTH - 1)) + 0.85);
29 double to_angle = Math::pi / (2.0 * oversample * BLIP_RES);
30 double to_fraction = Math::pi / (2 * (HALF_SIZE - 1));
31 for (auto i : xrange(HALF_SIZE)) {
32 double angle = ((i - HALF_SIZE) * 2 + 1) * to_angle;
33 out[i] = cstd::sin<2>(angle) / angle;
34 out[i] *= 0.54 - 0.46 * cstd::cos<2>((2 * i + 1) * to_fraction);
35 }
36
37 // need mirror slightly past center for calculation
38 for (auto i : xrange(BLIP_RES)) {
39 out[HALF_SIZE + i] = out[HALF_SIZE - 1 - i];
40 }
41
42 // find rescale factor
43 double total = 0.0;
44 for (auto i : xrange(HALF_SIZE)) {
45 total += out[i];
46 }
47 double rescale = 1.0 / (2.0 * total);
48
49 // integrate, first difference, rescale, convert to float
50 constexpr int IMPULSES_SIZE = BLIP_RES * (BLIP_IMPULSE_WIDTH / 2) + 1;
51 float imp[IMPULSES_SIZE] = {};
52 double sum = 0.0;
53 double next = 0.0;
54 for (auto i : xrange(IMPULSES_SIZE)) {
55 imp[i] = float((next - sum) * rescale);
56 sum += fImpulse[i];
57 next += fImpulse[i + BLIP_RES];
58 }
59 // Original code would now apply a correction on each kernel so that
60 // the (integer) coefficients sum up to 'kernelUnit'. I've measured
61 // that after switching to float coefficients this correction is
62 // roughly equal to std::numeric_limits<float>::epsilon(). So it's no
63 // longer meaningful.
64
65 // reshuffle values to a more cache friendly order
66 std::array<std::array<float, BLIP_IMPULSE_WIDTH>, BLIP_RES> result = {};
67 for (auto phase : xrange(BLIP_RES)) {
68 const auto* imp_fwd = &imp[BLIP_RES - phase];
69 const auto* imp_rev = &imp[phase];
70 auto* p = result[phase].data();
71 for (auto i : xrange(BLIP_IMPULSE_WIDTH / 2)) {
72 *p++ = imp_fwd[BLIP_RES * i];
73 }
74 for (int i = BLIP_IMPULSE_WIDTH / 2 - 1; i >= 0; --i) {
75 *p++ = imp_rev[BLIP_RES * i];
76 }
77 }
78 return result;
79}();
80
81
83{
84 if (false) {
85 for (auto i : xrange(BLIP_RES)) {
86 std::cout << "\t{ " << impulses[i][0];
87 for (auto j : xrange(1, BLIP_IMPULSE_WIDTH)) {
88 std::cout << ", " << impulses[i][j];
89 }
90 std::cout << " },\n";
91 }
92 }
93
94 offset = 0;
95 accum = 0;
96 availSamp = 0;
97 memset(buffer, 0, sizeof(buffer));
98}
99
100void BlipBuffer::addDelta(TimeIndex time, float delta)
101{
102 unsigned tmp = time.toInt() + BLIP_IMPULSE_WIDTH;
103 assert(tmp < BUFFER_SIZE);
104 availSamp = std::max<int>(availSamp, tmp);
105
106 unsigned phase = time.fractAsInt();
107 unsigned ofst = time.toInt() + offset;
108 const float* __restrict impulse = impulses[phase].data();
109 if ((ofst + BLIP_IMPULSE_WIDTH) <= BUFFER_SIZE) [[likely]] {
110 float* __restrict result = &buffer[ofst];
111 for (auto i : xrange(BLIP_IMPULSE_WIDTH)) {
112 result[i] += impulse[i] * delta;
113 }
114 } else {
115 for (auto i : xrange(BLIP_IMPULSE_WIDTH)) {
116 buffer[(ofst + i) & BUFFER_MASK] += impulse[i] * delta;
117 }
118 }
119}
120
121constexpr float BASS_FACTOR = 511.0f / 512.0f;
122
123template<unsigned PITCH>
124void BlipBuffer::readSamplesHelper(float* __restrict out, unsigned samples) __restrict
125{
126 assert((offset + samples) <= BUFFER_SIZE);
127 auto acc = accum;
128 unsigned ofst = offset;
129 for (auto i : xrange(samples)) {
130 out[i * PITCH] = acc;
131 acc *= BASS_FACTOR;
132 acc += buffer[ofst];
133 buffer[ofst] = 0.0f;
134 ++ofst;
135 }
136 accum = acc;
137 offset = ofst & BUFFER_MASK;
138}
139
140static bool isSilent(float x)
141{
142 // 'accum' falls away slowly (via BASS_FACTOR). Because it's a float it
143 // takes a long time before it's really zero. But much sooner we can
144 // already say it's practically silent. This check is safe when the
145 // input is in range [-1..+1] (does not say silent too soon). When the
146 // input is in range [-32768..+32767] it takes longer before we switch
147 // to silent mode (but still less than a second).
148 constexpr float threshold = 1.0f / 32768;
149 return std::abs(x) < threshold;
150}
151
152template<unsigned PITCH>
153bool BlipBuffer::readSamples(float* __restrict out, unsigned samples)
154{
155 if (availSamp <= 0) {
156 #ifdef DEBUG
157 // buffer contains all zeros (only check this in debug mode)
158 assert(ranges::all_of(buffer, [](const auto& b) { return b == 0.0f; }));
159 #endif
160 if (isSilent(accum)) {
161 return false; // muted
162 }
163 auto acc = accum;
164 for (auto i : xrange(samples)) {
165 out[i * PITCH] = acc;
166 acc *= BASS_FACTOR;
167 }
168 accum = acc;
169 } else {
170 availSamp -= samples;
171 unsigned t1 = std::min(samples, BUFFER_SIZE - offset);
172 readSamplesHelper<PITCH>(out, t1);
173 if (t1 < samples) {
174 assert(offset == 0);
175 unsigned t2 = samples - t1;
176 assert(t2 < BUFFER_SIZE);
177 readSamplesHelper<PITCH>(&out[t1 * PITCH], t2);
178 }
179 assert(offset < BUFFER_SIZE);
180 }
181 return true;
182}
183
184template bool BlipBuffer::readSamples<1>(float*, unsigned);
185template bool BlipBuffer::readSamples<2>(float*, unsigned);
186
187} // namespace openmsx
bool readSamples(float *out, unsigned samples)
Definition: BlipBuffer.cc:153
static constexpr int BLIP_PHASE_BITS
Definition: BlipBuffer.hh:19
void addDelta(TimeIndex time, float delta)
Definition: BlipBuffer.cc:100
A fixed point number, implemented by a 32-bit signed integer.
Definition: FixedPoint.hh:15
constexpr int toInt() const
Returns the integer part (rounded down) of this fixed point number.
Definition: FixedPoint.hh:76
constexpr unsigned fractAsInt() const
Returns the fractional part of this value as an integer.
Definition: FixedPoint.hh:144
constexpr double pi
Definition: Math.hh:21
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:265
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr int BLIP_IMPULSE_WIDTH
Definition: BlipBuffer.cc:15
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:127
constexpr float BASS_FACTOR
Definition: BlipBuffer.cc:121
constexpr int BLIP_RES
Definition: BlipBuffer.cc:18
constexpr auto impulses
Definition: BlipBuffer.cc:22
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:163
auto sum(InputRange &&range, Proj proj={})
Definition: stl.hh:236
constexpr auto xrange(T e)
Definition: xrange.hh:133