Newer
Older
/* Copyright (c) 2001 Miller Puckette and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */
/* this file calls Ross Bencina's and Phil Burk's Portaudio package. It's
the main way in for Mac OS and, with Michael Casey's help, also into
Ivica Bukvic
committed
ASIO in Windows.
Both blocking and non-blocking call styles are supported. If non-blocking
is requested, either we call portaudio in non-blocking mode, or else we
call portaudio in callback mode and manage our own FIFO se we can offer
Pd "blocking" I/O calls. To do the latter we define FAKEBLOCKING; this
works better in MAXOSX (gets 40 msec lower latency!) and might also in
Windows. If FAKEBLOCKING is defined we can choose between two methods
for waiting on the (presumebly other-thread) I/O to complete, either
correct thread synchronization (by defining THREADSIGNAL) or just sleeping
and polling; the latter seems to work better so far.
*/
Ivica Bukvic
committed
/* dolist...
switch to usleep in s_inter.c
*/
#include "m_pd.h"
#include "s_stuff.h"
#include <stdio.h>
#include <stdlib.h>
Ivica Bukvic
committed
#include <string.h>
Ivica Bukvic
committed
#ifdef _MSC_VER
#define snprintf sprintf_s
#endif
#ifndef _WIN32 /* for the "dup2" workaround -- do we still need it? */
#include <unistd.h>
#endif
#ifdef HAVE_ALLOCA_H /* ifdef nonsense to find include for alloca() */
# include <alloca.h> /* linux, mac, mingw, cygwin */
#elif defined _MSC_VER
# include <malloc.h> /* MSVC */
Ivica Bukvic
committed
# include <stddef.h> /* BSDs for example */
#endif /* end alloca() ifdef nonsense */
#if 1
#define FAKEBLOCKING
Ivica Bukvic
committed
#if defined (FAKEBLOCKING) && defined(_WIN32)
#include <windows.h> /* for Sleep() */
#endif
/* define this to enable thread signaling instead of polling */
/* #define THREADSIGNAL */
/* LATER try to figure out how to handle default devices in portaudio;
the way s_audio.c handles them isn't going to work here. */
/* public interface declared in m_imp.h */
/* implementation */
Ivica Bukvic
committed
static PaStream *pa_stream;
static int pa_inchans, pa_outchans;
static float *pa_soundin, *pa_soundout;
Ivica Bukvic
committed
static int pa_started;
static int pa_nbuffers;
static int pa_dio_error;
Ivica Bukvic
committed
#ifdef FAKEBLOCKING
#include "s_audio_paring.h"
static PA_VOLATILE char *pa_outbuf;
static PA_VOLATILE sys_ringbuf pa_outring;
static PA_VOLATILE char *pa_inbuf;
static PA_VOLATILE sys_ringbuf pa_inring;
#ifdef THREADSIGNAL
#include <pthread.h>
pthread_mutex_t pa_mutex;
pthread_cond_t pa_sem;
#endif /* THREADSIGNAL */
#endif /* FAKEBLOCKING */
static void pa_init(void) /* Initialize PortAudio */
{
static int initialized;
if (!initialized)
{
Ivica Bukvic
committed
#ifdef __APPLE__
/* for some reason, on the Mac Pa_Initialize() closes file descriptor
1 (standard output) As a workaround, dup it to another number and dup2
it back afterward. */
Ivica Bukvic
committed
int another = open("/dev/null", 0);
dup2(another, 1);
Ivica Bukvic
committed
close(1);
close(another);
Ivica Bukvic
committed
fflush(stdout);
dup2(newfd, 1);
close(newfd);
}
Ivica Bukvic
committed
Ivica Bukvic
committed
post("Error opening audio: %s", err, Pa_GetErrorText(err));
return;
}
initialized = 1;
}
}
static int pa_lowlevel_callback(const void *inputBuffer,
Ivica Bukvic
committed
void *outputBuffer, unsigned long nframes,
const PaStreamCallbackTimeInfo *outTime, PaStreamCallbackFlags myflags,
void *userData)
{
int i;
Ivica Bukvic
committed
unsigned int n, j;
Ivica Bukvic
committed
if (nframes % DEFDACBLKSIZE)
Ivica Bukvic
committed
post("warning: audio nframes %ld not a multiple of blocksize %d",
nframes, (int)DEFDACBLKSIZE);
nframes -= (nframes % DEFDACBLKSIZE);
Ivica Bukvic
committed
for (n = 0; n < nframes; n += DEFDACBLKSIZE)
Ivica Bukvic
committed
if (inputBuffer != NULL)
{
fbuf = ((float *)inputBuffer) + n*pa_inchans;
soundiop = pa_soundin;
for (i = 0, fp2 = fbuf; i < pa_inchans; i++, fp2++)
for (j = 0, fp3 = fp2; j < DEFDACBLKSIZE;
j++, fp3 += pa_inchans)
*soundiop++ = *fp3;
}
else memset((void *)pa_soundin, 0,
DEFDACBLKSIZE * pa_inchans * sizeof(float));
memset((void *)pa_soundout, 0,
DEFDACBLKSIZE * pa_outchans * sizeof(float));
(*pa_callback)();
if (outputBuffer != NULL)
{
fbuf = ((float *)outputBuffer) + n*pa_outchans;
soundiop = pa_soundout;
for (i = 0, fp2 = fbuf; i < pa_outchans; i++, fp2++)
for (j = 0, fp3 = fp2; j < DEFDACBLKSIZE;
j++, fp3 += pa_outchans)
*fp3 = *soundiop++;
}
Ivica Bukvic
committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
return 0;
}
#ifdef FAKEBLOCKING
/* callback for "non-callback" case in which we actualy open portaudio
in callback mode but fake "blocking mode". We communicate with the
main thread via FIFO. First read the audio output FIFO (which
we sync on, not waiting for it but supplying zeros to the audio output if
there aren't enough samples in the FIFO when we are called), then write
to the audio input FIFO. The main thread will wait for the input fifo.
We can either throw it a pthreads condition or just allow the main thread
to poll for us; so far polling seems to work better. */
static int pa_fifo_callback(const void *inputBuffer,
void *outputBuffer, unsigned long nframes,
const PaStreamCallbackTimeInfo *outTime, PaStreamCallbackFlags myflags,
void *userData)
{
/* callback routine for non-callback client... throw samples into
and read them out of a FIFO */
int ch;
long fiforoom;
float *fbuf;
#if CHECKFIFOS
if (pa_inchans * sys_ringbuf_getreadavailable(&pa_outring) !=
pa_outchans * sys_ringbuf_getwriteavailable(&pa_inring))
post("warning: in and out rings unequal (%d, %d)",
sys_ringbuf_getreadavailable(&pa_outring),
sys_ringbuf_getwriteavailable(&pa_inring));
#endif
fiforoom = sys_ringbuf_getreadavailable(&pa_outring);
if ((unsigned)fiforoom >= nframes*pa_outchans*sizeof(float))
Ivica Bukvic
committed
if (outputBuffer)
sys_ringbuf_read(&pa_outring, outputBuffer,
nframes*pa_outchans*sizeof(float), pa_outbuf);
else if (pa_outchans)
post("audio error: no outputBuffer but output channels");
if (inputBuffer)
sys_ringbuf_write(&pa_inring, inputBuffer,
nframes*pa_inchans*sizeof(float), pa_inbuf);
else if (pa_inchans)
post("audio error: no inputBuffer but input channels");
}
else
{ /* PD could not keep up; generate zeros */
if (pa_started)
pa_dio_error = 1;
if (outputBuffer)
{
for (ch = 0; ch < pa_outchans; ch++)
{
unsigned long frame;
fbuf = ((float *)outputBuffer) + ch;
for (frame = 0; frame < nframes; frame++, fbuf += pa_outchans)
*fbuf = 0;
}
}
Ivica Bukvic
committed
#ifdef THREADSIGNAL
pthread_mutex_lock(&pa_mutex);
pthread_cond_signal(&pa_sem);
pthread_mutex_unlock(&pa_mutex);
#endif
Ivica Bukvic
committed
#endif /* FAKEBLOCKING */
PaError pa_open_callback(double sampleRate, int inchannels, int outchannels,
Ivica Bukvic
committed
int framesperbuf, int nbuffers, int indeviceno, int outdeviceno, PaStreamCallback *callbackfn)
{
long bytesPerSample;
PaError err;
PaStreamParameters instreamparams, outstreamparams;
Ivica Bukvic
committed
PaStreamParameters*p_instreamparams=0, *p_outstreamparams=0;
/* fprintf(stderr, "nchan %d, flags %d, bufs %d, framesperbuf %d\n",
nchannels, flags, nbuffers, framesperbuf); */
instreamparams.device = indeviceno;
instreamparams.channelCount = inchannels;
instreamparams.sampleFormat = paFloat32;
instreamparams.hostApiSpecificStreamInfo = 0;
outstreamparams.device = outdeviceno;
outstreamparams.channelCount = outchannels;
outstreamparams.sampleFormat = paFloat32;
outstreamparams.hostApiSpecificStreamInfo = 0;
Ivica Bukvic
committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#ifdef FAKEBLOCKING
instreamparams.suggestedLatency = outstreamparams.suggestedLatency = 0;
#else
instreamparams.suggestedLatency = outstreamparams.suggestedLatency =
nbuffers*framesperbuf/sampleRate;
#endif /* FAKEBLOCKING */
if( inchannels>0 && indeviceno >= 0)
p_instreamparams=&instreamparams;
if( outchannels>0 && outdeviceno >= 0)
p_outstreamparams=&outstreamparams;
err=Pa_IsFormatSupported(p_instreamparams, p_outstreamparams, sampleRate);
if (paFormatIsSupported != err)
{
/* check whether we have to change the numbers of channel and/or samplerate */
const PaDeviceInfo* info = 0;
double inRate=0, outRate=0;
if (inchannels>0)
{
if (NULL != (info = Pa_GetDeviceInfo( instreamparams.device )))
{
inRate=info->defaultSampleRate;
if(info->maxInputChannels<inchannels)
instreamparams.channelCount=info->maxInputChannels;
}
}
if (outchannels>0)
{
if (NULL != (info = Pa_GetDeviceInfo( outstreamparams.device )))
{
outRate=info->defaultSampleRate;
if(info->maxOutputChannels<outchannels)
outstreamparams.channelCount=info->maxOutputChannels;
}
}
if (err == paInvalidSampleRate)
{
sampleRate=outRate;
}
err=Pa_IsFormatSupported(p_instreamparams, p_outstreamparams,
sampleRate);
if (paFormatIsSupported != err)
goto error;
}
Ivica Bukvic
committed
&pa_stream,
p_instreamparams,
p_outstreamparams,
Ivica Bukvic
committed
framesperbuf,
paNoFlag, /* portaudio will clip for us */
Ivica Bukvic
committed
callbackfn,
0);
if (err != paNoError)
goto error;
Ivica Bukvic
committed
err = Pa_StartStream(pa_stream);
Ivica Bukvic
committed
post("error opening failed; closing audio stream: %s",
Pa_GetErrorText(err));
pa_close_audio();
Ivica Bukvic
committed
sys_dacsr=sampleRate;
return paNoError;
error:
pa_stream = NULL;
return err;
}
int pa_open_audio(int inchans, int outchans, int rate, t_sample *soundin,
t_sample *soundout, int framesperbuf, int nbuffers,
int indeviceno, int outdeviceno, t_audiocallback callbackfn)
Ivica Bukvic
committed
int j, devno, pa_indev = -1, pa_outdev = -1;
/* fprintf(stderr, "open callback %d\n", (callbackfn != 0)); */
/* post("in %d out %d rate %d device %d", inchans, outchans, rate, deviceno); */
Ivica Bukvic
committed
if (pa_stream)
pa_close_audio();
if (inchans > 0)
{
for (j = 0, devno = 0; j < Pa_GetDeviceCount(); j++)
{
const PaDeviceInfo *info = Pa_GetDeviceInfo(j);
if (info->maxInputChannels > 0)
{
if (devno == indeviceno)
{
Ivica Bukvic
committed
if (inchans > info->maxInputChannels)
inchans = info->maxInputChannels;
pa_indev = j;
break;
}
devno++;
}
}
}
if (outchans > 0)
{
for (j = 0, devno = 0; j < Pa_GetDeviceCount(); j++)
{
const PaDeviceInfo *info = Pa_GetDeviceInfo(j);
if (info->maxOutputChannels > 0)
{
if (devno == outdeviceno)
{
Ivica Bukvic
committed
if (outchans > info->maxOutputChannels)
outchans = info->maxOutputChannels;
pa_outdev = j;
break;
}
devno++;
}
}
}
Ivica Bukvic
committed
if (inchans > 0 && pa_indev == -1)
inchans = 0;
if (outchans > 0 && pa_outdev == -1)
outchans = 0;
if (sys_verbose)
{
post("input device %d, channels %d", pa_indev, inchans);
post("output device %d, channels %d", pa_outdev, outchans);
post("framesperbuf %d, nbufs %d", framesperbuf, nbuffers);
Ivica Bukvic
committed
post("rate %d", rate);
Ivica Bukvic
committed
pa_inchans = sys_inchannels = inchans;
pa_outchans = sys_outchannels = outchans;
pa_soundin = soundin;
pa_soundout = soundout;
Ivica Bukvic
committed
#ifdef FAKEBLOCKING
if (pa_inbuf)
free((char *)pa_inbuf), pa_inbuf = 0;
if (pa_outbuf)
free((char *)pa_outbuf), pa_outbuf = 0;
#endif
Ivica Bukvic
committed
return (0);
if (callbackfn)
{
pa_callback = callbackfn;
err = pa_open_callback(rate, inchans, outchans,
Ivica Bukvic
committed
framesperbuf, nbuffers, pa_indev, pa_outdev, pa_lowlevel_callback);
Ivica Bukvic
committed
#ifdef FAKEBLOCKING
if (pa_inchans)
{
pa_inbuf = malloc(nbuffers*framesperbuf*pa_inchans*sizeof(float));
sys_ringbuf_init(&pa_inring,
nbuffers*framesperbuf*pa_inchans*sizeof(float), pa_inbuf,
nbuffers*framesperbuf*pa_inchans*sizeof(float));
}
if (pa_outchans)
{
pa_outbuf = malloc(nbuffers*framesperbuf*pa_outchans*sizeof(float));
sys_ringbuf_init(&pa_outring,
nbuffers*framesperbuf*pa_outchans*sizeof(float), pa_outbuf, 0);
}
err = pa_open_callback(rate, inchans, outchans,
framesperbuf, nbuffers, pa_indev, pa_outdev, pa_fifo_callback);
#else
err = pa_open_callback(rate, inchans, outchans,
framesperbuf, nbuffers, pa_indev, pa_outdev, 0);
#endif
Ivica Bukvic
committed
pa_started = 0;
pa_nbuffers = nbuffers;
Ivica Bukvic
committed
post("Error opening audio: %s", Pa_GetErrorText(err));
/* Pa_Terminate(); */
return (1);
}
else if (sys_verbose)
post("... opened OK.");
return (0);
}
void pa_close_audio( void)
{
Ivica Bukvic
committed
if (pa_stream)
{
Pa_AbortStream(pa_stream);
Pa_CloseStream(pa_stream);
}
pa_stream = 0;
#ifdef FAKEBLOCKING
if (pa_inbuf)
free((char *)pa_inbuf), pa_inbuf = 0;
if (pa_outbuf)
free((char *)pa_outbuf), pa_outbuf = 0;
#endif
Ivica Bukvic
committed
t_sample *fp;
float *fp2, *fp3;
float *conversionbuf;
int j, k;
int rtnval = SENDDACS_YES;
#ifndef FAKEBLOCKING
Ivica Bukvic
committed
#endif /* FAKEBLOCKING */
if (!sys_inchannels && !sys_outchannels || !pa_stream)
return (SENDDACS_NO);
conversionbuf = (float *)alloca((sys_inchannels > sys_outchannels?
sys_inchannels:sys_outchannels) * DEFDACBLKSIZE * sizeof(float));
#ifdef FAKEBLOCKING
if (!sys_inchannels) /* if no input channels sync on output */
Ivica Bukvic
committed
#ifdef THREADSIGNAL
pthread_mutex_lock(&pa_mutex);
#endif
while (sys_ringbuf_getwriteavailable(&pa_outring) <
(long)(sys_outchannels * DEFDACBLKSIZE * sizeof(float)))
Ivica Bukvic
committed
rtnval = SENDDACS_SLEPT;
#ifdef THREADSIGNAL
pthread_cond_wait(&pa_sem, &pa_mutex);
#else
#ifdef _WIN32
Sleep(1);
#else
usleep(1000);
#endif /* _WIN32 */
#endif /* THREADSIGNAL */
Ivica Bukvic
committed
#ifdef THREADSIGNAL
pthread_mutex_unlock(&pa_mutex);
#endif
Ivica Bukvic
committed
/* write output */
if (sys_outchannels)
Ivica Bukvic
committed
for (j = 0, fp = sys_soundout, fp2 = conversionbuf;
j < sys_outchannels; j++, fp2++)
for (k = 0, fp3 = fp2; k < DEFDACBLKSIZE;
k++, fp++, fp3 += sys_outchannels)
*fp3 = *fp;
sys_ringbuf_write(&pa_outring, conversionbuf,
sys_outchannels*(DEFDACBLKSIZE*sizeof(float)), pa_outbuf);
Ivica Bukvic
committed
if (sys_inchannels) /* if there is input sync on it */
{
#ifdef THREADSIGNAL
pthread_mutex_lock(&pa_mutex);
#endif
while (sys_ringbuf_getreadavailable(&pa_inring) <
(long)(sys_inchannels * DEFDACBLKSIZE * sizeof(float)))
Ivica Bukvic
committed
rtnval = SENDDACS_SLEPT;
#ifdef THREADSIGNAL
pthread_cond_wait(&pa_sem, &pa_mutex);
#else
#ifdef _WIN32
Sleep(1);
#else
usleep(1000);
#endif /* _WIN32 */
#endif /* THREADSIGNAL */
Ivica Bukvic
committed
#ifdef THREADSIGNAL
pthread_mutex_unlock(&pa_mutex);
Ivica Bukvic
committed
}
if (sys_inchannels)
Ivica Bukvic
committed
sys_ringbuf_read(&pa_inring, conversionbuf,
sys_inchannels*(DEFDACBLKSIZE*sizeof(float)), pa_inbuf);
for (j = 0, fp = sys_soundin, fp2 = conversionbuf;
j < sys_inchannels; j++, fp2++)
for (k = 0, fp3 = fp2; k < DEFDACBLKSIZE;
k++, fp++, fp3 += sys_inchannels)
*fp = *fp3;
Ivica Bukvic
committed
#else /* FAKEBLOCKING */
timebefore = sys_getrealtime();
/* write output */
if (sys_outchannels)
Ivica Bukvic
committed
if (!pa_started)
{
memset(conversionbuf, 0,
sys_outchannels * DEFDACBLKSIZE * sizeof(float));
for (j = 0; j < pa_nbuffers-1; j++)
Pa_WriteStream(pa_stream, conversionbuf, DEFDACBLKSIZE);
}
for (j = 0, fp = sys_soundout, fp2 = conversionbuf;
j < sys_outchannels; j++, fp2++)
for (k = 0, fp3 = fp2; k < DEFDACBLKSIZE;
k++, fp++, fp3 += sys_outchannels)
*fp3 = *fp;
Pa_WriteStream(pa_stream, conversionbuf, DEFDACBLKSIZE);
Ivica Bukvic
committed
if (sys_inchannels)
Ivica Bukvic
committed
Pa_ReadStream(pa_stream, conversionbuf, DEFDACBLKSIZE);
for (j = 0, fp = sys_soundin, fp2 = conversionbuf;
j < sys_inchannels; j++, fp2++)
for (k = 0, fp3 = fp2; k < DEFDACBLKSIZE;
k++, fp++, fp3 += sys_inchannels)
*fp = *fp3;
Ivica Bukvic
committed
if (sys_getrealtime() - timebefore > 0.002)
Ivica Bukvic
committed
rtnval = SENDDACS_SLEPT;
Ivica Bukvic
committed
#endif /* FAKEBLOCKING */
pa_started = 1;
Ivica Bukvic
committed
memset(sys_soundout, 0, DEFDACBLKSIZE*sizeof(t_sample)*sys_outchannels);
return (rtnval);
}
/* scanning for devices */
void pa_getdevs(char *indevlist, int *nindevs,
char *outdevlist, int *noutdevs, int *canmulti,
int maxndev, int devdescsize)
{
int i, nin = 0, nout = 0, ndev;
*canmulti = 1; /* one dev each for input and output */
ndev = Pa_GetDeviceCount();
for (i = 0; i < ndev; i++)
{
const PaDeviceInfo *pdi = Pa_GetDeviceInfo(i);
if (pdi->maxInputChannels > 0 && nin < maxndev)
{
Ivica Bukvic
committed
/* LATER figure out how to get API name correctly */
snprintf(indevlist + nin * devdescsize, devdescsize,
#ifdef _WIN32
"%s:%s", (pdi->hostApi == 0 ? "MMIO" : (pdi->hostApi == 1 ? "ASIO" : "?")),
#else
#ifdef __APPLE__
"%s",
#else
"(%d) %s", pdi->hostApi,
#endif
#endif
pdi->name);
nin++;
}
if (pdi->maxOutputChannels > 0 && nout < maxndev)
{
Ivica Bukvic
committed
snprintf(outdevlist + nout * devdescsize, devdescsize,
#ifdef _WIN32
"%s:%s", (pdi->hostApi == 0 ? "MMIO" : (pdi->hostApi == 1 ? "ASIO" : "?")),
#else
#ifdef __APPLE__
"%s",
#else
"(%d) %s", pdi->hostApi,
#endif
#endif
pdi->name);
nout++;
}
}
*nindevs = nin;
*noutdevs = nout;
}