openMSX
SDLSoundDriver.cc
Go to the documentation of this file.
1 #include "SDLSoundDriver.hh"
2 #include "Reactor.hh"
3 #include "MSXMixer.hh"
4 #include "MSXMotherBoard.hh"
5 #include "RealTime.hh"
6 #include "GlobalSettings.hh"
7 #include "ThrottleManager.hh"
8 #include "MSXException.hh"
9 #include "Math.hh"
10 #include "Timer.hh"
11 #include <algorithm>
12 #include <cassert>
13 #include <cstring>
14 
15 namespace openmsx {
16 
18  unsigned wantedFreq, unsigned wantedSamples)
19  : reactor(reactor_)
20  , muted(true)
21 {
22  SDL_AudioSpec desired;
23  desired.freq = wantedFreq;
24  desired.samples = Math::ceil2(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 = 3 * (obtained.size / sizeof(float)) + 2;
41  mixBuffer.resize(mixBufferSize);
42  reInit();
43 }
44 
46 {
47  SDL_CloseAudioDevice(deviceID);
48 }
49 
50 void 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 
85 void 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(reinterpret_cast<float*>(strm), len / sizeof(float));
90 }
91 
92 unsigned SDLSoundDriver::getBufferFilled() const
93 {
94  int result = writeIdx - readIdx;
95  if (result < 0) result += mixBufferSize;
96  assert((0 <= result) && (unsigned(result) < mixBufferSize));
97  return result;
98 }
99 
100 unsigned SDLSoundDriver::getBufferFree() const
101 {
102  // we can't distinguish completely filled from completely empty
103  // (in both cases readIx would be equal to writeIdx), so instead
104  // we define full as '(writeIdx + 2) == readIdx' (note that index
105  // increases in steps of 2 (stereo)).
106  int result = mixBufferSize - 2 - getBufferFilled();
107  assert((0 <= result) && (unsigned(result) < mixBufferSize));
108  return result;
109 }
110 
111 void SDLSoundDriver::audioCallback(float* stream, unsigned len)
112 {
113  assert((len & 1) == 0); // stereo
114  unsigned available = getBufferFilled();
115  unsigned num = std::min(len, available);
116  if ((readIdx + num) < mixBufferSize) {
117  memcpy(stream, &mixBuffer[readIdx], num * sizeof(float));
118  readIdx += num;
119  } else {
120  unsigned len1 = mixBufferSize - readIdx;
121  memcpy(stream, &mixBuffer[readIdx], len1 * sizeof(float));
122  unsigned len2 = num - len1;
123  memcpy(&stream[len1], &mixBuffer[0], len2 * sizeof(float));
124  readIdx = len2;
125  }
126  int missing = len - available;
127  if (missing > 0) {
128  // buffer underrun
129  memset(&stream[available], 0, missing * sizeof(float));
130  }
131 }
132 
133 void SDLSoundDriver::uploadBuffer(float* buffer, unsigned len)
134 {
135  SDL_LockAudioDevice(deviceID);
136  len *= 2; // stereo
137  unsigned free = getBufferFree();
138  if (len > free) {
139  auto* board = reactor.getMotherBoard();
140  if (board && !board->getMSXMixer().isSynchronousMode() && // when not recording
142  do {
143  SDL_UnlockAudioDevice(deviceID);
144  Timer::sleep(5000); // 5ms
145  SDL_LockAudioDevice(deviceID);
146  board->getRealTime().resync();
147  free = getBufferFree();
148  } while (len > free);
149  } else {
150  // drop excess samples
151  len = free;
152  }
153  }
154  assert(len <= free);
155  if ((writeIdx + len) < mixBufferSize) {
156  memcpy(&mixBuffer[writeIdx], buffer, len * sizeof(float));
157  writeIdx += len;
158  } else {
159  unsigned len1 = mixBufferSize - writeIdx;
160  memcpy(&mixBuffer[writeIdx], buffer, len1 * sizeof(float));
161  unsigned len2 = len - len1;
162  memcpy(&mixBuffer[0], &buffer[len1], len2 * sizeof(float));
163  writeIdx = len2;
164  }
165 
166  SDL_UnlockAudioDevice(deviceID);
167 }
168 
169 } // namespace openmsx
ThrottleManager & getThrottleManager()
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
Contains the main loop of openMSX.
Definition: Reactor.hh:68
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:374
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:104
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
void uploadBuffer(float *buffer, unsigned len) override
unsigned getSamples() const override
Get the number of samples that should be created 'per fragment'.
bool isThrottled() const
Ask if throttling is enabled.
constexpr T ceil2(T x) noexcept
Returns the smallest number that is both >=a and a power of two.
Definition: Math.hh:85
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
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