1 //  portaudio backend
2 //  Copyright (C) 2006 - 2013 Tim Blechmann
3 //
4 //  This program is free software; you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation; either version 2 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; see the file COPYING.  If not, write to
16 //  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 //  Boston, MA 02111-1307, USA.
18 
19 #pragma once
20 
21 #include <cstdio>
22 #include <string>
23 #include <algorithm>
24 
25 #include "portaudio.h"
26 #ifdef HAVE_PORTAUDIO_CONFIG_H
27 #    include "portaudio/portaudio_config.h"
28 #endif /* HAVE_PORTAUDIO_CONFIG_H */
29 
30 #include "audio_backend_common.hpp"
31 #include "utilities/branch_hints.hpp"
32 #include "cpu_time_info.hpp"
33 
34 namespace nova {
35 
36 /** \brief portaudio backend for supernova
37  *
38  *  */
39 template <typename engine_functor, typename sample_type = float, bool blocking = false>
40 class portaudio_backend : public detail::audio_backend_base<sample_type, float, blocking, false>,
41                           public detail::audio_settings_basic,
42                           protected engine_functor {
43     typedef detail::audio_backend_base<sample_type, float, blocking, false> super;
44 
45 public:
portaudio_backend(void)46     portaudio_backend(void) {
47         int err = Pa_Initialize();
48         report_error(err, true);
49 
50         list_devices();
51 
52 #ifdef PA_HAVE_JACK
53         PaJack_SetClientName("SuperNova");
54 #endif
55     }
56 
~portaudio_backend(void)57     ~portaudio_backend(void) {
58         if (audio_is_active())
59             deactivate_audio();
60 
61         close_stream();
62 
63         int err = Pa_Terminate();
64         report_error(err);
65     }
66 
get_audio_blocksize(void) const67     uint32_t get_audio_blocksize(void) const { return blocksize_; }
68 
get_latency(void) const69     uint32_t get_latency(void) const { return latency_; }
70 
71 private:
report_error(int err,bool throw_exception=false)72     static void report_error(int err, bool throw_exception = false) {
73         if (err < 0) {
74             engine_functor::log_printf_("PortAudio error: %s\n", Pa_GetErrorText(err));
75             if (throw_exception)
76                 throw std::runtime_error("PortAudio error");
77         }
78     }
79 
80 public:
list_devices(void)81     static void list_devices(void) {
82         int device_number = Pa_GetDeviceCount();
83         if (device_number < 0)
84             report_error(device_number);
85 
86         printf("Available Audio Devices:\n");
87         for (int i = 0; i != device_number; ++i) {
88             const PaDeviceInfo* device_info = Pa_GetDeviceInfo(i);
89             if (device_info) {
90                 printf("%d: %s (%d inputs, %d outputs)\n", i, device_info->name, device_info->maxInputChannels,
91                        device_info->maxOutputChannels);
92             }
93         }
94         printf("\n");
95     }
96 
match_device(std::string const & device,int & r_device_index)97     bool match_device(std::string const& device, int& r_device_index) {
98         if (device.empty())
99             return true;
100 
101         int device_number = Pa_GetDeviceCount();
102         if (device_number < 0) {
103             report_error(device_number);
104             return false;
105         }
106 
107         for (int i = 0; i != device_number; ++i) {
108             if (device_name(i) == device) {
109                 r_device_index = i;
110                 return true;
111             }
112         }
113         return false;
114     }
115 
report_latency()116     void report_latency() {
117         const PaStreamInfo* psi = Pa_GetStreamInfo(stream);
118         if (psi) {
119             fprintf(stdout, "  Sample rate: %.3f\n", psi->sampleRate);
120             fprintf(stdout, "  Latency (in/out): %.3f / %.3f sec\n", psi->inputLatency, psi->outputLatency);
121         }
122     }
123 
open_stream(std::string const & input_device,unsigned int inchans,std::string const & output_device,unsigned int outchans,unsigned int samplerate,unsigned int pa_blocksize,int h_blocksize)124     bool open_stream(std::string const& input_device, unsigned int inchans, std::string const& output_device,
125                      unsigned int outchans, unsigned int samplerate, unsigned int pa_blocksize, int h_blocksize) {
126         int input_device_index, output_device_index;
127         if (!match_device(input_device, input_device_index) || !match_device(output_device, output_device_index))
128             return false;
129 
130         PaStreamParameters in_parameters, out_parameters;
131         PaTime suggestedLatencyIn, suggestedLatencyOut;
132 
133         if (h_blocksize == 0) {
134             if (inchans)
135                 suggestedLatencyIn = Pa_GetDeviceInfo(input_device_index)->defaultHighInputLatency;
136             if (outchans)
137                 suggestedLatencyOut = Pa_GetDeviceInfo(output_device_index)->defaultHighOutputLatency;
138         } else {
139             if (h_blocksize < 0) {
140                 if (inchans)
141                     suggestedLatencyIn = Pa_GetDeviceInfo(input_device_index)->defaultLowInputLatency;
142                 if (outchans)
143                     suggestedLatencyOut = Pa_GetDeviceInfo(output_device_index)->defaultLowOutputLatency;
144             } else
145                 suggestedLatencyIn = suggestedLatencyOut = (double)h_blocksize / (double)samplerate;
146         }
147 
148         if (inchans) {
149             const PaDeviceInfo* device_info = Pa_GetDeviceInfo(input_device_index);
150 
151             inchans = std::min(inchans, (unsigned int)device_info->maxInputChannels);
152 
153             in_parameters.device = input_device_index;
154             in_parameters.channelCount = inchans;
155             in_parameters.sampleFormat = paFloat32 | paNonInterleaved;
156             in_parameters.suggestedLatency = suggestedLatencyIn;
157             in_parameters.hostApiSpecificStreamInfo = nullptr;
158         }
159 
160         if (outchans) {
161             const PaDeviceInfo* device_info = Pa_GetDeviceInfo(output_device_index);
162 
163             outchans = std::min(outchans, (unsigned int)device_info->maxOutputChannels);
164 
165             out_parameters.device = output_device_index;
166             out_parameters.channelCount = outchans;
167             out_parameters.sampleFormat = paFloat32 | paNonInterleaved;
168             out_parameters.suggestedLatency = suggestedLatencyOut;
169             out_parameters.hostApiSpecificStreamInfo = nullptr;
170         }
171 
172         PaStreamParameters* in_stream_parameters = inchans ? &in_parameters : nullptr;
173         PaStreamParameters* out_stream_parameters = outchans ? &out_parameters : nullptr;
174 
175         PaError supported = Pa_IsFormatSupported(in_stream_parameters, out_stream_parameters, samplerate);
176         report_error(supported);
177         if (supported != 0)
178             return false;
179 
180         engine_initalised = false;
181         blocksize_ = pa_blocksize;
182 
183         PaError opened = Pa_OpenStream(&stream, in_stream_parameters, out_stream_parameters, samplerate, pa_blocksize,
184                                        paNoFlag, &portaudio_backend::pa_process, this);
185 
186         report_error(opened);
187 
188         if (opened != paNoError)
189             return false;
190         else {
191             const PaStreamInfo* psi = Pa_GetStreamInfo(stream);
192             if (psi)
193                 latency_ = (uint32_t)(psi->outputLatency * psi->sampleRate);
194             fprintf(stdout, "  latency: %d\n", latency_);
195         }
196 
197         input_channels = inchans;
198         super::input_samples.resize(inchans);
199         output_channels = outchans;
200         super::output_samples.resize(outchans);
201         samplerate_ = samplerate;
202 
203         cpu_time_accumulator.resize(samplerate_, blocksize_, 1);
204 
205         return true;
206     }
207 
close_stream(void)208     void close_stream(void) {
209         if (stream == nullptr)
210             return;
211 
212         deactivate_audio();
213 
214         int err = Pa_CloseStream(stream);
215         report_error(err);
216         stream = nullptr;
217     }
218 
activate_audio()219     void activate_audio() {
220         assert(stream);
221         int err = Pa_StartStream(stream);
222         report_error(err);
223     }
224 
audio_is_active(void)225     bool audio_is_active(void) {
226         int is_active = Pa_IsStreamActive(stream);
227         if (is_active == 1)
228             return true;
229         if (is_active == 0)
230             return false;
231 
232         report_error(is_active);
233         return false;
234     }
235 
deactivate_audio()236     void deactivate_audio() {
237         if (audio_is_active()) {
238             PaError err = Pa_StopStream(stream);
239             report_error(err);
240         }
241     }
242 
audiostream_ready(void)243     bool audiostream_ready(void) { return stream; }
244 
get_cpuload(float & peak,float & average) const245     void get_cpuload(float& peak, float& average) const { cpu_time_accumulator.get(peak, average); }
246 
default_device_names()247     std::pair<std::string, std::string> default_device_names() {
248         const PaDeviceIndex default_input = Pa_GetDefaultInputDevice();
249         const PaDeviceIndex default_output = Pa_GetDefaultOutputDevice();
250 
251         std::cout << default_input << " " << default_output;
252 
253         return std::make_pair(device_name(default_input), device_name(default_output));
254     }
255 
256 private:
device_name(PaDeviceIndex device_index)257     std::string device_name(PaDeviceIndex device_index) {
258         const PaDeviceInfo* device_info = Pa_GetDeviceInfo(device_index);
259         return std::string(device_info->name);
260     }
261 
perform(const void * inputBuffer,void * outputBuffer,unsigned long frames,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags statusFlags)262     int perform(const void* inputBuffer, void* outputBuffer, unsigned long frames,
263                 const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
264         if (unlikely(!engine_initalised)) {
265             engine_functor::init_thread();
266             engine_functor::sync_clock();
267             engine_initalised = true;
268         }
269 
270         if (statusFlags & (paInputOverflow | paInputUnderflow | paOutputOverflow | paOutputUnderflow))
271             engine_functor::sync_clock();
272 
273         const float* inputs[input_channels];
274         float* const* in = static_cast<float* const*>(inputBuffer);
275         for (uint16_t i = 0; i != input_channels; ++i)
276             inputs[i] = in[i];
277 
278         float* outputs[output_channels];
279         float** out = static_cast<float**>(outputBuffer);
280         for (uint16_t i = 0; i != output_channels; ++i)
281             outputs[i] = out[i];
282 
283         unsigned long processed = 0;
284         while (processed != frames) {
285             super::fetch_inputs(inputs, blocksize_, input_channels);
286             engine_functor::run_tick();
287             super::deliver_outputs(outputs, blocksize_, output_channels);
288             processed += blocksize_;
289         }
290 
291         cpu_time_accumulator.update(Pa_GetStreamCpuLoad(stream) * 100.0);
292         return paContinue;
293     }
294 
pa_process(const void * input,void * output,unsigned long frame_count,const PaStreamCallbackTimeInfo * time_info,PaStreamCallbackFlags status_flags,void * user_data)295     static int pa_process(const void* input, void* output, unsigned long frame_count,
296                           const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags,
297                           void* user_data) {
298         portaudio_backend* self = static_cast<portaudio_backend*>(user_data);
299         return self->perform(input, output, frame_count, time_info, status_flags);
300     }
301 
302     PaStream* stream = nullptr;
303     uint32_t blocksize_ = 0;
304     bool engine_initalised = false;
305     cpu_time_info cpu_time_accumulator;
306 
307     uint32_t latency_ = 0;
308 };
309 
310 } /* namespace nova */
311