From 9c0e19a3be2288db79e2502e5fa450c3e20a668d Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Fri, 9 May 2003 16:04:00 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r610, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611 --- pd/portaudio_v18/LICENSE.txt | 65 + pd/portaudio_v18/MSP-README.txt | 6 + pd/portaudio_v18/README.txt | 81 ++ pd/portaudio_v18/pa_common/pa_convert.c | 470 ++++++ pd/portaudio_v18/pa_common/pa_host.h | 189 +++ pd/portaudio_v18/pa_common/pa_lib.c | 806 +++++++++++ pd/portaudio_v18/pa_common/pa_trace.c | 83 ++ pd/portaudio_v18/pa_common/pa_trace.h | 67 + pd/portaudio_v18/pa_common/portaudio.h | 463 ++++++ pd/portaudio_v18/pa_mac_core/notes.txt | 34 + pd/portaudio_v18/pa_mac_core/pa_mac_core.c | 2116 ++++++++++++++++++++++++++++ pd/portaudio_v18/pablio/README.txt | 39 + pd/portaudio_v18/pablio/pablio.c | 327 +++++ pd/portaudio_v18/pablio/pablio.def | 35 + pd/portaudio_v18/pablio/pablio.h | 109 ++ pd/portaudio_v18/pablio/pablio_pd.c | 341 +++++ pd/portaudio_v18/pablio/pablio_pd.h | 110 ++ pd/portaudio_v18/pablio/ringbuffer.c | 199 +++ pd/portaudio_v18/pablio/ringbuffer.h | 102 ++ pd/portaudio_v18/pablio/ringbuffer_pd.c | 214 +++ pd/portaudio_v18/pablio/test_rw.c | 99 ++ pd/portaudio_v18/pablio/test_rw_echo.c | 123 ++ pd/portaudio_v18/pablio/test_w_saw.c | 108 ++ pd/portaudio_v18/pablio/test_w_saw8.c | 106 ++ pd/portaudio_v18/pablio/test_w_saw_pd.c | 108 ++ 25 files changed, 6400 insertions(+) create mode 100644 pd/portaudio_v18/LICENSE.txt create mode 100644 pd/portaudio_v18/MSP-README.txt create mode 100644 pd/portaudio_v18/README.txt create mode 100644 pd/portaudio_v18/pa_common/pa_convert.c create mode 100644 pd/portaudio_v18/pa_common/pa_host.h create mode 100644 pd/portaudio_v18/pa_common/pa_lib.c create mode 100644 pd/portaudio_v18/pa_common/pa_trace.c create mode 100644 pd/portaudio_v18/pa_common/pa_trace.h create mode 100644 pd/portaudio_v18/pa_common/portaudio.h create mode 100644 pd/portaudio_v18/pa_mac_core/notes.txt create mode 100644 pd/portaudio_v18/pa_mac_core/pa_mac_core.c create mode 100644 pd/portaudio_v18/pablio/README.txt create mode 100644 pd/portaudio_v18/pablio/pablio.c create mode 100644 pd/portaudio_v18/pablio/pablio.def create mode 100644 pd/portaudio_v18/pablio/pablio.h create mode 100644 pd/portaudio_v18/pablio/pablio_pd.c create mode 100644 pd/portaudio_v18/pablio/pablio_pd.h create mode 100644 pd/portaudio_v18/pablio/ringbuffer.c create mode 100644 pd/portaudio_v18/pablio/ringbuffer.h create mode 100644 pd/portaudio_v18/pablio/ringbuffer_pd.c create mode 100644 pd/portaudio_v18/pablio/test_rw.c create mode 100644 pd/portaudio_v18/pablio/test_rw_echo.c create mode 100644 pd/portaudio_v18/pablio/test_w_saw.c create mode 100644 pd/portaudio_v18/pablio/test_w_saw8.c create mode 100644 pd/portaudio_v18/pablio/test_w_saw_pd.c (limited to 'pd/portaudio_v18') diff --git a/pd/portaudio_v18/LICENSE.txt b/pd/portaudio_v18/LICENSE.txt new file mode 100644 index 00000000..105da3f7 --- /dev/null +++ b/pd/portaudio_v18/LICENSE.txt @@ -0,0 +1,65 @@ +Portable header file to contain: +/* + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +Implementation files to contain: +/* + * PortAudio Portable Real-Time Audio Library + * Latest version at: http://www.audiomulch.com/portaudio/ + * Implementation + * Copyright (c) 1999-2000 + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ \ No newline at end of file diff --git a/pd/portaudio_v18/MSP-README.txt b/pd/portaudio_v18/MSP-README.txt new file mode 100644 index 00000000..c134e3a9 --- /dev/null +++ b/pd/portaudio_v18/MSP-README.txt @@ -0,0 +1,6 @@ +These files are from the V18 "patch" branch, snapshot of 030324. We just use +this for Mac now, and using v19 instead for linux and MSW. + +I changed some code in pablio.c as marked. + +-MSP diff --git a/pd/portaudio_v18/README.txt b/pd/portaudio_v18/README.txt new file mode 100644 index 00000000..d1e5d7d6 --- /dev/null +++ b/pd/portaudio_v18/README.txt @@ -0,0 +1,81 @@ +README for PortAudio +Implementations for PC DirectSound and Mac SoundManager + +/* + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com// + * + * Copyright (c) 1999-2000 Phil Burk and Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +PortAudio is a portable audio I/O library designed for cross-platform +support of audio. It uses a callback mechanism to request audio processing. +Audio can be generated in various formats, including 32 bit floating point, +and will be converted to the native format internally. + +Documentation: + See "pa_common/portaudio.h" for API spec. + See docs folder for a tutorial. + Also see http://www.portaudio.com/docs/ + And see "pa_tests/patest_saw.c" for an example. + +For information on compiling programs with PortAudio, please see the +tutorial at: + + http://www.portaudio.com/docs/pa_tutorial.html + +Important Files and Folders: + pa_common/ = platform independant code + pa_common/portaudio.h = header file for PortAudio API. Specifies API. + pa_common/pa_lib.c = host independant code for all implementations. + + pablio = simple blocking read/write interface + +Platform Implementations + pa_asio = ASIO for Windows and Macintosh + pa_beos = BeOS + pa_mac = Macintosh Sound Manager for OS 8,9 and Carbon + pa_mac_core = Macintosh Core Audio for OS X + pa_sgi = Silicon Graphics AL + pa_unix_oss = OSS implementation for various Unixes + pa_win_ds = Windows Direct Sound + pa_win_wmme = Windows MME (most widely supported) + +Test Programs + pa_tests/pa_fuzz.c = guitar fuzz box + pa_tests/pa_devs.c = print a list of available devices + pa_tests/pa_minlat.c = determine minimum latency for your machine + pa_tests/paqa_devs.c = self test that opens all devices + pa_tests/paqa_errs.c = test error detection and reporting + pa_tests/patest_clip.c = hear a sine wave clipped and unclipped + pa_tests/patest_dither.c = hear effects of dithering (extremely subtle) + pa_tests/patest_pink.c = fun with pink noise + pa_tests/patest_record.c = record and playback some audio + pa_tests/patest_maxsines.c = how many sine waves can we play? Tests Pa_GetCPULoad(). + pa_tests/patest_sine.c = output a sine wave in a simple PA app + pa_tests/patest_sync.c = test syncronization of audio and video + pa_tests/patest_wire.c = pass input to output, wire simulator diff --git a/pd/portaudio_v18/pa_common/pa_convert.c b/pd/portaudio_v18/pa_common/pa_convert.c new file mode 100644 index 00000000..72e021eb --- /dev/null +++ b/pd/portaudio_v18/pa_common/pa_convert.c @@ -0,0 +1,470 @@ +/* + * pa_conversions.c + * portaudio + * + * Created by Phil Burk on Mon Mar 18 2002. + * + */ +#include + +#include "portaudio.h" +#include "pa_host.h" + +#define CLIP( val, min, max ) { val = ((val) < (min)) ? min : (((val) < (max)) ? (max) : (val)); } + +/*************************************************************************/ +static void PaConvert_Float32_Int16( + float *sourceBuffer, int sourceStride, + short *targetBuffer, int targetStride, + int numSamples ) +{ + int i; + for( i=0; ipast_NativeInputSampleFormat = nativeInputSampleFormat; + past->past_InputConversionSourceStride = 1; + past->past_InputConversionTargetStride = 1; + + if( nativeInputSampleFormat != past->past_InputSampleFormat ) + { + int ifDither = (past->past_Flags & paDitherOff) == 0; + past->past_InputConversionProc = PaConvert_SelectProc( nativeInputSampleFormat, + past->past_InputSampleFormat, 0, ifDither ); + if( past->past_InputConversionProc == NULL ) return paSampleFormatNotSupported; + } + else + { + past->past_InputConversionProc = NULL; /* no conversion necessary */ + } + + return paNoError; +} + +/*************************************************************************/ +PaError PaConvert_SetupOutput( internalPortAudioStream *past, + PaSampleFormat nativeOutputSampleFormat ) +{ + + past->past_NativeOutputSampleFormat = nativeOutputSampleFormat; + past->past_OutputConversionSourceStride = 1; + past->past_OutputConversionTargetStride = 1; + + if( nativeOutputSampleFormat != past->past_OutputSampleFormat ) + { + int ifDither = (past->past_Flags & paDitherOff) == 0; + int ifClip = (past->past_Flags & paClipOff) == 0; + + past->past_OutputConversionProc = PaConvert_SelectProc( past->past_OutputSampleFormat, + nativeOutputSampleFormat, ifClip, ifDither ); + if( past->past_OutputConversionProc == NULL ) return paSampleFormatNotSupported; + } + else + { + past->past_OutputConversionProc = NULL; /* no conversion necessary */ + } + + return paNoError; +} + +/************************************************************************* +** Called by host code. +** Convert input from native format to user format, +** call user code, +** then convert output to native format. +** Returns result from user callback. +*/ +long PaConvert_Process( internalPortAudioStream *past, + void *nativeInputBuffer, + void *nativeOutputBuffer ) +{ + int userResult; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + + /* Get native input data. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + if( past->past_InputSampleFormat == past->past_NativeInputSampleFormat ) + { + /* Already in native format so just read directly from native buffer. */ + inputBuffer = nativeInputBuffer; + } + else + { + inputBuffer = past->past_InputBuffer; + /* Convert input data to user format. */ + (*past->past_InputConversionProc)(nativeInputBuffer, past->past_InputConversionSourceStride, + inputBuffer, past->past_InputConversionTargetStride, + past->past_FramesPerUserBuffer * past->past_NumInputChannels ); + } + } + + /* Are we doing output? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + outputBuffer = (past->past_OutputConversionProc == NULL) ? + nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + /* Advance frame counter for timestamp. */ + past->past_FrameCount += past->past_FramesPerUserBuffer; // FIXME - should this be in here? + + /* Convert to native format if necessary. */ + if( (past->past_OutputConversionProc != NULL ) && (outputBuffer != NULL) ) + { + (*past->past_OutputConversionProc)( outputBuffer, past->past_OutputConversionSourceStride, + nativeOutputBuffer, past->past_OutputConversionTargetStride, + past->past_FramesPerUserBuffer * past->past_NumOutputChannels ); + } + + return userResult; +} diff --git a/pd/portaudio_v18/pa_common/pa_host.h b/pd/portaudio_v18/pa_common/pa_host.h new file mode 100644 index 00000000..db898fe0 --- /dev/null +++ b/pd/portaudio_v18/pa_common/pa_host.h @@ -0,0 +1,189 @@ +#ifndef PA_HOST_H +#define PA_HOST_H + +/* + * $Id: pa_host.h,v 1.3.4.1 2003/02/11 21:33:58 philburk Exp $ + * Host dependant internal API for PortAudio + * + * Author: Phil Burk + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.softsynth.com/portaudio/ + * DirectSound and Macintosh Implementation + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifndef SUPPORT_AUDIO_CAPTURE +#define SUPPORT_AUDIO_CAPTURE (1) +#endif + +#ifndef int32 + typedef long int32; +#endif +#ifndef uint32 + typedef unsigned long uint32; +#endif +#ifndef int16 + typedef short int16; +#endif +#ifndef uint16 + typedef unsigned short uint16; +#endif + +/* Used to convert between various sample formats. */ +typedef void (PortAudioConverter)( + void *inputBuffer, int inputStride, + void *outputBuffer, int outputStride, + int numSamples ); + +#define PA_MAGIC (0x18273645) + +/************************************************************************************/ +/****************** Structures ******************************************************/ +/************************************************************************************/ + +typedef struct internalPortAudioStream +{ + uint32 past_Magic; /* ID for struct to catch bugs. */ + + /* Begin user specified information. */ + uint32 past_FramesPerUserBuffer; + uint32 past_NumUserBuffers; + double past_SampleRate; /* Closest supported sample rate. */ + int past_NumInputChannels; + int past_NumOutputChannels; + PaDeviceID past_InputDeviceID; + PaDeviceID past_OutputDeviceID; + PaSampleFormat past_InputSampleFormat; + PaSampleFormat past_OutputSampleFormat; + PortAudioCallback *past_Callback; + void *past_UserData; + uint32 past_Flags; + /* End user specified information. */ + + void *past_DeviceData; + PaSampleFormat past_NativeOutputSampleFormat; + PaSampleFormat past_NativeInputSampleFormat; + + /* Flags for communicating between foreground and background. */ + volatile int past_IsActive; /* Background is still playing. */ + volatile int past_StopSoon; /* Background should keep playing when buffers empty. */ + volatile int past_StopNow; /* Background should stop playing now. */ + /* These buffers are used when the native format does not match the user format. */ + void *past_InputBuffer; + uint32 past_InputBufferSize; /* Size in bytes of the input buffer. */ + void *past_OutputBuffer; + uint32 past_OutputBufferSize; + /* Measurements */ + uint32 past_NumCallbacks; + PaTimestamp past_FrameCount; /* Frames output to buffer. */ + /* For measuring CPU utilization. */ + double past_AverageInsideCount; + double past_AverageTotalCount; + double past_Usage; + int past_IfLastExitValid; + /* Format Conversion */ + /* These are setup by PaConversion_Setup() */ + PortAudioConverter *past_InputConversionProc; + int past_InputConversionSourceStride; + int past_InputConversionTargetStride; + PortAudioConverter *past_OutputConversionProc; + int past_OutputConversionSourceStride; + int past_OutputConversionTargetStride; +} +internalPortAudioStream; + +/************************************************************************************/ +/******** These functions must be provided by a platform implementation. ************/ +/************************************************************************************/ + +PaError PaHost_Init( void ); +PaError PaHost_Term( void ); + +PaError PaHost_OpenStream( internalPortAudioStream *past ); +PaError PaHost_CloseStream( internalPortAudioStream *past ); + +PaError PaHost_StartOutput( internalPortAudioStream *past ); +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ); +PaError PaHost_StartInput( internalPortAudioStream *past ); +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ); +PaError PaHost_StartEngine( internalPortAudioStream *past ); +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ); +PaError PaHost_StreamActive( internalPortAudioStream *past ); + +void *PaHost_AllocateFastMemory( long numBytes ); +void PaHost_FreeFastMemory( void *addr, long numBytes ); + +/* This only called if PA_VALIDATE_RATE IS CALLED. */ +PaError PaHost_ValidateSampleRate( PaDeviceID id, double requestedFrameRate, + double *closestFrameRatePtr ); + +/**********************************************************************/ +/************ Common Utility Routines provided by PA ******************/ +/**********************************************************************/ + +/* PaHost_IsInitialized() returns non-zero if PA is initialized, 0 otherwise */ +int PaHost_IsInitialized( void ); + +internalPortAudioStream* PaHost_GetStreamRepresentation( PortAudioStream *stream ); + +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, + int numRates, double frameRate ); + +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ); + +/* Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit 1.31 fixed point value prior to >>15. +** Range of output is +/- 65535 +** Multiply by PA_DITHER_SCALE to get a float between -2.0 and 2.0. */ +#define PA_DITHER_BITS (15) +#define PA_DITHER_SCALE (1.0f / ((1< +#include +#include +#include + +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifdef _WIN32 +#ifndef __MWERKS__ +#include +#endif /* __MWERKS__ */ +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/* The reason we might NOT want to validate the rate before opening the stream + * is because many DirectSound drivers lie about the rates they actually support. + */ +#define PA_VALIDATE_RATE (0) /* If true validate sample rate against driver info. */ + +/* +O- maybe not allocate past_InputBuffer and past_OutputBuffer if not needed for conversion +*/ + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +static int gInitCount = 0; /* Count number of times Pa_Initialize() called to allow nesting and overlapping. */ + +static PaError Pa_KillStream( PortAudioStream *stream, int abort ); + +/***********************************************************************/ +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, int numRates, double frameRate ) +{ + double err, minErr = allowableError; + int i, bestFit = -1; + + for( i=0; inumSampleRates == -1 ) + { + /* Is it out of range? */ + if( (requestedFrameRate < pdi->sampleRates[0]) || + (requestedFrameRate > pdi->sampleRates[1]) ) + { + return paInvalidSampleRate; + } + + *closestFrameRatePtr = requestedFrameRate; + } + else + { + bestRateIndex = PaHost_FindClosestTableEntry( 1.0, pdi->sampleRates, pdi->numSampleRates, requestedFrameRate ); + if( bestRateIndex < 0 ) return paInvalidSampleRate; + *closestFrameRatePtr = pdi->sampleRates[bestRateIndex]; + } + return paNoError; +} + +/*************************************************************************/ +PaError Pa_OpenStream( + PortAudioStream** streamPtrPtr, + PaDeviceID inputDeviceID, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDeviceID, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + unsigned long streamFlags, + PortAudioCallback *callback, + void *userData ) +{ + internalPortAudioStream *past = NULL; + PaError result = paNoError; + int bitsPerInputSample; + int bitsPerOutputSample; + /* Print passed parameters. */ + DBUG(("Pa_OpenStream( %p, %d, %d, %d, %p, /* input */ \n", + streamPtrPtr, inputDeviceID, numInputChannels, + inputSampleFormat, inputDriverInfo )); + DBUG((" %d, %d, %d, %p, /* output */\n", + outputDeviceID, numOutputChannels, + outputSampleFormat, outputDriverInfo )); + DBUG((" %g, %d, %d, 0x%x, , %p )\n", + sampleRate, framesPerBuffer, numberOfBuffers, + streamFlags, userData )); + + /* Check for parameter errors. */ + if( (streamFlags & ~(paClipOff | paDitherOff)) != 0 ) return paInvalidFlag; + if( streamPtrPtr == NULL ) return paBadStreamPtr; + if( inputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( outputDriverInfo != NULL ) return paHostError; /* REVIEW */ + if( (inputDeviceID < 0) && ( outputDeviceID < 0) ) return paInvalidDeviceId; + if( (outputDeviceID >= Pa_CountDevices()) || (inputDeviceID >= Pa_CountDevices()) ) + { + return paInvalidDeviceId; + } + if( (numInputChannels <= 0) && ( numOutputChannels <= 0) ) return paInvalidChannelCount; + +#if SUPPORT_AUDIO_CAPTURE + if( inputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( inputSampleFormat ); + if( size < 0 ) return size; + bitsPerInputSample = 8 * size; + if( (numInputChannels <= 0) ) return paInvalidChannelCount; + } +#else + if( inputDeviceID >= 0 ) + { + return paInvalidChannelCount; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + else + { + if( numInputChannels > 0 ) return paInvalidChannelCount; + bitsPerInputSample = 0; + } + + if( outputDeviceID >= 0 ) + { + PaError size = Pa_GetSampleSize( outputSampleFormat ); + if( size < 0 ) return size; + bitsPerOutputSample = 8 * size; + if( (numOutputChannels <= 0) ) return paInvalidChannelCount; + } + else + { + if( numOutputChannels > 0 ) return paInvalidChannelCount; + bitsPerOutputSample = 0; + } + + if( callback == NULL ) return paNullCallback; + + /* Allocate and clear stream structure. */ + past = (internalPortAudioStream *) PaHost_AllocateFastMemory( sizeof(internalPortAudioStream) ); + if( past == NULL ) return paInsufficientMemory; + memset( past, 0, sizeof(internalPortAudioStream) ); + AddTraceMessage("Pa_OpenStream: past", (long) past ); + + past->past_Magic = PA_MAGIC; /* Set ID to catch bugs. */ + past->past_FramesPerUserBuffer = framesPerBuffer; + past->past_NumUserBuffers = numberOfBuffers; /* NOTE - PaHost_OpenStream() MUST CHECK FOR ZERO! */ + past->past_Callback = callback; + past->past_UserData = userData; + past->past_OutputSampleFormat = outputSampleFormat; + past->past_InputSampleFormat = inputSampleFormat; + past->past_OutputDeviceID = outputDeviceID; + past->past_InputDeviceID = inputDeviceID; + past->past_NumInputChannels = numInputChannels; + past->past_NumOutputChannels = numOutputChannels; + past->past_Flags = streamFlags; + + /* Check for absurd sample rates. */ + if( (sampleRate < 1000.0) || (sampleRate > 200000.0) ) + { + result = paInvalidSampleRate; + goto cleanup; + } + + /* Allocate buffers that may be used for format conversion from user to native buffers. */ + if( numInputChannels > 0 ) + { + +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( inputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + /* Allocate single Input buffer for passing formatted samples to user callback. */ + past->past_InputBufferSize = framesPerBuffer * numInputChannels * ((bitsPerInputSample+7) / 8); + past->past_InputBuffer = PaHost_AllocateFastMemory(past->past_InputBufferSize); + if( past->past_InputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_InputBuffer = NULL; + } + + /* Allocate single Output buffer. */ + if( numOutputChannels > 0 ) + { +#if PA_VALIDATE_RATE + result = PaHost_ValidateSampleRate( outputDeviceID, sampleRate, &past->past_SampleRate ); + if( result < 0 ) + { + goto cleanup; + } +#else + past->past_SampleRate = sampleRate; +#endif + past->past_OutputBufferSize = framesPerBuffer * numOutputChannels * ((bitsPerOutputSample+7) / 8); + past->past_OutputBuffer = PaHost_AllocateFastMemory(past->past_OutputBufferSize); + if( past->past_OutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + } + else + { + past->past_OutputBuffer = NULL; + } + + result = PaHost_OpenStream( past ); + if( result < 0 ) goto cleanup; + + *streamPtrPtr = (void *) past; + + return result; + +cleanup: + if( past != NULL ) Pa_CloseStream( past ); + *streamPtrPtr = NULL; + return result; +} + + +/*************************************************************************/ +PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ) +{ + return Pa_OpenStream( + stream, + ((numInputChannels > 0) ? Pa_GetDefaultInputDeviceID() : paNoDevice), + numInputChannels, sampleFormat, NULL, + ((numOutputChannels > 0) ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + numOutputChannels, sampleFormat, NULL, + sampleRate, framesPerBuffer, numberOfBuffers, paNoFlag, callback, userData ); +} + +/*************************************************************************/ +PaError Pa_CloseStream( PortAudioStream* stream) +{ + PaError result; + internalPortAudioStream *past; + + DBUG(("Pa_CloseStream()\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + Pa_AbortStream( past ); + result = PaHost_CloseStream( past ); + + if( past->past_InputBuffer ) PaHost_FreeFastMemory( past->past_InputBuffer, past->past_InputBufferSize ); + if( past->past_OutputBuffer ) PaHost_FreeFastMemory( past->past_OutputBuffer, past->past_OutputBufferSize ); + PaHost_FreeFastMemory( past, sizeof(internalPortAudioStream) ); + + return result; +} + +/*************************************************************************/ +PaError Pa_StartStream( PortAudioStream *stream ) +{ + PaError result = paHostError; + internalPortAudioStream *past; + + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + past->past_FrameCount = 0.0; + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StartInput( past ); + DBUG(("Pa_StartStream: PaHost_StartInput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StartOutput( past ); + DBUG(("Pa_StartStream: PaHost_StartOutput returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + result = PaHost_StartEngine( past ); + DBUG(("Pa_StartStream: PaHost_StartEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + + return paNoError; + +error: + return result; +} + +/*************************************************************************/ +PaError Pa_StopStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 0 ); +} + +/*************************************************************************/ +PaError Pa_AbortStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 1 ); +} + +/*************************************************************************/ +static PaError Pa_KillStream( PortAudioStream *stream, int abort ) +{ + PaError result = paNoError; + internalPortAudioStream *past; + + DBUG(("Pa_StopStream().\n")); + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + + if( (past->past_NumInputChannels > 0) || (past->past_NumOutputChannels > 0) ) + { + result = PaHost_StopEngine( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopEngine returned = 0x%X.\n", result)); + if( result < 0 ) goto error; + } + + if( past->past_NumInputChannels > 0 ) + { + result = PaHost_StopInput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopInput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + + if( past->past_NumOutputChannels > 0 ) + { + result = PaHost_StopOutput( past, abort ); + DBUG(("Pa_StopStream: PaHost_StopOutput returned = 0x%X.\n", result)); + if( result != paNoError ) goto error; + } + +error: + past->past_Usage = 0; + past->past_IfLastExitValid = 0; + + return result; +} + +/*************************************************************************/ +PaError Pa_StreamActive( PortAudioStream *stream ) +{ + internalPortAudioStream *past; + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return PaHost_StreamActive( past ); +} + +/*************************************************************************/ +const char *Pa_GetErrorText( PaError errnum ) +{ + const char *msg; + + switch(errnum) + { + case paNoError: msg = "Success"; break; + case paHostError: msg = "Host error."; break; + case paInvalidChannelCount: msg = "Invalid number of channels."; break; + case paInvalidSampleRate: msg = "Invalid sample rate."; break; + case paInvalidDeviceId: msg = "Invalid device ID."; break; + case paInvalidFlag: msg = "Invalid flag."; break; + case paSampleFormatNotSupported: msg = "Sample format not supported"; break; + case paBadIODeviceCombination: msg = "Illegal combination of I/O devices."; break; + case paInsufficientMemory: msg = "Insufficient memory."; break; + case paBufferTooBig: msg = "Buffer too big."; break; + case paBufferTooSmall: msg = "Buffer too small."; break; + case paNullCallback: msg = "No callback routine specified."; break; + case paBadStreamPtr: msg = "Invalid stream pointer."; break; + case paTimedOut : msg = "Wait Timed Out."; break; + case paInternalError: msg = "Internal PortAudio Error."; break; + case paDeviceUnavailable: msg = "Device Unavailable."; break; + default: msg = "Illegal error number."; break; + } + return msg; +} + +/* + Get CPU Load as a fraction of total CPU time. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + The amount may vary depending on CPU load. + This function may be called from the callback function. +*/ +double Pa_GetCPULoad( PortAudioStream* stream) +{ + internalPortAudioStream *past; + if( stream == NULL ) return (double) paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return past->past_Usage; +} + +/*************************************************************************/ +internalPortAudioStream* PaHost_GetStreamRepresentation( PortAudioStream *stream ) +{ + internalPortAudioStream* result = (internalPortAudioStream*) stream; + + if( result == NULL || result->past_Magic != PA_MAGIC ) + return NULL; + else + return result; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +** Range of output is +/- 32767 +*/ +#define PA_DITHER_BITS (15) +#define PA_DITHER_SCALE (1.0f / ((1<>DITHER_SHIFT) + (((long)randSeed2)>>DITHER_SHIFT); + /* High pass filter to reduce audibility. */ + highPass = current - previous; + previous = current; + return highPass; +} + +/************************************************************************* +** Called by host code. +** Convert input from Int16, call user code, then convert output +** to Int16 format for native use. +** Assumes host native format is paInt16. +** Returns result from user callback. +*/ +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ) +{ + long temp; + int userResult; + unsigned int i; + void *inputBuffer = NULL; + void *outputBuffer = NULL; + +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( (past->past_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + /* Convert from native format to PA format. */ + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumInputChannels; + switch(past->past_InputSampleFormat) + { + + case paFloat32: + { + float *inBufPtr = (float *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + for( i=0; ipast_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8); + } + } + else + { + for( i=0; i> 8; /* PLB20010820 */ + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (char)(temp >> 8); + } + } + break; + } + + case paUInt8: + { + /* Convert 16 bit data to 8 bit unsigned chars */ + unsigned char *inBufPtr = (unsigned char *) past->past_InputBuffer; + inputBuffer = past->past_InputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 8) + 0x80); + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i> 8; /* PLB20010820 */ + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (unsigned char)((temp>>8) + 0x80); /* PLB20010820 */ + } + } + break; + } + + default: + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + + /* Are we doing output time? */ + if( (past->past_NumOutputChannels > 0) && (nativeOutputBuffer != NULL) ) + { + /* May already be in native format so just write directly to native buffer. */ + outputBuffer = (past->past_OutputSampleFormat == paInt16) ? + (void*)nativeOutputBuffer : past->past_OutputBuffer; + } + /* + AddTraceMessage("Pa_CallConvertInt16: inputBuffer = ", (int) inputBuffer ); + AddTraceMessage("Pa_CallConvertInt16: outputBuffer = ", (int) outputBuffer ); + */ + /* Call user callback routine. */ + userResult = past->past_Callback( + inputBuffer, + outputBuffer, + past->past_FramesPerUserBuffer, + past->past_FrameCount, + past->past_UserData ); + + past->past_FrameCount += (PaTimestamp) past->past_FramesPerUserBuffer; + + /* Convert to native format if necessary. */ + if( outputBuffer != NULL ) + { + unsigned int samplesPerBuffer = past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + switch(past->past_OutputSampleFormat) + { + case paFloat32: + { + float *outBufPtr = (float *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + if( past->past_Flags & paClipOff ) /* NOTHING */ + { + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + } + else + { + /* If you dither then you have to clip because dithering could push the signal out of range! */ + for( i=0; i 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt32: + { + int *outBufPtr = (int *) past->past_OutputBuffer; + if( past->past_Flags & paDitherOff ) + { + for( i=0; i> 16 ); + } + } + else + { + for( i=0; i> 1) + PaConvert_TriangularDither(); + temp = temp >> 15; + *nativeOutputBuffer++ = (short)((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + } + } + break; + } + + case paInt8: + { + char *outBufPtr = (char *) past->past_OutputBuffer; + for( i=0; ipast_OutputBuffer; + for( i=0; i 0 ) return paNoError; + ResetTraceMessages(); + return PaHost_Init(); +} + +PaError Pa_Terminate( void ) +{ + PaError result = paNoError; + + if( gInitCount == 0 ) return paNoError; + else if( --gInitCount == 0 ) + { + result = PaHost_Term(); + DumpTraceMessages(); + } + return result; +} + +int PaHost_IsInitialized() +{ + return gInitCount; +} + +/*************************************************************************/ +PaError Pa_GetSampleSize( PaSampleFormat format ) +{ + int size; + switch(format ) + { + + case paUInt8: + case paInt8: + size = 1; + break; + + case paInt16: + size = 2; + break; + + case paPackedInt24: + size = 3; + break; + + case paFloat32: + case paInt32: + case paInt24: + size = 4; + break; + + default: + size = paSampleFormatNotSupported; + break; + } + return (PaError) size; +} + + diff --git a/pd/portaudio_v18/pa_common/pa_trace.c b/pd/portaudio_v18/pa_common/pa_trace.c new file mode 100644 index 00000000..d55a6d37 --- /dev/null +++ b/pd/portaudio_v18/pa_common/pa_trace.c @@ -0,0 +1,83 @@ +/* + * $Id: pa_trace.c,v 1.1.1.1 2002/01/22 00:52:11 phil Exp $ + * Portable Audio I/O Library Trace Facility + * Store trace information in real-time for later printing. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "pa_trace.h" + +#if TRACE_REALTIME_EVENTS + +static char *traceTextArray[MAX_TRACE_RECORDS]; +static int traceIntArray[MAX_TRACE_RECORDS]; +static int traceIndex = 0; +static int traceBlock = 0; + +/*********************************************************************/ +void ResetTraceMessages() +{ + traceIndex = 0; +} + +/*********************************************************************/ +void DumpTraceMessages() +{ + int i; + int numDump = (traceIndex < MAX_TRACE_RECORDS) ? traceIndex : MAX_TRACE_RECORDS; + + printf("DumpTraceMessages: traceIndex = %d\n", traceIndex ); + for( i=0; i RingBuffer -> input.AudioConverter -> + * PortAudio callback -> output.AudioConverter -> PaOSX_CoreAudioIOCallback() output buffers + * + * For two separate devices, we have to use two separate callbacks. + * We pass data between them using a RingBuffer FIFO. + * The processing pipeline for PA_MODE_IO_TWO_DEVICES is split into two threads: + * + * PaOSX_CoreAudioInputCallback() input buffers -> RingBuffer + * + * RingBuffer -> input.AudioConverter -> + * PortAudio callback -> output.AudioConverter -> PaOSX_CoreAudioIOCallback() output buffers + * + * License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * CHANGE HISTORY: + + 3.29.2001 - Phil Burk - First pass... converted from Window MME code with help from Darren. + 3.30.2001 - Darren Gibbs - Added more support for dynamically querying device info. + 12.7.2001 - Gord Peters - Tweaks to compile on PA V17 and OS X 10.1 + 2.7.2002 - Darren and Phil - fixed isInput so GetProperty works better, + fixed device queries for numChannels and sampleRates, + one CoreAudio device now maps to separate input and output PaDevices, + audio input works if using same CoreAudio device (some HW devices make separate CoreAudio devices). + 2.22.2002 - Stephane Letz - Explicit cast needed for compilation with Code Warrior 7 + 3.19.2002 - Phil Burk - Added paInt16, paInt8, format using new "pa_common/pa_convert.c" file. + Return error if opened in mono mode cuz not supported. [Supported 10.12.2002] + Add support for Pa_GetCPULoad(); + Fixed timestamp in callback and Pa_StreamTime() (Thanks n++k for the advice!) + Check for invalid sample rates and return an error. + Check for getenv("PA_MIN_LATENCY_MSEC") to set latency externally. + Better error checking for invalid channel counts and invalid devices. + 3.29.2002 - Phil Burk - Fixed Pa_GetCPULoad() for small buffers. + 3.31.2002 - Phil Burk - Use getrusage() instead of gettimeofday() for CPU Load calculation. + 10.12.2002 - Phil Burk - Use AudioConverter to allow wide range of sample rates, and mono. + Use FIFO (from pablio/rinbuffer.h) so that we can pull data through converter. + Added PaOSX_FixVolumeScalar() to make iMic audible. + 10.17.2002 - Phil Burk - Support full duplex between two different devices. + Name internal functions PaOSX_* + Dumped useless PA_MIN_LATENCY_MSEC environment variable. + Use kAudioDevicePropertyStreamFormatMatch to determine max channels. + 02.03.2003 - Phil Burk - always use AudioConverters so that we can adapt when format changes. + Synchronize with device when format changes. + 02.13.2003 - Phil Burk - scan for maxChannels because FormatMatch won't tell us. + 03.05.2003 - Phil Burk and Dominic Mazzoni - interleave and deinterleave multiple + CoreAudio buffers. Needed for MOTU828 and some other N>2 channel devices. + See code related to "streamInterleavingBuffer". + 03.06.2003 - Phil Burk and Ryan Francesconi - fixed numChannels query for MOTU828. + Handle fact that MOTU828 gives you 8 channels even when you ask for 2! +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" +#include "ringbuffer.h" + +/************************************************* Constants ********/ +#define SET_DEVICE_BUFFER_SIZE (1) + +/* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */ +#define PA_TRACE_RUN (0) +#define PA_TRACE_START_STOP (0) + +#define PA_MIN_LATENCY_MSEC (20) /* FIXME */ +#define MIN_TIMEOUT_MSEC (3000) + +#define PRINT(x) { printf x; fflush(stdout); } +#define PRINT_ERR( msg, err ) PRINT(( msg ": error = 0x%0lX = '%s'\n", (err), ErrorToString(err)) ) +#define DBUG(x) /* PRINT(x) */ +#define DBUGBACK(x) /* if( sMaxBackgroundErrorMessages-- > 0 ) PRINT(x) */ +#define DBUGX(x) + +// define value of isInput passed to CoreAudio routines +#define IS_INPUT (true) +#define IS_OUTPUT (false) + +typedef enum PaDeviceMode +{ + PA_MODE_OUTPUT_ONLY, + PA_MODE_INPUT_ONLY, + PA_MODE_IO_ONE_DEVICE, + PA_MODE_IO_TWO_DEVICES +} PaDeviceMode; + +#define PA_USING_OUTPUT (pahsc->mode != PA_MODE_INPUT_ONLY) +#define PA_USING_INPUT (pahsc->mode != PA_MODE_OUTPUT_ONLY) + +/************************************************************** + * Information needed by PortAudio specific to a CoreAudio device. + */ +typedef struct PaHostInOut_s +{ + AudioDeviceID audioDeviceID; /* CoreAudio specific ID */ + int bytesPerUserNativeBuffer; /* User buffer size in native host format. Depends on numChannels. */ + AudioConverterRef converter; + void *converterBuffer; + int numChannels; + /** Used for interleaving or de-interleaving multiple streams for devices like MOTU828. */ + int streamInterleavingBufferLen; /**< size in bytes */ + Float32 *streamInterleavingBuffer; +} PaHostInOut; + +/************************************************************** + * Structure for internal host specific stream data. + * This is allocated on a per stream basis. + */ +typedef struct PaHostSoundControl +{ + PaHostInOut input; + PaHostInOut output; + AudioDeviceID primaryDeviceID; + PaDeviceMode mode; + RingBuffer ringBuffer; + char *ringBufferData; + Boolean formatListenerCalled; + /* For measuring CPU utilization. */ + struct rusage entryRusage; + double inverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */ +} PaHostSoundControl; + +/************************************************************** + * Structure for internal extended device info query. + * There will be one or two PortAudio devices for each Core Audio device: + * one input and or one output. + */ +typedef struct PaHostDeviceInfo +{ + PaDeviceInfo paInfo; + AudioDeviceID audioDeviceID; +} +PaHostDeviceInfo; + +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumPaDevices = 0; /* Total number of PaDeviceInfos */ +static int sNumInputDevices = 0; /* Total number of input PaDeviceInfos */ +static int sNumOutputDevices = 0; +static int sNumCoreDevices = 0; +static AudioDeviceID *sCoreDeviceIDs; // Array of Core AudioDeviceIDs +static PaHostDeviceInfo *sDeviceInfos = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sSavedHostError = 0; + +static const double supportedSampleRateRange[] = { 8000.0, 96000.0 }; /* FIXME - go to double HW rate. */ +static const char sMapperSuffixInput[] = " - Input"; +static const char sMapperSuffixOutput[] = " - Output"; + +/* Debug support. */ +//static int sMaxBackgroundErrorMessages = 100; +//static int sCoverageCounter = 1; // used to check code coverage during validation + +/* We index the input devices first, then the output devices. */ +#define LOWEST_INPUT_DEVID (0) +#define HIGHEST_INPUT_DEVID (sNumInputDevices - 1) +#define LOWEST_OUTPUT_DEVID (sNumInputDevices) +#define HIGHEST_OUTPUT_DEVID (sNumPaDevices - 1) + +/************************************************* Macros ********/ + +/************************************************* Prototypes **********/ + +static PaError PaOSX_QueryDevices( void ); +static int PaOSX_ScanDevices( Boolean isInput ); +static int PaOSX_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ); +static PaDeviceID PaOSX_QueryDefaultInputDevice( void ); +static PaDeviceID PaOSX_QueryDefaultOutputDevice( void ); +static void PaOSX_CalcHostBufferSize( internalPortAudioStream *past ); + +static OSStatus PAOSX_DevicePropertyListener (AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData); + +/**********************************************************************/ +/* OS X errors are 4 character ID that can be printed. + * Note that uses a static pad so result must be printed immediately. + */ +static OSStatus statusText[2] = { 0, 0 }; +static const char *ErrorToString( OSStatus err ) +{ + const char *str; + + switch (err) + { + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + default: + statusText[0] = err; + str = (const char *)statusText; + break; + } + + return str; +} + +/**********************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<past_DeviceData; + if( pahsc == NULL ) return; + /* Query user CPU timer for usage analysis and to prevent overuse of CPU. */ + getrusage( RUSAGE_SELF, &pahsc->entryRusage ); +} + +static long SubtractTime_AminusB( struct timeval *timeA, struct timeval *timeB ) +{ + long secs = timeA->tv_sec - timeB->tv_sec; + long usecs = secs * 1000000; + usecs += (timeA->tv_usec - timeB->tv_usec); + return usecs; +} + +/****************************************************************************** +** Measure fractional CPU load based on real-time it took to calculate +** buffers worth of output. +*/ +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + struct rusage currentRusage; + long usecsElapsed; + double newUsage; + +#define LOWPASS_COEFFICIENT_0 (0.95) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + + if( getrusage( RUSAGE_SELF, ¤tRusage ) == 0 ) + { + usecsElapsed = SubtractTime_AminusB( ¤tRusage.ru_utime, &pahsc->entryRusage.ru_utime ); + + /* Use inverse because it is faster than the divide. */ + newUsage = usecsElapsed * pahsc->inverseMicrosPerHostBuffer; + + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} +/****************************************** END CPU UTILIZATION *******/ + +/************************************************************************/ +static PaDeviceID PaOSX_QueryDefaultInputDevice( void ) +{ + OSStatus err = noErr; + UInt32 count; + int i; + AudioDeviceID tempDeviceID = kAudioDeviceUnknown; + PaDeviceID defaultDeviceID = paNoDevice; + + // get the default output device for the HAL + // it is required to pass the size of the data to be returned + count = sizeof(tempDeviceID); + err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultInputDevice, &count, (void *) &tempDeviceID); + if (err != noErr) goto error; + + // scan input devices to see which one matches this device + defaultDeviceID = paNoDevice; + for( i=LOWEST_INPUT_DEVID; i<=HIGHEST_INPUT_DEVID; i++ ) + { + DBUG(("PaOSX_QueryDefaultInputDevice: i = %d, aDevId = %ld\n", i, sDeviceInfos[i].audioDeviceID )); + if( sDeviceInfos[i].audioDeviceID == tempDeviceID ) + { + defaultDeviceID = i; + break; + } + } +error: + return defaultDeviceID; +} + +/************************************************************************/ +static PaDeviceID PaOSX_QueryDefaultOutputDevice( void ) +{ + OSStatus err = noErr; + UInt32 count; + int i; + AudioDeviceID tempDeviceID = kAudioDeviceUnknown; + PaDeviceID defaultDeviceID = paNoDevice; + + // get the default output device for the HAL + // it is required to pass the size of the data to be returned + count = sizeof(tempDeviceID); + err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &count, (void *) &tempDeviceID); + if (err != noErr) goto error; + + // scan output devices to see which one matches this device + defaultDeviceID = paNoDevice; + for( i=LOWEST_OUTPUT_DEVID; i<=HIGHEST_OUTPUT_DEVID; i++ ) + { + DBUG(("PaOSX_QueryDefaultOutputDevice: i = %d, aDevId = %ld\n", i, sDeviceInfos[i].audioDeviceID )); + if( sDeviceInfos[i].audioDeviceID == tempDeviceID ) + { + defaultDeviceID = i; + break; + } + } +error: + return defaultDeviceID; +} + +/******************************************************************/ +static PaError PaOSX_QueryDevices( void ) +{ + OSStatus err = noErr; + UInt32 outSize; + Boolean outWritable; + int numBytes; + + // find out how many Core Audio devices there are, if any + outSize = sizeof(outWritable); + err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outSize, &outWritable); + if (err != noErr) + { + PRINT_ERR("Couldn't get info about list of audio devices", err); + sSavedHostError = err; + return paHostError; + } + + // calculate the number of device available + sNumCoreDevices = outSize / sizeof(AudioDeviceID); + + // Bail if there aren't any devices + if (sNumCoreDevices < 1) + { + PRINT(("No Devices Available")); + return paHostError; + } + + // make space for the devices we are about to get + sCoreDeviceIDs = (AudioDeviceID *)malloc(outSize); + + // get an array of AudioDeviceIDs + err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *)sCoreDeviceIDs); + if (err != noErr) + { + PRINT_ERR("Couldn't get list of audio device IDs", err); + sSavedHostError = err; + return paHostError; + } + + // Allocate structures to hold device info pointers. + // There will be a maximum of two Pa devices per Core Audio device, input and/or output. + numBytes = sNumCoreDevices * 2 * sizeof(PaHostDeviceInfo); + sDeviceInfos = (PaHostDeviceInfo *) PaHost_AllocateFastMemory( numBytes ); + if( sDeviceInfos == NULL ) return paInsufficientMemory; + + // Scan all the Core Audio devices to see which support input and allocate a + // PaHostDeviceInfo structure for each one. + DBUG(("PaOSX_QueryDevices: scan for input ======================\n")); + PaOSX_ScanDevices( IS_INPUT ); + sNumInputDevices = sNumPaDevices; + // Now scan all the output devices. + DBUG(("PaOSX_QueryDevices: scan for output ======================\n")); + PaOSX_ScanDevices( IS_OUTPUT ); + sNumOutputDevices = sNumPaDevices - sNumInputDevices; + + // Figure out which of the devices that we scanned is the default device. + sDefaultInputDeviceID = PaOSX_QueryDefaultInputDevice(); + sDefaultOutputDeviceID = PaOSX_QueryDefaultOutputDevice(); + + return paNoError; +} + + +/*************************************************************************/ +/* Query a device for its sample rate. + * @return positive rate or 0.0 on error. + */ +static Float64 PaOSX_GetDeviceSampleRate( AudioDeviceID deviceID, Boolean isInput ) +{ + OSStatus err = noErr; + AudioStreamBasicDescription formatDesc; + UInt32 dataSize; + dataSize = sizeof(formatDesc); + err = AudioDeviceGetProperty( deviceID, 0, isInput, + kAudioDevicePropertyStreamFormat, &dataSize, &formatDesc); + if( err != noErr ) return 0.0; + else return formatDesc.mSampleRate; +} + +/*************************************************************************/ +/* Allocate a string containing the device name. */ +static char *PaOSX_DeviceNameFromID(AudioDeviceID deviceID, Boolean isInput ) +{ + OSStatus err = noErr; + UInt32 outSize; + Boolean outWritable; + char *deviceName = nil; + + // query size of name + err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &outSize, &outWritable); + if (err == noErr) + { + deviceName = (char*)malloc( outSize + 1); + if( deviceName ) + { + err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &outSize, deviceName); + if (err != noErr) + PRINT_ERR("Couldn't get audio device name", err); + } + } + + return deviceName; +} + +/************************************************************************* +** Scan all of the Core Audio devices to see which support selected +** input or output mode. +** Changes sNumDevices, and fills in sDeviceInfos. +*/ +static int PaOSX_ScanDevices( Boolean isInput ) +{ + int coreDeviceIndex; + int result; + PaHostDeviceInfo *hostDeviceInfo; + int numAdded = 0; + + for( coreDeviceIndex=0; coreDeviceIndex 0 ) + { + sNumPaDevices += 1; // bump global counter if we got one + numAdded += 1; + } + else if( result < 0 ) return result; + } + return numAdded; +} + +/************************************************************************* +** Determine the maximum number of channels a device will support. +** @return maxChannels or negative error. +*/ +static int PaOSX_GetMaxChannels( AudioDeviceID devID, Boolean isInput ) +{ + OSStatus err; + UInt32 outSize; + AudioStreamBasicDescription formatDesc; + int maxChannels; + int numChannels; + Boolean gotMax; + + // Scan to find highest matching format. + // Unfortunately some devices won't just return maxChannels for the match. + // For example, some 8 channel devices return 2 when given 256 as input. + gotMax = false; + maxChannels = 0; + while( !gotMax ) + { + + memset( &formatDesc, 0, sizeof(formatDesc)); + numChannels = maxChannels + 2; + DBUG(("PaOSX_GetMaxChannels: try numChannels = %d = %d + 2\n", + numChannels, maxChannels )); + formatDesc.mChannelsPerFrame = numChannels; + outSize = sizeof(formatDesc); + + err = AudioDeviceGetProperty( devID, 0, + isInput, kAudioDevicePropertyStreamFormatMatch, &outSize, &formatDesc); + + DBUG(("PaOSX_GetMaxChannels: err 0x%0x, formatDesc.mChannelsPerFrame= %d\n", + err, formatDesc.mChannelsPerFrame )); + if( err != noErr ) + { + gotMax = true; + } + else + { + // This value worked so we have a new candidate for maxChannels. + if (formatDesc.mChannelsPerFrame > numChannels) + { + maxChannels = formatDesc.mChannelsPerFrame; + } + else if(formatDesc.mChannelsPerFrame < numChannels) + { + gotMax = true; + } + else + { + maxChannels = numChannels; + } + } + } + return maxChannels; +} + +/************************************************************************* +** Try to fill in the device info for this device. +** Return 1 if a good device that PA can use. +** Return 0 if not appropriate +** or return negative error. +** +*/ +static int PaOSX_QueryDeviceInfo( PaHostDeviceInfo *hostDeviceInfo, int coreDeviceIndex, Boolean isInput ) +{ + OSStatus err; + UInt32 outSize; + AudioStreamBasicDescription formatDesc; + AudioDeviceID devID; + PaDeviceInfo *deviceInfo = &hostDeviceInfo->paInfo; + int maxChannels; + + deviceInfo->structVersion = 1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + + deviceInfo->sampleRates = supportedSampleRateRange; // because we use sample rate converter to get continuous rates + deviceInfo->numSampleRates = -1; + + devID = sCoreDeviceIDs[ coreDeviceIndex ]; + hostDeviceInfo->audioDeviceID = devID; + DBUG(("PaOSX_QueryDeviceInfo: coreDeviceIndex = %d, devID = %d, isInput = %d\n", + coreDeviceIndex, (int) devID, isInput )); + + // Get data format info from the device. + outSize = sizeof(formatDesc); + err = AudioDeviceGetProperty(devID, 0, isInput, kAudioDevicePropertyStreamFormat, &outSize, &formatDesc); + // This just may not be an appropriate device for input or output so leave quietly. + if( (err != noErr) || (formatDesc.mChannelsPerFrame == 0) ) goto error; + + DBUG(("PaOSX_QueryDeviceInfo: mFormatID = 0x%x\n", (unsigned int) formatDesc.mFormatID)); + DBUG(("PaOSX_QueryDeviceInfo: mFormatFlags = 0x%x\n",(unsigned int) formatDesc.mFormatFlags)); + + // Right now the Core Audio headers only define one formatID: LinearPCM + // Apparently LinearPCM must be Float32 for now. + if( (formatDesc.mFormatID == kAudioFormatLinearPCM) && + ((formatDesc.mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) ) + { + deviceInfo->nativeSampleFormats = paFloat32; + } + else + { + PRINT(("PaOSX_QueryDeviceInfo: ERROR - not LinearPCM & Float32!!!\n")); + return paSampleFormatNotSupported; + } + + maxChannels = PaOSX_GetMaxChannels( devID, isInput ); + if( maxChannels <= 0 ) goto error; + if( isInput ) + { + deviceInfo->maxInputChannels = maxChannels; + } + else + { + deviceInfo->maxOutputChannels = maxChannels; + } + + // Get the device name + deviceInfo->name = PaOSX_DeviceNameFromID( devID, isInput ); + DBUG(("PaOSX_QueryDeviceInfo: name = %s\n", deviceInfo->name )); + return 1; + +error: + return 0; +} + +/**********************************************************************/ +static PaError PaOSX_MaybeQueryDevices( void ) +{ + if( sNumPaDevices == 0 ) + { + return PaOSX_QueryDevices(); + } + return 0; +} + +static char zeroPad[256] = { 0 }; + +/********************************************************************** +** This is the proc that supplies the data to the AudioConverterFillBuffer call. +** We can pass back arbitrarily sized blocks so if the FIFO region is split +** just pass back the first half. +*/ +static OSStatus PaOSX_InputConverterCallbackProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData) +{ + internalPortAudioStream *past = (internalPortAudioStream *) inUserData; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + void *dataPtr1; + long size1; + void *dataPtr2; + long size2; + + /* Pass contiguous region from FIFO directly to converter. */ + RingBuffer_GetReadRegions( &pahsc->ringBuffer, *outDataSize, + &dataPtr1, &size1, &dataPtr2, &size2 ); + + if( size1 > 0 ) + { + *outData = dataPtr1; + *outDataSize = size1; + RingBuffer_AdvanceReadIndex( &pahsc->ringBuffer, size1 ); + DBUGX(("PaOSX_InputConverterCallbackProc: read %ld bytes from FIFO.\n", size1 )); + } + else + { + DBUGBACK(("PaOSX_InputConverterCallbackProc: got no data!\n")); + *outData = zeroPad; /* Give it zero data to keep it happy. */ + *outDataSize = sizeof(zeroPad); + } + return noErr; +} + +/***************************************************************************** +** Get audio input, if any, from passed in buffer, or from converter or from FIFO, +** then run PA callback and output data. +*/ +static OSStatus PaOSX_LoadAndProcess( internalPortAudioStream *past, + void *inputBuffer, void *outputBuffer ) +{ + OSStatus err = noErr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( past->past_StopSoon ) + { + if( outputBuffer ) + { + /* Clear remainder of audio buffer if we are waiting for stop. */ + AddTraceMessage("PaOSX_LoadAndProcess: zero rest of wave buffer ", i ); + memset( outputBuffer, 0, pahsc->output.bytesPerUserNativeBuffer ); + } + } + else + { + /* Do we need data from the converted input? */ + if( PA_USING_INPUT ) + { + UInt32 size = pahsc->input.bytesPerUserNativeBuffer; + err = AudioConverterFillBuffer( + pahsc->input.converter, + PaOSX_InputConverterCallbackProc, + past, + &size, + pahsc->input.converterBuffer); + if( err != noErr ) return err; + inputBuffer = pahsc->input.converterBuffer; + } + + /* Fill part of audio converter buffer by converting input to user format, + * calling user callback, then converting output to native format. */ + if( PaConvert_Process( past, inputBuffer, outputBuffer )) + { + past->past_StopSoon = 1; + } + } + return err; +} + +/***************************************************************************** +** This is the proc that supplies the data to the AudioConverterFillBuffer call +*/ +static OSStatus PaOSX_OutputConverterCallbackProc (AudioConverterRef inAudioConverter, + UInt32* outDataSize, + void** outData, + void* inUserData) +{ + internalPortAudioStream *past = (internalPortAudioStream *) inUserData; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + *outData = pahsc->output.converterBuffer; + *outDataSize = pahsc->output.bytesPerUserNativeBuffer; + + return PaOSX_LoadAndProcess ( past, pahsc->input.converterBuffer, pahsc->output.converterBuffer ); +} + +/********************************************************************** +** If data available, write it to the Ring Buffer so we can +** pull it from the other side. +*/ +static OSStatus PaOSX_WriteInputRingBuffer( internalPortAudioStream *past, + const AudioBufferList* inInputData ) +{ + int numBytes = 0; + int currentInterleavedChannelIndex; + int numFramesInInputBuffer; + int numInterleavedChannels; + int numChannelsRemaining; + int i; + long writeRoom; + char *inputNativeBufferfPtr = NULL; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* Do we need to interleave the buffers first? */ + if( past->past_NumInputChannels != inInputData->mBuffers[0].mNumberChannels ) + { + + numFramesInInputBuffer = inInputData->mBuffers[0].mDataByteSize / (sizeof(float) * inInputData->mBuffers[0].mNumberChannels); + + numBytes = numFramesInInputBuffer * sizeof(float) * past->past_NumInputChannels; + + /* Allocate temporary buffer if needed. */ + if ( (pahsc->input.streamInterleavingBuffer != NULL) && + (pahsc->input.streamInterleavingBufferLen < numBytes) ) + { + PaHost_FreeFastMemory( pahsc->input.streamInterleavingBuffer, pahsc->input.streamInterleavingBufferLen ); + pahsc->input.streamInterleavingBuffer = NULL; + } + if ( pahsc->input.streamInterleavingBuffer == NULL ) + { + pahsc->input.streamInterleavingBufferLen = numBytes; + pahsc->input.streamInterleavingBuffer = (float *)PaHost_AllocateFastMemory( pahsc->input.streamInterleavingBufferLen ); + } + + /* Perform interleaving by writing to temp buffer. */ + currentInterleavedChannelIndex = 0; + numInterleavedChannels = past->past_NumInputChannels; + numChannelsRemaining = numInterleavedChannels; + + for( i=0; imNumberBuffers; i++ ) + { + int j; + int numBufChannels = inInputData->mBuffers[i].mNumberChannels; + /* Don't use more than we need or more than we have. */ + int numChannelsUsedInThisBuffer = (numChannelsRemaining < numBufChannels ) ? + numChannelsRemaining : numBufChannels; + for( j=0; jinput.streamInterleavingBuffer[ k*numInterleavedChannels + currentInterleavedChannelIndex ] = + ((float *)inInputData->mBuffers[i].mData)[ k*numBufChannels + j ]; + } + currentInterleavedChannelIndex++; + } + numChannelsRemaining -= numChannelsUsedInThisBuffer; + if( numChannelsRemaining <= 0 ) break; + } + + inputNativeBufferfPtr = (char *)pahsc->input.streamInterleavingBuffer; + } + else + { + inputNativeBufferfPtr = (char*)inInputData->mBuffers[0].mData; + numBytes += inInputData->mBuffers[0].mDataByteSize; + } + + writeRoom = RingBuffer_GetWriteAvailable( &pahsc->ringBuffer ); + + if( numBytes <= writeRoom ) + { + RingBuffer_Write( &pahsc->ringBuffer, inputNativeBufferfPtr, numBytes ); + DBUGBACK(("PaOSX_WriteInputRingBuffer: wrote %ld bytes to FIFO.\n", inInputData->mBuffers[0].mDataByteSize)); + } // FIXME else drop samples on floor, remember overflow??? + + return noErr; +} + +/********************************************************************** +** Use any available input buffers by writing to RingBuffer. +** Process input if PA_MODE_INPUT_ONLY. +*/ +static OSStatus PaOSX_HandleInput( internalPortAudioStream *past, + const AudioBufferList* inInputData ) +{ + OSStatus err = noErr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( inInputData->mNumberBuffers > 0 ) + { + /* Write to FIFO here if we are only using this callback. */ + if( (pahsc->mode == PA_MODE_INPUT_ONLY) || (pahsc->mode == PA_MODE_IO_ONE_DEVICE) ) + { + err = PaOSX_WriteInputRingBuffer( past, inInputData ); + if( err != noErr ) goto error; + } + } + + if( pahsc->mode == PA_MODE_INPUT_ONLY ) + { + /* Generate user buffers as long as we have a half full input FIFO. */ + long halfSize = pahsc->ringBuffer.bufferSize / 2; + while( (RingBuffer_GetReadAvailable( &pahsc->ringBuffer ) >= halfSize) && + (past->past_StopSoon == 0) ) + { + err = PaOSX_LoadAndProcess ( past, NULL, NULL ); + if( err != noErr ) goto error; + } + } + +error: + return err; +} + +/********************************************************************** +** Fill any available output buffers. +*/ +static OSStatus PaOSX_HandleOutput( internalPortAudioStream *past, + AudioBufferList* outOutputData ) +{ + OSStatus err = noErr; + void *outputNativeBufferfPtr = NULL; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + UInt32 numBytes = 0; + int numChannelsRemaining; + Boolean deinterleavingNeeded; + int numFramesInOutputBuffer; + + deinterleavingNeeded = past->past_NumOutputChannels != outOutputData->mBuffers[0].mNumberChannels; + + numFramesInOutputBuffer = outOutputData->mBuffers[0].mDataByteSize / (sizeof(float) * outOutputData->mBuffers[0].mNumberChannels); + + if( pahsc->mode != PA_MODE_INPUT_ONLY ) + { + /* If we are using output, then we need an empty output buffer. */ + if( outOutputData->mNumberBuffers > 0 ) + { + + /* If we have multiple CoreAudio buffers, then we will need to deinterleave after conversion. */ + if( deinterleavingNeeded ) + { + numBytes = numFramesInOutputBuffer * sizeof(float) * past->past_NumOutputChannels; + + /* Free old buffer if we are allocating new one. */ + if ( (pahsc->output.streamInterleavingBuffer != NULL) && + (pahsc->output.streamInterleavingBufferLen < numBytes) ) + { + PaHost_FreeFastMemory( pahsc->output.streamInterleavingBuffer, pahsc->output.streamInterleavingBufferLen ); + pahsc->output.streamInterleavingBuffer = NULL; + } + /* Allocate interleaving buffer if needed. */ + if ( pahsc->output.streamInterleavingBuffer == NULL ) + { + pahsc->output.streamInterleavingBufferLen = numBytes; + pahsc->output.streamInterleavingBuffer = (float *)PaHost_AllocateFastMemory( pahsc->output.streamInterleavingBufferLen ); + } + + outputNativeBufferfPtr = (void*)pahsc->output.streamInterleavingBuffer; + } + else + { + numBytes = outOutputData->mBuffers[0].mDataByteSize; + outputNativeBufferfPtr = (void*)outOutputData->mBuffers[0].mData; + } + + /* Pull code from PA user through converter. */ + err = AudioConverterFillBuffer( + pahsc->output.converter, + PaOSX_OutputConverterCallbackProc, + past, + &numBytes, + outputNativeBufferfPtr); + if( err != noErr ) + { + PRINT_ERR("PaOSX_HandleOutput: AudioConverterFillBuffer failed", err); + goto error; + } + + /* Deinterleave data from PortAudio and write to multiple CoreAudio buffers. */ + if( deinterleavingNeeded ) + { + int numInterleavedChannels = past->past_NumOutputChannels; + int i, currentInterleavedChannelIndex = 0; + numChannelsRemaining = numInterleavedChannels; + + for( i=0; imNumberBuffers; i++ ) + { + int numBufChannels = outOutputData->mBuffers[i].mNumberChannels; + int j; + /* Don't use more than we need or more than we have. */ + int numChannelsUsedInThisBuffer = (numChannelsRemaining < numBufChannels ) ? + numChannelsRemaining : numBufChannels; + + for( j=0; jmBuffers[i].mData)[ k*numBufChannels + j ] = + pahsc->output.streamInterleavingBuffer[ k*numInterleavedChannels + currentInterleavedChannelIndex ]; + } + currentInterleavedChannelIndex++; + } + + numChannelsRemaining -= numChannelsUsedInThisBuffer; + if( numChannelsRemaining <= 0 ) break; + } + } + } + } + +error: + return err; +} + +/****************************************************************** + * This callback is used when two separate devices are used for input and output. + * This often happens when using USB devices which present as two devices: input and output. + * It just writes its data to a FIFO so that it can be read by the main callback + * proc PaOSX_CoreAudioIOCallback(). + */ +static OSStatus PaOSX_CoreAudioInputCallback (AudioDeviceID inDevice, const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, + void* contextPtr) +{ + OSStatus err = noErr; + internalPortAudioStream *past = (internalPortAudioStream *) contextPtr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* If there is a FIFO for input then write to it. */ + if( pahsc->ringBufferData != NULL ) + { + err = PaOSX_WriteInputRingBuffer( past, inInputData ); + if( err != noErr ) goto error; + } +error: + return err; +} + +/****************************************************************** + * This is the primary callback for CoreAudio. + * It can handle input and/or output for a single device. + * It takes input from CoreAudio, converts it and passes it to the + * PortAudio user callback. Then takes the PA results and passes it + * back to CoreAudio. + */ +static OSStatus PaOSX_CoreAudioIOCallback (AudioDeviceID inDevice, const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, + void* contextPtr) +{ + OSStatus err = noErr; + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + past = (internalPortAudioStream *) contextPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* Has someone asked us to abort by calling Pa_AbortStream()? */ + if( past->past_StopNow ) + { + past->past_IsActive = 0; /* Will cause thread to return. */ + } + /* Has someone asked us to stop by calling Pa_StopStream() + * OR has a user callback returned '1' to indicate finished. + */ + else if( past->past_StopSoon ) + { + // FIXME - Pretend all done. Should wait for audio to play out but CoreAudio latency very low. + past->past_IsActive = 0; /* Will cause thread to return. */ + } + else + { + /* use time stamp from CoreAudio if valid */ + if( inOutputTime->mFlags & kAudioTimeStampSampleTimeValid) + { + past->past_FrameCount = inOutputTime->mSampleTime; + } + else if( inInputTime->mFlags & kAudioTimeStampSampleTimeValid) + { + past->past_FrameCount = inInputTime->mSampleTime; + } + + /* Measure CPU load. */ + Pa_StartUsageCalculation( past ); + past->past_NumCallbacks += 1; + + /* Process full input buffer. */ + err = PaOSX_HandleInput( past, inInputData ); + if( err != 0 ) goto error; + + /* Fill up empty output buffers. */ + err = PaOSX_HandleOutput( past, outOutputData ); + if( err != 0 ) goto error; + + Pa_EndUsageCalculation( past ); + } + + if( err != 0 ) DBUG(("PaOSX_CoreAudioIOCallback: returns %ld.\n", err )); + +error: + return err; +} + +/*******************************************************************/ +/** Attempt to set device sample rate. + * This is not critical because we use an AudioConverter but we may + * get better fidelity if we can avoid resampling. + * + * Only set format once because some devices take time to settle. + * Return flag indicating whether format changed so we know whether to wait + * for DevicePropertyListener to get called. + * + * @return negative error, zero if no change, or one if changed successfully. + */ +static PaError PaOSX_SetFormat( AudioDeviceID devID, Boolean isInput, + double desiredRate, int desiredNumChannels ) +{ + AudioStreamBasicDescription formatDesc; + PaError result = 0; + OSStatus err; + UInt32 dataSize; + Float64 originalRate; + int originalChannels; + + /* Get current device format. This is critical because if we pass + * zeros for unspecified fields then the iMic device gets switched to a 16 bit + * integer format!!! I don't know if this is a Mac bug or not. But it only + * started happening when I upgraded from OS X V10.1 to V10.2 (Jaguar). + */ + dataSize = sizeof(formatDesc); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyStreamFormat, &dataSize, &formatDesc); + if( err != noErr ) + { + PRINT_ERR("PaOSX_SetFormat: Could not get format.", err); + sSavedHostError = err; + return paHostError; + } + + originalRate = formatDesc.mSampleRate; + originalChannels = formatDesc.mChannelsPerFrame; + + // Is it already set to the correct format? + if( (originalRate != desiredRate) || (originalChannels != desiredNumChannels) ) + { + DBUG(("PaOSX_SetFormat: try to change sample rate to %f.\n", desiredRate )); + DBUG(("PaOSX_SetFormat: try to set number of channels to %d\n", desiredNumChannels)); + + formatDesc.mSampleRate = desiredRate; + formatDesc.mChannelsPerFrame = desiredNumChannels; + formatDesc.mBytesPerFrame = formatDesc.mChannelsPerFrame * sizeof(float); + formatDesc.mBytesPerPacket = formatDesc.mBytesPerFrame * formatDesc.mFramesPerPacket; + + err = AudioDeviceSetProperty( devID, 0, 0, + isInput, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); + if (err != noErr) + { + /* Could not set to desired rate so query for closest match. */ + dataSize = sizeof(formatDesc); + err = AudioDeviceGetProperty( devID, 0, + isInput, kAudioDevicePropertyStreamFormatMatch, &dataSize, &formatDesc); + + DBUG(("PaOSX_SetFormat: closest rate is %f.\n", formatDesc.mSampleRate )); + DBUG(("PaOSX_SetFormat: closest numChannels is %d.\n", (int)formatDesc.mChannelsPerFrame )); + // Set to closest if different from original. + if( (err == noErr) && + ((originalRate != formatDesc.mSampleRate) || + (originalChannels != formatDesc.mChannelsPerFrame)) ) + { + err = AudioDeviceSetProperty( devID, 0, 0, + isInput, kAudioDevicePropertyStreamFormat, sizeof(formatDesc), &formatDesc); + if( err == noErr ) result = 1; + } + } + else result = 1; + } + + return result; +} + +/******************************************************************* + * Check volume level of device. If below threshold, then set to newLevel. + * Using volume instead of decibels because decibel range varies by device. + */ +static void PaOSX_FixVolumeScalars( AudioDeviceID devID, Boolean isInput, + int numChannels, double threshold, double newLevel ) +{ + OSStatus err = noErr; + UInt32 dataSize; + int iChannel; + +/* The master channel is 0. Left and right are channels 1 and 2. */ +/* Fix volume. */ + for( iChannel = 0; iChannel<=numChannels; iChannel++ ) + { + Float32 fdata32; + dataSize = sizeof( fdata32 ); + err = AudioDeviceGetProperty( devID, iChannel, isInput, + kAudioDevicePropertyVolumeScalar, &dataSize, &fdata32 ); + if( err == noErr ) + { + DBUG(("kAudioDevicePropertyVolumeScalar for channel %d = %f\n", iChannel, fdata32)); + if( fdata32 <= (Float32) threshold ) + { + dataSize = sizeof( fdata32 ); + fdata32 = (Float32) newLevel; + err = AudioDeviceSetProperty( devID, 0, iChannel, isInput, + kAudioDevicePropertyVolumeScalar, dataSize, &fdata32 ); + if( err != noErr ) + { + PRINT(("Warning: audio volume is very low and could not be turned up.\n")); + } + else + { + PRINT(("Volume for audio channel %d was <= %4.2f so set to %4.2f by PortAudio!\n", + iChannel, threshold, newLevel )); + } + } + } + } +/* Unmute if muted. */ + for( iChannel = 0; iChannel<=numChannels; iChannel++ ) + { + UInt32 uidata32; + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, iChannel, isInput, + kAudioDevicePropertyMute, &dataSize, &uidata32 ); + if( err == noErr ) + { + DBUG(("uidata32 for channel %d = %ld\n", iChannel, uidata32)); + if( uidata32 == 1 ) // muted? + { + dataSize = sizeof( uidata32 ); + uidata32 = 0; // unmute + err = AudioDeviceSetProperty( devID, 0, iChannel, isInput, + kAudioDevicePropertyMute, dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT(("Warning: audio is muted and could not be unmuted!\n")); + } + else + { + PRINT(("Audio channel %d was unmuted by PortAudio!\n", iChannel )); + } + } + } + } + +} + +#if 0 +static void PaOSX_DumpDeviceInfo( AudioDeviceID devID, Boolean isInput ) +{ + OSStatus err = noErr; + UInt32 dataSize; + UInt32 uidata32; + Float32 fdata32; + AudioValueRange audioRange; + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyLatency, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading kAudioDevicePropertyLatency", err); + return; + } + PRINT(("kAudioDevicePropertyLatency = %d\n", (int)uidata32 )); + + dataSize = sizeof( fdata32 ); + err = AudioDeviceGetProperty( devID, 1, isInput, + kAudioDevicePropertyVolumeScalar, &dataSize, &fdata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading kAudioDevicePropertyVolumeScalar", err); + return; + } + PRINT(("kAudioDevicePropertyVolumeScalar = %f\n", fdata32 )); + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferSize, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size", err); + return; + } + PRINT(("kAudioDevicePropertyBufferSize = %d bytes\n", (int)uidata32 )); + + dataSize = sizeof( audioRange ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferSizeRange, &dataSize, &audioRange ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size range", err); + return; + } + PRINT(("kAudioDevicePropertyBufferSizeRange = %g to %g bytes\n", audioRange.mMinimum, audioRange.mMaximum )); + + dataSize = sizeof( uidata32 ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferFrameSize, &dataSize, &uidata32 ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size", err); + return; + } + PRINT(("kAudioDevicePropertyBufferFrameSize = %d frames\n", (int)uidata32 )); + + dataSize = sizeof( audioRange ); + err = AudioDeviceGetProperty( devID, 0, isInput, + kAudioDevicePropertyBufferFrameSizeRange, &dataSize, &audioRange ); + if( err != noErr ) + { + PRINT_ERR("Error reading buffer size range", err); + return; + } + PRINT(("kAudioDevicePropertyBufferFrameSizeRange = %g to %g frames\n", audioRange.mMinimum, audioRange.mMaximum )); + + return; +} +#endif + +/*******************************************************************/ +static OSStatus PAOSX_DevicePropertyListener (AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData) +{ + PaHostSoundControl *pahsc; + internalPortAudioStream *past; + UInt32 dataSize; + OSStatus err = noErr; + AudioStreamBasicDescription userStreamFormat, hardwareStreamFormat; + Boolean updateInverseMicros; + Boolean updateConverter; + + past = (internalPortAudioStream *) inClientData; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + DBUG(("PAOSX_DevicePropertyListener: called with propertyID = 0x%0X\n", (unsigned int) inPropertyID )); + + updateInverseMicros = (inDevice == pahsc->primaryDeviceID) && + ((inPropertyID == kAudioDevicePropertyStreamFormat) || + (inPropertyID == kAudioDevicePropertyBufferFrameSize)); + + updateConverter = (inPropertyID == kAudioDevicePropertyStreamFormat); + + // Sample rate needed for both. + if( updateConverter || updateInverseMicros ) + { + + /* Get target device format */ + dataSize = sizeof(hardwareStreamFormat); + err = AudioDeviceGetProperty(inDevice, 0, isInput, + kAudioDevicePropertyStreamFormat, &dataSize, &hardwareStreamFormat); + if( err != noErr ) + { + PRINT_ERR("PAOSX_DevicePropertyListener: Could not get device format", err); + sSavedHostError = err; + goto error; + } + } + + if( updateConverter ) + { + DBUG(("PAOSX_DevicePropertyListener: HW rate = %f\n", hardwareStreamFormat.mSampleRate )); + DBUG(("PAOSX_DevicePropertyListener: user rate = %f\n", past->past_SampleRate )); + DBUG(("PAOSX_DevicePropertyListener: HW mChannelsPerFrame = %d\n", (int)hardwareStreamFormat.mChannelsPerFrame )); + + /* Set source user format. */ + userStreamFormat = hardwareStreamFormat; + userStreamFormat.mSampleRate = past->past_SampleRate; // sample rate of the user synthesis code + userStreamFormat.mChannelsPerFrame = (isInput) ? past->past_NumInputChannels : past->past_NumOutputChannels; // the number of channels in each frame + DBUG(("PAOSX_DevicePropertyListener: User mChannelsPerFrame = %d\n", (int)userStreamFormat.mChannelsPerFrame )); + + userStreamFormat.mBytesPerFrame = userStreamFormat.mChannelsPerFrame * sizeof(float); + userStreamFormat.mBytesPerPacket = userStreamFormat.mBytesPerFrame * userStreamFormat.mFramesPerPacket; + + /* Don't use AudioConverter for merging channels. */ + if( hardwareStreamFormat.mChannelsPerFrame > userStreamFormat.mChannelsPerFrame ) + { + hardwareStreamFormat.mChannelsPerFrame = userStreamFormat.mChannelsPerFrame; + hardwareStreamFormat.mBytesPerFrame = userStreamFormat.mBytesPerFrame; + hardwareStreamFormat.mBytesPerPacket = userStreamFormat.mBytesPerPacket; + } + + if( isInput ) + { + if( pahsc->input.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->input.converter)); + } + + // Convert from hardware format to user format. + err = AudioConverterNew ( + &hardwareStreamFormat, + &userStreamFormat, + &pahsc->input.converter ); + if( err != noErr ) + { + PRINT_ERR("Could not create input format converter", err); + sSavedHostError = err; + goto error; + } + } + else + { + if( pahsc->output.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->output.converter)); + } + + // Convert from user format to hardware format. + err = AudioConverterNew ( + &userStreamFormat, + &hardwareStreamFormat, + &pahsc->output.converter ); + if( err != noErr ) + { + PRINT_ERR("Could not create output format converter", err); + sSavedHostError = err; + goto error; + } + } + } + + if( updateInverseMicros ) + { + // Update coefficient used to calculate CPU Load based on sampleRate and bufferSize. + UInt32 ioBufferSize; + dataSize = sizeof(ioBufferSize); + err = AudioDeviceGetProperty( inDevice, 0, isInput, + kAudioDevicePropertyBufferFrameSize, &dataSize, + &ioBufferSize); + if( err == noErr ) + { + pahsc->inverseMicrosPerHostBuffer = hardwareStreamFormat.mSampleRate / + (1000000.0 * ioBufferSize); + } + } + +error: + pahsc->formatListenerCalled = true; + return err; +} + +/* Allocate FIFO between Device callback and Converter callback so that device can push data +* and converter can pull data. +*/ +static PaError PaOSX_CreateInputRingBuffer( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + OSStatus err = noErr; + UInt32 dataSize; + double sampleRateRatio; + long numBytes; + UInt32 framesPerHostBuffer; + UInt32 bytesForDevice; + UInt32 bytesForUser; + AudioStreamBasicDescription formatDesc; + + dataSize = sizeof(formatDesc); + err = AudioDeviceGetProperty( pahsc->input.audioDeviceID, 0, IS_INPUT, + kAudioDevicePropertyStreamFormat, &dataSize, &formatDesc); + if( err != noErr ) + { + PRINT_ERR("PaOSX_CreateInputRingBuffer: Could not get I/O buffer size.\n", err); + sSavedHostError = err; + return paHostError; + } + + // If device is delivering audio faster than being consumed then buffer must be bigger. + sampleRateRatio = formatDesc.mSampleRate / past->past_SampleRate; + + // Get size of CoreAudio IO buffers. + dataSize = sizeof(framesPerHostBuffer); + err = AudioDeviceGetProperty( pahsc->input.audioDeviceID, 0, IS_INPUT, + kAudioDevicePropertyBufferFrameSize, &dataSize, + &framesPerHostBuffer); + if( err != noErr ) + { + PRINT_ERR("PaOSX_CreateInputRingBuffer: Could not get I/O buffer size.\n", err); + sSavedHostError = err; + return paHostError; + } + + bytesForDevice = framesPerHostBuffer * formatDesc.mChannelsPerFrame * sizeof(Float32) * 2; + + bytesForUser = past->past_FramesPerUserBuffer * past->past_NumInputChannels * + sizeof(Float32) * 3 * sampleRateRatio; + + // Ring buffer should be large enough to consume audio input from device, + // and to deliver a complete user buffer. + numBytes = (bytesForDevice > bytesForUser) ? bytesForDevice : bytesForUser; + + numBytes = RoundUpToNextPowerOf2( numBytes ); + + DBUG(("PaOSX_CreateInputRingBuffer: FIFO numBytes = %ld\n", numBytes)); + pahsc->ringBufferData = PaHost_AllocateFastMemory( numBytes ); + if( pahsc->ringBufferData == NULL ) + { + return paInsufficientMemory; + } + RingBuffer_Init( &pahsc->ringBuffer, numBytes, pahsc->ringBufferData ); + // make it look full at beginning + RingBuffer_AdvanceWriteIndex( &pahsc->ringBuffer, numBytes ); + + return paNoError; +} + +/****************************************************************** + * Try to set the I/O bufferSize of the device. + * Scale the size by the ratio of the sample rates so that the converter will have + * enough data to operate on. + */ +static OSStatus PaOSX_SetDeviceBufferSize( AudioDeviceID devID, Boolean isInput, int framesPerUserBuffer, Float64 sampleRateRatio ) +{ + UInt32 dataSize; + UInt32 ioBufferSize; + int scaler; + + scaler = (int) sampleRateRatio; + if( sampleRateRatio > (Float64) scaler ) scaler += 1; + DBUG(("PaOSX_SetDeviceBufferSize: buffer size scaler = %d\n", scaler )); + ioBufferSize = framesPerUserBuffer * scaler; + + // Limit buffer size to reasonable value. + if( ioBufferSize < 128 ) ioBufferSize = 128; + + dataSize = sizeof(ioBufferSize); + return AudioDeviceSetProperty( devID, 0, 0, isInput, + kAudioDevicePropertyBufferFrameSize, dataSize, + &ioBufferSize); +} + + +/*******************************************************************/ +static PaError PaOSX_OpenCommonDevice( internalPortAudioStream *past, + PaHostInOut *inOut, Boolean isInput ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + PaError result = paNoError; + OSStatus err = noErr; + Float64 deviceRate; + + PaOSX_FixVolumeScalars( inOut->audioDeviceID, isInput, + inOut->numChannels, 0.1, 0.9 ); + + // The HW device format changes are asynchronous. + // So we don't know when or if the PAOSX_DevicePropertyListener() will + // get called. To be safe, call the listener now to forcibly create the converter. + if( inOut->converter == NULL ) + { + err = PAOSX_DevicePropertyListener (inOut->audioDeviceID, + 0, isInput, kAudioDevicePropertyStreamFormat, past); + if (err != kAudioHardwareNoError) + { + PRINT_ERR("PaOSX_OpenCommonDevice: PAOSX_DevicePropertyListener failed.\n", err); + sSavedHostError = err; + return paHostError; + } + } + + // Add listener for when format changed by other apps. + DBUG(("PaOSX_OpenCommonDevice: call AudioDeviceAddPropertyListener()\n" )); + err = AudioDeviceAddPropertyListener( inOut->audioDeviceID, 0, isInput, + kAudioDevicePropertyStreamFormat, + (AudioDevicePropertyListenerProc) PAOSX_DevicePropertyListener, past ); + if (err != noErr) + { + return -1; // FIXME + } + + // Only change format if current HW format is different. + // Don't bother to check result because we are going to use an AudioConverter anyway. + pahsc->formatListenerCalled = false; + result = PaOSX_SetFormat( inOut->audioDeviceID, isInput, past->past_SampleRate, inOut->numChannels ); + // Synchronize with device because format changes put some devices into unusable mode. + if( result > 0 ) + { + const int sleepDurMsec = 50; + int spinCount = MIN_TIMEOUT_MSEC / sleepDurMsec; + while( !pahsc->formatListenerCalled && (spinCount > 0) ) + { + Pa_Sleep( sleepDurMsec ); // FIXME - use a semaphore or signal + spinCount--; + } + if( !pahsc->formatListenerCalled ) + { + PRINT(("PaOSX_OpenCommonDevice: timed out waiting for device format to settle.\n")); + } + result = 0; + } + +#if SET_DEVICE_BUFFER_SIZE + // Try to set the I/O bufferSize of the device. + { + Float64 ratio; + deviceRate = PaOSX_GetDeviceSampleRate( inOut->audioDeviceID, isInput ); + if( deviceRate <= 0.0 ) deviceRate = past->past_SampleRate; + ratio = deviceRate / past->past_SampleRate ; + err = PaOSX_SetDeviceBufferSize( inOut->audioDeviceID, isInput, + past->past_FramesPerUserBuffer, ratio ); + if( err != noErr ) + { + DBUG(("PaOSX_OpenCommonDevice: Could not set I/O buffer size.\n")); + } + } +#endif + + /* Allocate an input buffer because we need it between the user callback and the converter. */ + inOut->converterBuffer = PaHost_AllocateFastMemory( inOut->bytesPerUserNativeBuffer ); + if( inOut->converterBuffer == NULL ) + { + return paInsufficientMemory; + } + + return result; +} + +/*******************************************************************/ +static PaError PaOSX_OpenInputDevice( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + const PaHostDeviceInfo *hostDeviceInfo; + PaError result = paNoError; + + DBUG(("PaOSX_OpenInputDevice: -------------\n")); + + if( (past->past_InputDeviceID < LOWEST_INPUT_DEVID) || + (past->past_InputDeviceID > HIGHEST_INPUT_DEVID) ) + { + return paInvalidDeviceId; + } + hostDeviceInfo = &sDeviceInfos[past->past_InputDeviceID]; + + if( past->past_NumInputChannels > hostDeviceInfo->paInfo.maxInputChannels ) + { + return paInvalidChannelCount; /* Too many channels! */ + } + pahsc->input.numChannels = past->past_NumInputChannels; + + // setup PA conversion procedure + result = PaConvert_SetupInput( past, paFloat32 ); + + result = PaOSX_OpenCommonDevice( past, &pahsc->input, IS_INPUT ); + if( result != paNoError ) goto error; + + // Allocate a ring buffer so we can push in data from device, and pull through AudioConverter. + result = PaOSX_CreateInputRingBuffer( past ); + if( result != paNoError ) goto error; + +error: + return result; +} + +/*******************************************************************/ +static PaError PaOSX_OpenOutputDevice( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + const PaHostDeviceInfo *hostDeviceInfo; + PaError result = paNoError; + + DBUG(("PaOSX_OpenOutputDevice: -------------\n")); + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + // Validate DeviceID + DBUG(("PaOSX_OpenOutputDevice: deviceID = 0x%x\n", past->past_OutputDeviceID)); + if( (past->past_OutputDeviceID < LOWEST_OUTPUT_DEVID) || + (past->past_OutputDeviceID > HIGHEST_OUTPUT_DEVID) ) + { + return paInvalidDeviceId; + } + hostDeviceInfo = &sDeviceInfos[past->past_OutputDeviceID]; + + // Validate number of channels. + if( past->past_NumOutputChannels > hostDeviceInfo->paInfo.maxOutputChannels ) + { + return paInvalidChannelCount; /* Too many channels! */ + } + pahsc->output.numChannels = past->past_NumOutputChannels; + + // setup conversion procedure + result = PaConvert_SetupOutput( past, paFloat32 ); + if( result != paNoError ) goto error; + + result = PaOSX_OpenCommonDevice( past, &pahsc->output, IS_OUTPUT ); + if( result != paNoError ) goto error; + +error: + return result; +} + +/******************************************************************* +* Determine how many User Buffers we can put into our CoreAudio stream buffer. +* Uses: +* past->past_FramesPerUserBuffer, etc. +* Sets: +* past->past_NumUserBuffers +* pahsc->input.bytesPerUserNativeBuffer +* pahsc->output.bytesPerUserNativeBuffer +*/ +static void PaOSX_CalcHostBufferSize( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = ( PaHostSoundControl *)past->past_DeviceData; + + // Determine number of user buffers based strictly on minimum reasonable buffer size. + // We ignore the Pa_OpenStream numBuffer parameter because CoreAudio has a big + // mix buffer and handles latency automatically. + past->past_NumUserBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + + // calculate buffer sizes in bytes + pahsc->input.bytesPerUserNativeBuffer = past->past_FramesPerUserBuffer * + Pa_GetSampleSize(paFloat32) * past->past_NumInputChannels; + pahsc->output.bytesPerUserNativeBuffer = past->past_FramesPerUserBuffer * + Pa_GetSampleSize(paFloat32) * past->past_NumOutputChannels; + + DBUG(("PaOSX_CalcNumHostBuffers: past_NumUserBuffers = %ld\n", past->past_NumUserBuffers )); + DBUG(("PaOSX_CalcNumHostBuffers: input.bytesPerUserNativeBuffer = %d\n", pahsc->input.bytesPerUserNativeBuffer )); + DBUG(("PaOSX_CalcNumHostBuffers: output.bytesPerUserNativeBuffer = %d\n", pahsc->output.bytesPerUserNativeBuffer )); +} + +/*****************************************************************************/ +/************** Internal Host API ********************************************/ +/*****************************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + Boolean useInput; + Boolean useOutput; + + assert( past->past_Magic == PA_MAGIC ); + + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) malloc(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + pahsc->primaryDeviceID = kAudioDeviceUnknown; + pahsc->input.audioDeviceID = kAudioDeviceUnknown; + pahsc->output.audioDeviceID = kAudioDeviceUnknown; + + PaOSX_CalcHostBufferSize( past ); + + useOutput = (past->past_OutputDeviceID != paNoDevice) && (past->past_NumOutputChannels > 0); + useInput = (past->past_InputDeviceID != paNoDevice) && (past->past_NumInputChannels > 0); + + // Set device IDs and determine IO Device mode. + if( useOutput ) + { + pahsc->output.audioDeviceID = sDeviceInfos[past->past_OutputDeviceID].audioDeviceID; + pahsc->primaryDeviceID = pahsc->output.audioDeviceID; + if( useInput ) + { + pahsc->input.audioDeviceID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; + pahsc->mode = ( pahsc->input.audioDeviceID != pahsc->primaryDeviceID ) ? + PA_MODE_IO_TWO_DEVICES : PA_MODE_IO_ONE_DEVICE; + } + else + { + pahsc->mode = PA_MODE_OUTPUT_ONLY; + } + } + else + { + /* Just input, not output. */ + pahsc->input.audioDeviceID = sDeviceInfos[past->past_InputDeviceID].audioDeviceID; + pahsc->primaryDeviceID = pahsc->input.audioDeviceID; + pahsc->mode = PA_MODE_INPUT_ONLY; + } + + DBUG(("outputDeviceID = %ld\n", pahsc->output.audioDeviceID )); + DBUG(("inputDeviceID = %ld\n", pahsc->input.audioDeviceID )); + DBUG(("primaryDeviceID = %ld\n", pahsc->primaryDeviceID )); + + /* ------------------ OUTPUT */ + if( useOutput ) + { + result = PaOSX_OpenOutputDevice( past ); + if( result < 0 ) goto error; + } + + /* ------------------ INPUT */ + if( useInput ) + { + result = PaOSX_OpenInputDevice( past ); + if( result < 0 ) goto error; + } + + return result; + +error: + PaHost_CloseStream( past ); + return result; +} + +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + return 0; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + return 0; +} + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + OSStatus err = noErr; + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + past->past_StopSoon = 0; + past->past_StopNow = 0; + past->past_IsActive = 1; + +/* If full duplex and using two separate devices then start input device. */ + if( pahsc->mode == PA_MODE_IO_TWO_DEVICES ) + { + // Associate an IO proc with the device and pass a pointer to the audio data context + err = AudioDeviceAddIOProc(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback, past); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceAddIOProc secondary failed", err ); + goto error; + } + + // start playing sound through the device + err = AudioDeviceStart(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceStart secondary failed", err ); + PRINT(("The program may succeed if you run it again!\n")); + goto error; + } + } + + // Associate an IO proc with the device and pass a pointer to the audio data context + err = AudioDeviceAddIOProc(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback, past); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceAddIOProc primary failed", err ); + goto error; + } + + // start playing sound through the device + err = AudioDeviceStart(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) + { + PRINT_ERR("PaHost_StartEngine: AudioDeviceStart primary failed", err ); + PRINT(("The program may succeed if you run it again!\n")); + goto error; + } + + return result; + +error: + sSavedHostError = err; + return paHostError; +} + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + OSStatus err = noErr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + + /* Tell background thread to stop generating more data and to let current data play out. */ + past->past_StopSoon = 1; + /* If aborting, tell background thread to stop NOW! */ + if( abort ) past->past_StopNow = 1; + past->past_IsActive = 0; + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopOutput: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + + // FIXME - we should ask proc to stop instead of stopping abruptly + err = AudioDeviceStop(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) + { + goto error; + } + + err = AudioDeviceRemoveIOProc(pahsc->primaryDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioIOCallback); + if (err != noErr) goto error; + +/* If full duplex and using two separate devices then stop second input device. */ + if( pahsc->mode == PA_MODE_IO_TWO_DEVICES ) + { + err = AudioDeviceStop(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) goto error; + err = AudioDeviceRemoveIOProc(pahsc->input.audioDeviceID, (AudioDeviceIOProc)PaOSX_CoreAudioInputCallback); + if (err != noErr) goto error; + } + + return paNoError; + +error: + sSavedHostError = err; + return paHostError; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + //PaOSX_DumpDeviceInfo( sDeviceInfos[past->past_OutputDeviceID].audioDeviceID, IS_OUTPUT ); + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_CloseStream: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + // Stop Listener callbacks ASAP before dismantling stream. + if( PA_USING_INPUT ) + { + AudioDeviceRemovePropertyListener( pahsc->input.audioDeviceID, 0, IS_INPUT, + kAudioDevicePropertyStreamFormat, + (AudioDevicePropertyListenerProc) PAOSX_DevicePropertyListener ); + } + + if( PA_USING_OUTPUT ) + { + AudioDeviceRemovePropertyListener( pahsc->output.audioDeviceID, 0, IS_OUTPUT, + kAudioDevicePropertyStreamFormat, + (AudioDevicePropertyListenerProc) PAOSX_DevicePropertyListener ); + } + + if( pahsc->output.converterBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->output.converterBuffer, pahsc->output.bytesPerUserNativeBuffer ); + } + if( pahsc->input.converterBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->input.converterBuffer, pahsc->input.bytesPerUserNativeBuffer ); + } + if( pahsc->ringBufferData != NULL ) + { + PaHost_FreeFastMemory( pahsc->ringBufferData, pahsc->ringBuffer.bufferSize ); + } + if( pahsc->output.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->output.converter)); + } + if( pahsc->input.converter != NULL ) + { + verify_noerr(AudioConverterDispose (pahsc->input.converter)); + } + if ( pahsc->input.streamInterleavingBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->input.streamInterleavingBuffer, pahsc->input.streamInterleavingBufferLen ); + } + if ( pahsc->output.streamInterleavingBuffer != NULL ) + { + PaHost_FreeFastMemory( pahsc->output.streamInterleavingBuffer, pahsc->output.streamInterleavingBufferLen ); + } + + free( pahsc ); + past->past_DeviceData = NULL; + + return paNoError; +} + +/********************************************************************** +** Initialize Host dependant part of API. +*/ +PaError PaHost_Init( void ) +{ + return PaOSX_MaybeQueryDevices(); +} + +/************************************************************************* +** Cleanup device info. +*/ +PaError PaHost_Term( void ) +{ + int i; + + if( sDeviceInfos != NULL ) + { + for( i=0; ipast_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) past->past_IsActive; +} + +/*****************************************************************************/ +/************** External User API ********************************************/ +/*****************************************************************************/ + +/********************************************************************** +** Query devices and use result. +*/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result = PaOSX_MaybeQueryDevices(); + if( result < 0 ) return result; + return sDefaultInputDeviceID; +} + +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result = PaOSX_MaybeQueryDevices(); + if( result < 0 ) return result; + return sDefaultOutputDeviceID; +} + + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Because CoreAudio manages latency, this just selects +** a reasonably small number of buffers. +*/ +int Pa_GetMinNumBuffers( int framesPerBuffer, double framesPerSecond ) +{ + int minBuffers; + double denominator; + int minLatencyMsec = PA_MIN_LATENCY_MSEC; + denominator = 1000.0 * framesPerBuffer; + minBuffers = (int) (((minLatencyMsec * framesPerSecond) + denominator - 1) / denominator ); + if( minBuffers < 1 ) minBuffers = 1; + return minBuffers; +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + usleep( msec * 1000 ); +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + AudioTimeStamp timeStamp; + PaTimestamp streamTime; + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + AudioDeviceGetCurrentTime(pahsc->primaryDeviceID, &timeStamp); + + streamTime = ( timeStamp.mFlags & kAudioTimeStampSampleTimeValid) ? + timeStamp.mSampleTime : past->past_FrameCount; + + return streamTime; +} + +/************************************************************************************/ +long Pa_GetHostError() +{ + return sSavedHostError; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + if( sNumPaDevices <= 0 ) Pa_Initialize(); + return sNumPaDevices; +} + +/************************************************************************* +** PaDeviceInfo structures have already been created +** so just return the pointer. +** +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + if( id < 0 || id >= sNumPaDevices ) + return NULL; + + return &sDeviceInfos[id].paInfo; +} + + diff --git a/pd/portaudio_v18/pablio/README.txt b/pd/portaudio_v18/pablio/README.txt new file mode 100644 index 00000000..99c7d146 --- /dev/null +++ b/pd/portaudio_v18/pablio/README.txt @@ -0,0 +1,39 @@ +README for PABLIO +Portable Audio Blocking I/O Library +Author: Phil Burk + +PABLIO is a simplified interface to PortAudio that provide +read/write style blocking I/O. + +Please see the .DOC file for documentation. + +/* + * More information on PortAudio at: http://www.portaudio.com + * Copyright (c) 1999-2000 Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + diff --git a/pd/portaudio_v18/pablio/pablio.c b/pd/portaudio_v18/pablio/pablio.c new file mode 100644 index 00000000..d3a1bcf2 --- /dev/null +++ b/pd/portaudio_v18/pablio/pablio.c @@ -0,0 +1,327 @@ +/* + * $Id: pablio.c,v 1.1.1.1.4.4 2003/03/13 17:28:33 philburk Exp $ + * pablio.c + * Portable Audio Blocking Input/Output utility. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* History: + * PLB021214 - check for valid stream in CloseAudioStream() to prevent hang. + * add timeOutMSec to CloseAudioStream() to prevent hang. + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include "pablio.h" +#include + +/************************************************************************/ +/******** Constants *****************************************************/ +/************************************************************************/ + +#define FRAMES_PER_BUFFER (256) + +/************************************************************************/ +/******** Prototypes ****************************************************/ +/************************************************************************/ + +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ); +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ); + +/************************************************************************/ +/******** Functions *****************************************************/ +/************************************************************************/ + +/* Called from PortAudio. + * Read and write data only if there is room in FIFOs. + */ +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + PABLIO_Stream *data = (PABLIO_Stream*)userData; + long numBytes = data->bytesPerFrame * framesPerBuffer; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int i; + int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + for( i=numRead; ibuffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesWritten; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesRead; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ) +{ + int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + return bytesEmpty / aStream->bytesPerFrame; +} + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ) +{ + int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO ); + return bytesFull / aStream->bytesPerFrame; +} + +/************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2; + aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame; + + /* Initialize PortAudio */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + /* Warning: numFrames must be larger than amount of data processed per interrupt + * inside PA to prevent glitches. Just to be safe, adjust size upwards. + */ + minNumBuffers = 2 * Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + numFrames = minNumBuffers * FRAMES_PER_BUFFER; + /* The PortAudio callback runs in a high priority thread. But PABLIO + * runs in a normal foreground thread. So we may have much worse + * latency in PABLIO. So adjust latency to a safe level. + */ + { + const int safeLatencyMSec = 200; + int minLatencyMSec = (int) ((1000 * numFrames) / sampleRate); + if( minLatencyMSec < safeLatencyMSec ) + { + numFrames = (int) ((safeLatencyMSec * sampleRate) / 1000); + } + } + numFrames = RoundUpToNextPowerOf2( numFrames ); + + /* Initialize Ring Buffers */ + doRead = ((flags & PABLIO_READ) != 0); + doWrite = ((flags & PABLIO_WRITE) != 0); + if(doRead) + { + err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + } + if(doWrite) + { + long numBytes; + err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes ); + } + + /* Open a PortAudio stream that we will use to communicate with the underlying + * audio drivers. */ + err = Pa_OpenStream( + &aStream->stream, + (doRead ? Pa_GetDefaultInputDeviceID() : paNoDevice), + (doRead ? aStream->samplesPerFrame : 0 ), + format, + NULL, + (doWrite ? Pa_GetDefaultOutputDeviceID() : paNoDevice), + (doWrite ? aStream->samplesPerFrame : 0 ), + format, + NULL, + sampleRate, + FRAMES_PER_BUFFER, + minNumBuffers, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + blockingIOCallback, + aStream ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( aStream->stream ); + if( err != paNoError ) goto error; + + *rwblPtr = aStream; + return paNoError; + +error: + CloseAudioStream( aStream ); + *rwblPtr = NULL; + return err; +} + +/************************************************************/ +PaError CloseAudioStream( PABLIO_Stream *aStream ) +{ + PaError err = paNoError; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + if( aStream->stream != NULL ) /* Make sure stream was opened. PLB021214 */ + { + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + int timeOutMSec = 2000; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( (bytesEmpty < byteSize) && (timeOutMSec > 0) ) + { + Pa_Sleep( 20 ); + timeOutMSec -= 20; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + } + +error: + Pa_Terminate(); + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/pd/portaudio_v18/pablio/pablio.def b/pd/portaudio_v18/pablio/pablio.def new file mode 100644 index 00000000..9e2c4e3c --- /dev/null +++ b/pd/portaudio_v18/pablio/pablio.def @@ -0,0 +1,35 @@ +LIBRARY PABLIOV18 +DESCRIPTION 'PABLIO Portable Audio Blocking I/O' + +EXPORTS + ; Explicit exports can go here + Pa_Initialize @1 + Pa_Terminate @2 + Pa_GetHostError @3 + Pa_GetErrorText @4 + Pa_CountDevices @5 + Pa_GetDefaultInputDeviceID @6 + Pa_GetDefaultOutputDeviceID @7 + Pa_GetDeviceInfo @8 + Pa_OpenStream @9 + Pa_OpenDefaultStream @10 + Pa_CloseStream @11 + Pa_StartStream @12 + Pa_StopStream @13 + Pa_StreamActive @14 + Pa_StreamTime @15 + Pa_GetCPULoad @16 + Pa_GetMinNumBuffers @17 + Pa_Sleep @18 + + OpenAudioStream @19 + CloseAudioStream @20 + WriteAudioStream @21 + ReadAudioStream @22 + + Pa_GetSampleSize @23 + + ;123456789012345678901234567890123456 + ;000000000111111111122222222223333333 + + diff --git a/pd/portaudio_v18/pablio/pablio.h b/pd/portaudio_v18/pablio/pablio.h new file mode 100644 index 00000000..a4871f38 --- /dev/null +++ b/pd/portaudio_v18/pablio/pablio.h @@ -0,0 +1,109 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: pablio.h,v 1.1.1.1 2002/01/22 00:52:53 phil Exp $ + * PABLIO.h + * Portable Audio Blocking read/write utility. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * Include file for PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include + +typedef struct +{ + RingBuffer inFIFO; + RingBuffer outFIFO; + PortAudioStream *stream; + int bytesPerFrame; + int samplesPerFrame; +} +PABLIO_Stream; + +/* Values for flags for OpenAudioStream(). */ +#define PABLIO_READ (1<<0) +#define PABLIO_WRITE (1<<1) +#define PABLIO_READ_WRITE (PABLIO_READ|PABLIO_WRITE) +#define PABLIO_MONO (1<<2) +#define PABLIO_STEREO (1<<3) + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ); + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ); + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + * and either PABLIO_MONO or PABLIO_STEREO + */ +PaError OpenAudioStream( PABLIO_Stream **aStreamPtr, double sampleRate, + PaSampleFormat format, long flags ); + +PaError CloseAudioStream( PABLIO_Stream *aStream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PABLIO_H */ diff --git a/pd/portaudio_v18/pablio/pablio_pd.c b/pd/portaudio_v18/pablio/pablio_pd.c new file mode 100644 index 00000000..2596b73c --- /dev/null +++ b/pd/portaudio_v18/pablio/pablio_pd.c @@ -0,0 +1,341 @@ +/* + * $Id: pablio_pd.c,v 1.1.1.1 2003-05-09 16:04:00 ggeiger Exp $ + * pablio.c + * Portable Audio Blocking Input/Output utility. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* History: + * PLB021214 - check for valid stream in CloseAudioStream() to prevent hang. + * add timeOutMSec to CloseAudioStream() to prevent hang. + */ + + /* changes by Miller Puckette (MSP) to support Pd: device selection, + settable audio buffer size, and settable number of channels. + LATER also fix it to poll for input and output fifo fill points. */ + +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include "pablio_pd.h" /* MSP */ +#include + + /* MSP -- FRAMES_PER_BUFFER constant removed */ + +/************************************************************************/ +/******** Prototypes ****************************************************/ +/************************************************************************/ + +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +static PaError PABLIO_InitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame ); +static PaError PABLIO_TermFIFO( RingBuffer *rbuf ); + +/************************************************************************/ +/******** Functions *****************************************************/ +/************************************************************************/ + +/* Called from PortAudio. + * Read and write data only if there is room in FIFOs. + */ +static int blockingIOCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + PABLIO_Stream *data = (PABLIO_Stream*)userData; + long numBytes = data->bytesPerFrame * framesPerBuffer; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer != NULL ) + { + RingBuffer_Write( &data->inFIFO, inputBuffer, numBytes ); + } + if( outputBuffer != NULL ) + { + int i; + int numRead = RingBuffer_Read( &data->outFIFO, outputBuffer, numBytes ); + /* Zero out remainder of buffer if we run out of data. */ + for( i=numRead; ibuffer ) free( rbuf->buffer ); + rbuf->buffer = NULL; + return paNoError; +} + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesWritten; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesWritten = RingBuffer_Write( &aStream->outFIFO, p, numBytes ); + numBytes -= bytesWritten; + p += bytesWritten; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ) +{ + long bytesRead; + char *p = (char *) data; + long numBytes = aStream->bytesPerFrame * numFrames; + while( numBytes > 0) + { + bytesRead = RingBuffer_Read( &aStream->inFIFO, p, numBytes ); + numBytes -= bytesRead; + p += bytesRead; + if( numBytes > 0) Pa_Sleep(10); + } + return numFrames; +} + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ) +{ + int bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + return bytesEmpty / aStream->bytesPerFrame; +} + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ) +{ + int bytesFull = RingBuffer_GetReadAvailable( &aStream->inFIFO ); + return bytesFull / aStream->bytesPerFrame; +} + +/************************************************************/ +static unsigned long RoundUpToNextPowerOf2( unsigned long n ) +{ + long numBits = 0; + if( ((n-1) & n) == 0) return n; /* Already Power of two. */ + while( n > 0 ) + { + n= n>>1; + numBits++; + } + return (1<samplesPerFrame = ((flags&PABLIO_MONO) != 0) ? 1 : 2; + aStream->bytesPerFrame = bytesPerSample * aStream->samplesPerFrame; + + /* Initialize PortAudio */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + +/* Warning: numFrames must be larger than amount of data processed per + interrupt inside PA to prevent glitches. */ /* MSP... */ + minNumBuffers = Pa_GetMinNumBuffers(framesperbuf, sampleRate); + if (minNumBuffers > nbuffers) + fprintf(stderr, + "warning: number of buffers %d less than recommended minimum %d\n", + (int)nbuffers, (int)minNumBuffers); + numFrames = nbuffers * framesperbuf; /* ...MSP */ + + + /* Initialize Ring Buffers */ + doRead = ((flags & PABLIO_READ) != 0); + doWrite = ((flags & PABLIO_WRITE) != 0); + if(doRead) + { + err = PABLIO_InitFIFO( &aStream->inFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + } + if(doWrite) + { + long numBytes; + err = PABLIO_InitFIFO( &aStream->outFIFO, numFrames, aStream->bytesPerFrame ); + if( err != paNoError ) goto error; + /* Make Write FIFO appear full initially. */ + numBytes = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + RingBuffer_AdvanceWriteIndex( &aStream->outFIFO, numBytes ); + } + + /* Open a PortAudio stream that we will use to communicate with the underlying + * audio drivers. */ + err = Pa_OpenStream( + &aStream->stream, + (doRead ? indeviceno : paNoDevice), /* MSP */ + (doRead ? aStream->samplesPerFrame : 0 ), + format, + NULL, + (doWrite ? outdeviceno : paNoDevice), /* MSP */ + (doWrite ? aStream->samplesPerFrame : 0 ), + format, + NULL, + sampleRate, + framesperbuf, /* MSP */ + nbuffers, /* MSP */ + paNoFlag, /* MSP -- portaudio will clip for us */ + blockingIOCallback, + aStream ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( aStream->stream ); + if( err != paNoError ) /* MSP... */ + { + fprintf(stderr, "Pa_StartStream failed; closing audio stream...\n"); + CloseAudioStream( aStream ); + goto error; + } /* ...MSP */ + + *rwblPtr = aStream; + return paNoError; + +error: + CloseAudioStream( aStream ); + *rwblPtr = NULL; + return err; +} + +/************************************************************/ +PaError CloseAudioStream( PABLIO_Stream *aStream ) +{ + PaError err = paNoError; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + if( aStream->stream != NULL ) /* Make sure stream was opened. PLB021214 */ + { + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + int timeOutMSec = 2000; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( (bytesEmpty < byteSize) && (timeOutMSec > 0) ) + { + Pa_Sleep( 20 ); + timeOutMSec -= 20; + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + } + +error: + Pa_Terminate(); + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/pd/portaudio_v18/pablio/pablio_pd.h b/pd/portaudio_v18/pablio/pablio_pd.h new file mode 100644 index 00000000..a99e74b6 --- /dev/null +++ b/pd/portaudio_v18/pablio/pablio_pd.h @@ -0,0 +1,110 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: pablio_pd.h,v 1.1.1.1 2003-05-09 16:04:00 ggeiger Exp $ + * PABLIO.h + * Portable Audio Blocking read/write utility. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * Include file for PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "portaudio.h" +#include "ringbuffer.h" +#include + +typedef struct +{ + RingBuffer inFIFO; + RingBuffer outFIFO; + PaStream *stream; /* MSP -- was PortAudioStream; probably an error */ + int bytesPerFrame; + int samplesPerFrame; +} +PABLIO_Stream; + +/* Values for flags for OpenAudioStream(). */ +#define PABLIO_READ (1<<0) +#define PABLIO_WRITE (1<<1) +#define PABLIO_READ_WRITE (PABLIO_READ|PABLIO_WRITE) +#define PABLIO_MONO (1<<2) +#define PABLIO_STEREO (1<<3) + +/************************************************************ + * Write data to ring buffer. + * Will not return until all the data has been written. + */ +long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Read data from ring buffer. + * Will not return until all the data has been read. + */ +long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames ); + +/************************************************************ + * Return the number of frames that could be written to the stream without + * having to wait. + */ +long GetAudioStreamWriteable( PABLIO_Stream *aStream ); + +/************************************************************ + * Return the number of frames that are available to be read from the + * stream without having to wait. + */ +long GetAudioStreamReadable( PABLIO_Stream *aStream ); + +/************************************************************ + * Opens a PortAudio stream with default characteristics. + * Allocates PABLIO_Stream structure. + * + * flags parameter can be an ORed combination of: + * PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE, + */ +PaError OpenAudioStream( PABLIO_Stream **rwblPtr, double sampleRate, + PaSampleFormat format, long flags, int nchannels, + int framesperbuf, int nbuffers, + int indeviceno, int outdeviceno); /* MSP */ + +PaError CloseAudioStream( PABLIO_Stream *aStream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PABLIO_H */ diff --git a/pd/portaudio_v18/pablio/ringbuffer.c b/pd/portaudio_v18/pablio/ringbuffer.c new file mode 100644 index 00000000..e0c02890 --- /dev/null +++ b/pd/portaudio_v18/pablio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id: ringbuffer.c,v 1.1.1.1 2002/01/22 00:52:53 phil Exp $ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +/*************************************************************************** + * Initialize FIFO. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + if( ((numBytes-1) & numBytes) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + rbuf->bigMask = (numBytes*2)-1; + rbuf->smallMask = (numBytes)-1; + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->writeIndex = (rbuf->writeIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + return rbuf->readIndex = (rbuf->readIndex + numBytes) & rbuf->bigMask; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/pd/portaudio_v18/pablio/ringbuffer.h b/pd/portaudio_v18/pablio/ringbuffer.h new file mode 100644 index 00000000..4be71d18 --- /dev/null +++ b/pd/portaudio_v18/pablio/ringbuffer.h @@ -0,0 +1,102 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: ringbuffer.h,v 1.1.1.1.4.1 2003/03/13 17:28:14 philburk Exp $ + * ringbuffer.h + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include +#include +#include "ringbuffer.h" +#include + +typedef struct +{ + long bufferSize; /* Number of bytes in FIFO. Power of 2. Set by RingBuffer_Init. */ +/* These are declared volatile because they are written by a different thread than the reader. */ + volatile long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + volatile long readIndex; /* Index of next readable byte. Set by RingBuffer_AdvanceReadIndex. */ + long bigMask; /* Used for wrapping indices with extra bit to distinguish full/empty. */ + long smallMask; /* Used for fitting indices to buffer. */ + char *buffer; +} +RingBuffer; +/* + * Initialize Ring Buffer. + * numBytes must be power of 2, returns -1 if not. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ); + +/* Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ); + +/* Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ); +/* Return number of bytes available for read. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ); +/* Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ); +/* Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ); + +/* Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ); + +/* Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ); + +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _RINGBUFFER_H */ diff --git a/pd/portaudio_v18/pablio/ringbuffer_pd.c b/pd/portaudio_v18/pablio/ringbuffer_pd.c new file mode 100644 index 00000000..97e060c1 --- /dev/null +++ b/pd/portaudio_v18/pablio/ringbuffer_pd.c @@ -0,0 +1,214 @@ +/* + * $Id: ringbuffer_pd.c,v 1.1.1.1 2003-05-09 16:04:00 ggeiger Exp $ + * ringbuffer.c + * Ring Buffer utility.. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +/* + * modified 2002/07/13 by olaf.matthes@gmx.de to allow any number if channels + * + */ + +#include +#include +#include +#include "ringbuffer.h" +#include + +/*************************************************************************** + * Initialize FIFO. + */ +long RingBuffer_Init( RingBuffer *rbuf, long numBytes, void *dataPtr ) +{ + rbuf->bufferSize = numBytes; + rbuf->buffer = (char *)dataPtr; + RingBuffer_Flush( rbuf ); + return 0; +} +/*************************************************************************** +** Return number of bytes available for reading. */ +long RingBuffer_GetReadAvailable( RingBuffer *rbuf ) +{ + long ret = (rbuf->writeIndex - rbuf->readIndex) + rbuf->bufferSize; + if (ret >= 2 * rbuf->bufferSize) + ret -= 2 * rbuf->bufferSize; + return ( ret ); +} +/*************************************************************************** +** Return number of bytes available for writing. */ +long RingBuffer_GetWriteAvailable( RingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - RingBuffer_GetReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read. */ +void RingBuffer_Flush( RingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetWriteRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetWriteAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex; + while (index >= rbuf->bufferSize) + index -= rbuf->bufferSize; + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} + + +/*************************************************************************** +*/ +long RingBuffer_AdvanceWriteIndex( RingBuffer *rbuf, long numBytes ) +{ + long ret = (rbuf->writeIndex + numBytes); + if ( ret > 2 * rbuf->bufferSize) + ret -= 2 * rbuf->bufferSize; /* check for end of buffer */ + return rbuf->writeIndex = ret; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or numBytes, whichever is smaller. +*/ +long RingBuffer_GetReadRegions( RingBuffer *rbuf, long numBytes, + void **dataPtr1, long *sizePtr1, + void **dataPtr2, long *sizePtr2 ) +{ + long index; + long available = RingBuffer_GetReadAvailable( rbuf ); + if( numBytes > available ) numBytes = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex; + while (index > rbuf->bufferSize) + index -= rbuf->bufferSize; + + if( (index + numBytes) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + long firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = numBytes - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index]; + *sizePtr1 = numBytes; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + return numBytes; +} +/*************************************************************************** +*/ +long RingBuffer_AdvanceReadIndex( RingBuffer *rbuf, long numBytes ) +{ + long ret = (rbuf->readIndex + numBytes); + if( ret > 2 * rbuf->bufferSize) + ret -= 2 * rbuf->bufferSize; + return rbuf->readIndex = ret; +} + +/*************************************************************************** +** Return bytes written. */ +long RingBuffer_Write( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numWritten; + void *data1, *data2; + numWritten = RingBuffer_GetWriteRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1 ); + data = ((char *)data) + size1; + memcpy( data2, data, size2 ); + } + else + { + memcpy( data1, data, size1 ); + } + RingBuffer_AdvanceWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return bytes read. */ +long RingBuffer_Read( RingBuffer *rbuf, void *data, long numBytes ) +{ + long size1, size2, numRead; + void *data1, *data2; + numRead = RingBuffer_GetReadRegions( rbuf, numBytes, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1 ); + data = ((char *)data) + size1; + memcpy( data, data2, size2 ); + } + else + { + memcpy( data, data1, size1 ); + } + RingBuffer_AdvanceReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/pd/portaudio_v18/pablio/test_rw.c b/pd/portaudio_v18/pablio/test_rw.c new file mode 100644 index 00000000..27a94b43 --- /dev/null +++ b/pd/portaudio_v18/pablio/test_rw.c @@ -0,0 +1,99 @@ +/* + * $Id: test_rw.c,v 1.2 2002/02/22 22:06:23 philburk Exp $ + * test_rw.c + * Read input from one stream and write it to another. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include "pablio.h" + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (5) +#define SAMPLES_PER_FRAME (2) +#define FRAMES_PER_BLOCK (64) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + SAMPLE samples[SAMPLES_PER_FRAME * FRAMES_PER_BLOCK]; + PaError err; + PABLIO_Stream *aStream; + + printf("Full duplex sound test using PortAudio and RingBuffers\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Process samples in the foreground. */ + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Read one block of data into sample array from audio input. */ + ReadAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + /* Write that same block of data to output. */ + WriteAudioStream( aStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aStream ); + + printf("Full duplex sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/pd/portaudio_v18/pablio/test_rw_echo.c b/pd/portaudio_v18/pablio/test_rw_echo.c new file mode 100644 index 00000000..7bc4e9b4 --- /dev/null +++ b/pd/portaudio_v18/pablio/test_rw_echo.c @@ -0,0 +1,123 @@ +/* + * $Id: test_rw_echo.c,v 1.1.1.1 2002/01/22 00:52:54 phil Exp $ + * test_rw_echo.c + * Echo delayed input to output. + * + * Author: Phil Burk, http://www.softsynth.com/portaudio/ + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * Note that if you need low latency, you should not use PABLIO. + * Use the PA_OpenStream callback technique which is lower level + * than PABLIO. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +/* +** Note that many of the older ISA sound cards on PCs do NOT support +** full duplex audio (simultaneous record and playback). +** And some only support full duplex at lower sample rates. +*/ +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (20) +#define SAMPLES_PER_FRAME (2) + +/* Select whether we will use floats or shorts. */ +#if 1 +#define SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif + +#define NUM_ECHO_FRAMES (2*SAMPLE_RATE) +SAMPLE samples[NUM_ECHO_FRAMES][SAMPLES_PER_FRAME] = {0.0}; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + PABLIO_Stream *aInStream; + PABLIO_Stream *aOutStream; + int index; + + printf("Full duplex sound test using PABLIO\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + /* Open input first so it can start to fill buffers. */ + err = OpenAudioStream( &aInStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_READ | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened input\n"); fflush(stdout); /**/ + + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, SAMPLE_TYPE, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + /* printf("opened output\n"); fflush(stdout); /**/ + + /* Process samples in the foreground. */ + index = 0; + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i++ ) + { + /* Write old frame of data to output. */ + /* samples[index][1] = (i&256) * (1.0f/256.0f); /* sawtooth */ + WriteAudioStream( aOutStream, &samples[index][0], 1 ); + + /* Read one frame of data into sample array for later output. */ + ReadAudioStream( aInStream, &samples[index][0], 1 ); + index += 1; + if( index >= NUM_ECHO_FRAMES ) index = 0; + + if( (i & 0xFFFF) == 0 ) printf("i = %d\n", i ); fflush(stdout); /**/ + } + + CloseAudioStream( aOutStream ); + CloseAudioStream( aInStream ); + + printf("R/W echo sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PortAudio\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/pd/portaudio_v18/pablio/test_w_saw.c b/pd/portaudio_v18/pablio/test_w_saw.c new file mode 100644 index 00000000..ca727aa2 --- /dev/null +++ b/pd/portaudio_v18/pablio/test_w_saw.c @@ -0,0 +1,108 @@ +/* + * $Id: test_w_saw.c,v 1.1.1.1.4.1 2003/02/12 02:22:21 philburk Exp $ + * test_w_saw.c + * Generate stereo sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (15) +#define SAMPLES_PER_FRAME (2) + +#define FREQUENCY (220.0f) +#define PHASE_INCREMENT (2.0f * FREQUENCY / SAMPLE_RATE) +#define FRAMES_PER_BLOCK (100) + +float samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +float phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paFloat32, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases. */ + phases[0] = 0.0; + phases[1] = 0.0; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j 1.0f ) phases[0] -= 2.0f; + samples[j][0] = phases[0]; + + /* On the second channel, generate a sawtooth wave a fifth higher. */ + phases[1] += PHASE_INCREMENT * (3.0f / 2.0f); + if( phases[1] > 1.0f ) phases[1] -= 2.0f; + samples[j][1] = phases[1]; + } + + /* Write samples to output. */ + WriteAudioStream( aOutStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aOutStream ); + + printf("Sawtooth sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PABLIO\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} diff --git a/pd/portaudio_v18/pablio/test_w_saw8.c b/pd/portaudio_v18/pablio/test_w_saw8.c new file mode 100644 index 00000000..0f7e02e3 --- /dev/null +++ b/pd/portaudio_v18/pablio/test_w_saw8.c @@ -0,0 +1,106 @@ +/* + * $Id: test_w_saw8.c,v 1.1.1.1 2002/01/22 00:52:55 phil Exp $ + * test_w_saw8.c + * Generate stereo 8 bit sawtooth waveforms. + * + * Author: Phil Burk, http://www.softsynth.com + * + * This program uses PABLIO, the Portable Audio Blocking I/O Library. + * PABLIO is built on top of PortAudio, the Portable Audio Library. + * + * For more information see: http://www.audiomulch.com/portaudio/ + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include "pablio.h" +#include + +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + + +#define FRAMES_PER_BLOCK (100) + +unsigned char samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +unsigned char phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate unsigned 8 bit sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paUInt8, + (PABLIO_WRITE | PABLIO_STEREO) ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases to "ground" level for paUInt8. */ + phases[0] = 128; + phases[1] = 128; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j +#include +#include +#include "pablio_pd.h" +#include + +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (6) +#define SAMPLES_PER_FRAME (2) + +#define FREQUENCY (220.0f) +#define PHASE_INCREMENT (2.0f * FREQUENCY / SAMPLE_RATE) +#define FRAMES_PER_BLOCK (100) + +float samples[FRAMES_PER_BLOCK][SAMPLES_PER_FRAME]; +float phases[SAMPLES_PER_FRAME]; + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + PaError err; + PABLIO_Stream *aOutStream; + + printf("Generate sawtooth waves using PABLIO.\n"); + fflush(stdout); + + /* Open simplified blocking I/O layer on top of PortAudio. */ + err = OpenAudioStream( &aOutStream, SAMPLE_RATE, paFloat32, + PABLIO_WRITE, 2, 512, 8, -1, -1 ); + if( err != paNoError ) goto error; + + /* Initialize oscillator phases. */ + phases[0] = 0.0; + phases[1] = 0.0; + + for( i=0; i<(NUM_SECONDS * SAMPLE_RATE); i += FRAMES_PER_BLOCK ) + { + /* Generate sawtooth waveforms in a block for efficiency. */ + for( j=0; j 1.0f ) phases[0] -= 2.0f; + samples[j][0] = phases[0]; + + /* On the second channel, generate a sawtooth wave a fifth higher. */ + phases[1] += PHASE_INCREMENT * (3.0f / 2.0f); + if( phases[1] > 1.0f ) phases[1] -= 2.0f; + samples[j][1] = phases[1]; + } + + /* Write samples to output. */ + WriteAudioStream( aOutStream, samples, FRAMES_PER_BLOCK ); + } + + CloseAudioStream( aOutStream ); + + printf("Sawtooth sound test complete.\n" ); + fflush(stdout); + return 0; + +error: + fprintf( stderr, "An error occured while using PABLIO\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return -1; +} -- cgit v1.2.1