1 /*
2 * audio.cc
3 * DIN Is Noise is copyright (c) 2006-2021 Jagannathan Sampath
4 * DIN Is Noise is released under GNU Public License 2.0
5 * For more information, please visit https://dinisnoise.org/
6 */
7
8
9 #include "main.h"
10 #include "audio.h"
11 #include "chrono.h"
12 #include "command.h"
13 #include <math.h>
14 #include <fstream>
15 #include <cstring>
16 #include <limits.h>
17 #include <stdlib.h>
18 #include <string>
19 #include "utils.h"
20 #include "RtError.h"
21 #include "log.h"
22 #include "console.h"
23 using namespace std;
24
25 extern string user_data_dir;
26 extern string APP_NAME;
27
28 #if defined __UNIX_JACK__
29 #define SOUND_API RtAudio::UNIX_JACK
30 #elif defined __LINUX_ALSA__
31 #define SOUND_API RtAudio::LINUX_ALSA
32 #elif defined __MACOSX_CORE__
33 #define SOUND_API RtAudio::MACOSX_CORE
34 #elif defined __WINDOWS_DS__
35 #define SOUND_API RtAudio::WINDOWS_DS
36 #endif
37
audio_out()38 audio_out::audio_out () : dac (SOUND_API) {
39
40 samples_buffers = 0;
41 available = 0;
42 result = ams = fms = vol = gatr = mix = mixa = bufL = bufR = fdr1 = fdr2 = 0;
43
44 prefs_name = user_data_dir + "audio_prefs";
45 load_prefs ();
46
47 probe ();
48 open ();
49
50 }
51
~audio_out()52 audio_out::~audio_out () {
53
54 save_prefs ();
55
56 int num_ptrs = 11;
57 sample_t* ptr [] = {samples_buffers, result, ams, fms, vol, gatr, mix, bufL, bufR, fdr1, fdr2, mixa};
58 for (int i = 0; i < num_ptrs; ++i) if (ptr[i]) delete[] ptr[i];
59
60 if (available) delete[] available;
61
62 dlog << "--- destroyed audio ---" << endl;
63
64 }
65
alloc()66 void audio_out::alloc () {
67
68 // alloc memory for various buffers
69 //
70
71 samples_per_buffer = num_channels * samples_per_channel;
72 samples_buffer_size = sizeof (sample_t) * samples_per_buffer;
73 samples_channel_size = sizeof (sample_t) * samples_per_channel;
74
75 if (samples_buffers) delete[] samples_buffers;
76 samples_buffers = new sample_t [num_samples_buffers * samples_per_buffer];
77
78 if (available) delete[] available;
79 available = new int [num_samples_buffers];
80 memset (available, 0, num_samples_buffers * sizeof (int));
81
82 readi = writei = 0;
83 readp = writep = samples_buffers;
84
85 int num_ptrs = 11;
86 sample_t** ptr [] = {&result, &ams, &fms, &vol, &gatr, &mix, &bufL, &bufR, &fdr1, &fdr2, &mixa};
87 for (int i = 0; i < num_ptrs; ++i) {
88 if (ptr[i]) delete[] *ptr[i];
89 *ptr[i] = new sample_t [samples_per_channel];
90 memset (*ptr[i], 0, samples_channel_size);
91 }
92
93 }
94
open()95 int audio_out::open () {
96 return open (current_device, sample_rate, samples_per_channel);
97 }
98
open(int id,unsigned int sr,unsigned int spc)99 int audio_out::open (int id, unsigned int sr, unsigned int spc) {
100
101 RtAudio::StreamParameters parameters;
102 if (id > -1 && id < num_devices) {
103 parameters.deviceId = id;
104 } else {
105 id = default_device;
106 parameters.deviceId = id;
107 }
108
109 current_device = id;
110
111 // current sample rate exists?
112 find_sample_rate_id (sr);
113 sr = infos[id].sampleRates[i_sample_rate];
114
115 parameters.nChannels = num_channels;
116
117 RtAudio::StreamOptions options;
118 options.flags = RTAUDIO_NONINTERLEAVED;
119
120 try {
121 dac.openStream (¶meters, 0, RTAUDIO_FLOAT32, sr, &spc, audio_wanted, 0, &options);
122 set_samples_per_channel (spc);
123 alloc ();
124 set_sample_rate (sr);
125 dlog << "+++ opened audio stream +++" << endl;
126 return 1;
127 } catch (RtError& e) {
128 dlog << "!!! couldnt open audio stream !!!" << endl;
129 }
130 return 0;
131
132 }
133
start()134 int audio_out::start () {
135 try {
136 dac.startStream ();
137 dlog << "+++ " << APP_NAME << " is now streaming audio +++" << endl;
138 } catch (RtError& e) {
139 dlog << "!!! couldnt start the audio stream !!!" << endl;
140 }
141 return 0;
142 }
143
close()144 int audio_out::close () {
145 if (dac.isStreamOpen ()) {
146 dac.stopStream ();
147 dac.closeStream ();
148 dlog << "+++ stopped and closed audio stream +++" << endl;
149 return 1;
150 }
151 return 0;
152 }
153
load_prefs()154 void audio_out::load_prefs () {
155 //
156 // load audio preferences from disk
157 //
158
159 ifstream file (prefs_name.c_str(), ios::in);
160 string ignore;
161
162 if (!file) {
163 dlog << "!!! bad audio_prefs file. sorry, have to abort !!!" << endl;
164 exit (1);
165 } else {
166 file >> ignore >> current_device;
167 file >> ignore >> num_channels;
168 int spc; file >> ignore >> spc; set_samples_per_channel (spc);
169 file >> ignore >> sample_rate;
170 file >> ignore >> num_samples_buffers;
171 }
172 }
173
save_prefs()174 void audio_out::save_prefs () {
175 ofstream file (prefs_name.c_str(), ios::out);
176 if (file) {
177 if (current_device == default_device) current_device = -1;
178 file << "current_device " << current_device << endl;
179 file << "num_channels " << num_channels << endl;
180 file << "samples_per_channel " << samples_per_channel << endl;
181 file << "sample_rate " << sample_rate << endl;
182 file << "num_samples_buffers " << num_samples_buffers << endl;
183 dlog << "+++ saved audio prefrences in " << prefs_name << " +++" << endl;
184 } else dlog << "!!! couldnt write audio preferences file: " << prefs_name << " !!!" << endl;
185 }
186
set_samples_per_channel(int spc)187 void audio_out::set_samples_per_channel (int spc) {
188 samples_per_channel = spc;
189 last_sample = samples_per_channel - 1;
190 }
191
set_sample_rate(int s)192 void audio_out::set_sample_rate (int s) {
193 SAMPLE_RATE = sample_rate = s;
194 SAMPLE_DURATION = 1.0f / SAMPLE_RATE;
195 clk.delta_ticks = samples_per_channel;
196 clk.delta_secs = samples_per_channel * SAMPLE_DURATION;
197 }
198
audio_wanted(void * ob,void * ib,unsigned int spc,double t,RtAudioStreamStatus status,void * data)199 int audio_out::audio_wanted (void *ob, void *ib, unsigned int spc, double t, RtAudioStreamStatus status, void *data) {
200
201 // stream audio buffer to audio card
202 //
203
204 if (aout.available[aout.readi]) { // samples buffer written by main thread ready for streaming
205 memcpy (ob, aout.readp, aout.samples_buffer_size); // copy written buffer into audio card
206
207 // update samples buffer status to let main thread write
208 aout.available [aout.readi] = 0;
209 if (++aout.readi >= aout.num_samples_buffers) {
210 aout.readi = 0;
211 aout.readp = aout.samples_buffers;
212 } else aout.readp += aout.samples_per_buffer;
213
214 ++clk; // update audio clock
215 }
216 return 0;
217
218 }
219
probe()220 void audio_out::probe () {
221
222 names.clear ();
223 num_devices = dac.getDeviceCount ();
224 if (num_devices == 0) {
225 dlog << "!!! no audio devices found !!!" << endl;
226 #ifdef __UNIX_JACK__
227 cout << "!!! Please start JACK before running DIN Is Noise !!!" << endl;
228 cout << "!!! DIN Is Noise aborted !!!" << endl;
229 exit (1);
230 #endif
231 } else {
232 last_device = num_devices - 1;
233 dlog << "+++ found " << num_devices << " audio devices +++" << endl;
234 infos.resize (num_devices);
235 default_device = dac.getDefaultOutputDevice ();
236 dlog << "+++ default output device = " << default_device << " +++" << endl;
237 if (current_device == INVALID) current_device = default_device;
238 next_device = current_device;
239 for (int i = 0; i < num_devices; ++i) {
240 RtAudio::DeviceInfo& info = infos[i];
241 info = dac.getDeviceInfo (i);
242 names.push_back (info.name);
243 char br = '[';
244 dlog << "name = " << info.name << " input [default?] = " << info.inputChannels << br << info.isDefaultInput << "], output[default?] = " << info.outputChannels << br << info.isDefaultOutput << "] duplex = " << info.duplexChannels << spc << endl;
245 dlog << "sample rates: ";
246 for (int i = 0, j = info.sampleRates.size (); i < j; ++i) {
247 dlog << info.sampleRates[i] << spc;
248 }
249 dlog << endl;
250 }
251 }
252
253 }
254
goto_next_device(int i)255 int audio_out::goto_next_device (int i) {
256 next_device = current_device + i;
257 wrap (0, next_device, last_device);
258 if (infos[next_device].outputChannels == 0) {
259 cons << RED << names[next_device] << " has no output channels" << eol;
260 return 0;
261 }
262 current_device = next_device;
263 find_sample_rate_id (sample_rate);
264 return 1;
265 }
266
find_sample_rate_id(unsigned int sr)267 void audio_out::find_sample_rate_id (unsigned int sr) {
268 i_sample_rate = 0;
269 std::vector<unsigned int> srates = infos[current_device].sampleRates;
270 for (int i = 0, j = srates.size (); i < j; ++i) {
271 if (sr == srates[i]) {
272 i_sample_rate = i;
273 break;
274 }
275 }
276 }
277
goto_next_sample_rate_id(int i)278 int audio_out::goto_next_sample_rate_id (int i) {
279 i_sample_rate += i;
280 vector<unsigned int> srates = infos[current_device].sampleRates;
281 int nrates = srates.size ();
282 if (i_sample_rate < 0) i_sample_rate = nrates - 1; else if (i_sample_rate >= nrates) i_sample_rate = 0;
283 return srates[i_sample_rate];
284 }
285
list()286 void audio_out::list () {
287 default_device = dac.getDefaultOutputDevice ();
288 for (int i = 0; i < num_devices; ++i) {
289 RtAudio::DeviceInfo& info = infos[i];
290 cons << GREEN << "name = " << info.name << " input = " << info.inputChannels << " output = " << info.outputChannels \
291 << " duplex = " << info.duplexChannels << eol;
292 cons << CYAN << " sample rates: ";
293 for (int i = 0, j = info.sampleRates.size (); i < j; ++i) cons << int (info.sampleRates[i]) << spc;
294 cons << eol;
295 }
296 cons << YELLOW << "number of devices = " << num_devices << eol;
297 cons << "final device = " << last_device << ", next device = " << next_device << eol;
298 cons << "default device = " << default_device << ", current device = " << current_device << eol;
299 }
300