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  : ResampleAlgo(input_)
41  , hostClock(hostClock_)
42  , step(FP::roundRatioDown(emuSampleRate, hostClock.getFreq()))
43 {
44  ranges::fill(lastInput, 0.0f);
45 }
46 
47 static bool isSilent(float x)
48 {
49  constexpr float threshold = 1.0f / 32768;
50  return std::abs(x) < threshold;
51 }
52 
53 template <unsigned CHANNELS>
54 bool ResampleLQ<CHANNELS>::fetchData(EmuTime::param time, unsigned& valid)
55 {
56  auto& emuClk = getEmuClock();
57  unsigned emuNum = emuClk.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  emuClk += emuNum;
73 
74  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
75  assert((uintptr_t(&buffer[2 * CHANNELS]) & 15) == 0);
76 
77  if (!input.generateInput(&buffer[2 * CHANNELS], emuNum)) {
78  // New input is all zero
79  if (ranges::all_of(lastInput, [](auto& l) { return isSilent(l); })) {
80  // Old input was also all zero, then the resampled
81  // output will be all zero as well.
82  return false;
83  }
84  memset(&buffer[CHANNELS], 0, emuNum * CHANNELS * sizeof(float));
85  }
86  for (unsigned j = 0; j < 2 * CHANNELS; ++j) {
87  buffer[j] = lastInput[j];
88  lastInput[j] = buffer[emuNum * CHANNELS + j];
89  }
90  return true;
91 }
92 
94 
95 template <unsigned CHANNELS>
97  ResampledSoundDevice& input_,
98  const DynamicClock& hostClock_, unsigned emuSampleRate)
99  : ResampleLQ<CHANNELS>(input_, hostClock_, emuSampleRate)
100 {
101  assert(emuSampleRate < hostClock_.getFreq()); // only upsampling
102 }
103 
104 template <unsigned CHANNELS>
106  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
107 {
108  auto& emuClk = this->getEmuClock();
109  EmuTime host1 = this->hostClock.getFastAdd(1);
110  assert(host1 > emuClk.getTime());
111  FP pos;
112  emuClk.getTicksTill(host1, pos);
113  assert(pos.toInt() < 2);
114 
115  unsigned valid; // only indices smaller than this number are valid
116  if (!this->fetchData(time, valid)) return false;
117 
118  // this is currently only used to upsample cassette player sound,
119  // sound quality is not so important here, so use 0-th order
120  // interpolation (instead of 1st-order).
121  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
122  for (unsigned i = 0; i < hostNum; ++i) {
123  unsigned p = pos.toInt();
124  assert(p < valid);
125  for (unsigned j = 0; j < CHANNELS; ++j) {
126  dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j];
127  }
128  pos += this->step;
129  }
130 
131  return true;
132 }
133 
135 
136 template <unsigned CHANNELS>
138  ResampledSoundDevice& input_,
139  const DynamicClock& hostClock_, unsigned emuSampleRate)
140  : ResampleLQ<CHANNELS>(input_, hostClock_, emuSampleRate)
141 {
142  assert(emuSampleRate > hostClock_.getFreq()); // can only do downsampling
143 }
144 
145 template <unsigned CHANNELS>
147  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
148 {
149  auto& emuClk = this->getEmuClock();
150  EmuTime host1 = this->hostClock.getFastAdd(1);
151  assert(host1 > emuClk.getTime());
152  FP pos;
153  emuClk.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:16
unlikely
#define unlikely(x)
Definition: likely.hh:15
openmsx::ResampleLQ::lastInput
float lastInput[2 *CHANNELS]
Definition: ResampleLQ.hh:29
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:137
openmsx::ResampleLQUp
Definition: ResampleLQ.hh:45
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:54
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:1419
openmsx::ResampleLQDown
Definition: ResampleLQ.hh:33
openmsx::ResampleLQUp::ResampleLQUp
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock, unsigned emuSampleRate)
Definition: ResampleLQ.cc:96
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::ResampleAlgo
Definition: ResampleAlgo.hh:11
openmsx::FixedPoint
A fixed point number, implemented by a 32-bit signed integer.
Definition: FixedPoint.hh:15
ResampledSoundDevice.hh