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