openMSX
SDLSoundDriver.cc
Go to the documentation of this file.
1 #include "SDLSoundDriver.hh"
2 #include "Reactor.hh"
3 #include "MSXMotherBoard.hh"
4 #include "RealTime.hh"
5 #include "GlobalSettings.hh"
6 #include "ThrottleManager.hh"
7 #include "MSXException.hh"
8 #include "Math.hh"
9 #include "Timer.hh"
10 #include <algorithm>
11 #include <cassert>
12 #include <cstring>
13 
14 namespace openmsx {
15 
17  unsigned wantedFreq, unsigned wantedSamples)
18  : reactor(reactor_)
19  , muted(true)
20 {
21  SDL_AudioSpec desired;
22  desired.freq = wantedFreq;
23  desired.samples = Math::ceil2(wantedSamples);
24  desired.channels = 2; // stereo
25  desired.format = AUDIO_F32SYS;
26  desired.callback = audioCallbackHelper; // must be a static method
27  desired.userdata = this;
28 
29  SDL_AudioSpec obtained;
30  deviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &desired, &obtained,
31  SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
32  if (!deviceID) {
33  throw MSXException("Unable to open SDL audio: ", SDL_GetError());
34  }
35 
36  frequency = obtained.freq;
37  fragmentSize = obtained.samples;
38 
39  mixBufferSize = 3 * (obtained.size / sizeof(float)) + 2;
40  mixBuffer.resize(mixBufferSize);
41  reInit();
42 }
43 
45 {
46  SDL_CloseAudioDevice(deviceID);
47 }
48 
49 void SDLSoundDriver::reInit()
50 {
51  SDL_LockAudioDevice(deviceID);
52  readIdx = 0;
53  writeIdx = 0;
54  SDL_UnlockAudioDevice(deviceID);
55 }
56 
58 {
59  if (!muted) {
60  muted = true;
61  SDL_PauseAudioDevice(deviceID, 1);
62  }
63 }
64 
66 {
67  if (muted) {
68  muted = false;
69  reInit();
70  SDL_PauseAudioDevice(deviceID, 0);
71  }
72 }
73 
75 {
76  return frequency;
77 }
78 
80 {
81  return fragmentSize;
82 }
83 
84 void SDLSoundDriver::audioCallbackHelper(void* userdata, uint8_t* strm, int len)
85 {
86  assert((len & 7) == 0); // stereo, 32 bit float
87  static_cast<SDLSoundDriver*>(userdata)->
88  audioCallback(reinterpret_cast<float*>(strm), len / sizeof(float));
89 }
90 
91 unsigned SDLSoundDriver::getBufferFilled() const
92 {
93  int result = writeIdx - readIdx;
94  if (result < 0) result += mixBufferSize;
95  assert((0 <= result) && (unsigned(result) < mixBufferSize));
96  return result;
97 }
98 
99 unsigned SDLSoundDriver::getBufferFree() const
100 {
101  // we can't distinguish completely filled from completely empty
102  // (in both cases readIx would be equal to writeIdx), so instead
103  // we define full as '(writeIdx + 2) == readIdx' (note that index
104  // increases in steps of 2 (stereo)).
105  int result = mixBufferSize - 2 - getBufferFilled();
106  assert((0 <= result) && (unsigned(result) < mixBufferSize));
107  return result;
108 }
109 
110 void SDLSoundDriver::audioCallback(float* stream, unsigned len)
111 {
112  assert((len & 1) == 0); // stereo
113  unsigned available = getBufferFilled();
114  unsigned num = std::min(len, available);
115  if ((readIdx + num) < mixBufferSize) {
116  memcpy(stream, &mixBuffer[readIdx], num * sizeof(float));
117  readIdx += num;
118  } else {
119  unsigned len1 = mixBufferSize - readIdx;
120  memcpy(stream, &mixBuffer[readIdx], len1 * sizeof(float));
121  unsigned len2 = num - len1;
122  memcpy(&stream[len1], &mixBuffer[0], len2 * sizeof(float));
123  readIdx = len2;
124  }
125  int missing = len - available;
126  if (missing > 0) {
127  // buffer underrun
128  memset(&stream[available], 0, missing * sizeof(float));
129  }
130 }
131 
132 void SDLSoundDriver::uploadBuffer(float* buffer, unsigned len)
133 {
134  SDL_LockAudioDevice(deviceID);
135  len *= 2; // stereo
136  unsigned free = getBufferFree();
137  if (len > free) {
139  do {
140  SDL_UnlockAudioDevice(deviceID);
141  Timer::sleep(5000); // 5ms
142  SDL_LockAudioDevice(deviceID);
143  if (MSXMotherBoard* board = reactor.getMotherBoard()) {
144  board->getRealTime().resync();
145  }
146  free = getBufferFree();
147  } while (len > free);
148  } else {
149  // drop excess samples
150  len = free;
151  }
152  }
153  assert(len <= free);
154  if ((writeIdx + len) < mixBufferSize) {
155  memcpy(&mixBuffer[writeIdx], buffer, len * sizeof(float));
156  writeIdx += len;
157  } else {
158  unsigned len1 = mixBufferSize - writeIdx;
159  memcpy(&mixBuffer[writeIdx], buffer, len1 * sizeof(float));
160  unsigned len2 = len - len1;
161  memcpy(&mixBuffer[0], &buffer[len1], len2 * sizeof(float));
162  writeIdx = len2;
163  }
164 
165  SDL_UnlockAudioDevice(deviceID);
166 }
167 
168 } // namespace openmsx
ThrottleManager & getThrottleManager()
Contains the main loop of openMSX.
Definition: Reactor.hh:66
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
void mute() override
Mute the sound system.
void unmute() override
Unmute the sound system.
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:368
bool isThrottled() const
Ask if throttling is enabled.
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:103
unsigned getSamples() const override
Get the number of samples that should be created &#39;per fragment&#39;.
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:120
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void uploadBuffer(float *buffer, unsigned len) override
void sleep(uint64_t us)
Sleep for the specified amount of time (in us).
Definition: Timer.cc:28
unsigned getFrequency() const override
Returns the actual sample frequency.
SDLSoundDriver(const SDLSoundDriver &)=delete
constexpr T ceil2(T x) noexcept
Returns the smallest number that is both >=a and a power of two.
Definition: Math.hh:84