Portaudio: Mix channels if mono (don't just output left channel)
[lsnes.git] / portaudio / sound-portaudio.cpp
blob591371be36521dc62c79a79913af05ff111483f2
1 #include "window.hpp"
2 #include "render.hpp"
3 #include <cstring>
4 #include "command.hpp"
5 #include "framerate.hpp"
6 #include "misc.hpp"
7 #include "lsnes.hpp"
8 #include "settings.hpp"
9 #include <vector>
10 #include <iostream>
11 #include <csignal>
12 #include "keymapper.hpp"
13 #include "framerate.hpp"
14 #include <sstream>
15 #include <fstream>
16 #include <cassert>
17 #include <boost/lexical_cast.hpp>
19 #include <portaudio.h>
20 #include <string>
21 #include <map>
22 #include <stdexcept>
24 namespace
26 uint32_t audio_playback_freq = 0;
27 const size_t audiobuf_size = 8192;
28 uint16_t audiobuf[audiobuf_size];
29 volatile size_t audiobuf_get = 0;
30 volatile size_t audiobuf_put = 0;
31 uint64_t sampledup_ctr = 0;
32 uint64_t sampledup_inc = 0;
33 uint64_t sampledup_mod = 0;
34 uint32_t use_rate_n = 32000;
35 uint32_t use_rate_d = 1;
36 PaDeviceIndex current_device = paNoDevice;
37 bool stereo = false;
38 PaStream* s = NULL;
39 bool init_flag = false;
41 void calculate_sampledup(uint32_t rate_n, uint32_t rate_d)
43 if(!audio_playback_freq) {
44 //Sound disabled.
45 sampledup_ctr = 0;
46 sampledup_inc = 0;
47 sampledup_mod = 0;
48 } else {
49 sampledup_ctr = 0;
50 sampledup_inc = rate_n;
51 sampledup_mod = rate_d * audio_playback_freq + rate_n;
55 int audiocb(const void *input, void *output, unsigned long frame_count,
56 const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags flags, void* user)
58 static uint16_t lprev, rprev;
59 int16_t* _output = reinterpret_cast<int16_t*>(output);
60 for(unsigned long i = 0; i < frame_count; i++) {
61 uint16_t l, r;
62 if(audiobuf_get == audiobuf_put) {
63 l = lprev;
64 r = rprev;
65 } else {
66 l = lprev = audiobuf[audiobuf_get++];
67 r = rprev = audiobuf[audiobuf_get++];
68 if(audiobuf_get == audiobuf_size)
69 audiobuf_get = 0;
71 if(!stereo)
72 l = l / 2 + r / 2;
73 *(_output++) = l - 32768;
74 if(stereo)
75 *(_output++) = r - 32768;
77 return 0;
80 bool switch_devices(PaDeviceIndex newdevice)
82 //Check that the new device index is valid at all.
83 if((newdevice >= 0 && !Pa_GetDeviceInfo(newdevice)) || (newdevice < 0 && newdevice != paNoDevice)) {
84 window::out() << "Invalid device " << newdevice << std::endl;
85 return false;
87 if(newdevice != paNoDevice && Pa_GetDeviceInfo(newdevice)->maxOutputChannels < 1) {
88 window::out() << "Portaudio: Device " << newdevice << " is not capable of playing sound."
89 << std::endl;
90 return false;
92 //If audio is open somewhere, close it.
93 if(current_device != paNoDevice) {
94 PaError err = Pa_StopStream(s);
95 if(err != paNoError) {
96 window::out() << "Portaudio error (stop): " << Pa_GetErrorText(err) << std::endl;
97 return false;
99 err = Pa_CloseStream(s);
100 if(err != paNoError) {
101 window::out() << "Portaudio error (close): " << Pa_GetErrorText(err) << std::endl;
102 return false;
104 current_device = paNoDevice;
105 //Sound disabled.
106 sampledup_ctr = 0;
107 sampledup_inc = 0;
108 sampledup_mod = 0;
110 //If new audio device is to be opened, try to do it.
111 if(newdevice != paNoDevice) {
112 const PaDeviceInfo* inf = Pa_GetDeviceInfo(newdevice);
113 PaStreamParameters output;
114 memset(&output, 0, sizeof(output));
115 output.device = newdevice;
116 output.channelCount = (inf->maxOutputChannels > 1) ? 2 : 1;
117 output.sampleFormat = paInt16;
118 output.suggestedLatency = inf->defaultLowOutputLatency;
119 output.hostApiSpecificStreamInfo = NULL;
120 PaError err = Pa_OpenStream(&s, NULL, &output, inf->defaultSampleRate, 0, 0, audiocb, NULL);
121 if(err != paNoError) {
122 window::out() << "Portaudio: error (open): " << Pa_GetErrorText(err) << std::endl
123 << "\tOn device: '" << inf->name << "'" << std::endl;
124 return false;
127 stereo = (output.channelCount == 2);
128 err = Pa_StartStream(s);
129 if(err != paNoError) {
130 window::out() << "Portaudio error (start): " << Pa_GetErrorText(err) << std::endl
131 << "\tOn device: '" << inf->name << "'" << std::endl;
132 return false;
134 audio_playback_freq = inf->defaultSampleRate;
135 calculate_sampledup(use_rate_n, use_rate_d);
136 window::out() << "Portaudio: Opened " << inf->defaultSampleRate << "Hz "
137 << (stereo ? "Stereo" : "Mono") << " sound on '" << inf->name << "'" << std::endl;
138 window::out() << "Switched to sound device '" << inf->name << "'" << std::endl;
139 } else
140 window::out() << "Switched to sound device NULL" << std::endl;
141 current_device = newdevice;
142 return true;
147 void window::sound_enable(bool enable) throw()
149 PaError err;
150 if(enable)
151 err = Pa_StartStream(s);
152 else
153 err = Pa_StopStream(s);
154 if(err != paNoError)
155 window::out() << "Portaudio error (start/stop): " << Pa_GetErrorText(err) << std::endl;
158 void sound_init()
160 PaError err = Pa_Initialize();
161 if(err != paNoError) {
162 window::out() << "Portaudio error (init): " << Pa_GetErrorText(err) << std::endl;
163 window::out() << "Audio can't be initialized, audio playback disabled" << std::endl;
164 return;
166 init_flag = true;
168 PaDeviceIndex forcedevice = paNoDevice;
169 if(getenv("LSNES_FORCE_AUDIO_OUT")) {
170 forcedevice = atoi(getenv("LSNES_FORCE_AUDIO_OUT"));
171 window::out() << "Attempting to force sound output " << forcedevice << std::endl;
173 window::out() << "Detected " << Pa_GetDeviceCount() << " sound output devices." << std::endl;
174 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++)
175 window::out() << "Audio device " << j << ": " << Pa_GetDeviceInfo(j)->name << std::endl;
176 bool any_success = false;
177 if(forcedevice == paNoDevice) {
178 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++) {
179 any_success |= switch_devices(j);
180 if(any_success)
181 break;
183 } else
184 any_success |= switch_devices(forcedevice);
185 if(!any_success)
186 window::out() << "Portaudio: Can't open any sound device, audio disabled" << std::endl;
189 void sound_quit()
191 if(!init_flag)
192 return;
193 switch_devices(paNoDevice);
194 Pa_Terminate();
195 init_flag = false;
198 void window::play_audio_sample(uint16_t left, uint16_t right) throw()
200 if(!init_flag)
201 return;
202 sampledup_ctr += sampledup_inc;
203 while(sampledup_ctr < sampledup_mod) {
204 audiobuf[audiobuf_put++] = left;
205 audiobuf[audiobuf_put++] = right;
206 if(audiobuf_put == audiobuf_size)
207 audiobuf_put = 0;
208 sampledup_ctr += sampledup_inc;
210 sampledup_ctr -= sampledup_mod;
213 void window::set_sound_rate(uint32_t rate_n, uint32_t rate_d)
215 if(!init_flag)
216 return;
217 uint32_t g = gcd(rate_n, rate_d);
218 use_rate_n = rate_n / g;
219 use_rate_d = rate_d / g;
220 calculate_sampledup(use_rate_n, use_rate_d);
223 bool window::sound_initialized()
225 return init_flag;
228 void window::set_sound_device(const std::string& dev)
230 if(dev == "null") {
231 if(!switch_devices(paNoDevice))
232 throw std::runtime_error("Failed to switch sound outputs");
233 } else {
234 PaDeviceIndex idx;
235 try {
236 idx = boost::lexical_cast<PaDeviceIndex>(dev);
237 if(idx < 0 || !Pa_GetDeviceInfo(idx))
238 throw std::runtime_error("foo");
239 } catch(std::exception& e) {
240 throw std::runtime_error("Invalid device '" + dev + "'");
242 if(!switch_devices(idx))
243 throw std::runtime_error("Failed to switch sound outputs");
247 std::string window::get_current_sound_device()
249 if(current_device == paNoDevice)
250 return "null";
251 else {
252 std::ostringstream str;
253 str << current_device;
254 return str.str();
258 std::map<std::string, std::string> window::get_sound_devices()
260 std::map<std::string, std::string> ret;
261 ret["null"] = "null sound output";
262 for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++) {
263 std::ostringstream str;
264 str << j;
265 ret[str.str()] = Pa_GetDeviceInfo(j)->name;
267 return ret;
270 const char* sound_plugin_name = "Portaudio sound plugin";