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, const DynamicClock& hostClock)
22 {
23  std::unique_ptr<ResampleLQ<CHANNELS>> result;
24  if (input.getEmuClock().getPeriod() >= hostClock.getPeriod()) {
25  result = std::make_unique<ResampleLQUp <CHANNELS>>(input, hostClock);
26  } else {
27  result = std::make_unique<ResampleLQDown<CHANNELS>>(input, hostClock);
28  }
29  return result;
30 }
31 
32 template <unsigned CHANNELS>
34  ResampledSoundDevice& input_, const DynamicClock& hostClock_)
35  : ResampleAlgo(input_)
36  , hostClock(hostClock_)
37  , step([&]{ // calculate 'getEmuClock().getFreq() / hostClock.getFreq()', but with less rounding errors
38  uint64_t emuPeriod = input_.getEmuClock().getPeriod().length(); // unknown units
39  uint64_t hostPeriod = hostClock.getPeriod().length(); // unknown units, but same as above
40  assert(unsigned( emuPeriod) == emuPeriod);
41  assert(unsigned(hostPeriod) == hostPeriod);
42  return FP::roundRatioDown(hostPeriod, emuPeriod);
43  }())
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  auto& emuClk = getEmuClock();
58  unsigned emuNum = emuClk.getTicksTill(time);
59  valid = 2 + emuNum;
60 
61  unsigned required = emuNum + 4;
62  if (unlikely(required > bufferSize)) {
63  // grow buffer (3 extra to be able to align)
64  bufferStorage.resize(required + 3);
65  // align at 16-byte boundary
66  auto p = reinterpret_cast<uintptr_t>(bufferStorage.data());
67  aBuffer = reinterpret_cast<float*>((p + 15) & ~15);
68  // calculate actual usable size (the aligned portion)
69  bufferSize = (bufferStorage.data() + bufferStorage.size()) - aBuffer;
70  assert(bufferSize >= required);
71  }
72 
73  emuClk += emuNum;
74 
75  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
76  assert((uintptr_t(&buffer[2 * CHANNELS]) & 15) == 0);
77 
78  if (!input.generateInput(&buffer[2 * CHANNELS], emuNum)) {
79  // New input is all zero
80  if (ranges::all_of(lastInput, [](auto& l) { return isSilent(l); })) {
81  // Old input was also all zero, then the resampled
82  // output will be all zero as well.
83  return false;
84  }
85  memset(&buffer[CHANNELS], 0, emuNum * CHANNELS * sizeof(float));
86  }
87  for (unsigned j = 0; j < 2 * CHANNELS; ++j) {
88  buffer[j] = lastInput[j];
89  lastInput[j] = buffer[emuNum * CHANNELS + j];
90  }
91  return true;
92 }
93 
95 
96 template <unsigned CHANNELS>
98  ResampledSoundDevice& input_, const DynamicClock& hostClock_)
99  : ResampleLQ<CHANNELS>(input_, hostClock_)
100 {
101  assert(input_.getEmuClock().getFreq() <= 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_, const DynamicClock& hostClock_)
139  : ResampleLQ<CHANNELS>(input_, hostClock_)
140 {
141  assert(input_.getEmuClock().getFreq() >= hostClock_.getFreq()); // can only do downsampling
142 }
143 
144 template <unsigned CHANNELS>
146  float* __restrict dataOut, unsigned hostNum, EmuTime::param time)
147 {
148  auto& emuClk = this->getEmuClock();
149  EmuTime host1 = this->hostClock.getFastAdd(1);
150  assert(host1 > emuClk.getTime());
151  FP pos;
152  emuClk.getTicksTill(host1, pos);
153 
154  unsigned valid;
155  if (!this->fetchData(time, valid)) return false;
156 
157  auto* buffer = &aBuffer[4 - 2 * CHANNELS];
158  for (unsigned i = 0; i < hostNum; ++i) {
159  unsigned p = pos.toInt();
160  assert((p + 1) < valid);
161  FP fract = pos.fract();
162  for (unsigned j = 0; j < CHANNELS; ++j) {
163  auto s0 = buffer[(p + 0) * CHANNELS + j];
164  auto s1 = buffer[(p + 1) * CHANNELS + j];
165  auto out = s0 + (fract.toFloat() * (s1 - s0));
166  dataOut[i * CHANNELS + j] = out;
167  }
168  pos += this->step;
169  }
170  return true;
171 }
172 
173 
174 // Force template instantiation.
175 template class ResampleLQ<1>;
176 template class ResampleLQ<2>;
177 
178 } // namespace openmsx
CHANNELS
constexpr unsigned CHANNELS
Definition: YM2413Test.cc:20
ResampleLQ.hh
openmsx::ResampleLQ::ResampleLQ
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:33
openmsx::ResampledSoundDevice
Definition: ResampledSoundDevice.hh:17
unlikely
#define unlikely(x)
Definition: likely.hh:15
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:43
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:55
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:137
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:1419
openmsx::ResampleLQDown
Definition: ResampleLQ.hh:32
openmsx::ResampleLQ::create
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:20
openmsx::ResampleLQ::hostClock
const DynamicClock & hostClock
Definition: ResampleLQ.hh:24
openmsx::ResampleLQUp::ResampleLQUp
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:97
ranges::fill
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:191
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::ResampleAlgo
Definition: ResampleAlgo.hh:12
ResampledSoundDevice.hh