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 <algorithm>
6 #include <array>
7 #include <cstring>
8 #include <cassert>
9 #include <iostream>
10 
11 namespace openmsx {
12 
13 // The input sample stream can only use this many bits out of the available 32
14 // bits. So 29 bits means the sample values must be in range [-256M, 256M].
15 constexpr int BLIP_SAMPLE_BITS = 29;
16 
17 // Number of samples in a (pre-calculated) impulse-response wave-form.
18 constexpr int BLIP_IMPULSE_WIDTH = 16;
19 
20 // Derived constants
22 
23 
24 // Precalculated impulse table.
25 constexpr auto impulses = [] {
26  constexpr int HALF_SIZE = BLIP_RES / 2 * (BLIP_IMPULSE_WIDTH - 1);
27  double fimpulse[HALF_SIZE + 2 * BLIP_RES] = {};
28  double* out = &fimpulse[BLIP_RES];
29 
30  // generate sinc, apply hamming window
31  double oversample = ((4.5 / (BLIP_IMPULSE_WIDTH - 1)) + 0.85);
32  double to_angle = M_PI / (2.0 * oversample * BLIP_RES);
33  double to_fraction = M_PI / (2 * (HALF_SIZE - 1));
34  for (int i = 0; i < HALF_SIZE; ++i) {
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 (int i = 0; i < BLIP_RES; ++i) {
42  out[HALF_SIZE + i] = out[HALF_SIZE - 1 - i];
43  }
44 
45  // find rescale factor
46  double total = 0.0;
47  for (int i = 0; i < HALF_SIZE; ++i) {
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  float imp[IMPULSES_SIZE] = {};
55  double sum = 0.0;
56  double next = 0.0;
57  for (int i = 0; i < IMPULSES_SIZE; ++i) {
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 (int phase = 0; phase < BLIP_RES; ++phase) {
71  const auto* imp_fwd = &imp[BLIP_RES - phase];
72  const auto* imp_rev = &imp[phase];
73  auto* p = result[phase].data();
74  for (int i = 0; i < BLIP_IMPULSE_WIDTH / 2; ++i) {
75  *p++ = imp_fwd[BLIP_RES * i];
76  }
77  for (int i = BLIP_IMPULSE_WIDTH / 2 - 1; i >= 0; --i) {
78  *p++ = imp_rev[BLIP_RES * i];
79  }
80  }
81  return result;
82 }();
83 
84 
86 {
87  if (false) {
88  for (int i = 0; i < BLIP_RES; ++i) {
89  std::cout << "\t{ " << impulses[i][0];
90  for (int j = 1; j < BLIP_IMPULSE_WIDTH; ++j) {
91  std::cout << ", " << impulses[i][j];
92  }
93  std::cout << " },\n";
94  }
95  }
96 
97  offset = 0;
98  accum = 0;
99  availSamp = 0;
100  memset(buffer, 0, sizeof(buffer));
101 }
102 
103 void BlipBuffer::addDelta(TimeIndex time, float delta)
104 {
105  unsigned tmp = time.toInt() + BLIP_IMPULSE_WIDTH;
106  assert(tmp < BUFFER_SIZE);
107  availSamp = std::max<int>(availSamp, tmp);
108 
109  unsigned phase = time.fractAsInt();
110  unsigned ofst = time.toInt() + offset;
111  const float* __restrict impulse = impulses[phase].data();
112  if (likely((ofst + BLIP_IMPULSE_WIDTH) <= BUFFER_SIZE)) {
113  float* __restrict result = &buffer[ofst];
114  for (int i = 0; i < BLIP_IMPULSE_WIDTH; ++i) {
115  result[i] += impulse[i] * delta;
116  }
117  } else {
118  for (int i = 0; i < BLIP_IMPULSE_WIDTH; ++i) {
119  buffer[(ofst + i) & BUFFER_MASK] += impulse[i] * delta;
120  }
121  }
122 }
123 
124 constexpr float BASS_FACTOR = 511.0f / 512.0f;
125 
126 template<unsigned PITCH>
127 void BlipBuffer::readSamplesHelper(float* __restrict out, unsigned samples) __restrict
128 {
129  assert((offset + samples) <= BUFFER_SIZE);
130  auto acc = accum;
131  unsigned ofst = offset;
132  for (unsigned i = 0; i < samples; ++i) {
133  out[i * PITCH] = acc;
134  acc *= BASS_FACTOR;
135  acc += buffer[ofst];
136  buffer[ofst] = 0.0f;
137  ++ofst;
138  }
139  accum = acc;
140  offset = ofst & BUFFER_MASK;
141 }
142 
143 static bool isSilent(float x)
144 {
145  // 'accum' falls away slowly (via BASS_FACTOR). Because it's a float it
146  // takes a long time before it's really zero. But much sooner we can
147  // already say it's practically silent. This check is safe when the
148  // input is in range [-1..+1] (does not say silent too soon). When the
149  // input is in range [-32768..+32767] it takes longer before we switch
150  // to silent mode (but still less than a second).
151  constexpr float threshold = 1.0f / 32768;
152  return std::abs(x) < threshold;
153 }
154 
155 template <unsigned PITCH>
156 bool BlipBuffer::readSamples(float* __restrict out, unsigned samples)
157 {
158  if (availSamp <= 0) {
159  #ifdef DEBUG
160  // buffer contains all zeros (only check this in debug mode)
161  for (unsigned i = 0; i < BUFFER_SIZE; ++i) {
162  assert(buffer[i] == 0.0f);
163  }
164  #endif
165  if (isSilent(accum)) {
166  return false; // muted
167  }
168  auto acc = accum;
169  for (unsigned i = 0; i < samples; ++i) {
170  out[i * PITCH] = acc;
171  acc *= BASS_FACTOR;
172  }
173  accum = acc;
174  } else {
175  availSamp -= samples;
176  unsigned t1 = std::min(samples, BUFFER_SIZE - offset);
177  readSamplesHelper<PITCH>(out, t1);
178  if (t1 < samples) {
179  assert(offset == 0);
180  unsigned t2 = samples - t1;
181  assert(t2 < BUFFER_SIZE);
182  readSamplesHelper<PITCH>(&out[t1 * PITCH], t2);
183  }
184  assert(offset < BUFFER_SIZE);
185  }
186  return true;
187 }
188 
189 template bool BlipBuffer::readSamples<1>(float*, unsigned);
190 template bool BlipBuffer::readSamples<2>(float*, unsigned);
191 
192 } // namespace openmsx
BlipBuffer.hh
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
cstd.hh
openmsx::BLIP_RES
constexpr int BLIP_RES
Definition: BlipBuffer.cc:21
openmsx::FixedPoint::toInt
constexpr int toInt() const
Returns the integer part (rounded down) of this fixed point number.
Definition: FixedPoint.hh:76
openmsx::BASS_FACTOR
constexpr float BASS_FACTOR
Definition: BlipBuffer.cc:124
openmsx::impulses
constexpr auto impulses
Definition: BlipBuffer.cc:25
likely.hh
openmsx::BLIP_IMPULSE_WIDTH
constexpr int BLIP_IMPULSE_WIDTH
Definition: BlipBuffer.cc:18
M_PI
#define M_PI
Definition: Math.hh:27
openmsx::BlipBuffer::addDelta
void addDelta(TimeIndex time, float delta)
Definition: BlipBuffer.cc:103
openmsx::BlipBuffer::BlipBuffer
BlipBuffer()
Definition: BlipBuffer.cc:85
openmsx::BlipBuffer::BLIP_PHASE_BITS
static constexpr int BLIP_PHASE_BITS
Definition: BlipBuffer.hh:19
likely
#define likely(x)
Definition: likely.hh:14
openmsx::x
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:1419
openmsx::BLIP_SAMPLE_BITS
constexpr int BLIP_SAMPLE_BITS
Definition: BlipBuffer.cc:15
openmsx::FixedPoint::fractAsInt
constexpr unsigned fractAsInt() const
Returns the fractional part of this value as an integer.
Definition: FixedPoint.hh:144
openmsx::BlipBuffer::readSamples
bool readSamples(float *out, unsigned samples)
Definition: BlipBuffer.cc:156
Math.hh
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
sum
auto sum(InputRange &&range)
Definition: stl.hh:293
openmsx::FixedPoint
A fixed point number, implemented by a 32-bit signed integer.
Definition: FixedPoint.hh:15