Tweak format of command help files and do some further command cleanup
[lsnes.git] / src / platform / libao / sound.cpp
blobe3609064a9187c7ae89deaf102ad25993fb1ec48
1 #include "lsnes.hpp"
3 #include "cmdhelp/libao-sound.hpp"
4 #include "core/audioapi-driver.hpp"
5 #include "core/command.hpp"
6 #include "core/dispatch.hpp"
7 #include "core/framerate.hpp"
8 #include "core/misc.hpp"
9 #include "core/framerate.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/settings.hpp"
12 #include "core/messages.hpp"
13 #include "core/window.hpp"
14 #include "library/framebuffer.hpp"
15 #include "library/string.hpp"
17 #include <cstring>
18 #include <cstdlib>
19 #include <vector>
20 #include <iostream>
21 #include <csignal>
22 #include <sstream>
23 #include <fstream>
24 #include <cassert>
25 #include "core/audioapi.hpp"
26 #include "core/instance.hpp"
27 #include "library/minmax.hpp"
28 #include "library/workthread.hpp"
29 #include <string>
30 #include <map>
31 #include <stdexcept>
32 #include <ao/ao.h>
33 #include <boost/lexical_cast.hpp>
35 namespace
37 std::string current_device = "";
38 ao_device* volatile cdev = NULL;
39 bool init_flag = false;
40 bool was_enabled = false;
41 int driver_id = 0;
43 class cb_thread : public workthread
45 public:
46 cb_thread() { fire(); }
47 void entry()
49 int16_t buffer[1024];
50 while(true) {
51 lsnes_instance.audio->get_mixed(buffer, 512, true);
52 if(!was_enabled)
53 memset(buffer, 0, sizeof(buffer));
54 ao_device* d = cdev;
55 if(d)
56 ao_play(d, reinterpret_cast<char*>(buffer), 2048);
57 else
58 usleep(10000);
63 bool switch_devices(int newdevice, std::string name)
65 //If audio is open, close it.
66 if(cdev) {
67 ao_device* d = cdev;
68 cdev = NULL;
69 //Wait a bit for the device to close.
70 usleep(50000);
71 ao_close(d);
72 current_device = "";
73 lsnes_instance.audio->voice_rate(0, 0);
75 //Open new audio.
76 if(newdevice != -1) {
77 char matrix[128];
78 strcpy(matrix, "L,R");
79 ao_sample_format sformat;
80 ao_option* options = NULL;
81 sformat.bits = 16;
82 sformat.rate = 48000;
83 sformat.channels = 2;
84 sformat.byte_format = AO_FMT_NATIVE;
85 sformat.matrix = matrix;
86 std::string idno = (stringfmt() << driver_id).str();
87 ao_append_option(&options, "id", idno.c_str());
88 cdev = ao_open_live(newdevice, &sformat, options);
89 if(!cdev) {
90 int err = errno;
91 //Error.
92 switch(err) {
93 case AO_ENODRIVER:
94 throw std::runtime_error("Bad driver ID");
95 case AO_ENOTLIVE:
96 throw std::runtime_error("Driver doesn't support playback");
97 case AO_EBADOPTION:
98 throw std::runtime_error("Bad option value");
99 case AO_EOPENDEVICE:
100 throw std::runtime_error("Can not open device");
101 case AO_EFAIL:
102 throw std::runtime_error("Unknown error");
103 default:
104 (stringfmt() << "Error code " << err).throwex();
107 lsnes_instance.audio->voice_rate(0, 48000);
109 if(cdev) {
110 current_device = name;
111 messages << "Switched to audio output " << name << std::endl;
112 } else {
113 current_device = "";
114 messages << "Switched to null audio output" << std::endl;
116 return true;
119 command::fnptr<const std::string&> x(lsnes_cmds, CLIBAOSND::setid,
120 [](const std::string& value) throw(std::bad_alloc, std::runtime_error) {
121 driver_id = parse_value<int>(value);
124 struct _audioapi_driver drv = {
125 .init = []() -> void {
126 ao_initialize();
127 init_flag = true;
128 current_device = "";
130 int dcount;
131 int defaultid = ao_default_driver_id();
132 std::string dname = "";
133 ao_info** drvs = ao_driver_info_list(&dcount);
134 messages << "Detected " << dcount << " sound output devices." << std::endl;
135 for(int j = 0; j < dcount; j++) {
136 messages << "Audio device " << drvs[j]->short_name << ": " << drvs[j]->name
137 << std::endl;
138 if(j == defaultid)
139 dname = drvs[j]->short_name;
141 new cb_thread;
142 was_enabled = true;
143 try {
144 switch_devices(defaultid, dname);
145 } catch(...) {
146 switch_devices(-1, "");
149 .quit = []() -> void {
150 if(!init_flag)
151 return;
152 switch_devices(-1, "");
153 ao_shutdown();
154 init_flag = false;
156 .enable = [](bool enable) -> void { was_enabled = enable; },
157 .initialized = []() -> bool { return init_flag; },
158 .set_device = [](const std::string& pdev, const std::string& rdev) -> void {
159 if(rdev != "null")
160 //Sound input not supported.
161 throw std::runtime_error("Invalid sound input device");
162 if(pdev == "null") {
163 if(!switch_devices(-1, ""))
164 throw std::runtime_error("Failed to switch sound outputs");
165 } else {
166 int idx = ao_driver_id(pdev.c_str());
167 if(idx == -1)
168 throw std::runtime_error("Invalid output device '" + pdev + "'");
169 try {
170 if(!switch_devices(idx, pdev))
171 throw std::runtime_error("Failed to switch sound outputs");
172 } catch(std::exception& e) {
173 throw std::runtime_error(std::string("Failed to switch sound outputs: ") +
174 e.what());
178 .get_device = [](bool rec) -> std::string {
179 if(rec)
180 return "null"; //Sound input not supported.
181 if(current_device == "")
182 return "null";
183 else
184 return current_device;
186 .get_devices = [](bool rec) -> std::map<std::string, std::string> {
187 std::map<std::string, std::string> ret;
188 if(rec) {
189 ret["null"] = "null sound input";
190 return ret;
192 ret["null"] = "null sound output";
193 int dcount;
194 ao_info** drvs = ao_driver_info_list(&dcount);
195 if(!drvs)
196 return ret;
197 for(int j = 0; j < dcount; j++)
198 if(drvs[j]->type == AO_TYPE_LIVE)
199 ret[drvs[j]->short_name] = drvs[j]->name;
200 return ret;
202 .name = []() -> const char* { return "Libao sound plugin"; }
204 audioapi_driver _drv(drv);