2012-05-26 14:49:10 +02:00
|
|
|
//=============================================================================
|
|
|
|
// MusE Score
|
|
|
|
// Linux Music Score Editor
|
|
|
|
// $Id: pa.cpp 5662 2012-05-23 07:35:47Z wschweer $
|
|
|
|
//
|
|
|
|
// Copyright (C) 2002-2010 Werner Schweer and others
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License version 2.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
#include "preferences.h"
|
|
|
|
#include "libmscore/score.h"
|
|
|
|
#include "musescore.h"
|
|
|
|
#include "seq.h"
|
|
|
|
#include "pa.h"
|
|
|
|
|
|
|
|
#ifdef USE_ALSA
|
|
|
|
#include "alsa.h"
|
|
|
|
#include "alsamidi.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <portaudio.h>
|
|
|
|
#include "mididriver.h"
|
2016-08-09 00:25:42 +02:00
|
|
|
|
|
|
|
#ifdef USE_PORTMIDI
|
2012-05-26 14:49:10 +02:00
|
|
|
#include "pm.h"
|
2016-08-09 00:25:42 +02:00
|
|
|
#endif
|
2012-05-26 14:49:10 +02:00
|
|
|
|
2013-05-13 18:49:17 +02:00
|
|
|
namespace Ms {
|
|
|
|
|
2012-05-26 14:49:10 +02:00
|
|
|
static PaStream* stream;
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// paCallback
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int paCallback(const void*, void* out, long unsigned frames,
|
|
|
|
const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void *)
|
|
|
|
{
|
2017-05-14 17:43:28 +02:00
|
|
|
seq->setInitialMillisecondTimestampWithLatency();
|
2012-05-26 14:49:10 +02:00
|
|
|
seq->process((unsigned)frames, (float*)out);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Portaudio
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Portaudio::Portaudio(Seq* s)
|
|
|
|
: Driver(s)
|
|
|
|
{
|
2013-06-27 11:57:24 +02:00
|
|
|
_sampleRate = 48000; // will be replaced by device default sample rate
|
2012-05-26 14:49:10 +02:00
|
|
|
initialized = false;
|
2014-06-03 17:14:35 +02:00
|
|
|
state = Transport::STOP;
|
2012-05-26 14:49:10 +02:00
|
|
|
seekflag = false;
|
|
|
|
midiDriver = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// ~Portaudio
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Portaudio::~Portaudio()
|
|
|
|
{
|
|
|
|
if (initialized) {
|
|
|
|
PaError err = Pa_CloseStream(stream);
|
|
|
|
if (err != paNoError)
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio close stream failed: %s", Pa_GetErrorText(err));
|
2012-05-26 14:49:10 +02:00
|
|
|
Pa_Terminate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// init
|
|
|
|
// return false on error
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-04 19:59:33 +02:00
|
|
|
bool Portaudio::init(bool)
|
2012-05-26 14:49:10 +02:00
|
|
|
{
|
|
|
|
PaError err = Pa_Initialize();
|
|
|
|
if (err != paNoError) {
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio initialize failed: %s", Pa_GetErrorText(err));
|
2012-05-26 14:49:10 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
initialized = true;
|
|
|
|
if (MScore::debugMode)
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("using PortAudio Version: %s", Pa_GetVersionText());
|
2012-05-26 14:49:10 +02:00
|
|
|
|
2017-11-29 08:21:36 +01:00
|
|
|
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
|
2017-05-14 17:43:28 +02:00
|
|
|
if (idx < 0) {
|
2012-05-26 14:49:10 +02:00
|
|
|
idx = Pa_GetDefaultOutputDevice();
|
2017-05-14 17:43:28 +02:00
|
|
|
qDebug("No device selected. PortAudio detected %d devices. Will use the default device (index %d).", Pa_GetDeviceCount(), idx);
|
|
|
|
}
|
2012-05-26 14:49:10 +02:00
|
|
|
|
2013-06-27 11:57:24 +02:00
|
|
|
const PaDeviceInfo* di = Pa_GetDeviceInfo(idx);
|
2014-12-11 12:01:30 +01:00
|
|
|
|
Implemented caching signals to prevent not generated sound in case of framesBuff from PortAudio is too small
If we don't specify framesPerBuffer parameter in Pa_OpenStream, PortAudio will choose optimal value for particular callback call. It can vary from run to run even on the same hardware depending on available system resources.
While generating signal, interpolating and applying effects, we assume that framesBuffer contains more than minimal number of frames to generate envelope. BTW, it is not true. If framesBuffer is smaller, algos cannot generate correct sound and just keep silence.
I've implemented cache which keeps generated values from dsp algorithms and applies it step-by-step to buffer values from pa_callback. Cache is filled each time algos generate dsp values. If buffer frames are not enough to generate envelope, algos generate values for further calls and keep it in cache.
Required number of frames has been selected as a number of frames for one phase multiplying by number of phases. Actually, smaller numbers of this value generates good results, but it is better to keep it as max as possible to provide perfect sound.
Code changes:
- Replaced C-like variables with std containers for comfortable debugging and better usage
- Extracted similar code calls to separate methods
- Implemented cache as std constainers, so also implemented convertion from std::vector to C-like float* to fill the pa buffer
- Changed the logic of applying effects and interpolation, it is now possible to use them separately. This is required to fill effects several times after calculating the interpolation is finished.
Removed std::vector<float> to keep cache - process buff values on the fly. Performance is better, but still glitches on https://musescore.com/user/166080/scores/175421. BuffSize = 64 with my wired headphones.
2018-02-24 15:58:22 +01:00
|
|
|
//select default output device if no device or device without output channels have been selected
|
|
|
|
if (di == nullptr || di->maxOutputChannels < 1)
|
2014-02-22 12:24:20 +01:00
|
|
|
di = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());
|
2014-12-11 12:01:30 +01:00
|
|
|
|
2015-05-19 18:33:02 +02:00
|
|
|
if (!di)
|
|
|
|
return false; // Portaudio is not properly initialized; disable audio
|
2013-06-27 11:57:24 +02:00
|
|
|
_sampleRate = int(di->defaultSampleRate);
|
|
|
|
|
2012-05-26 14:49:10 +02:00
|
|
|
/* Open an audio I/O stream. */
|
|
|
|
struct PaStreamParameters out;
|
|
|
|
memset(&out, 0, sizeof(out));
|
|
|
|
|
|
|
|
out.device = idx;
|
|
|
|
out.channelCount = 2;
|
|
|
|
out.sampleFormat = paFloat32;
|
Implemented caching signals to prevent not generated sound in case of framesBuff from PortAudio is too small
If we don't specify framesPerBuffer parameter in Pa_OpenStream, PortAudio will choose optimal value for particular callback call. It can vary from run to run even on the same hardware depending on available system resources.
While generating signal, interpolating and applying effects, we assume that framesBuffer contains more than minimal number of frames to generate envelope. BTW, it is not true. If framesBuffer is smaller, algos cannot generate correct sound and just keep silence.
I've implemented cache which keeps generated values from dsp algorithms and applies it step-by-step to buffer values from pa_callback. Cache is filled each time algos generate dsp values. If buffer frames are not enough to generate envelope, algos generate values for further calls and keep it in cache.
Required number of frames has been selected as a number of frames for one phase multiplying by number of phases. Actually, smaller numbers of this value generates good results, but it is better to keep it as max as possible to provide perfect sound.
Code changes:
- Replaced C-like variables with std containers for comfortable debugging and better usage
- Extracted similar code calls to separate methods
- Implemented cache as std constainers, so also implemented convertion from std::vector to C-like float* to fill the pa buffer
- Changed the logic of applying effects and interpolation, it is now possible to use them separately. This is required to fill effects several times after calculating the interpolation is finished.
Removed std::vector<float> to keep cache - process buff values on the fly. Performance is better, but still glitches on https://musescore.com/user/166080/scores/175421. BuffSize = 64 with my wired headphones.
2018-02-24 15:58:22 +01:00
|
|
|
out.suggestedLatency = di->defaultLowOutputLatency;
|
2012-05-26 14:49:10 +02:00
|
|
|
out.hostApiSpecificStreamInfo = 0;
|
|
|
|
|
|
|
|
err = Pa_OpenStream(&stream, 0, &out, double(_sampleRate), 0, 0, paCallback, (void*)this);
|
|
|
|
if (err != paNoError) {
|
|
|
|
// fall back to default device:
|
|
|
|
out.device = Pa_GetDefaultOutputDevice();
|
|
|
|
err = Pa_OpenStream(&stream, 0, &out, double(_sampleRate), 0, 0, paCallback, (void*)this);
|
|
|
|
if (err != paNoError) {
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio open stream %d failed: %s", idx, Pa_GetErrorText(err));
|
2012-05-26 14:49:10 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2013-06-27 12:06:48 +02:00
|
|
|
const PaStreamInfo* si = Pa_GetStreamInfo(stream);
|
|
|
|
if (si)
|
|
|
|
_sampleRate = int(si->sampleRate);
|
2012-05-26 14:49:10 +02:00
|
|
|
#ifdef USE_ALSA
|
|
|
|
midiDriver = new AlsaMidiDriver(seq);
|
|
|
|
#endif
|
|
|
|
#ifdef USE_PORTMIDI
|
|
|
|
midiDriver = new PortMidiDriver(seq);
|
|
|
|
#endif
|
|
|
|
if (midiDriver && !midiDriver->init()) {
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Init midi driver failed");
|
2012-05-26 14:49:10 +02:00
|
|
|
delete midiDriver;
|
|
|
|
midiDriver = 0;
|
|
|
|
#ifdef USE_PORTMIDI
|
|
|
|
return true; // return OK for audio driver; midi is only input
|
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// apiList
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QStringList Portaudio::apiList() const
|
|
|
|
{
|
|
|
|
QStringList al;
|
|
|
|
|
|
|
|
PaHostApiIndex apis = Pa_GetHostApiCount();
|
|
|
|
for (PaHostApiIndex i = 0; i < apis; ++i) {
|
|
|
|
const PaHostApiInfo* info = Pa_GetHostApiInfo(i);
|
|
|
|
if (info)
|
2016-03-16 14:35:34 +01:00
|
|
|
al.append(QString::fromLocal8Bit(info->name));
|
2012-05-26 14:49:10 +02:00
|
|
|
}
|
|
|
|
return al;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// deviceList
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QStringList Portaudio::deviceList(int apiIdx)
|
|
|
|
{
|
|
|
|
QStringList dl;
|
|
|
|
const PaHostApiInfo* info = Pa_GetHostApiInfo(apiIdx);
|
|
|
|
if (info) {
|
|
|
|
for (int i = 0; i < info->deviceCount; ++i) {
|
|
|
|
PaDeviceIndex idx = Pa_HostApiDeviceIndexToDeviceIndex(apiIdx, i);
|
|
|
|
const PaDeviceInfo* di = Pa_GetDeviceInfo(idx);
|
|
|
|
if (di)
|
2016-03-16 14:35:34 +01:00
|
|
|
dl.append(QString::fromLocal8Bit(di->name));
|
2012-05-26 14:49:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return dl;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// deviceIndex
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Portaudio::deviceIndex(int apiIdx, int apiDevIdx)
|
|
|
|
{
|
|
|
|
return Pa_HostApiDeviceIndexToDeviceIndex(apiIdx, apiDevIdx);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// start
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-30 15:38:54 +02:00
|
|
|
bool Portaudio::start(bool)
|
2012-05-26 14:49:10 +02:00
|
|
|
{
|
|
|
|
PaError err = Pa_StartStream(stream);
|
|
|
|
if (err != paNoError) {
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio: start stream failed: %s", Pa_GetErrorText(err));
|
2012-05-26 14:49:10 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// stop
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Portaudio::stop()
|
|
|
|
{
|
|
|
|
PaError err = Pa_StopStream(stream); // sometimes the program hangs here on exit
|
|
|
|
if (err != paNoError) {
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio: stop failed: %s", Pa_GetErrorText(err));
|
2012-05-26 14:49:10 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// framePos
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Portaudio::framePos() const
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// startTransport
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Portaudio::startTransport()
|
|
|
|
{
|
2014-06-03 17:14:35 +02:00
|
|
|
state = Transport::PLAY;
|
2012-05-26 14:49:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// stopTransport
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Portaudio::stopTransport()
|
|
|
|
{
|
2014-06-03 17:14:35 +02:00
|
|
|
state = Transport::STOP;
|
2012-05-26 14:49:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// getState
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-06-03 17:14:35 +02:00
|
|
|
Transport Portaudio::getState()
|
2012-05-26 14:49:10 +02:00
|
|
|
{
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// midiRead
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Portaudio::midiRead()
|
|
|
|
{
|
|
|
|
if (midiDriver)
|
|
|
|
midiDriver->read();
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:43:28 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// putEvent
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-04-02 01:35:20 +02:00
|
|
|
#ifdef USE_PORTMIDI
|
|
|
|
|
2017-05-14 17:43:28 +02:00
|
|
|
// Prevent killing sequencer with wrong data
|
|
|
|
#define less128(__less) ((__less >=0 && __less <= 127) ? __less : 0)
|
|
|
|
|
|
|
|
// TODO: this was copied from Jack version...I'd like to eventually unify these two, so that they handle midi event types in the same manner
|
|
|
|
void Portaudio::putEvent(const NPlayEvent& e, unsigned framePos)
|
|
|
|
{
|
|
|
|
PortMidiDriver* portMidiDriver = static_cast<PortMidiDriver*>(midiDriver);
|
|
|
|
if (!portMidiDriver || !portMidiDriver->getOutputStream() || !portMidiDriver->canOutput())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int portIdx = seq->score()->midiPort(e.channel());
|
|
|
|
int chan = seq->score()->midiChannel(e.channel());
|
|
|
|
|
|
|
|
if (portIdx < 0 ) {
|
|
|
|
qDebug("Portaudio::putEvent: invalid port %d", portIdx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (midiOutputTrace) {
|
|
|
|
int a = e.dataA();
|
|
|
|
int b = e.dataB();
|
|
|
|
qDebug("MidiOut<%d>: Portaudio: %02x %02x %02x, chan: %i", portIdx, e.type(), a, b, chan);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(e.type()) {
|
|
|
|
case ME_NOTEON:
|
|
|
|
case ME_NOTEOFF:
|
|
|
|
case ME_POLYAFTER:
|
|
|
|
case ME_CONTROLLER:
|
|
|
|
// Catch CTRL_PROGRAM and let other ME_CONTROLLER events to go
|
|
|
|
if (e.dataA() == CTRL_PROGRAM) {
|
|
|
|
// Convert CTRL_PROGRAM event to ME_PROGRAM
|
|
|
|
long msg = Pm_Message(ME_PROGRAM | chan, less128(e.dataB()), 0);
|
|
|
|
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
|
|
|
|
if (error != pmNoError) {
|
|
|
|
qDebug("Portaudio: error %d", error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2017-06-09 13:30:29 +02:00
|
|
|
// fall through
|
2017-05-14 17:43:28 +02:00
|
|
|
case ME_PITCHBEND:
|
|
|
|
{
|
|
|
|
long msg = Pm_Message(e.type() | chan, less128(e.dataA()), less128(e.dataB()));
|
|
|
|
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
|
|
|
|
if (error != pmNoError) {
|
|
|
|
qDebug("Portaudio: error %d", error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_PROGRAM:
|
|
|
|
case ME_AFTERTOUCH:
|
|
|
|
{
|
|
|
|
long msg = Pm_Message(e.type() | chan, less128(e.dataA()), 0);
|
|
|
|
PmError error = Pm_WriteShort(portMidiDriver->getOutputStream(), seq->getCurrentMillisecondTimestampWithLatency(framePos), msg);
|
|
|
|
if (error != pmNoError) {
|
|
|
|
qDebug("Portaudio: error %d", error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ME_SONGPOS:
|
|
|
|
case ME_CLOCK:
|
|
|
|
case ME_START:
|
|
|
|
case ME_CONTINUE:
|
|
|
|
case ME_STOP:
|
|
|
|
qDebug("Portaudio: event type %x not supported", e.type());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-04-02 01:35:20 +02:00
|
|
|
#endif
|
2017-05-14 17:43:28 +02:00
|
|
|
|
2012-05-26 14:49:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// currentApi
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Portaudio::currentApi() const
|
|
|
|
{
|
2017-11-29 08:21:36 +01:00
|
|
|
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
|
2012-05-26 14:49:10 +02:00
|
|
|
if (idx < 0)
|
|
|
|
idx = Pa_GetDefaultOutputDevice();
|
|
|
|
|
|
|
|
for (int api = 0; api < Pa_GetHostApiCount(); ++api) {
|
|
|
|
const PaHostApiInfo* info = Pa_GetHostApiInfo(api);
|
|
|
|
if (info) {
|
|
|
|
for (int k = 0; k < info->deviceCount; ++k) {
|
|
|
|
PaDeviceIndex i = Pa_HostApiDeviceIndexToDeviceIndex(api, k);
|
|
|
|
if (i == idx)
|
|
|
|
return api;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio: no current api found for device %d", idx);
|
2012-05-26 14:49:10 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// currentDevice
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Portaudio::currentDevice() const
|
|
|
|
{
|
2017-11-29 08:21:36 +01:00
|
|
|
PaDeviceIndex idx = preferences.getInt(PREF_IO_PORTAUDIO_DEVICE);
|
2012-05-26 14:49:10 +02:00
|
|
|
if (idx < 0)
|
|
|
|
idx = Pa_GetDefaultOutputDevice();
|
|
|
|
|
|
|
|
for (int api = 0; api < Pa_GetHostApiCount(); ++api) {
|
|
|
|
const PaHostApiInfo* info = Pa_GetHostApiInfo(api);
|
|
|
|
if (info) {
|
|
|
|
for (int k = 0; k < info->deviceCount; ++k) {
|
|
|
|
PaDeviceIndex i = Pa_HostApiDeviceIndexToDeviceIndex(api, k);
|
|
|
|
if (i == idx)
|
|
|
|
return k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-03 20:20:36 +02:00
|
|
|
qDebug("Portaudio: no current ApiDevice found for device %d", idx);
|
2012-05-26 14:49:10 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2013-05-13 18:49:17 +02:00
|
|
|
}
|
2012-05-26 14:49:10 +02:00
|
|
|
|