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