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