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