openMSX
ResampleLQ.cc
Go to the documentation of this file.
1 #include "ResampleLQ.hh"
3 #include "likely.hh"
4 #include "ranges.hh"
5 #include <cassert>
6 #include <cstring>
7 #include <memory>
8 #include <vector>
9 
10 namespace openmsx {
11 
12 // 16-byte aligned buffer of ints (shared among all instances of this resampler)
13 static std::vector<float> bufferStorage; // (possibly) unaligned storage
14 static unsigned bufferSize = 0; // usable buffer size (aligned portion)
15 static float* aBuffer = nullptr; // pointer to aligned sub-buffer
16 
18 
19 template<unsigned CHANNELS>
20 std::unique_ptr<ResampleLQ<CHANNELS>> ResampleLQ<CHANNELS>::create(
21  ResampledSoundDevice& input,
22  const DynamicClock& hostClock, unsigned emuSampleRate)
23 {
24  std::unique_ptr<ResampleLQ<CHANNELS>> result;
25  unsigned hostSampleRate = hostClock.getFreq();
26  if (emuSampleRate < hostSampleRate) {
27  result = std::make_unique<ResampleLQUp <CHANNELS>>(
28  input, hostClock, emuSampleRate);
29  } else {
30  result = std::make_unique<ResampleLQDown<CHANNELS>>(
31  input, hostClock, emuSampleRate);
32  }
33  return result;
34 }
35 
36 template <unsigned CHANNELS>
38  ResampledSoundDevice& input_,
39  const DynamicClock& hostClock_, unsigned emuSampleRate)
40  : input(input_)
41  , hostClock(hostClock_)
42  , emuClock(hostClock.getTime(), emuSampleRate)
43  , step(FP::roundRatioDown(emuSampleRate, hostClock.getFreq()))
44 {
45  ranges::fill(lastInput, 0.0f);
46 }
47 
48 static bool isSilent(float x)
49 {
50  constexpr float threshold = 1.0f / 32768;
51  return std::abs(x) < threshold;
52 }
53 
54 template <unsigned CHANNELS>
55 bool ResampleLQ<CHANNELS>::fetchData(EmuTime::param time, unsigned& valid)
56 {
57  unsigned emuNum = emuClock.getTicksTill(time);
58  valid = 2 + emuNum;
59 
60  unsigned required = emuNum + 4;
61  if (unlikely(required > bufferSize)) {
62  // grow buffer (3 extra to be able to align)
63  bufferStorage.resize(required + 3);
64  // align at 16-byte boundary
65  auto p = reinterpret_cast<uintptr_t>(bufferStorage.data());
66  aBuffer = reinterpret_cast<float*>((p + 15) & ~15);
67  // calculate actual usable size (the aligned portion)
68  bufferSize = (bufferStorage.data() + bufferStorage.size()) - aBuffer;
69  assert(bufferSize >= required);
70  }
71 
72  emuClock += emuNum;
73  assert(emuClock.getTime() <= time);
74  assert(emuClock.getFastAdd(1) > time);
75 
76  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
77  assert((uintptr_t(&buffer[2 * CHANNELS]) & 15) == 0);
78 
79  if (!input.generateInput(&buffer[2 * CHANNELS], emuNum)) {
80  // New input is all zero
81  if (ranges::all_of(lastInput, [](auto& l) { return isSilent(l); })) {
82  // Old input was also all zero, then the resampled
83  // output will be all zero as well.
84  return false;
85  }
86  memset(&buffer[CHANNELS], 0, emuNum * CHANNELS * sizeof(float));
87  }
88  for (unsigned j = 0; j < 2 * CHANNELS; ++j) {
89  buffer[j] = lastInput[j];
90  lastInput[j] = buffer[emuNum * CHANNELS + j];
91  }
92  return true;
93 }
94 
96 
97 template <unsigned CHANNELS>
99  ResampledSoundDevice& input_,
100  const DynamicClock& hostClock_, unsigned emuSampleRate)
101  : ResampleLQ<CHANNELS>(input_, hostClock_, emuSampleRate)
102 {
103  assert(emuSampleRate < hostClock_.getFreq()); // only upsampling
104 }
105 
106 template <unsigned CHANNELS>
108  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
109 {
110  EmuTime host1 = this->hostClock.getFastAdd(1);
111  assert(host1 > this->emuClock.getTime());
112  FP pos;
113  this->emuClock.getTicksTill(host1, pos);
114  assert(pos.toInt() < 2);
115 
116  unsigned valid; // only indices smaller than this number are valid
117  if (!this->fetchData(time, valid)) return false;
118 
119  // this is currently only used to upsample cassette player sound,
120  // sound quality is not so important here, so use 0-th order
121  // interpolation (instead of 1st-order).
122  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
123  for (unsigned i = 0; i < hostNum; ++i) {
124  unsigned p = pos.toInt();
125  assert(p < valid);
126  for (unsigned j = 0; j < CHANNELS; ++j) {
127  dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j];
128  }
129  pos += this->step;
130  }
131 
132  return true;
133 }
134 
136 
137 template <unsigned CHANNELS>
139  ResampledSoundDevice& input_,
140  const DynamicClock& hostClock_, unsigned emuSampleRate)
141  : ResampleLQ<CHANNELS>(input_, hostClock_, emuSampleRate)
142 {
143  assert(emuSampleRate > hostClock_.getFreq()); // can only do downsampling
144 }
145 
146 template <unsigned CHANNELS>
148  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
149 {
150  EmuTime host1 = this->hostClock.getFastAdd(1);
151  assert(host1 > this->emuClock.getTime());
152  FP pos;
153  this->emuClock.getTicksTill(host1, pos);
154 
155  unsigned valid;
156  if (!this->fetchData(time, valid)) return false;
157 
158  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
159  for (unsigned i = 0; i < hostNum; ++i) {
160  unsigned p = pos.toInt();
161  assert((p + 1) < valid);
162  FP fract = pos.fract();
163  for (unsigned j = 0; j < CHANNELS; ++j) {
164  auto s0 = buffer[(p + 0) * CHANNELS + j];
165  auto s1 = buffer[(p + 1) * CHANNELS + j];
166  auto out = s0 + (fract.toFloat() * (s1 - s0));
167  dataOut[i * CHANNELS + j] = out;
168  }
169  pos += this->step;
170  }
171  return true;
172 }
173 
174 
175 // Force template instantiation.
176 template class ResampleLQ<1>;
177 template class ResampleLQ<2>;
178 
179 } // namespace openmsx
CHANNELS
constexpr unsigned CHANNELS
Definition: YM2413Test.cc:20
ResampleLQ.hh
openmsx::ResampledSoundDevice
Definition: ResampledSoundDevice.hh:15
unlikely
#define unlikely(x)
Definition: likely.hh:15
openmsx::ResampleLQ::lastInput
float lastInput[2 *CHANNELS]
Definition: ResampleLQ.hh:31
openmsx::DynamicClock::getFreq
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
Definition: DynamicClock.hh:123
openmsx::ResampleLQDown::ResampleLQDown
ResampleLQDown(ResampledSoundDevice &input, const DynamicClock &hostClock, unsigned emuSampleRate)
Definition: ResampleLQ.cc:138
openmsx::ResampleLQUp
Definition: ResampleLQ.hh:47
ranges.hh
ranges::all_of
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:119
step
constexpr auto step
Definition: eeprom.cc:9
openmsx::ResampleLQ::fetchData
bool fetchData(EmuTime::param time, unsigned &valid)
Definition: ResampleLQ.cc:55
likely.hh
openmsx::DynamicClock
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:16
openmsx::ResampleLQ::create
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock, unsigned emuSampleRate)
Definition: ResampleLQ.cc:20
openmsx::ResampleLQ::ResampleLQ
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock, unsigned emuSampleRate)
Definition: ResampleLQ.cc:37
openmsx::ResampleLQ
Definition: ResampleLQ.hh:14
openmsx::x
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:1377
openmsx::ResampleLQDown
Definition: ResampleLQ.hh:35
openmsx::ResampleLQUp::ResampleLQUp
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock, unsigned emuSampleRate)
Definition: ResampleLQ.cc:98
ranges::fill
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:191
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::FixedPoint
A fixed point number, implemented by a 32-bit signed integer.
Definition: FixedPoint.hh:15
ResampledSoundDevice.hh