OXYGENHUB
◆ main openmsx / src / sound / ResampleBlip.cc
3.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ResampleBlip.hh"
2
3
#include "ResampledSoundDevice.hh"
4
5
#include "narrow.hh"
6
#include "one_of.hh"
7
#include "small_buffer.hh"
8
#include "xrange.hh"
9
10
#include <algorithm>
11
#include <array>
12
#include <cassert>
13
14
namespace openmsx {
15
16
template<unsigned CHANNELS>
17
ResampleBlip<CHANNELS>::ResampleBlip(
18
		ResampledSoundDevice& input_, const DynamicClock& hostClock_)
19
	: ResampleAlgo(input_)
20
	, hostClock(hostClock_)
21
	, step([&]{ // calculate 'hostClock.getFreq() / getEmuClock().getFreq()', but with less rounding errors
22
			uint64_t emuPeriod = input_.getEmuClock().getPeriod().toUint64(); // unknown units
23
			uint64_t hostPeriod = hostClock.getPeriod().toUint64(); // unknown units, but same as above
24
			return FP::roundRatioDown(narrow<unsigned>(emuPeriod),
25
			                          narrow<unsigned>(hostPeriod));
26
		}())
27
{
28
	std::ranges::fill(lastInput, 0.0f);
29
}
30
31
template<unsigned CHANNELS>
32
bool ResampleBlip<CHANNELS>::generateOutputImpl(float* dataOut, size_t hostNum,
33
                                                EmuTime time)
34
{
35
	auto& emuClk = getEmuClock();
36
	if (unsigned emuNum = emuClk.getTicksTill(time); emuNum > 0) {
37
		// 3 extra for padding, CHANNELS extra for sentinel
38
		// Clang will produce a link error if the length expression is put
39
		// inside the macro.
40
		const unsigned len = emuNum * CHANNELS + std::max(3u, CHANNELS);
41
		small_buffer<float, 8192> buf(uninitialized_tag{}, len); // typical ~5194 (PSG, samples=1024) but could be larger
42
		EmuTime emu1 = emuClk.getFastAdd(1); // time of 1st emu-sample
43
		assert(emu1 > hostClock.getTime());
44
		if (input.generateInput(buf.data(), emuNum)) {
45
			FP pos1;
46
			hostClock.getTicksTill(emu1, pos1);
47
			for (auto ch : xrange(CHANNELS)) {
48
				// In case of PSG (and to a lesser degree SCC) it happens
49
				// very often that two consecutive samples have the same
50
				// value. We can benefit from this by setting a sentinel
51
				// at the end of the buffer and move the end-of-loop test
52
				// into the 'samples differ' branch.
53
				assert(emuNum > 0);
54
				buf[CHANNELS * emuNum + ch] =
55
					buf[CHANNELS * (emuNum - 1) + ch] + 1.0f;
56
				FP pos = pos1;
57
				auto last = lastInput[ch]; // local var is slightly faster
58
				for (unsigned i = 0; /**/; ++i) {
59
					if (auto delta = buf[CHANNELS * i + ch] - last;
60
					    delta != 0) [[unlikely]] {
61
						if (i == emuNum) {
62
							break;
63
						}
64
						last = buf[CHANNELS * i + ch];
65
						blip[ch].addDelta(
66
							BlipBuffer::TimeIndex(pos),
67
							delta);
68
					}
69
					pos += step;
70
				}
71
				lastInput[ch] = last;
72
			}
73
		} else {
74
			// input all zero
75
			BlipBuffer::TimeIndex pos;
76
			hostClock.getTicksTill(emu1, pos);
77
			for (auto ch : xrange(CHANNELS)) {
78
				if (lastInput[ch] != 0.0f) {
79
					auto delta = -lastInput[ch];
80
					lastInput[ch] = 0.0f;
81
					blip[ch].addDelta(pos, delta);
82
				}
83
			}
84
		}
85
		emuClk += emuNum;
86
	}
87
88
	std::array<bool, CHANNELS> results;
89
	for (auto ch : xrange(CHANNELS)) {
90
		results[ch] = blip[ch].template readSamples<CHANNELS>(dataOut + ch, hostNum);
91
	}
92
	static_assert(CHANNELS == one_of(1u, 2u), "either mono or stereo");
93
	if constexpr (CHANNELS == 1) {
94
		return results[0];
95
	} else {
96
		if (results[0] == results[1]) {
97
			// Both muted or both unmuted
98
			return results[0];
99
		} else {
100
			// One channel muted, the other not.
101
			// We have to set the muted channel to all-zero.
102
			unsigned offset = results[0] ? 1 : 0;
103
			for (auto i : xrange(hostNum)) {
104
				dataOut[2 * i + offset] = 0.0f;
105
			}
106
			return true;
107
		}
108
	}
109
}
110
111
// Force template instantiation.
112
template class ResampleBlip<1>;
113
template class ResampleBlip<2>;
114
115
} // namespace openmsx