OXYGENHUB
◆ main openmsx / src / sound / BlipBuffer.cc
5.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "BlipBuffer.hh"
2
3
#include "Math.hh"
4
#include "cstd.hh"
5
#include "narrow.hh"
6
#include "ranges.hh"
7
#include "xrange.hh"
8
9
#include <algorithm>
10
#include <array>
11
#include <cassert>
12
#include <iostream>
13
14
namespace openmsx {
15
16
// Number of samples in a (pre-calculated) impulse-response wave-form.
17
static constexpr int BLIP_IMPULSE_WIDTH = 16;
18
19
// Derived constants
20
static constexpr int BLIP_RES = 1 << BlipBuffer::BLIP_PHASE_BITS;
21
22
23
// Precalculated impulse table.
24
static constexpr auto impulses = [] {
25
	constexpr int HALF_SIZE = BLIP_RES / 2 * (BLIP_IMPULSE_WIDTH - 1);
26
	std::array<double, BLIP_RES + HALF_SIZE + BLIP_RES> fImpulse = {};
27
	std::span<double, HALF_SIZE> out = subspan<HALF_SIZE>(fImpulse, BLIP_RES);
28
	std::span<double, BLIP_RES>  end = subspan<BLIP_RES >(fImpulse, BLIP_RES + HALF_SIZE);
29
30
	// generate sinc, apply hamming window
31
	double overSample = ((4.5 / (BLIP_IMPULSE_WIDTH - 1)) + 0.85);
32
	double to_angle = Math::pi / (2.0 * overSample * BLIP_RES);
33
	double to_fraction = Math::pi / (2 * (HALF_SIZE - 1));
34
	for (auto i : xrange(HALF_SIZE)) {
35
		double angle = ((i - HALF_SIZE) * 2 + 1) * to_angle;
36
		out[i] = cstd::sin<2>(angle) / angle;
37
		out[i] *= 0.54 - 0.46 * cstd::cos<2>((2 * i + 1) * to_fraction);
38
	}
39
40
	// need mirror slightly past center for calculation
41
	for (auto i : xrange(BLIP_RES)) {
42
		end[i] = out[HALF_SIZE - 1 - i];
43
	}
44
45
	// find rescale factor
46
	double total = 0.0;
47
	for (auto i : xrange(HALF_SIZE)) {
48
		total += out[i];
49
	}
50
	double rescale = 1.0 / (2.0 * total);
51
52
	// integrate, first difference, rescale, convert to float
53
	constexpr int IMPULSES_SIZE = BLIP_RES * (BLIP_IMPULSE_WIDTH / 2) + 1;
54
	std::array<float, IMPULSES_SIZE> imp = {};
55
	double sum = 0.0;
56
	double next = 0.0;
57
	for (auto i : xrange(IMPULSES_SIZE)) {
58
		imp[i] = float((next - sum) * rescale);
59
		sum += fImpulse[i];
60
		next += fImpulse[i + BLIP_RES];
61
	}
62
	// Original code would now apply a correction on each kernel so that
63
	// the (integer) coefficients sum up to 'kernelUnit'. I've measured
64
	// that after switching to float coefficients this correction is
65
	// roughly equal to std::numeric_limits<float>::epsilon(). So it's no
66
	// longer meaningful.
67
68
	// reshuffle values to a more cache friendly order
69
	std::array<std::array<float, BLIP_IMPULSE_WIDTH>, BLIP_RES> result = {};
70
	for (auto phase : xrange(BLIP_RES)) {
71
		const auto* imp_fwd = &imp[BLIP_RES - phase];
72
		const auto* imp_rev = &imp[phase];
73
		auto* p = result[phase].data();
74
		for (size_t i : xrange(BLIP_IMPULSE_WIDTH / 2)) {
75
			*p++ = imp_fwd[BLIP_RES * i];
76
		}
77
		for (ptrdiff_t i = BLIP_IMPULSE_WIDTH / 2 - 1; i >= 0; --i) {
78
			*p++ = imp_rev[BLIP_RES * i];
79
		}
80
	}
81
	return result;
82
}();
83
84
85
BlipBuffer::BlipBuffer()
86
{
87
	if (false) {
88
		for (auto i : xrange(BLIP_RES)) {
89
			std::cout << "\t{ " << impulses[i][0];
90
			for (auto j : xrange(1, BLIP_IMPULSE_WIDTH)) {
91
				std::cout << ", " << impulses[i][j];
92
			}
93
			std::cout << " },\n";
94
		}
95
	}
96
97
	std::ranges::fill(buffer, 0);
98
}
99
100
void BlipBuffer::addDelta(TimeIndex time, float delta)
101
{
102
	unsigned tmp = time.toInt() + BLIP_IMPULSE_WIDTH;
103
	assert(tmp < BUFFER_SIZE);
104
	availSamp = std::max(availSamp, narrow<ptrdiff_t>(tmp));
105
106
	auto phase = time.fractAsInt();
107
	auto 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
121
static constexpr float BASS_FACTOR = 511.0f / 512.0f;
122
123
template<size_t PITCH>
124
void BlipBuffer::readSamplesHelper(float* __restrict out, size_t samples)
125
{
126
	assert((offset + samples) <= BUFFER_SIZE);
127
	auto acc = accum;
128
	auto 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
140
static 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
152
// TODO replace 'out + samples + PITCH' with 'stride_view' ???
153
template<size_t PITCH>
154
bool BlipBuffer::readSamples(float* __restrict out, size_t samples)
155
{
156
	if (availSamp <= 0) {
157
		#ifdef DEBUG
158
		// buffer contains all zeros (only check this in debug mode)
159
		assert(std::ranges::all_of(buffer, [](const auto& b) { return b == 0.0f; }));
160
		#endif
161
		if (isSilent(accum)) {
162
			return false; // muted
163
		}
164
		auto acc = accum;
165
		for (auto i : xrange(samples)) {
166
			out[i * PITCH] = acc;
167
			acc *= BASS_FACTOR;
168
		}
169
		accum = acc;
170
	} else {
171
		availSamp -= narrow<ptrdiff_t>(samples);
172
		auto t1 = std::min(samples, BUFFER_SIZE - offset);
173
		readSamplesHelper<PITCH>(out, t1);
174
		if (t1 < samples) {
175
			assert(offset == 0);
176
			auto t2 = samples - t1;
177
			assert(t2 < BUFFER_SIZE);
178
			readSamplesHelper<PITCH>(&out[t1 * PITCH], t2);
179
		}
180
		assert(offset < BUFFER_SIZE);
181
	}
182
	return true;
183
}
184
185
template bool BlipBuffer::readSamples<1>(float*, size_t);
186
template bool BlipBuffer::readSamples<2>(float*, size_t);
187
188
} // namespace openmsx