openMSX
ResampleLQ.cc
Go to the documentation of this file.
1 #include "ResampleLQ.hh"
3 #include "likely.hh"
4 #include "ranges.hh"
5 #include "xrange.hh"
6 #include <cassert>
7 #include <cstring>
8 #include <memory>
9 #include <vector>
10 
11 namespace openmsx {
12 
13 // 16-byte aligned buffer of ints (shared among all instances of this resampler)
14 static std::vector<float> bufferStorage; // (possibly) unaligned storage
15 static unsigned bufferSize = 0; // usable buffer size (aligned portion)
16 static float* aBuffer = nullptr; // pointer to aligned sub-buffer
17 
19 
20 template<unsigned CHANNELS>
21 std::unique_ptr<ResampleLQ<CHANNELS>> ResampleLQ<CHANNELS>::create(
22  ResampledSoundDevice& input, const DynamicClock& hostClock)
23 {
24  std::unique_ptr<ResampleLQ<CHANNELS>> result;
25  if (input.getEmuClock().getPeriod() >= hostClock.getPeriod()) {
26  result = std::make_unique<ResampleLQUp <CHANNELS>>(input, hostClock);
27  } else {
28  result = std::make_unique<ResampleLQDown<CHANNELS>>(input, hostClock);
29  }
30  return result;
31 }
32 
33 template<unsigned CHANNELS>
35  ResampledSoundDevice& input_, const DynamicClock& hostClock_)
36  : ResampleAlgo(input_)
37  , hostClock(hostClock_)
38  , step([&]{ // calculate 'getEmuClock().getFreq() / hostClock.getFreq()', but with less rounding errors
39  uint64_t emuPeriod = input_.getEmuClock().getPeriod().length(); // unknown units
40  uint64_t hostPeriod = hostClock.getPeriod().length(); // unknown units, but same as above
41  assert(unsigned( emuPeriod) == emuPeriod);
42  assert(unsigned(hostPeriod) == hostPeriod);
43  return FP::roundRatioDown(hostPeriod, emuPeriod);
44  }())
45 {
46  ranges::fill(lastInput, 0.0f);
47 }
48 
49 [[nodiscard]] static bool isSilent(float x)
50 {
51  constexpr float threshold = 1.0f / 32768;
52  return std::abs(x) < threshold;
53 }
54 
55 template<unsigned CHANNELS>
56 bool ResampleLQ<CHANNELS>::fetchData(EmuTime::param time, unsigned& valid)
57 {
58  auto& emuClk = getEmuClock();
59  unsigned emuNum = emuClk.getTicksTill(time);
60  valid = 2 + emuNum;
61 
62  unsigned required = emuNum + 4;
63  if (unlikely(required > bufferSize)) {
64  // grow buffer (3 extra to be able to align)
65  bufferStorage.resize(required + 3);
66  // align at 16-byte boundary
67  auto p = reinterpret_cast<uintptr_t>(bufferStorage.data());
68  aBuffer = reinterpret_cast<float*>((p + 15) & ~15);
69  // calculate actual usable size (the aligned portion)
70  bufferSize = (bufferStorage.data() + bufferStorage.size()) - aBuffer;
71  assert(bufferSize >= required);
72  }
73 
74  emuClk += emuNum;
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 (auto j : xrange(2 * CHANNELS)) {
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_, const DynamicClock& hostClock_)
100  : ResampleLQ<CHANNELS>(input_, hostClock_)
101 {
102  assert(input_.getEmuClock().getFreq() <= hostClock_.getFreq()); // only upsampling
103 }
104 
105 template<unsigned CHANNELS>
107  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
108 {
109  auto& emuClk = this->getEmuClock();
110  EmuTime host1 = this->hostClock.getFastAdd(1);
111  assert(host1 > emuClk.getTime());
112  FP pos;
113  emuClk.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 (auto i : xrange(hostNum)) {
124  unsigned p = pos.toInt();
125  assert(p < valid);
126  for (auto j : xrange(CHANNELS)) {
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_, const DynamicClock& hostClock_)
140  : ResampleLQ<CHANNELS>(input_, hostClock_)
141 {
142  assert(input_.getEmuClock().getFreq() >= 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 (auto i : xrange(hostNum)) {
160  unsigned p = pos.toInt();
161  assert((p + 1) < valid);
162  FP fract = pos.fract();
163  for (auto j : xrange(CHANNELS)) {
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::ResampleLQ::ResampleLQ
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:34
openmsx::ResampledSoundDevice
Definition: ResampledSoundDevice.hh:17
unlikely
#define unlikely(x)
Definition: likely.hh:15
xrange
constexpr auto xrange(T e)
Definition: xrange.hh:155
openmsx::DynamicClock::getFreq
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
Definition: DynamicClock.hh:124
openmsx::ResampleLQUp
Definition: ResampleLQ.hh:44
ranges.hh
openmsx::EmuDuration::length
constexpr uint64_t length() const
Definition: EmuDuration.hh:50
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:56
likely.hh
openmsx::DynamicClock
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:17
openmsx::FixedPoint::roundRatioDown
static constexpr FixedPoint roundRatioDown(unsigned n, unsigned d)
Definition: FixedPoint.hh:59
openmsx::ResampleLQDown::ResampleLQDown
ResampleLQDown(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:138
openmsx::ResampledSoundDevice::getEmuClock
DynamicClock & getEmuClock()
Definition: ResampledSoundDevice.hh:27
openmsx::ResampleLQ
Definition: ResampleLQ.hh:15
openmsx::DynamicClock::getPeriod
EmuDuration getPeriod() const
Returns the length of one clock-cycle.
Definition: DynamicClock.hh:131
openmsx::x
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:1414
openmsx::ResampleLQDown
Definition: ResampleLQ.hh:33
openmsx::ResampleLQ::create
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:21
openmsx::ResampleLQ::hostClock
const DynamicClock & hostClock
Definition: ResampleLQ.hh:25
openmsx::ResampleLQUp::ResampleLQUp
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:98
ranges::fill
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:197
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
xrange.hh
openmsx::ResampleAlgo
Definition: ResampleAlgo.hh:12
ResampledSoundDevice.hh