If initsram/initstate points to LSS file, pull the matching member
[lsnes.git] / src / platform / portaudio / sound.cpp
blobbcc6871cabdd7714a6347cf3f3b3f08554eafeb9
1 #include "lsnes.hpp"
3 #include "core/audioapi-driver.hpp"
4 #include "core/command.hpp"
5 #include "core/dispatch.hpp"
6 #include "core/framerate.hpp"
7 #include "core/instance.hpp"
8 #include "core/messages.hpp"
9 #include "core/framerate.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/settings.hpp"
12 #include "core/window.hpp"
13 #include "library/framebuffer.hpp"
15 #include <cstring>
16 #include <cstdlib>
17 #include <vector>
18 #include <iostream>
19 #include <csignal>
20 #include <sstream>
21 #include <fstream>
22 #include <cassert>
23 #include "core/audioapi.hpp"
24 #include "library/minmax.hpp"
25 #include <string>
26 #include <map>
27 #include <stdexcept>
28 #include <portaudio.h>
29 #include <boost/lexical_cast.hpp>
31 namespace
33 //Input stream info.
34 uint32_t current_rfreq = 0;
35 PaDeviceIndex current_rdev = paNoDevice;
36 bool flag_rstereo = false;
37 PaStream* stream_r = NULL;
38 //Output stream info.
39 uint32_t current_pfreq = 0;
40 PaDeviceIndex current_pdev = paNoDevice;
41 bool flag_pstereo = false;
42 PaStream* stream_p = NULL;
43 //Some global info.
44 bool init_flag = false;
45 bool was_enabled = false;
47 int audiocb(const void *input, void *output, unsigned long frame_count,
48 const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags flags, void* user)
50 const unsigned voice_blocksize = 256;
51 if(output && current_pfreq) {
52 int16_t voicebuf[voice_blocksize];
53 unsigned long pframe_count = frame_count;
54 int16_t* _output = reinterpret_cast<int16_t*>(output);
55 size_t ptr = 0;
56 while(pframe_count > 0) {
57 unsigned bsize = min(voice_blocksize / 2, static_cast<unsigned>(pframe_count));
58 lsnes_instance.audio->get_mixed(voicebuf, bsize, flag_pstereo);
59 unsigned limit = bsize * (flag_pstereo ? 2 : 1);
60 if(was_enabled)
61 for(size_t i = 0; i < limit; i++)
62 _output[ptr++] = voicebuf[i];
63 else
64 for(size_t i = 0; i < limit; i++)
65 _output[ptr++] = 0;
66 pframe_count -= bsize;
69 if(input && current_rfreq) {
70 const int16_t* _input = reinterpret_cast<const int16_t*>(input);
71 unsigned long rframe_count = frame_count;
72 float voicebuf2[voice_blocksize];
73 size_t iptr = 0;
74 while(rframe_count > 0) {
75 unsigned bsize = min(voice_blocksize / 2, static_cast<unsigned>(rframe_count));
76 if(flag_rstereo)
77 for(size_t i = 0; i < bsize; i++) {
78 float l = _input[iptr++];
79 float r = _input[iptr++];
80 voicebuf2[i] = (l + r) / 2;
82 else
83 for(size_t i = 0; i < bsize; i++)
84 voicebuf2[i] = _input[iptr++];
85 lsnes_instance.audio->put_voice(voicebuf2, bsize);
86 rframe_count -= bsize;
89 return 0;
92 PaStreamParameters* get_output_parameters(PaDeviceIndex idx_in, PaDeviceIndex idx_out)
94 static PaStreamParameters output;
95 if(idx_out == paNoDevice)
96 return NULL;
97 const PaDeviceInfo* inf = Pa_GetDeviceInfo(idx_out);
98 if(!inf || !inf->maxOutputChannels)
99 return NULL;
100 memset(&output, 0, sizeof(output));
101 output.device = idx_out;
102 output.channelCount = (inf->maxOutputChannels > 1) ? 2 : 1;
103 output.sampleFormat = paInt16;
104 output.suggestedLatency = inf->defaultLowOutputLatency;
105 output.hostApiSpecificStreamInfo = NULL;
106 return &output;
109 PaStreamParameters* get_input_parameters(PaDeviceIndex idx_in, PaDeviceIndex idx_out)
111 static PaStreamParameters input;
112 if(idx_in == paNoDevice)
113 return NULL;
114 const PaDeviceInfo* inf = Pa_GetDeviceInfo(idx_in);
115 if(!inf || !inf->maxInputChannels)
116 return NULL;
117 memset(&input, 0, sizeof(input));
118 input.device = idx_in;
119 input.channelCount = (inf->maxInputChannels > 1) ? 2 : 1;
120 input.sampleFormat = paInt16;
121 input.suggestedLatency = inf->defaultLowInputLatency;
122 input.hostApiSpecificStreamInfo = NULL;
123 return &input;
126 bool dispose_stream(PaStream* s)
128 if(!s)
129 return true;
130 PaError err;
131 err = Pa_StopStream(s);
132 if(err != paNoError) {
133 messages << "Portaudio error (stop): " << Pa_GetErrorText(err)
134 << std::endl;
135 return false;
137 err = Pa_CloseStream(s);
138 if(err != paNoError) {
139 messages << "Portaudio error (close): " << Pa_GetErrorText(err) << std::endl;
140 return false;
142 return true;
145 bool check_indev(PaDeviceIndex dev)
147 if(getenv("LSNES_NO_SOUND_IN"))
148 return false;
149 const PaDeviceInfo* inf = NULL;
150 if(dev != paNoDevice)
151 inf = Pa_GetDeviceInfo(dev);
152 if(dev != paNoDevice && (!inf || !inf->maxInputChannels)) {
153 messages << "Portaudio: Trying to switch to invalid input device" << std::endl;
154 return false;
156 return true;
159 bool check_outdev(PaDeviceIndex dev)
161 const PaDeviceInfo* inf = NULL;
162 if(dev != paNoDevice)
163 inf = Pa_GetDeviceInfo(dev);
164 if(dev != paNoDevice && (!inf || !inf->maxOutputChannels)) {
165 messages << "Portaudio: Trying to switch to invalid output device" << std::endl;
166 return false;
168 return true;
171 void close_output()
173 dispose_stream(stream_p);
174 stream_p = NULL;
175 current_pdev = paNoDevice;
176 current_pfreq = 0;
177 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
180 void close_input()
182 dispose_stream(stream_r);
183 stream_r = NULL;
184 current_rdev = paNoDevice;
185 current_rfreq = 0;
186 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
189 void close_all()
191 close_input();
192 close_output();
195 void print_status(unsigned flags)
197 if(flags & 2) {
198 if(current_pfreq)
199 messages << "Portaudio: Output: " << current_pfreq << "Hz "
200 << (flag_pstereo ? "Stereo" : "Mono") << " on '"
201 << Pa_GetDeviceInfo(current_pdev)->name << std::endl;
202 else
203 messages << "Portaudio: No sound output" << std::endl;
205 if(flags & 1) {
206 if(current_rfreq)
207 messages << "Portaudio: Input: " << current_rfreq << "Hz "
208 << (flag_rstereo ? "Stereo" : "Mono") << " on '"
209 << Pa_GetDeviceInfo(current_rdev)->name << std::endl;
210 else
211 messages << "Portaudio: No sound input" << std::endl;
215 //Switch output device only in split-duplex configuration.
216 unsigned switch_devices_outonly(PaDeviceIndex dev)
218 if(!check_outdev(dev))
219 return 1;
220 close_output();
221 if(dev == paNoDevice) {
222 messages << "Sound output closed." << std::endl;
223 return 3;
225 PaStreamParameters* output = get_output_parameters(current_rdev, dev);
226 auto inf = Pa_GetDeviceInfo(dev);
227 PaError err = Pa_OpenStream(&stream_p, NULL, output, inf->defaultSampleRate, 0, 0, audiocb, NULL);
228 if(err != paNoError) {
229 messages << "Portaudio: error (open): " << Pa_GetErrorText(err) << std::endl;
230 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
231 return 1;
233 flag_pstereo = (output && output->channelCount == 2);
234 err = Pa_StartStream(stream_p);
235 if(err != paNoError) {
236 messages << "Portaudio error (start): " << Pa_GetErrorText(err) << std::endl;
237 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
238 return 1;
240 const PaStreamInfo* si = Pa_GetStreamInfo(stream_p);
241 current_pfreq = output ? si->sampleRate : 0;
242 current_pdev = dev;
243 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
244 print_status(2);
245 return 3;
248 //Switch input device only in split-duplex configuration.
249 unsigned switch_devices_inonly(PaDeviceIndex dev)
251 if(!check_indev(dev))
252 return 2;
253 close_input();
254 if(dev == paNoDevice) {
255 messages << "Sound input closed." << std::endl;
256 return 3;
258 PaStreamParameters* input = get_input_parameters(dev, current_pdev);
259 auto inf = Pa_GetDeviceInfo(dev);
260 PaError err = Pa_OpenStream(&stream_r, input, NULL, inf->defaultSampleRate, 0, 0, audiocb, NULL);
261 if(err != paNoError) {
262 messages << "Portaudio: error (open): " << Pa_GetErrorText(err) << std::endl;
263 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
264 return 2;
266 flag_rstereo = (input && input->channelCount == 2);
267 err = Pa_StartStream(stream_r);
268 if(err != paNoError) {
269 messages << "Portaudio error (start): " << Pa_GetErrorText(err) << std::endl;
270 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
271 return 2;
273 const PaStreamInfo* si = Pa_GetStreamInfo(stream_r);
274 current_rfreq = input ? si->sampleRate : 0;
275 current_rdev = dev;
276 lsnes_instance.audio->voice_rate(current_rfreq, current_pfreq);
277 print_status(1);
278 return 3;
281 //Switch to split-duplex configuration.
282 unsigned switch_devices_split(PaDeviceIndex rdev, PaDeviceIndex pdev)
284 unsigned status = 3;
285 if(!check_indev(rdev) || !check_outdev(pdev))
286 return 0;
287 close_all();
288 status &= switch_devices_outonly(pdev);
289 status &= switch_devices_inonly(rdev);
290 return status;
293 unsigned switch_devices(PaDeviceIndex new_in, PaDeviceIndex new_out)
295 bool switch_in = (new_in != current_rdev);
296 bool switch_out = (new_out != current_pdev);
297 if(!switch_in && !switch_out)
298 return 3;
299 else if(!switch_in)
300 return switch_devices_outonly(new_out);
301 else if(!switch_out)
302 return switch_devices_inonly(new_in);
303 else
304 return switch_devices_split(new_in, new_out);
307 bool initial_switch_devices(PaDeviceIndex force_in, PaDeviceIndex force_out)
309 if(force_in == paNoDevice)
310 force_in = Pa_GetDefaultInputDevice();
311 if(force_out == paNoDevice)
312 force_out = Pa_GetDefaultOutputDevice();
313 unsigned r = switch_devices(force_in, force_out);
314 return (r != 0);
317 PaDeviceIndex output_to_index(const std::string& dev)
319 PaDeviceIndex idx;
320 if(dev == "null") {
321 idx = paNoDevice;
322 } else {
323 try {
324 idx = boost::lexical_cast<PaDeviceIndex>(dev);
325 if(idx < 0 || !Pa_GetDeviceInfo(idx))
326 throw std::runtime_error("foo");
327 } catch(std::exception& e) {
328 throw std::runtime_error("Invalid device '" + dev + "'");
331 return idx;
334 struct _audioapi_driver drv = {
335 .init = []() -> void {
336 PaError err = Pa_Initialize();
337 if(err != paNoError) {
338 messages << "Portaudio error (init): " << Pa_GetErrorText(err) << std::endl;
339 messages << "Audio can't be initialized, audio playback disabled" << std::endl;
340 return;
342 init_flag = true;
344 PaDeviceIndex forcedevice_in = paNoDevice;
345 PaDeviceIndex forcedevice_out = paNoDevice;
346 if(getenv("LSNES_FORCE_AUDIO_OUT")) {
347 forcedevice_out = atoi(getenv("LSNES_FORCE_AUDIO_OUT"));
348 messages << "Attempting to force sound output " << forcedevice_out << std::endl;
350 if(getenv("LSNES_FORCE_AUDIO_IN")) {
351 forcedevice_in = atoi(getenv("LSNES_FORCE_AUDIO_IN"));
352 messages << "Attempting to force sound input " << forcedevice_in << std::endl;
354 messages << "Detected " << Pa_GetDeviceCount() << " sound devices." << std::endl;
355 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++)
356 messages << "Audio device " << j << ": " << Pa_GetDeviceInfo(j)->name << std::endl;
357 was_enabled = true;
358 bool any_success = initial_switch_devices(forcedevice_in, forcedevice_out);
359 if(!any_success)
360 messages << "Portaudio: Can't open any sound device, audio disabled" << std::endl;
362 .quit = []() -> void {
363 if(!init_flag)
364 return;
365 switch_devices(paNoDevice, paNoDevice);
366 Pa_Terminate();
367 init_flag = false;
369 .enable = [](bool _enable) -> void { was_enabled = _enable; },
370 .initialized = []() -> bool { return init_flag; },
371 .set_device = [](const std::string& pdev, const std::string& rdev) -> void {
372 PaDeviceIndex pidx = output_to_index(pdev);
373 PaDeviceIndex ridx = output_to_index(rdev);
374 auto ret = switch_devices(ridx, pidx);
375 if(ret == 1)
376 throw std::runtime_error("Can't change output device");
377 if(ret == 2)
378 throw std::runtime_error("Can't change input device");
379 if(ret == 0)
380 throw std::runtime_error("Can't change audio devices");
382 .get_device = [](bool rec) -> std::string {
383 if(rec)
384 if(current_rdev == paNoDevice)
385 return "null";
386 else
387 return (stringfmt() << current_rdev).str();
388 else
389 if(current_pdev == paNoDevice)
390 return "null";
391 else
392 return (stringfmt() << current_pdev).str();
394 .get_devices = [](bool rec) -> std::map<std::string, std::string> {
395 std::map<std::string, std::string> ret;
396 ret["null"] = rec ? "null sound input" : "null sound output";
397 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++) {
398 std::ostringstream str;
399 str << j;
400 auto devinfo = Pa_GetDeviceInfo(j);
401 if(rec && !getenv("LSNES_NO_SOUND_IN") && devinfo->maxInputChannels)
402 ret[str.str()] = devinfo->name;
403 if(!rec && devinfo->maxOutputChannels)
404 ret[str.str()] = devinfo->name;
406 return ret;
408 .name = []() -> const char* { return "Portaudio sound plugin"; }
410 struct audioapi_driver _drv(drv);