openMSX
ResampleLQ.cc
Go to the documentation of this file.
1#include "ResampleLQ.hh"
3#include "narrow.hh"
4#include "ranges.hh"
5#include "xrange.hh"
6#include <cassert>
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 size_t 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 return FP::roundRatioDown(narrow<unsigned>(hostPeriod),
41 narrow<unsigned>(emuPeriod));
42 }())
43{
44 ranges::fill(lastInput, 0.0f);
45}
46
47[[nodiscard]] static bool isSilent(float x)
48{
49 constexpr float threshold = 1.0f / 32768;
50 return std::abs(x) < threshold;
51}
52
53template<unsigned CHANNELS>
54bool 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 (required > bufferSize) [[unlikely]] {
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 ranges::fill(std::span{&buffer[CHANNELS], emuNum * CHANNELS}, 0);
85 }
86 for (auto j : xrange(2 * CHANNELS)) {
87 buffer[j] = lastInput[j];
88 lastInput[j] = buffer[emuNum * CHANNELS + j];
89 }
90 return true;
91}
92
94
95template<unsigned CHANNELS>
97 ResampledSoundDevice& input_, const DynamicClock& hostClock_)
98 : ResampleLQ<CHANNELS>(input_, hostClock_)
99{
100 assert(input_.getEmuClock().getFreq() <= hostClock_.getFreq()); // only upsampling
101}
102
103template<unsigned CHANNELS>
105 float* __restrict dataOut, size_t hostNum, EmuTime::param time)
106{
107 auto& emuClk = this->getEmuClock();
108 EmuTime host1 = this->hostClock.getFastAdd(1);
109 assert(host1 > emuClk.getTime());
110 FP pos;
111 emuClk.getTicksTill(host1, pos);
112 assert(pos.toInt() < 2);
113
114 unsigned valid; // only indices smaller than this number are valid
115 if (!this->fetchData(time, valid)) return false;
116
117 // this is currently only used to upsample cassette player sound,
118 // sound quality is not so important here, so use 0-th order
119 // interpolation (instead of 1st-order).
120 auto* buffer = &aBuffer[4 - 2 * CHANNELS];
121 for (auto i : xrange(hostNum)) {
122 unsigned p = pos.toInt();
123 assert(p < valid);
124 for (auto j : xrange(CHANNELS)) {
125 dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j];
126 }
127 pos += this->step;
128 }
129
130 return true;
131}
132
134
135template<unsigned CHANNELS>
137 ResampledSoundDevice& input_, const DynamicClock& hostClock_)
138 : ResampleLQ<CHANNELS>(input_, hostClock_)
139{
140 assert(input_.getEmuClock().getFreq() >= hostClock_.getFreq()); // can only do downsampling
141}
142
143template<unsigned CHANNELS>
145 float* __restrict dataOut, size_t hostNum, EmuTime::param time)
146{
147 auto& emuClk = this->getEmuClock();
148 EmuTime host1 = this->hostClock.getFastAdd(1);
149 assert(host1 > emuClk.getTime());
150 FP pos;
151 emuClk.getTicksTill(host1, pos);
152
153 unsigned valid;
154 if (!this->fetchData(time, valid)) return false;
155
156 auto* buffer = &aBuffer[4 - 2 * CHANNELS];
157 for (auto i : xrange(hostNum)) {
158 unsigned p = pos.toInt();
159 assert((p + 1) < valid);
160 FP fract = pos.fract();
161 for (auto j : xrange(CHANNELS)) {
162 auto s0 = buffer[(p + 0) * CHANNELS + j];
163 auto s1 = buffer[(p + 1) * CHANNELS + j];
164 auto out = s0 + (fract.toFloat() * (s1 - s0));
165 dataOut[i * CHANNELS + j] = out;
166 }
167 pos += this->step;
168 }
169 return true;
170}
171
172
173// Force template instantiation.
174template class ResampleLQ<1>;
175template class ResampleLQ<2>;
176
177} // namespace openmsx
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:17
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:52
static constexpr FixedPoint roundRatioDown(unsigned n, unsigned d)
Definition: FixedPoint.hh:60
ResampleLQDown(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:136
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:96
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:33
bool fetchData(EmuTime::param time, unsigned &valid)
Definition: ResampleLQ.cc:54
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition: ResampleLQ.cc:20
const DynamicClock & hostClock
Definition: ResampleLQ.hh:26
This file implemented 3 utility functions:
Definition: Autofire.cc:9
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:186
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
constexpr auto xrange(T e)
Definition: xrange.hh:132