openMSX
SDLSoundDriver.cc
Go to the documentation of this file.
1#include "SDLSoundDriver.hh"
2
3#include "Mixer.hh"
4#include "Reactor.hh"
5#include "MSXMixer.hh"
6#include "MSXMotherBoard.hh"
7#include "RealTime.hh"
8#include "GlobalSettings.hh"
9#include "ThrottleManager.hh"
10#include "MSXException.hh"
11#include "Timer.hh"
12
13#include "narrow.hh"
14
15#include <algorithm>
16#include <bit>
17#include <cassert>
18
19namespace openmsx {
20
22 unsigned wantedFreq, unsigned wantedSamples)
23 : reactor(reactor_)
24{
25 SDL_AudioSpec desired;
26 desired.freq = narrow<int>(wantedFreq);
27 desired.samples = narrow<Uint16>(std::bit_ceil(wantedSamples));
28 desired.channels = 2; // stereo
29 desired.format = AUDIO_F32SYS;
30 desired.callback = audioCallbackHelper; // must be a static method
31 desired.userdata = this;
32
33 SDL_AudioSpec obtained;
34 deviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &desired, &obtained,
35 SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
36 if (!deviceID) {
37 throw MSXException("Unable to open SDL audio: ", SDL_GetError());
38 }
39
40 frequency = obtained.freq;
41 fragmentSize = obtained.samples;
42
43 mixBufferSize = narrow<unsigned>(3 * (obtained.size / sizeof(StereoFloat)) + 1);
44 mixBuffer.resize(mixBufferSize);
45 reInit();
46}
47
49{
50 SDL_CloseAudioDevice(deviceID);
51}
52
53void SDLSoundDriver::reInit()
54{
55 SDL_LockAudioDevice(deviceID);
56 readIdx = 0;
57 writeIdx = 0;
58 SDL_UnlockAudioDevice(deviceID);
59}
60
62{
63 if (!muted) {
64 muted = true;
65 SDL_PauseAudioDevice(deviceID, 1);
66 }
67}
68
70{
71 if (muted) {
72 muted = false;
73 reInit();
74 SDL_PauseAudioDevice(deviceID, 0);
75 }
76}
77
79{
80 return frequency;
81}
82
84{
85 return fragmentSize;
86}
87
88void 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
96unsigned SDLSoundDriver::getBufferFilled() const
97{
98 int result = narrow_cast<int>(writeIdx - readIdx);
99 if (result < 0) result += narrow<int>(mixBufferSize);
100 assert((0 <= result) && (narrow<unsigned>(result) < mixBufferSize));
101 return result;
102}
103
104unsigned 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 unsigned result = mixBufferSize - 1 - getBufferFilled();
110 assert(narrow_cast<int>(result) >= 0);
111 assert(result < mixBufferSize);
112 return result;
113}
114
115void SDLSoundDriver::audioCallback(std::span<StereoFloat> stream)
116{
117 auto len = stream.size();
118
119 size_t available = getBufferFilled();
120 auto num = narrow<unsigned>(std::min(len, available));
121 if ((readIdx + num) < mixBufferSize) {
122 ranges::copy(std::span{&mixBuffer[readIdx], num}, stream);
123 readIdx += num;
124 } else {
125 unsigned len1 = mixBufferSize - readIdx;
126 ranges::copy(std::span{&mixBuffer[readIdx], len1}, stream);
127 unsigned len2 = num - len1;
128 ranges::copy(std::span{&mixBuffer[0], len2}, stream.subspan(len1));
129 readIdx = len2;
130 }
131 auto missing = narrow_cast<ptrdiff_t>(len - available);
132 if (missing > 0) {
133 // buffer underrun
134 ranges::fill(subspan(stream, available, missing), StereoFloat{});
135 }
136}
137
138void 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
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()) < mixBufferSize) {
160 ranges::copy(buffer, &mixBuffer[writeIdx]);
161 writeIdx += narrow<unsigned>(buffer.size());
162 } else {
163 unsigned len1 = mixBufferSize - writeIdx;
164 ranges::copy(buffer.subspan(0, len1), &mixBuffer[writeIdx]);
165 unsigned len2 = narrow<unsigned>(buffer.size()) - len1;
166 ranges::copy(buffer.subspan(len1, len2), &mixBuffer[0]);
167 writeIdx = len2;
168 }
169
170 SDL_UnlockAudioDevice(deviceID);
171}
172
173} // namespace openmsx
ThrottleManager & getThrottleManager()
Contains the main loop of openMSX.
Definition Reactor.hh:74
GlobalSettings & getGlobalSettings()
Definition Reactor.hh:116
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:409
void uploadBuffer(std::span< const StereoFloat > buffer) override
void unmute() override
Unmute the sound system.
void mute() override
Mute the sound system.
unsigned getFrequency() const override
Returns the actual sample frequency.
SDLSoundDriver(Reactor &reactor, unsigned wantedFreq, unsigned samples)
unsigned getSamples() const override
Get the number of samples that should be created 'per fragment'.
bool isThrottled() const
Ask if throttling is enabled.
void sleep(uint64_t us)
Sleep for the specified amount of time (in us).
Definition Timer.cc:27
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:250
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:471