OXYGENHUB
◆ main openmsx / src / sound / SDLSoundDriver.cc
4.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SDLSoundDriver.hh"
2
3
#include "MSXMixer.hh"
4
#include "Mixer.hh"
5
6
#include "GlobalSettings.hh"
7
#include "MSXException.hh"
8
#include "MSXMotherBoard.hh"
9
#include "Reactor.hh"
10
#include "RealTime.hh"
11
#include "ThrottleManager.hh"
12
#include "Timer.hh"
13
14
#include "narrow.hh"
15
16
#include <algorithm>
17
#include <bit>
18
#include <cassert>
19
20
namespace openmsx {
21
22
SDLSoundDriver::SDLSoundDriver(Reactor& reactor_,
23
                               unsigned wantedFreq, unsigned wantedSamples)
24
	: reactor(reactor_)
25
{
26
	SDL_AudioSpec desired;
27
	desired.freq     = narrow<int>(wantedFreq);
28
	desired.samples  = narrow<Uint16>(std::bit_ceil(wantedSamples));
29
	desired.channels = 2; // stereo
30
	desired.format   = AUDIO_F32SYS;
31
	desired.callback = audioCallbackHelper; // must be a static method
32
	desired.userdata = this;
33
34
	SDL_AudioSpec obtained;
35
	deviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &desired, &obtained,
36
	                               SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
37
	if (!deviceID) {
38
		throw MSXException("Unable to open SDL audio: ", SDL_GetError());
39
	}
40
41
	frequency = obtained.freq;
42
	fragmentSize = obtained.samples;
43
44
	mixBuffer.resize(3 * (obtained.size / sizeof(StereoFloat)) + 1);
45
	reInit();
46
}
47
48
SDLSoundDriver::~SDLSoundDriver()
49
{
50
	SDL_CloseAudioDevice(deviceID);
51
}
52
53
void SDLSoundDriver::reInit()
54
{
55
	SDL_LockAudioDevice(deviceID);
56
	readIdx  = 0;
57
	writeIdx = 0;
58
	SDL_UnlockAudioDevice(deviceID);
59
}
60
61
void SDLSoundDriver::mute()
62
{
63
	if (!muted) {
64
		muted = true;
65
		SDL_PauseAudioDevice(deviceID, 1);
66
	}
67
}
68
69
void SDLSoundDriver::unmute()
70
{
71
	if (muted) {
72
		muted = false;
73
		reInit();
74
		SDL_PauseAudioDevice(deviceID, 0);
75
	}
76
}
77
78
unsigned SDLSoundDriver::getFrequency() const
79
{
80
	return frequency;
81
}
82
83
unsigned SDLSoundDriver::getSamples() const
84
{
85
	return fragmentSize;
86
}
87
88
void SDLSoundDriver::audioCallbackHelper(void* userdata, uint8_t* strm, int len)
89
{
90
	assert((len & 7) == 0); // stereo, 32 bit float
91
	static_cast<SDLSoundDriver*>(userdata)->
92
		audioCallback(std::span{std::bit_cast<StereoFloat*>(strm),
93
		                        len / (2 * sizeof(float))});
94
}
95
96
unsigned SDLSoundDriver::getBufferFilled() const
97
{
98
	int result = narrow_cast<int>(writeIdx - readIdx);
99
	if (result < 0) result += narrow<int>(mixBuffer.size());
100
	assert((0 <= result) && (narrow<unsigned>(result) < mixBuffer.size()));
101
	return result;
102
}
103
104
unsigned SDLSoundDriver::getBufferFree() const
105
{
106
	// we can't distinguish completely filled from completely empty
107
	// (in both cases readIx would be equal to writeIdx), so instead
108
	// we define full as '(writeIdx + 1) == readIdx'.
109
	auto result = narrow<unsigned>(mixBuffer.size() - 1 - getBufferFilled());
110
	assert(narrow_cast<int>(result) >= 0);
111
	assert(result < mixBuffer.size());
112
	return result;
113
}
114
115
void SDLSoundDriver::audioCallback(std::span<StereoFloat> stream)
116
{
117
	auto len = stream.size();
118
119
	size_t available = getBufferFilled();
120
	if (auto num = std::min(len, available);
121
	    (readIdx + num) < mixBuffer.size()) {
122
		copy_to_range(mixBuffer.subspan(readIdx, num), stream);
123
		readIdx += narrow<unsigned>(num);
124
	} else {
125
		auto len1 = mixBuffer.size() - readIdx;
126
		copy_to_range(mixBuffer.subspan(readIdx, len1), stream);
127
		auto len2 = num - len1;
128
		copy_to_range(mixBuffer.first(len2), stream.subspan(len1));
129
		readIdx = narrow<unsigned>(len2);
130
	}
131
	auto missing = narrow_cast<ptrdiff_t>(len - available);
132
	if (missing > 0) {
133
		// buffer underrun
134
		std::ranges::fill(subspan(stream, available, missing), StereoFloat{});
135
	}
136
}
137
138
void SDLSoundDriver::uploadBuffer(std::span<const StereoFloat> buffer)
139
{
140
	SDL_LockAudioDevice(deviceID);
141
	unsigned free = getBufferFree();
142
	if (buffer.size() > free) {
143
		auto* board = reactor.getMotherBoard();
144
		if (board && !board->getMSXMixer().isSynchronousMode() && // when not recording
145
		    reactor.getGlobalSettings().getThrottleManager().isThrottled()) {
146
			do {
147
				SDL_UnlockAudioDevice(deviceID);
148
				Timer::sleep(5000); // 5ms
149
				SDL_LockAudioDevice(deviceID);
150
				board->getRealTime().resync();
151
				free = getBufferFree();
152
			} while (buffer.size() > free);
153
		} else {
154
			// drop excess samples
155
			buffer = buffer.subspan(0, free);
156
		}
157
	}
158
	assert(buffer.size() <= free);
159
	if ((writeIdx + buffer.size()) < mixBuffer.size()) {
160
		copy_to_range(buffer, mixBuffer.subspan(writeIdx));
161
		writeIdx += narrow<unsigned>(buffer.size());
162
	} else {
163
		auto len1 = mixBuffer.size() - writeIdx;
164
		copy_to_range(buffer.subspan(0, len1), mixBuffer.subspan(writeIdx));
165
		auto len2 = buffer.size() - len1;
166
		copy_to_range(buffer.subspan(len1, len2), std::span{mixBuffer});
167
		writeIdx = narrow<unsigned>(len2);
168
	}
169
170
	SDL_UnlockAudioDevice(deviceID);
171
}
172
173
} // namespace openmsx