openMSX
ResampleLQ.cc
Go to the documentation of this file.
1#include "ResampleLQ.hh"
2
4
5#include "narrow.hh"
6#include "ranges.hh"
7#include "xrange.hh"
8
9#include <bit>
10#include <cassert>
11#include <memory>
12#include <vector>
13
14namespace openmsx {
15
16// 16-byte aligned buffer of ints (shared among all instances of this resampler)
17static std::vector<float> bufferStorage; // (possibly) unaligned storage
18static size_t bufferSize = 0; // usable buffer size (aligned portion)
19static float* aBuffer = nullptr; // pointer to aligned sub-buffer
20
22
23template<unsigned CHANNELS>
24std::unique_ptr<ResampleLQ<CHANNELS>> ResampleLQ<CHANNELS>::create(
25 ResampledSoundDevice& input, const DynamicClock& hostClock)
26{
27 std::unique_ptr<ResampleLQ<CHANNELS>> result;
28 if (input.getEmuClock().getPeriod() >= hostClock.getPeriod()) {
29 result = std::make_unique<ResampleLQUp <CHANNELS>>(input, hostClock);
30 } else {
31 result = std::make_unique<ResampleLQDown<CHANNELS>>(input, hostClock);
32 }
33 return result;
34}
35
36template<unsigned CHANNELS>
38 ResampledSoundDevice& input_, const DynamicClock& hostClock_)
39 : ResampleAlgo(input_)
40 , hostClock(hostClock_)
41 , step([&]{ // calculate 'getEmuClock().getFreq() / hostClock.getFreq()', but with less rounding errors
42 uint64_t emuPeriod = input_.getEmuClock().getPeriod().length(); // unknown units
43 uint64_t hostPeriod = hostClock.getPeriod().length(); // unknown units, but same as above
44 return FP::roundRatioDown(narrow<unsigned>(hostPeriod),
45 narrow<unsigned>(emuPeriod));
46 }())
47{
48 ranges::fill(lastInput, 0.0f);
49}
50
51[[nodiscard]] static bool isSilent(float x)
52{
53 constexpr float threshold = 1.0f / 32768;
54 return std::abs(x) < threshold;
55}
56
57template<unsigned CHANNELS>
58bool ResampleLQ<CHANNELS>::fetchData(EmuTime::param time, unsigned& valid)
59{
60 auto& emuClk = getEmuClock();
61 unsigned emuNum = emuClk.getTicksTill(time);
62 valid = 2 + emuNum;
63
64 unsigned required = emuNum + 4;
65 if (required > bufferSize) [[unlikely]] {
66 // grow buffer (3 extra to be able to align)
67 bufferStorage.resize(required + 3);
68 // align at 16-byte boundary
69 auto p = std::bit_cast<uintptr_t>(bufferStorage.data());
70 aBuffer = std::bit_cast<float*>((p + 15) & ~15);
71 // calculate actual usable size (the aligned portion)
72 bufferSize = (bufferStorage.data() + bufferStorage.size()) - aBuffer;
73 assert(bufferSize >= required);
74 }
75
76 emuClk += emuNum;
77
78 auto* buffer = &aBuffer[4 - 2 * CHANNELS];
79 assert((uintptr_t(&buffer[2 * CHANNELS]) & 15) == 0);
80
81 if (!input.generateInput(&buffer[2 * CHANNELS], emuNum)) {
82 // New input is all zero
83 if (ranges::all_of(lastInput, [](auto& l) { return isSilent(l); })) {
84 // Old input was also all zero, then the resampled
85 // output will be all zero as well.
86 return false;
87 }
88 ranges::fill(std::span{&buffer[CHANNELS], emuNum * CHANNELS}, 0);
89 }
90 for (auto j : xrange(2 * CHANNELS)) {
91 buffer[j] = lastInput[j];
92 lastInput[j] = buffer[emuNum * CHANNELS + j];
93 }
94 return true;
95}
96
98
99template<unsigned CHANNELS>
101 ResampledSoundDevice& input_, const DynamicClock& hostClock_)
102 : ResampleLQ<CHANNELS>(input_, hostClock_)
103{
104 assert(input_.getEmuClock().getFreq() <= hostClock_.getFreq()); // only upsampling
105}
106
107template<unsigned CHANNELS>
109 float* __restrict dataOut, size_t hostNum, EmuTime::param time)
110{
111 auto& emuClk = this->getEmuClock();
112 EmuTime host1 = this->hostClock.getFastAdd(1);
113 assert(host1 > emuClk.getTime());
114 FP pos;
115 emuClk.getTicksTill(host1, pos);
116 assert(pos.toInt() < 2);
117
118 unsigned valid; // only indices smaller than this number are valid
119 if (!this->fetchData(time, valid)) return false;
120
121 // this is currently only used to upsample cassette player sound,
122 // sound quality is not so important here, so use 0-th order
123 // interpolation (instead of 1st-order).
124 auto* buffer = &aBuffer[4 - 2 * CHANNELS];
125 for (auto i : xrange(hostNum)) {
126 unsigned p = pos.toInt();
127 assert(p < valid);
128 for (auto j : xrange(CHANNELS)) {
129 dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j];
130 }
131 pos += this->step;
132 }
133
134 return true;
135}
136
138
139template<unsigned CHANNELS>
141 ResampledSoundDevice& input_, const DynamicClock& hostClock_)
142 : ResampleLQ<CHANNELS>(input_, hostClock_)
143{
144 assert(input_.getEmuClock().getFreq() >= hostClock_.getFreq()); // can only do downsampling
145}
146
147template<unsigned CHANNELS>
149 float* __restrict dataOut, size_t hostNum, EmuTime::param time)
150{
151 auto& emuClk = this->getEmuClock();
152 EmuTime host1 = this->hostClock.getFastAdd(1);
153 assert(host1 > emuClk.getTime());
154 FP pos;
155 emuClk.getTicksTill(host1, pos);
156
157 unsigned valid;
158 if (!this->fetchData(time, valid)) return false;
159
160 auto* buffer = &aBuffer[4 - 2 * CHANNELS];
161 for (auto i : xrange(hostNum)) {
162 unsigned p = pos.toInt();
163 assert((p + 1) < valid);
164 FP fract = pos.fract();
165 for (auto j : xrange(CHANNELS)) {
166 auto s0 = buffer[(p + 0) * CHANNELS + j];
167 auto s1 = buffer[(p + 1) * CHANNELS + j];
168 auto out = s0 + (fract.toFloat() * (s1 - s0));
169 dataOut[i * CHANNELS + j] = out;
170 }
171 pos += this->step;
172 }
173 return true;
174}
175
176
177// Force template instantiation.
178template class ResampleLQ<1>;
179template class ResampleLQ<2>;
180
181} // namespace openmsx
Represents a clock with a variable frequency.
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
static constexpr FixedPoint roundRatioDown(unsigned n, unsigned d)
Definition FixedPoint.hh:60
ResampleLQDown(ResampledSoundDevice &input, const DynamicClock &hostClock)
ResampleLQUp(ResampledSoundDevice &input, const DynamicClock &hostClock)
ResampleLQ(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition ResampleLQ.cc:37
bool fetchData(EmuTime::param time, unsigned &valid)
Definition ResampleLQ.cc:58
static std::unique_ptr< ResampleLQ< CHANNELS > > create(ResampledSoundDevice &input, const DynamicClock &hostClock)
Definition ResampleLQ.cc:24
const DynamicClock & hostClock
Definition ResampleLQ.hh:26
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:186
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
constexpr auto xrange(T e)
Definition xrange.hh:132