openMSX
ResampleLQ.cc
Go to the documentation of this file.
1#include "ResampleLQ.hh"
3#include "ranges.hh"
4#include "xrange.hh"
5#include <cassert>
6#include <cstring>
7#include <memory>
8#include <vector>
9
10namespace openmsx {
11
12// 16-byte aligned buffer of ints (shared among all instances of this resampler)
13static std::vector<float> bufferStorage; // (possibly) unaligned storage
14static unsigned bufferSize = 0; // usable buffer size (aligned portion)
15static float* aBuffer = nullptr; // pointer to aligned sub-buffer
16
18
19template<unsigned CHANNELS>
20std::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
32template<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[[nodiscard]] static bool isSilent(float x)
49{
50 constexpr float threshold = 1.0f / 32768;
51 return std::abs(x) < threshold;
52}
53
54template<unsigned CHANNELS>
55bool 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 (required > bufferSize) [[unlikely]] {
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 (auto j : xrange(2 * CHANNELS)) {
88 buffer[j] = lastInput[j];
89 lastInput[j] = buffer[emuNum * CHANNELS + j];
90 }
91 return true;
92}
93
95
96template<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
104template<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 (auto i : xrange(hostNum)) {
123 unsigned p = pos.toInt();
124 assert(p < valid);
125 for (auto j : xrange(CHANNELS)) {
126 dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j];
127 }
128 pos += this->step;
129 }
130
131 return true;
132}
133
135
136template<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
144template<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 (auto i : xrange(hostNum)) {
159 unsigned p = pos.toInt();
160 assert((p + 1) < valid);
161 FP fract = pos.fract();
162 for (auto j : xrange(CHANNELS)) {
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.
175template class ResampleLQ<1>;
176template class ResampleLQ<2>;
177
178} // namespace openmsx
constexpr unsigned CHANNELS
Definition: YM2413Test.cc:20
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:16
EmuDuration getPeriod() const
Returns the length of one clock-cycle.
unsigned getFreq() const
Returns the frequency (in Hz) at which this clock ticks.
constexpr uint64_t length() const
Definition: EmuDuration.hh:51
static constexpr FixedPoint roundRatioDown(unsigned n, unsigned d)
Definition: FixedPoint.hh:59
ResampleLQDown(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:137
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:97
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:33
bool fetchData(EmuTime::param time, unsigned &valid)
Definition: ResampleLQ.cc:55
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:20
const DynamicClock & hostClock
Definition: ResampleLQ.hh:25
constexpr auto step
Definition: eeprom.cc:9
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:127
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:163
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:256
constexpr auto xrange(T e)
Definition: xrange.hh:133