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-2019 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
42SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings)
43 : Sound(osystem),
44 myIsInitializedFlag(false),
45 myVolume(100),
46 myVolumeFactor(0xffff),
47 myDevice(0),
48 myEmulationTiming(nullptr),
49 myCurrentFragment(nullptr),
50 myUnderrun(false),
51 myAudioSettings(audioSettings)
52{
53 ASSERT_MAIN_THREAD;
54
55 Logger::debug("SoundSDL2::SoundSDL2 started ...");
56
57 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
58 ostringstream buf;
59
60 buf << "WARNING: Failed to initialize SDL audio system! " << endl
61 << " " << SDL_GetError() << endl;
62 Logger::error(buf.str());
63 return;
64 }
65
66 SDL_zero(myHardwareSpec);
67 if(!openDevice())
68 return;
69
70 mute(true);
71
72 Logger::debug("SoundSDL2::SoundSDL2 initialized");
73}
74
75// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
76SoundSDL2::~SoundSDL2()
77{
78 ASSERT_MAIN_THREAD;
79
80 if (!myIsInitializedFlag) return;
81
82 SDL_CloseAudioDevice(myDevice);
83 SDL_QuitSubSystem(SDL_INIT_AUDIO);
84}
85
86// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87bool SoundSDL2::openDevice()
88{
89 ASSERT_MAIN_THREAD;
90
91 SDL_AudioSpec desired;
92 desired.freq = myAudioSettings.sampleRate();
93 desired.format = AUDIO_F32SYS;
94 desired.channels = 2;
95 desired.samples = static_cast<Uint16>(myAudioSettings.fragmentSize());
96 desired.callback = callback;
97 desired.userdata = static_cast<void*>(this);
98
99 if(myIsInitializedFlag)
100 SDL_CloseAudioDevice(myDevice);
101 myDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &myHardwareSpec,
102 SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
103
104 if(myDevice == 0)
105 {
106 ostringstream buf;
107
108 buf << "WARNING: Couldn't open SDL audio device! " << endl
109 << " " << SDL_GetError() << endl;
110 Logger::error(buf.str());
111
112 return myIsInitializedFlag = false;
113 }
114 return myIsInitializedFlag = true;
115}
116
117// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
118void SoundSDL2::setEnabled(bool state)
119{
120 myAudioSettings.setEnabled(state);
121 if (myAudioQueue) myAudioQueue->ignoreOverflows(!state);
122
123 Logger::debug(state ? "SoundSDL2::setEnabled(true)" :
124 "SoundSDL2::setEnabled(false)");
125}
126
127// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
128void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
129 EmulationTiming* emulationTiming)
130{
131 string pre_about = myAboutString;
132
133 // Do we need to re-open the sound device?
134 // Only do this when absolutely necessary
135 if(myAudioSettings.sampleRate() != uInt32(myHardwareSpec.freq) ||
136 myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples))
137 openDevice();
138
139 myEmulationTiming = emulationTiming;
140
141 Logger::debug("SoundSDL2::open started ...");
142 mute(true);
143
144 audioQueue->ignoreOverflows(!myAudioSettings.enabled());
145 if(!myAudioSettings.enabled())
146 {
147 Logger::info("Sound disabled\n");
148 return;
149 }
150
151 myAudioQueue = audioQueue;
152 myUnderrun = true;
153 myCurrentFragment = nullptr;
154
155 // Adjust volume to that defined in settings
156 setVolume(myAudioSettings.volume());
157
158 initResampler();
159
160 // Show some info
161 myAboutString = about();
162 if(myAboutString != pre_about)
163 Logger::info(myAboutString);
164
165 // And start the SDL sound subsystem ...
166 mute(false);
167
168 Logger::debug("SoundSDL2::open finished");
169}
170
171// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
172void SoundSDL2::close()
173{
174 if(!myIsInitializedFlag) return;
175
176 mute(true);
177
178 if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment);
179 myAudioQueue.reset();
180 myCurrentFragment = nullptr;
181}
182
183// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
184bool SoundSDL2::mute(bool state)
185{
186 bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED;
187 if(myIsInitializedFlag)
188 SDL_PauseAudioDevice(myDevice, state ? 1 : 0);
189
190 return oldstate;
191}
192
193// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
194bool SoundSDL2::toggleMute()
195{
196 bool enabled = myAudioSettings.enabled();
197
198 setEnabled(!enabled);
199 myOSystem.console().initializeAudio();
200
201 string message = "Sound ";
202 message += !enabled ? "unmuted" : "muted";
203
204 myOSystem.frameBuffer().showMessage(message);
205
206 return enabled;
207}
208
209// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
210void SoundSDL2::setVolume(uInt32 percent)
211{
212 if(myIsInitializedFlag && (percent <= 100))
213 {
214 myAudioSettings.setVolume(percent);
215 myVolume = percent;
216
217 SDL_LockAudioDevice(myDevice);
218 myVolumeFactor = static_cast<float>(percent) / 100.f;
219 SDL_UnlockAudioDevice(myDevice);
220 }
221}
222
223// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
224void SoundSDL2::adjustVolume(Int8 direction)
225{
226 ostringstream strval;
227 string message;
228
229 Int32 percent = myVolume;
230
231 if(direction == -1)
232 percent -= 2;
233 else if(direction == 1)
234 percent += 2;
235
236 if((percent < 0) || (percent > 100))
237 return;
238
239 setVolume(percent);
240
241 // enabled audio if it is currently disabled
242 bool enabled = myAudioSettings.enabled();
243
244 if (percent > 0 && !enabled)
245 {
246 setEnabled(!enabled);
247 myOSystem.console().initializeAudio();
248 }
249
250 // Now show an onscreen message
251 strval << percent;
252 message = "Volume set to ";
253 message += strval.str();
254
255 myOSystem.frameBuffer().showMessage(message);
256}
257
258// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
259string SoundSDL2::about() const
260{
261 ostringstream buf;
262 buf << "Sound enabled:" << endl
263 << " Volume: " << myVolume << "%" << endl
264 << " Channels: " << uInt32(myHardwareSpec.channels)
265 << (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl
266 << " Preset: ";
267 switch (myAudioSettings.preset()) {
268 case AudioSettings::Preset::custom:
269 buf << "Custom" << endl;
270 break;
271 case AudioSettings::Preset::lowQualityMediumLag:
272 buf << "Low quality, medium lag" << endl;
273 break;
274 case AudioSettings::Preset::highQualityMediumLag:
275 buf << "High quality, medium lag" << endl;
276 break;
277 case AudioSettings::Preset::highQualityLowLag:
278 buf << "High quality, low lag" << endl;
279 break;
280 case AudioSettings::Preset::ultraQualityMinimalLag:
281 buf << "Ultra quality, minimal lag" << endl;
282 break;
283 }
284 buf << " Fragment size: " << uInt32(myHardwareSpec.samples) << " bytes" << endl
285 << " Sample rate: " << uInt32(myHardwareSpec.freq) << " Hz" << endl;
286 buf << " Resampling: ";
287 switch(myAudioSettings.resamplingQuality())
288 {
289 case AudioSettings::ResamplingQuality::nearestNeightbour:
290 buf << "Quality 1, nearest neighbor" << endl;
291 break;
292 case AudioSettings::ResamplingQuality::lanczos_2:
293 buf << "Quality 2, Lanczos (a = 2)" << endl;
294 break;
295 case AudioSettings::ResamplingQuality::lanczos_3:
296 buf << "Quality 3, Lanczos (a = 3)" << endl;
297 break;
298 }
299 buf << " Headroom: " << std::fixed << std::setprecision(1)
300 << (0.5 * myAudioSettings.headroom()) << " frames" << endl
301 << " Buffer size: " << std::fixed << std::setprecision(1)
302 << (0.5 * myAudioSettings.bufferSize()) << " frames" << endl;
303 return buf.str();
304}
305
306// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
307void SoundSDL2::processFragment(float* stream, uInt32 length)
308{
309 myResampler->fillFragment(stream, length);
310
311 for (uInt32 i = 0; i < length; i++) stream[i] = stream[i] * myVolumeFactor;
312}
313
314// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
315void SoundSDL2::initResampler()
316{
317 Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* {
318 Int16* nextFragment = nullptr;
319
320 if (myUnderrun)
321 nextFragment = myAudioQueue->size() >= myEmulationTiming->prebufferFragmentCount() ?
322 myAudioQueue->dequeue(myCurrentFragment) : nullptr;
323 else
324 nextFragment = myAudioQueue->dequeue(myCurrentFragment);
325
326 myUnderrun = nextFragment == nullptr;
327 if (nextFragment) myCurrentFragment = nextFragment;
328
329 return nextFragment;
330 };
331
332 Resampler::Format formatFrom =
333 Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo());
334 Resampler::Format formatTo =
335 Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1);
336
337 switch (myAudioSettings.resamplingQuality()) {
338 case AudioSettings::ResamplingQuality::nearestNeightbour:
339 myResampler = make_unique<SimpleResampler>(formatFrom, formatTo, nextFragmentCallback);
340 break;
341
342 case AudioSettings::ResamplingQuality::lanczos_2:
343 myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 2);
344 break;
345
346 case AudioSettings::ResamplingQuality::lanczos_3:
347 myResampler = make_unique<LanczosResampler>(formatFrom, formatTo, nextFragmentCallback, 3);
348 break;
349
350 default:
351 throw runtime_error("invalid resampling quality");
352 }
353}
354
355// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
356void SoundSDL2::callback(void* udata, uInt8* stream, int len)
357{
358 SoundSDL2* self = static_cast<SoundSDL2*>(udata);
359
360 if (self->myAudioQueue)
361 self->processFragment(reinterpret_cast<float*>(stream), len >> 2);
362 else
363 SDL_memset(stream, 0, len);
364}
365
366#endif // SOUND_SUPPORT
367