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