1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #ifdef SOUND_SUPPORT
19 
20 #include <sstream>
21 #include <cassert>
22 #include <cmath>
23 
24 #include "SDL_lib.hxx"
25 #include "Logger.hxx"
26 #include "FrameBuffer.hxx"
27 #include "Settings.hxx"
28 #include "System.hxx"
29 #include "OSystem.hxx"
30 #include "Console.hxx"
31 #include "SoundSDL2.hxx"
32 #include "AudioQueue.hxx"
33 #include "EmulationTiming.hxx"
34 #include "AudioSettings.hxx"
35 #include "audio/SimpleResampler.hxx"
36 #include "audio/LanczosResampler.hxx"
37 #include "StaggeredLogger.hxx"
38 
39 #include "ThreadDebugging.hxx"
40 
41 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2(OSystem & osystem,AudioSettings & audioSettings)42 SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings)
43   : Sound{osystem},
44     myAudioSettings{audioSettings}
45 {
46   ASSERT_MAIN_THREAD;
47 
48   Logger::debug("SoundSDL2::SoundSDL2 started ...");
49 
50   if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
51     ostringstream buf;
52 
53     buf << "WARNING: Failed to initialize SDL audio system! " << endl
54         << "         " << SDL_GetError() << endl;
55     Logger::error(buf.str());
56     return;
57   }
58 
59   queryHardware(myDevices);
60 
61   SDL_zero(myHardwareSpec);
62   if(!openDevice())
63     return;
64 
65   SoundSDL2::mute(true);
66 
67   Logger::debug("SoundSDL2::SoundSDL2 initialized");
68 }
69 
70 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
~SoundSDL2()71 SoundSDL2::~SoundSDL2()
72 {
73   ASSERT_MAIN_THREAD;
74 
75   if (!myIsInitializedFlag) return;
76 
77   SDL_CloseAudioDevice(myDevice);
78   SDL_QuitSubSystem(SDL_INIT_AUDIO);
79 }
80 
81 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
queryHardware(VariantList & devices)82 void SoundSDL2::queryHardware(VariantList& devices)
83 {
84   ASSERT_MAIN_THREAD;
85 
86   int numDevices = SDL_GetNumAudioDevices(0);
87 
88   // log the available audio devices
89   ostringstream s;
90   s << "Supported audio devices (" << numDevices << "):";
91   Logger::debug(s.str());
92 
93   VarList::push_back(devices, "Default", 0);
94   for(int i = 0; i < numDevices; ++i) {
95     ostringstream ss;
96 
97     ss << "  " << i + 1 << ": " << SDL_GetAudioDeviceName(i, 0);
98     Logger::debug(ss.str());
99 
100     VarList::push_back(devices, SDL_GetAudioDeviceName(i, 0), i + 1);
101   }
102 }
103 
104 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
openDevice()105 bool SoundSDL2::openDevice()
106 {
107   ASSERT_MAIN_THREAD;
108 
109   SDL_AudioSpec desired;
110   desired.freq   = myAudioSettings.sampleRate();
111   desired.format = AUDIO_F32SYS;
112   desired.channels = 2;
113   desired.samples  = static_cast<Uint16>(myAudioSettings.fragmentSize());
114   desired.callback = callback;
115   desired.userdata = static_cast<void*>(this);
116 
117   if(myIsInitializedFlag)
118     SDL_CloseAudioDevice(myDevice);
119 
120   myDeviceId = BSPF::clamp(myAudioSettings.device(), 0U, uInt32(myDevices.size() - 1));
121   const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr;
122 
123   myDevice = SDL_OpenAudioDevice(device, 0, &desired, &myHardwareSpec,
124                                  SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
125 
126   if(myDevice == 0)
127   {
128     ostringstream buf;
129 
130     buf << "WARNING: Couldn't open SDL audio device! " << endl
131         << "         " << SDL_GetError() << endl;
132     Logger::error(buf.str());
133 
134     return myIsInitializedFlag = false;
135   }
136   return myIsInitializedFlag = true;
137 }
138 
139 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setEnabled(bool state)140 void SoundSDL2::setEnabled(bool state)
141 {
142   myAudioSettings.setEnabled(state);
143   if (myAudioQueue) myAudioQueue->ignoreOverflows(!state);
144 
145   Logger::debug(state ? "SoundSDL2::setEnabled(true)" :
146                 "SoundSDL2::setEnabled(false)");
147 }
148 
149 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open(shared_ptr<AudioQueue> audioQueue,EmulationTiming * emulationTiming)150 void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
151                      EmulationTiming* emulationTiming)
152 {
153   string pre_about = myAboutString;
154 
155   // Do we need to re-open the sound device?
156   // Only do this when absolutely necessary
157   if(myAudioSettings.sampleRate() != uInt32(myHardwareSpec.freq) ||
158      myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples) ||
159      myAudioSettings.device() != myDeviceId)
160     openDevice();
161 
162   myEmulationTiming = emulationTiming;
163 
164   Logger::debug("SoundSDL2::open started ...");
165   mute(true);
166 
167   audioQueue->ignoreOverflows(!myAudioSettings.enabled());
168   if(!myAudioSettings.enabled())
169   {
170     Logger::info("Sound disabled\n");
171     return;
172   }
173 
174   myAudioQueue = audioQueue;
175   myUnderrun = true;
176   myCurrentFragment = nullptr;
177 
178   // Adjust volume to that defined in settings
179   setVolume(myAudioSettings.volume());
180 
181   initResampler();
182 
183   // Show some info
184   myAboutString = about();
185   if(myAboutString != pre_about)
186     Logger::info(myAboutString);
187 
188   // And start the SDL sound subsystem ...
189   mute(false);
190 
191   Logger::debug("SoundSDL2::open finished");
192 }
193 
194 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
close()195 void SoundSDL2::close()
196 {
197   if(!myIsInitializedFlag) return;
198 
199   mute(true);
200 
201   if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment);
202   myAudioQueue.reset();
203   myCurrentFragment = nullptr;
204 }
205 
206 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
mute(bool state)207 bool SoundSDL2::mute(bool state)
208 {
209   bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED;
210   if(myIsInitializedFlag)
211     SDL_PauseAudioDevice(myDevice, state ? 1 : 0);
212 
213   return oldstate;
214 }
215 
216 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleMute()217 bool SoundSDL2::toggleMute()
218 {
219   bool enabled = !myAudioSettings.enabled();
220 
221   setEnabled(enabled);
222   myOSystem.console().initializeAudio();
223 
224   string message = "Sound ";
225   message += enabled ? "unmuted" : "muted";
226 
227   myOSystem.frameBuffer().showTextMessage(message);
228 
229   //ostringstream strval;
230   //uInt32 volume;
231   //// Now show an onscreen message
232   //if(enabled)
233   //{
234   //  volume = myVolume;
235   //  strval << volume << "%";
236   //}
237   //else
238   //{
239   //  volume = 0;
240   //  strval << "Muted";
241   //}
242   //myOSystem.frameBuffer().showMessage("Volume", strval.str(), volume);
243 
244   return enabled;
245 }
246 
247 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setVolume(uInt32 percent)248 void SoundSDL2::setVolume(uInt32 percent)
249 {
250   if(myIsInitializedFlag && (percent <= 100))
251   {
252     myAudioSettings.setVolume(percent);
253     myVolume = percent;
254 
255     SDL_LockAudioDevice(myDevice);
256     myVolumeFactor = static_cast<float>(percent) / 100.F;
257     SDL_UnlockAudioDevice(myDevice);
258   }
259 }
260 
261 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
adjustVolume(int direction)262 void SoundSDL2::adjustVolume(int direction)
263 {
264   ostringstream strval;
265   Int32 percent = myVolume;
266 
267   percent = BSPF::clamp(percent + direction * 2, 0, 100);
268 
269   setVolume(percent);
270 
271   // Enable audio if it is currently disabled
272   bool enabled = myAudioSettings.enabled();
273 
274   if(percent > 0 && !enabled)
275   {
276     setEnabled(true);
277     myOSystem.console().initializeAudio();
278   }
279 
280   // Now show an onscreen message
281   if(percent)
282     strval << percent << "%";
283   else
284     strval << "Off";
285   myOSystem.frameBuffer().showGaugeMessage("Volume", strval.str(), percent);
286 }
287 
288 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
about() const289 string SoundSDL2::about() const
290 {
291   ostringstream buf;
292   buf << "Sound enabled:"  << endl
293       << "  Volume:   " << myVolume << "%" << endl
294       << "  Device:   " << myDevices.at(myDeviceId).first << endl
295       << "  Channels: " << uInt32(myHardwareSpec.channels)
296       << (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl
297       << "  Preset:   ";
298   switch (myAudioSettings.preset()) {
299     case AudioSettings::Preset::custom:
300       buf << "Custom" << endl;
301       break;
302     case AudioSettings::Preset::lowQualityMediumLag:
303       buf << "Low quality, medium lag" << endl;
304       break;
305     case AudioSettings::Preset::highQualityMediumLag:
306       buf << "High quality, medium lag" << endl;
307       break;
308     case AudioSettings::Preset::highQualityLowLag:
309       buf << "High quality, low lag" << endl;
310       break;
311     case AudioSettings::Preset::ultraQualityMinimalLag:
312       buf << "Ultra quality, minimal lag" << endl;
313       break;
314   }
315   buf << "    Fragment size: " << uInt32(myHardwareSpec.samples) << " bytes" << endl
316       << "    Sample rate:   " << uInt32(myHardwareSpec.freq) << " Hz" << endl;
317   buf << "    Resampling:    ";
318   switch(myAudioSettings.resamplingQuality())
319   {
320     case AudioSettings::ResamplingQuality::nearestNeightbour:
321       buf << "Quality 1, nearest neighbor" << endl;
322       break;
323     case AudioSettings::ResamplingQuality::lanczos_2:
324       buf << "Quality 2, Lanczos (a = 2)" << endl;
325       break;
326     case AudioSettings::ResamplingQuality::lanczos_3:
327       buf << "Quality 3, Lanczos (a = 3)" << endl;
328       break;
329   }
330   buf << "    Headroom:      " << std::fixed << std::setprecision(1)
331       << (0.5 * myAudioSettings.headroom()) << " frames" << endl
332       << "    Buffer size:   " << std::fixed << std::setprecision(1)
333       << (0.5 * myAudioSettings.bufferSize()) << " frames" << endl;
334   return buf.str();
335 }
336 
337 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
processFragment(float * stream,uInt32 length)338 void SoundSDL2::processFragment(float* stream, uInt32 length)
339 {
340   myResampler->fillFragment(stream, length);
341 
342   for (uInt32 i = 0; i < length; ++i)
343     stream[i] *= myVolumeFactor;
344 }
345 
346 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
initResampler()347 void SoundSDL2::initResampler()
348 {
349   Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
350     Int16* nextFragment = nullptr;
351 
352     if (myUnderrun)
353       nextFragment = myAudioQueue->size() >= myEmulationTiming->prebufferFragmentCount() ?
354           myAudioQueue->dequeue(myCurrentFragment) : nullptr;
355     else
356       nextFragment = myAudioQueue->dequeue(myCurrentFragment);
357 
358     myUnderrun = nextFragment == nullptr;
359     if (nextFragment) myCurrentFragment = nextFragment;
360 
361     return nextFragment;
362   };
363 
364   Resampler::Format formatFrom =
365     Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo());
366   Resampler::Format formatTo =
367     Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1);
368 
369   switch (myAudioSettings.resamplingQuality()) {
370     case AudioSettings::ResamplingQuality::nearestNeightbour:
371       myResampler = make_unique<SimpleResampler>(formatFrom, formatTo, nextFragmentCallback);
372       break;
373 
374     case AudioSettings::ResamplingQuality::lanczos_2:
375       myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 2);
376       break;
377 
378     case AudioSettings::ResamplingQuality::lanczos_3:
379       myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 3);
380       break;
381 
382     default:
383       throw runtime_error("invalid resampling quality");
384   }
385 }
386 
387 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
callback(void * udata,uInt8 * stream,int len)388 void SoundSDL2::callback(void* udata, uInt8* stream, int len)
389 {
390   SoundSDL2* self = static_cast<SoundSDL2*>(udata);
391 
392   if (self->myAudioQueue)
393     self->processFragment(reinterpret_cast<float*>(stream), len >> 2);
394   else
395     SDL_memset(stream, 0, len);
396 }
397 
398 #endif  // SOUND_SUPPORT
399