From: phil Date: Tue, 22 Jan 2002 00:51:49 +0000 (+0000) Subject: Initial revision X-Git-Tag: initial~1 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=c98797d938b4f5824f72dc7cbcb74d274d07007c;p=portaudio Initial revision --- c98797d938b4f5824f72dc7cbcb74d274d07007c diff --git a/Makefile.linux b/Makefile.linux new file mode 100644 index 0000000..9f67ce6 --- /dev/null +++ b/Makefile.linux @@ -0,0 +1,46 @@ +# Make PortAudio for Linux +# Updated 2001/08/25 Bill Eldridge bill@rfa.org +# Updated 2001/10/16, philburk@softsynth.com, s/unix_oss/unix_oss/ + +# A pretty bare makefile, that figures out all the test files +# and compiles them against the library in the pa_unix_oss directory. + +# Do "make all" and then when happy, "make libinstall" +# (if not happy, "make clean") + +# The ldconfig stuff in libinstall is the wrong way to do it - +# someone tell me the right way, please + + +LIBS = -lm -lpthread + +CDEFINES = -I../pa_common +CFLAGS = -g +LIBINST = /usr/local/lib + +TESTS:= $(wildcard pa_tests/pa*.c pa_tests/debug*.c) + +LIBFILES:= ./pa_common/pa_lib.c ./pa_unix_oss/pa_unix_oss.c + +.c.o: + -gcc -c -I./pa_common $< -o $*.o + -gcc $*.o -o $* -Lpa_unix_oss $(LIBS) -lportaudio + +all: sharedlib tests + +sharedlib: $(LIBFILES:.c=.o) + gcc -shared -o ./pa_unix_oss/libportaudio.so ./pa_common/pa_lib.o ./pa_unix_oss/pa_unix_oss.o + +libinstall: ./pa_unix_oss/libportaudio.so + @cp -f ./pa_unix_oss/libportaudio.so $(LIBINST) + @/sbin/ldconfig -v + +tests: $(TESTS:.c=.o) + +clean: + -@rm -f $(TESTS:.c=.o) + -@rm -f $(TESTS:.c=) + -@rm -f $(LIBFILES:.c=.o) + -@rm -f ./pa_unix_oss/libportaudio.so + + diff --git a/fixdir.bat b/fixdir.bat new file mode 100755 index 0000000..92d6c74 --- /dev/null +++ b/fixdir.bat @@ -0,0 +1,19 @@ +rem Use Astyle to fix style in 'C' files +cd %1% + +fixlines -p *.c +fixlines -p *.cpp +fixlines -p *.cc + +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.c +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.cpp +astyle --style=ansi -c -o --convert-tabs --indent-preprocessor *.cc +del *.orig +@rem convert line terminators to Unix style LFs +fixlines -u *.c +fixlines -u *.cpp +fixlines -u *.cc +fixlines -u *.h +del *.bak + +cd ..\ diff --git a/pa_asio/Callback_adaptation_.pdf b/pa_asio/Callback_adaptation_.pdf new file mode 100644 index 0000000..76bf678 Binary files /dev/null and b/pa_asio/Callback_adaptation_.pdf differ diff --git a/pa_asio/Pa_ASIO.pdf b/pa_asio/Pa_ASIO.pdf new file mode 100644 index 0000000..ac5ecad Binary files /dev/null and b/pa_asio/Pa_ASIO.pdf differ diff --git a/pa_asio/pa_asio.cpp b/pa_asio/pa_asio.cpp new file mode 100644 index 0000000..66adf48 --- /dev/null +++ b/pa_asio/pa_asio.cpp @@ -0,0 +1,3005 @@ +/* + * $Id$ + * Portable Audio I/O Library for ASIO Drivers + * + * Author: Stephane Letz + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 2000-2001 Stephane Letz, 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. + */ + +/* Modification History + + 08-03-01 First version : Stephane Letz + 08-06-01 Tweaks for PC, use C++, buffer allocation, Float32 to Int32 conversion : Phil Burk + 08-20-01 More conversion, PA_StreamTime, Pa_GetHostError : Stephane Letz + 08-21-01 PaUInt8 bug correction, implementation of ASIOSTFloat32LSB and ASIOSTFloat32MSB native formats : Stephane Letz + 08-24-01 MAX_INT32_FP hack, another Uint8 fix : Stephane and Phil + 08-27-01 Implementation of hostBufferSize < userBufferSize case, better management of the ouput buffer when + the stream is stopped : Stephane Letz + 08-28-01 Check the stream pointer for null in bufferSwitchTimeInfo, correct bug in bufferSwitchTimeInfo when + the stream is stopped : Stephane Letz + 10-12-01 Correct the PaHost_CalcNumHostBuffers function: computes FramesPerHostBuffer to be the lowest that + respect requested FramesPerUserBuffer and userBuffersPerHostBuffer : Stephane Letz + 10-26-01 Management of hostBufferSize and userBufferSize of any size : Stephane Letz + 10-27-01 Improve calculus of hostBufferSize to be multiple or divisor of userBufferSize if possible : Stephane and Phil + 10-29-01 Change MAX_INT32_FP to (2147483520.0f) to prevent roundup to 0x80000000 : Phil Burk + 10-31-01 Clear the ouput buffer and user buffers in PaHost_StartOutput, correct bug in GetFirstMultiple : Stephane Letz + 11-06-01 Rename functions : Stephane Letz + 11-08-01 New Pa_ASIO_Adaptor_Init function to init Callback adpatation variables, cleanup of Pa_ASIO_Callback_Input: Stephane Letz + 11-29-01 Break apart device loading to debug random failure in Pa_ASIO_QueryDeviceInfo ; Phil Burk + 01-03-02 Desallocate all resources in PaHost_Term for cases where Pa_CloseStream is not called properly : Stephane Letz + + TO DO : + + - Check Pa_StopSteam and Pa_AbortStream + - Optimization for Input only or Ouput only (really necessary ??) + - Opening of several streams +*/ + + +#include +#include + +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#include "asiosys.h" +#include "asio.h" +#include "asiodrivers.h" + +#if MAC +#include +#else +#include +#include +#include +#endif + +enum { + // number of input and outputs supported by the host application + // you can change these to higher or lower values + kMaxInputChannels = 32, + kMaxOutputChannels = 32 +}; + +/* ASIO specific device information. */ +typedef struct internalPortAudioDevice +{ + PaDeviceInfo pad_Info; +} internalPortAudioDevice; + + +/* ASIO driver internal data storage */ +typedef struct PaHostSoundControl +{ + // ASIOInit() + ASIODriverInfo pahsc_driverInfo; + + // ASIOGetChannels() + int32 pahsc_NumInputChannels; + int32 pahsc_NumOutputChannels; + + // ASIOGetBufferSize() - sizes in frames per buffer + int32 pahsc_minSize; + int32 pahsc_maxSize; + int32 pahsc_preferredSize; + int32 pahsc_granularity; + + // ASIOGetSampleRate() + ASIOSampleRate pahsc_sampleRate; + + // ASIOOutputReady() + bool pahsc_postOutput; + + // ASIOGetLatencies () + int32 pahsc_inputLatency; + int32 pahsc_outputLatency; + + // ASIOCreateBuffers () + ASIOBufferInfo bufferInfos[kMaxInputChannels + kMaxOutputChannels]; // buffer info's + + // ASIOGetChannelInfo() + ASIOChannelInfo pahsc_channelInfos[kMaxInputChannels + kMaxOutputChannels]; // channel info's + // The above two arrays share the same indexing, as the data in them are linked together + + // Information from ASIOGetSamplePosition() + // data is converted to double floats for easier use, however 64 bit integer can be used, too + double nanoSeconds; + double samples; + double tcSamples; // time code samples + + // bufferSwitchTimeInfo() + ASIOTime tInfo; // time info state + unsigned long sysRefTime; // system reference time, when bufferSwitch() was called + + // Signal the end of processing in this example + bool stopped; + + ASIOCallbacks pahsc_asioCallbacks; + + + int32 pahsc_userInputBufferFrameOffset; // Position in Input user buffer + int32 pahsc_userOutputBufferFrameOffset; // Position in Output user buffer + int32 pahsc_hostOutputBufferFrameOffset; // Position in Output ASIO buffer + + int32 past_FramesPerHostBuffer; // Number of frames in ASIO buffer + + int32 pahsc_InputBufferOffset; // Number of null frames for input buffer alignement + int32 pahsc_OutputBufferOffset; // Number of null frames for ouput buffer alignement + +#if MAC + UInt64 pahsc_EntryCount; + UInt64 pahsc_LastExitCount; +#elif WINDOWS + LARGE_INTEGER pahsc_EntryCount; + LARGE_INTEGER pahsc_LastExitCount; +#endif + + PaTimestamp pahsc_NumFramesDone; + + internalPortAudioStream *past; + +} PaHostSoundControl; + + +//---------------------------------------------------------- +// name of the ASIO device to be used +#if WINDOWS + //#define ASIO_DRIVER_NAME "ASIO Multimedia Driver" + #define ASIO_DRIVER_NAME "ASIO Sample" +#elif MAC + //#define ASIO_DRIVER_NAME "Apple Sound Manager" + #define ASIO_DRIVER_NAME "ASIO Delta1010" + //#define ASIO_DRIVER_NAME "DigiDesign DirectIO" +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) + +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) /**/ + +/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ +#define CARBON_COMPATIBLE (0) +#define PA_MAX_DEVICE_INFO (32) + +#define MIN_INT8 (-0x80) +#define MAX_INT8 (0x7F) + +#define MIN_INT8_FP ((float)-0x80) +#define MAX_INT8_FP ((float)0x7F) + +#define MIN_INT16_FP ((float)-0x8000) +#define MAX_INT16_FP ((float)0x7FFF) + +#define MIN_INT16 (-0x8000) +#define MAX_INT16 (0x7FFF) + +#define MAX_INT32_FP (2147483520.0f) /* 0x0x7FFFFF80 - seems safe */ + +/************************************************************************************/ +/****************** Data ************************************************************/ +/************************************************************************************/ +static int sNumDevices = 0; +static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 }; +static int32 sPaHostError = 0; +static int sDefaultOutputDeviceID = 0; +static int sDefaultInputDeviceID = 0; + +PaHostSoundControl asioDriverInfo = {0}; + +#ifdef MAC +static bool swap = true; +#elif WINDOWS +static bool swap = false; +#endif + +// Prototypes +void bufferSwitch(long index, ASIOBool processNow); +ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow); +void sampleRateChanged(ASIOSampleRate sRate); +long asioMessages(long selector, long value, void* message, double* opt); +static void Pa_StartUsageCalculation( internalPortAudioStream *past ); +static void Pa_EndUsageCalculation( internalPortAudioStream *past ); + +void Pa_ASIO_Convert_Inter_Input( + ASIOBufferInfo* nativeBuffer, + void* inputBuffer, + short NumInputChannels, + short NumOuputChannels, + short framePerBuffer, + short hostFrameOffset, + short userFrameOffset, + ASIOSampleType nativeFormat, + PaSampleFormat paFormat, + PaStreamFlags flags, + short index); + +void Pa_ASIO_Convert_Inter_Output( + ASIOBufferInfo* nativeBuffer, + void* outputBuffer, + short NumInputChannels, + short NumOuputChannels, + short framePerBuffer, + short hostFrameOffset, + short userFrameOffset, + ASIOSampleType nativeFormat, + PaSampleFormat paFormat, + PaStreamFlags flags, + short index); + + void Pa_ASIO_Clear_Output(ASIOBufferInfo* nativeBuffer, + ASIOSampleType nativeFormat, + short NumInputChannels, + short NumOuputChannels, + int index, + int hostFrameOffset, + int frames); + +void Pa_ASIO_Callback_Input(long index); +void Pa_ASIO_Callback_Output(long index, long framePerBuffer); +void Pa_ASIO_Callback_End(); +void Pa_ASIO_Clear_User_Buffers(); + +// Some external references +extern AsioDrivers* asioDrivers ; +bool loadAsioDriver(char *name); +unsigned long get_sys_reference_time(); + + +// For callback debugging on Mac : to be removed in the final version + +/* +#define MidiSharePPC_68k +#include + +void SendVal (long val) +{ + MidiEvPtr ev = MidiNewEv(typeTempo); + if (ev) { + Tempo(ev) = val; + MidiSendIm(0,ev); + } +} + +void MidiPrintText ( char * s) +{ + MidiEvPtr e; + long c = 0; + + if (e = MidiNewEv(typeTextual)){ + for (c = 0 ; *s ; s++,c++) MidiAddField (e ,*s); + MidiSendIm (0, e); + } +} +*/ + + +/************************************************************************************/ +/****************** Macro ************************************************************/ +/************************************************************************************/ + +#define SwapLong(v) ((((v)>>24)&0xFF)|(((v)>>8)&0xFF00)|(((v)&0xFF00)<<8)|(((v)&0xFF)<<24)) ; +#define SwapShort(v) ((((v)>>8)&0xFF)|(((v)&0xFF)<<8)) ; + +#define ClipShort(v) (((v)MAX_INT16)?MAX_INT16:(v))) +#define ClipChar(v) (((v)MAX_INT8)?MAX_INT8:(v))) +#define ClipFloat(v) (((v)<-1.0f)?-1.0f:(((v)>1.0f)?1.0f:(v))) + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef max +#define max(a,b) ((a)>=(b)?(a):(b)) +#endif + + +// Utilities for alignement buffer size computation +int PGCD (int a, int b) {return (b == 0) ? a : PGCD (b,a%b);} +int PPCM (int a, int b) {return (a*b) / PGCD (a,b);} + +// Takes the size of host buffer and user buffer : returns the number of frames needed for buffer alignement +int Pa_ASIO_CalcFrameShift (int M, int N) +{ + int res = 0; + for (int i = M; i < PPCM (M,N) ; i+=M) { res = max (res, i%N); } + return res; +} + +// We have the following relation : +// Pa_ASIO_CalcFrameShift (M,N) + M = Pa_ASIO_CalcFrameShift (N,M) + N + +/* ASIO sample type to PortAudio sample type conversion */ +static PaSampleFormat Pa_ASIO_Convert_SampleFormat(ASIOSampleType type) +{ + switch (type) { + + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + return paInt16; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + return paFloat32; + + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + return paInt32; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + return paInt24; + + default: + return paCustomFormat; + } +} + +/* Allocate ASIO buffers, initialise channels */ +static ASIOError Pa_ASIO_CreateBuffers (PaHostSoundControl *asioDriverInfo, long InputChannels, + long OutputChannels, long framesPerBuffer) +{ + ASIOError err; + int i; + + ASIOBufferInfo *info = asioDriverInfo->bufferInfos; + + // Check parameters + if ((InputChannels > kMaxInputChannels) || (OutputChannels > kMaxInputChannels)) return ASE_InvalidParameter; + + for(i = 0; i < InputChannels; i++, info++){ + info->isInput = ASIOTrue; + info->channelNum = i; + info->buffers[0] = info->buffers[1] = 0; + } + + for(i = 0; i < OutputChannels; i++, info++){ + info->isInput = ASIOFalse; + info->channelNum = i; + info->buffers[0] = info->buffers[1] = 0; + } + + // Set up the asioCallback structure and create the ASIO data buffer + asioDriverInfo->pahsc_asioCallbacks.bufferSwitch = &bufferSwitch; + asioDriverInfo->pahsc_asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioDriverInfo->pahsc_asioCallbacks.asioMessage = &asioMessages; + asioDriverInfo->pahsc_asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; + + DBUG(("PortAudio : ASIOCreateBuffers with size = %ld \n", framesPerBuffer)); + + err = ASIOCreateBuffers( asioDriverInfo->bufferInfos, InputChannels+OutputChannels, + framesPerBuffer, &asioDriverInfo->pahsc_asioCallbacks); + if (err != ASE_OK) return err; + + // Initialise buffers + for (i = 0; i < InputChannels + OutputChannels; i++) + { + asioDriverInfo->pahsc_channelInfos[i].channel = asioDriverInfo->bufferInfos[i].channelNum; + asioDriverInfo->pahsc_channelInfos[i].isInput = asioDriverInfo->bufferInfos[i].isInput; + err = ASIOGetChannelInfo(&asioDriverInfo->pahsc_channelInfos[i]); + if (err != ASE_OK) break; + } + + err = ASIOGetLatencies(&asioDriverInfo->pahsc_inputLatency, &asioDriverInfo->pahsc_outputLatency); + + DBUG(("PortAudio : InputLatency = %ld latency = %ld msec \n", + asioDriverInfo->pahsc_inputLatency, + (long)((asioDriverInfo->pahsc_inputLatency*1000)/ asioDriverInfo->past->past_SampleRate))); + DBUG(("PortAudio : OuputLatency = %ld latency = %ld msec \n", + asioDriverInfo->pahsc_outputLatency, + (long)((asioDriverInfo->pahsc_outputLatency*1000)/ asioDriverInfo->past->past_SampleRate))); + + return err; +} + + +/* + Query ASIO driver info : + + First we get all available ASIO drivers located in the ASIO folder, + then try to load each one. For each loaded driver, get all needed informations. +*/ +static PaError Pa_ASIO_QueryDeviceInfo( internalPortAudioDevice * ipad ) +{ + +#define NUM_STANDARDSAMPLINGRATES 3 /* 11.025, 22.05, 44.1 */ +#define NUM_CUSTOMSAMPLINGRATES 9 /* must be the same number of elements as in the array below */ +#define MAX_NUMSAMPLINGRATES (NUM_STANDARDSAMPLINGRATES+NUM_CUSTOMSAMPLINGRATES) + + ASIOSampleRate possibleSampleRates[] + = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0}; + + ASIOChannelInfo channelInfos; + long InputChannels,OutputChannels; + double *sampleRates; + char* names[PA_MAX_DEVICE_INFO] ; + PaDeviceInfo *dev; + int i; + int numDrivers; + ASIOError asioError; + + /* Allocate names */ + for (i = 0 ; i < PA_MAX_DEVICE_INFO ; i++) names[i] = (char*)PaHost_AllocateFastMemory(32); + + /* MUST BE CHECKED : to force fragments loading on Mac */ + loadAsioDriver("dummy"); + + /* Get names of all available ASIO drivers */ + asioDrivers->getDriverNames(names,PA_MAX_DEVICE_INFO); + + /* Check all available ASIO drivers */ +#if MAC + numDrivers = asioDrivers->getNumFragments(); +#elif WINDOWS + numDrivers = asioDrivers->asioGetNumDev(); +#endif + DBUG(("PaASIO_QueryDeviceInfo: numDrivers = %d\n", numDrivers )); + + for (int driver = 0 ; driver < numDrivers ; driver++) + { + + #if WINDOWS + asioDriverInfo.pahsc_driverInfo.asioVersion = 2; // FIXME - is this right? PLB + asioDriverInfo.pahsc_driverInfo.sysRef = GetDesktopWindow(); // FIXME - is this right? PLB + #endif + + /* If the driver can be loaded : */ + if ( !loadAsioDriver(names[driver]) ) + { + DBUG(("PaASIO_QueryDeviceInfo could not loadAsioDriver %s\n", names[driver])); + } + else if( (asioError = ASIOInit(&asioDriverInfo.pahsc_driverInfo)) != ASE_OK ) + { + DBUG(("PaASIO_QueryDeviceInfo: ASIOInit returned %d for %s\n", asioError, names[driver])); + } + else if( (ASIOGetChannels(&InputChannels, &OutputChannels) != ASE_OK)) + { + DBUG(("PaASIO_QueryDeviceInfo could not ASIOGetChannels for %s\n", names[driver])); + } + else + { + + /* Gets the name */ + dev = &(ipad[driver].pad_Info); + dev->name = names[driver]; + names[driver] = 0; + + /* Gets Input and Output channels number */ + dev->maxInputChannels = InputChannels; + dev->maxOutputChannels = OutputChannels; + + DBUG(("PaASIO_QueryDeviceInfo: InputChannels = %d\n", InputChannels )); + DBUG(("PaASIO_QueryDeviceInfo: OutputChannels = %d\n", OutputChannels )); + + /* Make room in case device supports all rates. */ + sampleRates = (double*)PaHost_AllocateFastMemory( MAX_NUMSAMPLINGRATES * sizeof(double)); + dev->sampleRates = sampleRates; + dev->numSampleRates = 0; + + /* Loop through the possible sampling rates and check each to see if the device supports it. */ + for (int index = 0; index < MAX_NUMSAMPLINGRATES; index++) { + if (ASIOCanSampleRate(possibleSampleRates[index]) != ASE_NoClock) { + DBUG(("PortAudio : possible sample rate = %d\n", (long)possibleSampleRates[index])); + dev->numSampleRates+=1; + *sampleRates = possibleSampleRates[index]; + sampleRates++; + } + } + + /* We assume that all channels have the same SampleType, so check the first */ + channelInfos.channel = 0; + channelInfos.isInput = 1; + ASIOGetChannelInfo(&channelInfos); + + dev->nativeSampleFormats = Pa_ASIO_Convert_SampleFormat(channelInfos.type); + + /* unload the driver */ + ASIODisposeBuffers(); + ASIOExit(); + + sNumDevices++; + } + } + + /* free only unused names */ + for (i = 0 ; i < PA_MAX_DEVICE_INFO ; i++) if (names[i]) PaHost_FreeFastMemory(names[i],32); + + return paNoError; +} + +//---------------------------------------------------------------------------------- +// TAKEN FROM THE ASIO SDK: +void sampleRateChanged(ASIOSampleRate sRate) +{ + // do whatever you need to do if the sample rate changed + // usually this only happens during external sync. + // Audio processing is not stopped by the driver, actual sample rate + // might not have even changed, maybe only the sample rate status of an + // AES/EBU or S/PDIF digital input at the audio device. + // You might have to update time/sample related conversion routines, etc. +} + +//---------------------------------------------------------------------------------- +// TAKEN FROM THE ASIO SDK: +long asioMessages(long selector, long value, void* message, double* opt) +{ + // currently the parameters "value", "message" and "opt" are not used. + long ret = 0; + switch(selector) + { + case kAsioSelectorSupported: + if(value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // the following three were added for ASIO 2.0, you don't necessarily have to support them + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + + case kAsioBufferSizeChange: + //printf("kAsioBufferSizeChange \n"); + break; + + case kAsioResetRequest: + // defer the task and perform the reset of the driver during the next "safe" situation + // You cannot reset the driver right now, as this code is called from the driver. + // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction + // Afterwards you initialize the driver again. + asioDriverInfo.stopped; // In this sample the processing will just stop + ret = 1L; + break; + case kAsioResyncRequest: + // This informs the application, that the driver encountered some non fatal data loss. + // It is used for synchronization purposes of different media. + // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the + // Windows Multimedia system, which could loose data because the Mutex was hold too long + // by another thread. + // However a driver can issue it in other situations, too. + ret = 1L; + break; + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were latencies changed. + // Beware, it this does not mean that the buffer sizes have changed! + // You might need to update internal delay data. + ret = 1L; + //printf("kAsioLatenciesChanged \n"); + break; + case kAsioEngineVersion: + // return the supported ASIO version of the host application + // If a host applications does not implement this selector, ASIO 1.0 is assumed + // by the driver + ret = 2L; + break; + case kAsioSupportsTimeInfo: + // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback + // is supported. + // For compatibility with ASIO 1.0 drivers the host application should always support + // the "old" bufferSwitch method, too. + ret = 1; + break; + case kAsioSupportsTimeCode: + // informs the driver wether application is interested in time code info. + // If an application does not need to know about time code, the driver has less work + // to do. + ret = 0; + break; + } + return ret; +} + + +//---------------------------------------------------------------------------------- +// conversion from 64 bit ASIOSample/ASIOTimeStamp to double float +#if NATIVE_INT64 + #define ASIO64toDouble(a) (a) +#else + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) +#endif + + +ASIOTime *bufferSwitchTimeInfo(ASIOTime *timeInfo, long index, ASIOBool processNow) +{ + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that you take care + // about thread synchronization. This is omitted here for simplicity. + + static processedSamples = 0; + int result = 0; + + // store the timeInfo for later use + asioDriverInfo.tInfo = *timeInfo; + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + + if (timeInfo->timeInfo.flags & kSystemTimeValid) + asioDriverInfo.nanoSeconds = ASIO64toDouble(timeInfo->timeInfo.systemTime); + else + asioDriverInfo.nanoSeconds = 0; + + if (timeInfo->timeInfo.flags & kSamplePositionValid) + asioDriverInfo.samples = ASIO64toDouble(timeInfo->timeInfo.samplePosition); + else + asioDriverInfo.samples = 0; + + if (timeInfo->timeCode.flags & kTcValid) + asioDriverInfo.tcSamples = ASIO64toDouble(timeInfo->timeCode.timeCodeSamples); + else + asioDriverInfo.tcSamples = 0; + + // get the system reference time + asioDriverInfo.sysRefTime = get_sys_reference_time(); + +#if 0 + // a few debug messages for the Windows device driver developer + // tells you the time when driver got its interrupt and the delay until the app receives + // the event notification. + static double last_samples = 0; + char tmp[128]; + sprintf (tmp, "diff: %d / %d ms / %d ms / %d samples \n", asioDriverInfo.sysRefTime - (long)(asioDriverInfo.nanoSeconds / 1000000.0), asioDriverInfo.sysRefTime, (long)(asioDriverInfo.nanoSeconds / 1000000.0), (long)(asioDriverInfo.samples - last_samples)); + OutputDebugString (tmp); + last_samples = asioDriverInfo.samples; +#endif + + // To avoid the callback accessing a desallocated stream + if( asioDriverInfo.past == NULL) return 0L; + + // Keep sample position + asioDriverInfo.pahsc_NumFramesDone = timeInfo->timeInfo.samplePosition.lo; + + /* Has a user callback returned '1' to indicate finished at the last ASIO callback? */ + if( asioDriverInfo.past->past_StopSoon ) { + + Pa_ASIO_Clear_Output(asioDriverInfo.bufferInfos, + asioDriverInfo.pahsc_channelInfos[0].type, + asioDriverInfo.pahsc_NumInputChannels , + asioDriverInfo.pahsc_NumOutputChannels, + index, + 0, + asioDriverInfo.past_FramesPerHostBuffer); + + asioDriverInfo.past->past_IsActive = 0; + + // Finally if the driver supports the ASIOOutputReady() optimization, do it here, all data are in place + if (asioDriverInfo.pahsc_postOutput) ASIOOutputReady(); + + }else { + + /* CPU usage */ + Pa_StartUsageCalculation(asioDriverInfo.past); + + Pa_ASIO_Callback_Input(index); + + // Finally if the driver supports the ASIOOutputReady() optimization, do it here, all data are in place + if (asioDriverInfo.pahsc_postOutput) ASIOOutputReady(); + + Pa_ASIO_Callback_End(); + + /* CPU usage */ + Pa_EndUsageCalculation(asioDriverInfo.past); + } + + return 0L; +} + + +//---------------------------------------------------------------------------------- +void bufferSwitch(long index, ASIOBool processNow) +{ + // the actual processing callback. + // Beware that this is normally in a seperate thread, hence be sure that you take care + // about thread synchronization. This is omitted here for simplicity. + + // as this is a "back door" into the bufferSwitchTimeInfo a timeInfo needs to be created + // though it will only set the timeInfo.samplePosition and timeInfo.systemTime fields and the according flags + + ASIOTime timeInfo; + memset (&timeInfo, 0, sizeof (timeInfo)); + + // get the time stamp of the buffer, not necessary if no + // synchronization to other media is required + if(ASIOGetSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK) + timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid; + + // Call the real callback + bufferSwitchTimeInfo (&timeInfo, index, processNow); +} + +//---------------------------------------------------------------------------------- +unsigned long get_sys_reference_time() +{ + // get the system reference time + #if WINDOWS + return timeGetTime(); + #elif MAC + static const double twoRaisedTo32 = 4294967296.; + UnsignedWide ys; + Microseconds(&ys); + double r = ((double)ys.hi * twoRaisedTo32 + (double)ys.lo); + return (unsigned long)(r / 1000.); + #endif +} + + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* High pass filter to reduce audibility. */ + highPass = current - previous; + previous = current; + return highPass; +} + +// TO BE COMPLETED WITH ALL SUPPORTED PA SAMPLE TYPES + +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Input_Int16_Flot32 (ASIOBufferInfo* nativeBuffer, float *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset,int userFrameOffset, bool swap) +{ + long temp; + int i,j; + + for( j=0; j>16; + userBufPtr += NumInputChannels; + } + } + } + else + { + for( j=0; j> 1) + Pa_TriangularDither(); + temp = temp >> 15; + temp = (short) ClipShort(temp); + *userBufPtr = temp; + userBufPtr += NumInputChannels; + } + } + + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +// MUST BE TESTED + void Input_Float32_Int16 (ASIOBufferInfo* nativeBuffer, short *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset,int userFrameOffset,uint32 flags,bool swap) +{ + long temp; + int i,j; + + if( flags & paDitherOff ) + { + for( j=0; j>8); + userBufPtr += NumInputChannels; + } + } + } + else + { + for( j=0; j> 8; + temp = ClipShort(temp); + *userBufPtr = (char)(temp>>8); + userBufPtr += NumInputChannels; + } + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Input_Int32_Int8 (ASIOBufferInfo* nativeBuffer, char *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset, int userFrameOffset, uint32 flags,bool swap) +{ + long temp; + int i,j; + + if( flags & paDitherOff ) + { + for( j=0; j>24); + userBufPtr += NumInputChannels; + } + } + } + else + { + for( j=0; j>16; // Shift to get a 16 bit value, then use the 16 bits to 8 bits code (MUST BE CHECHED) + temp += Pa_TriangularDither() >> 8; + temp = ClipShort(temp); + *userBufPtr = (char)(temp >> 8); + userBufPtr += NumInputChannels; + } + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +// MUST BE TESTED + + void Input_Float32_Int8 (ASIOBufferInfo* nativeBuffer, char *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset,int userFrameOffset, uint32 flags,bool swap) +{ + long temp; + int i,j; + + if( flags & paDitherOff ) + { + for( j=0; j>8) + 0x80); + userBufPtr += NumInputChannels; + } + } + } + else + { + for( j=0; j> 8; + temp = ClipShort(temp); + *userBufPtr = (unsigned char)((temp>>8) + 0x80); + userBufPtr += NumInputChannels; + } + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Input_Int32_IntU8 (ASIOBufferInfo* nativeBuffer, unsigned char *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset, int userFrameOffset,uint32 flags,bool swap) +{ + long temp; + int i,j; + + if( flags & paDitherOff ) + { + for( j=0; j>24) + 0x80); + userBufPtr += NumInputChannels; + } + } + } + else + { + for( j=0; j>16; // Shift to get a 16 bit value, then use the 16 bits to 8 bits code (MUST BE CHECHED) + temp += Pa_TriangularDither() >> 8; + temp = ClipShort(temp); + *userBufPtr = (unsigned char)((temp>>8) + 0x80); + userBufPtr += NumInputChannels; + } + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +// MUST BE TESTED + + void Input_Float32_IntU8 (ASIOBufferInfo* nativeBuffer, unsigned char *inBufPtr, int framePerBuffer, int NumInputChannels, int index, int hostFrameOffset,int userFrameOffset, uint32 flags,bool swap) +{ + long temp; + int i,j; + + if( flags & paDitherOff ) + { + for( j=0; j> 16); + if (swap) temp = SwapShort(temp); + asioBufPtr[i] = temp; + userBufPtr += NumOuputChannels; + } + } + } + else + { + for (j= 0; j < NumOuputChannels; j++) + { + short *asioBufPtr = &((short*)nativeBuffer[j+NumInputChannels].buffers[index])[hostFrameOffset]; + long *userBufPtr = &outBufPtr[j+(userFrameOffset*NumOuputChannels)]; + for( i=0; i> 1) + Pa_TriangularDither(); + temp = temp >> 15; + temp = (short) ClipShort(temp); + if (swap) temp = SwapShort(temp); + asioBufPtr[i] = temp; + userBufPtr += NumOuputChannels; + } + } + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Output_Int32_Int32(ASIOBufferInfo* nativeBuffer, long *outBufPtr, int framePerBuffer, int NumInputChannels, int NumOuputChannels, int index, int hostFrameOffset,int userFrameOffset,uint32 flags,bool swap) +{ + long temp; + int i,j; + + for (j= 0; j < NumOuputChannels; j++) + { + long *asioBufPtr = &((long*)nativeBuffer[j+NumInputChannels].buffers[index])[hostFrameOffset]; + long *userBufPtr = &outBufPtr[j+(userFrameOffset*NumOuputChannels)]; + for( i=0; ipast_FramesPerUserBuffer <= asioDriverInfo.past_FramesPerHostBuffer) { + asioDriverInfo.pahsc_hostOutputBufferFrameOffset = asioDriverInfo.pahsc_OutputBufferOffset; + asioDriverInfo.pahsc_userInputBufferFrameOffset = 0; // empty + asioDriverInfo.pahsc_userOutputBufferFrameOffset = asioDriverInfo.past->past_FramesPerUserBuffer; // empty + }else { + asioDriverInfo.pahsc_hostOutputBufferFrameOffset = 0; // empty + asioDriverInfo.pahsc_userInputBufferFrameOffset = asioDriverInfo.pahsc_InputBufferOffset; + asioDriverInfo.pahsc_userOutputBufferFrameOffset = asioDriverInfo.past->past_FramesPerUserBuffer; // empty + } +} + + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +// FIXME : optimization for Input only or output only modes (really necessary ??) +void Pa_ASIO_Callback_Input( long index) +{ + internalPortAudioStream *past = asioDriverInfo.past; + long framesInputHostBuffer = asioDriverInfo.past_FramesPerHostBuffer; // number of frames available into the host input buffer + long framesInputUserBuffer; // number of frames needed to complete the user input buffer + long framesOutputHostBuffer; // number of frames needed to complete the host output buffer + long framesOuputUserBuffer; // number of frames available into the user output buffer + long userResult; + long tmp; + + /* Fill host ASIO output with remaining frames in user output */ + framesOutputHostBuffer = asioDriverInfo.past_FramesPerHostBuffer; + framesOuputUserBuffer = asioDriverInfo.past->past_FramesPerUserBuffer - asioDriverInfo.pahsc_userOutputBufferFrameOffset; + tmp = min(framesOutputHostBuffer, framesOuputUserBuffer); + framesOutputHostBuffer -= tmp; + Pa_ASIO_Callback_Output(index,tmp); + + /* Available frames in hostInputBuffer */ + while (framesInputHostBuffer > 0) { + + /* Number of frames needed to complete an user input buffer */ + framesInputUserBuffer = asioDriverInfo.past->past_FramesPerUserBuffer - asioDriverInfo.pahsc_userInputBufferFrameOffset; + + if (framesInputHostBuffer >= framesInputUserBuffer) { + + /* Convert ASIO input to user input */ + Pa_ASIO_Convert_Inter_Input (asioDriverInfo.bufferInfos, + past->past_InputBuffer, + asioDriverInfo.pahsc_NumInputChannels , + asioDriverInfo.pahsc_NumOutputChannels, + framesInputUserBuffer, + asioDriverInfo.past_FramesPerHostBuffer - framesInputHostBuffer, + asioDriverInfo.pahsc_userInputBufferFrameOffset, + asioDriverInfo.pahsc_channelInfos[0].type, + past->past_InputSampleFormat, + past->past_Flags, + index); + + /* Call PortAudio callback */ + userResult = asioDriverInfo.past->past_Callback(past->past_InputBuffer, past->past_OutputBuffer, + past->past_FramesPerUserBuffer,past->past_FrameCount,past->past_UserData ); + + /* User callback has asked us to stop in the middle of the host buffer */ + if( userResult != 0) { + + /* Put 0 in the end of the output buffer */ + Pa_ASIO_Clear_Output(asioDriverInfo.bufferInfos, + asioDriverInfo.pahsc_channelInfos[0].type, + asioDriverInfo.pahsc_NumInputChannels , + asioDriverInfo.pahsc_NumOutputChannels, + index, + asioDriverInfo.pahsc_hostOutputBufferFrameOffset, + asioDriverInfo.past_FramesPerHostBuffer - asioDriverInfo.pahsc_hostOutputBufferFrameOffset); + + past->past_StopSoon = 1; + return; + } + + + /* Full user ouput buffer : write offset */ + asioDriverInfo.pahsc_userOutputBufferFrameOffset = 0; + + /* Empty user input buffer : read offset */ + asioDriverInfo.pahsc_userInputBufferFrameOffset = 0; + + /* Fill host ASIO output */ + tmp = min (past->past_FramesPerUserBuffer,framesOutputHostBuffer); + Pa_ASIO_Callback_Output(index,tmp); + + framesOutputHostBuffer -= tmp; + framesInputHostBuffer -= framesInputUserBuffer; + + }else { + + /* Convert ASIO input to user input */ + Pa_ASIO_Convert_Inter_Input (asioDriverInfo.bufferInfos, + past->past_InputBuffer, + asioDriverInfo.pahsc_NumInputChannels , + asioDriverInfo.pahsc_NumOutputChannels, + framesInputHostBuffer, + asioDriverInfo.past_FramesPerHostBuffer - framesInputHostBuffer, + asioDriverInfo.pahsc_userInputBufferFrameOffset, + asioDriverInfo.pahsc_channelInfos[0].type, + past->past_InputSampleFormat, + past->past_Flags, + index); + + /* Update pahsc_userInputBufferFrameOffset */ + asioDriverInfo.pahsc_userInputBufferFrameOffset += framesInputHostBuffer; + + /* Update framesInputHostBuffer */ + framesInputHostBuffer = 0; + } + } + +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +void Pa_ASIO_Callback_Output(long index, long framePerBuffer) +{ + internalPortAudioStream *past = asioDriverInfo.past; + + if (framePerBuffer > 0) { + + /* Convert user output to ASIO ouput */ + Pa_ASIO_Convert_Inter_Output (asioDriverInfo.bufferInfos, + past->past_OutputBuffer, + asioDriverInfo.pahsc_NumInputChannels, + asioDriverInfo.pahsc_NumOutputChannels, + framePerBuffer, + asioDriverInfo.pahsc_hostOutputBufferFrameOffset, + asioDriverInfo.pahsc_userOutputBufferFrameOffset, + asioDriverInfo.pahsc_channelInfos[0].type, + past->past_InputSampleFormat, + past->past_Flags, + index); + + /* Update hostOuputFrameOffset */ + asioDriverInfo.pahsc_hostOutputBufferFrameOffset += framePerBuffer; + + /* Update userOutputFrameOffset */ + asioDriverInfo.pahsc_userOutputBufferFrameOffset += framePerBuffer; + } +} +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Pa_ASIO_Callback_End() + { + /* Empty ASIO ouput : write offset */ + asioDriverInfo.pahsc_hostOutputBufferFrameOffset = 0; + } + +//------------------------------------------------------------------------------------------------------------------------------------------------------- +void Pa_ASIO_Clear_User_Buffers() +{ + if( asioDriverInfo.past->past_InputBuffer != NULL ) + { + memset( asioDriverInfo.past->past_InputBuffer, 0, asioDriverInfo.past->past_InputBufferSize ); + } + if( asioDriverInfo.past->past_OutputBuffer != NULL ) + { + memset( asioDriverInfo.past->past_OutputBuffer, 0, asioDriverInfo.past->past_OutputBufferSize ); + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------- + void Pa_ASIO_Clear_Output(ASIOBufferInfo* nativeBuffer, + ASIOSampleType nativeFormat, + short NumInputChannels, + short NumOuputChannels, + int index, + int hostFrameOffset, + int frames) +{ + + switch (nativeFormat) { + + case ASIOSTInt16MSB: + case ASIOSTInt16LSB: + case ASIOSTInt32MSB16: + case ASIOSTInt32LSB16: + Pa_ASIO_Clear_Output_16(nativeBuffer, frames, NumInputChannels, NumOuputChannels, index, hostFrameOffset); + break; + + case ASIOSTFloat64MSB: + case ASIOSTFloat64LSB: + break; + + case ASIOSTFloat32MSB: + case ASIOSTFloat32LSB: + case ASIOSTInt32MSB: + case ASIOSTInt32LSB: + case ASIOSTInt32MSB18: + case ASIOSTInt32MSB20: + case ASIOSTInt32MSB24: + case ASIOSTInt32LSB18: + case ASIOSTInt32LSB20: + case ASIOSTInt32LSB24: + Pa_ASIO_Clear_Output_32(nativeBuffer, frames, NumInputChannels, NumOuputChannels, index, hostFrameOffset); + break; + + case ASIOSTInt24MSB: + case ASIOSTInt24LSB: + break; + + default: + break; + } +} + + +//--------------------------------------------------------------------------------------- +void Pa_ASIO_Convert_Inter_Input( + ASIOBufferInfo* nativeBuffer, + void* inputBuffer, + short NumInputChannels, + short NumOuputChannels, + short framePerBuffer, + short hostFrameOffset, + short userFrameOffset, + ASIOSampleType nativeFormat, + PaSampleFormat paFormat, + PaStreamFlags flags, + short index) +{ + + if((NumInputChannels > 0) && (nativeBuffer != NULL)) + { + /* Convert from native format to PA format. */ + switch(paFormat) + { + case paFloat32: + { + float *inBufPtr = (float *) inputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Input_Int16_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Input_Int16_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset,!swap); + break; + case ASIOSTInt32LSB: + Input_Int32_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset,swap); + break; + case ASIOSTInt32MSB: + Input_Int32_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset,!swap); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset,swap); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Flot32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset, userFrameOffset,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + + break; + } + + case paInt32: + { + long *inBufPtr = (long *)inputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Input_Int16_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Input_Int16_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTInt32LSB: + Input_Int32_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt32MSB: + Input_Int32_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int32(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + + } + break; + } + + case paInt16: + { + short *inBufPtr = (short *) inputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Input_Int16_Int16(nativeBuffer, inBufPtr, framePerBuffer , NumInputChannels, index , hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Input_Int16_Int16(nativeBuffer, inBufPtr, framePerBuffer , NumInputChannels, index , hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTInt32LSB: + Input_Int32_Int16(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTInt32MSB: + Input_Int32_Int16(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int16(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int16(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + + } + break; + } + + case paInt8: + { + /* Convert 16 bit data to 8 bit chars */ + + char *inBufPtr = (char *) inputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Input_Int16_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset,flags,swap); + break; + case ASIOSTInt16MSB: + Input_Int16_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTInt32LSB: + Input_Int32_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTInt32MSB: + Input_Int32_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_Int8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + break; + } + + case paUInt8: + { + /* Convert 16 bit data to 8 bit unsigned chars */ + + unsigned char *inBufPtr = (unsigned char *)inputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Input_Int16_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTInt16MSB: + Input_Int16_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTInt32LSB: + Input_Int32_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset,flags,swap); + break; + case ASIOSTInt32MSB: + Input_Int32_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset,flags,swap); + break; + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + Input_Float32_IntU8(nativeBuffer, inBufPtr, framePerBuffer, NumInputChannels, index, hostFrameOffset,userFrameOffset,flags,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + + } + break; + } + + default: + break; + } + } +} + + +//--------------------------------------------------------------------------------------- +void Pa_ASIO_Convert_Inter_Output(ASIOBufferInfo* nativeBuffer, + void* outputBuffer, + short NumInputChannels, + short NumOuputChannels, + short framePerBuffer, + short hostFrameOffset, + short userFrameOffset, + ASIOSampleType nativeFormat, + PaSampleFormat paFormat, + PaStreamFlags flags, + short index) +{ + + if((NumOuputChannels > 0) && (nativeBuffer != NULL)) + { + /* Convert from PA format to native format */ + + switch(paFormat) + { + case paFloat32: + { + float *outBufPtr = (float *) outputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Output_Float32_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset, userFrameOffset, flags, swap); + break; + case ASIOSTInt16MSB: + Output_Float32_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset, userFrameOffset, flags,!swap); + break; + case ASIOSTInt32LSB: + Output_Float32_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset, userFrameOffset, flags,swap); + break; + case ASIOSTInt32MSB: + Output_Float32_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTFloat32LSB: + Output_Float32_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset,flags,swap); + break; + case ASIOSTFloat32MSB: + Output_Float32_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + break; + } + + case paInt32: + { + long *outBufPtr = (long *) outputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Output_Int32_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTInt16MSB: + Output_Int32_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTInt32LSB: + Output_Int32_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTInt32MSB: + Output_Int32_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + case ASIOSTFloat32LSB: + Output_Int32_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,swap); + break; + case ASIOSTFloat32MSB: + Output_Int32_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, flags,!swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + break; + } + + case paInt16: + { + short *outBufPtr = (short *) outputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Output_Int16_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Output_Int16_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTInt32LSB: + Output_Int16_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt32MSB: + Output_Int16_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTFloat32LSB: + Output_Int16_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTFloat32MSB: + Output_Int16_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + + } + break; + } + + + case paInt8: + { + char *outBufPtr = (char *) outputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Output_Int8_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Output_Int8_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTInt32LSB: + Output_Int8_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt32MSB: + Output_Int8_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTFloat32LSB: + Output_Int8_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTFloat32MSB: + Output_Int8_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + break; + } + + case paUInt8: + { + unsigned char *outBufPtr = (unsigned char *) outputBuffer; + + switch (nativeFormat) { + case ASIOSTInt16LSB: + Output_IntU8_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt16MSB: + Output_IntU8_Int16(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTInt32LSB: + Output_IntU8_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTInt32MSB: + Output_IntU8_Int32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + case ASIOSTFloat32LSB: + Output_IntU8_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, swap); + break; + case ASIOSTFloat32MSB: + Output_IntU8_Float32(nativeBuffer, outBufPtr, framePerBuffer, NumInputChannels, NumOuputChannels, index, hostFrameOffset,userFrameOffset, !swap); + break; + + case ASIOSTInt24LSB: // used for 20 bits as well + case ASIOSTInt24MSB: // used for 20 bits as well + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + + // these are used for 32 bit data buffer, with different alignment of the data inside + // 32 bit PCI bus systems can more easily used with these + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + DBUG(("Not yet implemented : please report the problem\n")); + break; + } + break; + } + + default: + break; + } + } + +} + + + +/* Load a ASIO driver corresponding to the required device */ +static PaError Pa_ASIO_loadDevice (long device) +{ + PaDeviceInfo * dev = &(sDevices[device].pad_Info); + + if (!loadAsioDriver((char *) dev->name)) return paHostError; + if (ASIOInit(&asioDriverInfo.pahsc_driverInfo) != ASE_OK) return paHostError; + if (ASIOGetChannels(&asioDriverInfo.pahsc_NumInputChannels, &asioDriverInfo.pahsc_NumOutputChannels) != ASE_OK) return paHostError; + if (ASIOGetBufferSize(&asioDriverInfo.pahsc_minSize, &asioDriverInfo.pahsc_maxSize, &asioDriverInfo.pahsc_preferredSize, &asioDriverInfo.pahsc_granularity) != ASE_OK) return paHostError; + + if(ASIOOutputReady() == ASE_OK) + asioDriverInfo.pahsc_postOutput = true; + else + asioDriverInfo.pahsc_postOutput = false; + + return paNoError; +} + +//--------------------------------------------------- +static int GetHighestBitPosition( unsigned long n ) +{ + int pos = -1; + while( n != 0 ) + { + pos++; + n = n >> 1; + } + return pos; +} + +//----------------------------------------------------------------------------- +static int GetFirstMultiple(long min, long val ){ return ((min + val - 1) / val) * val; } + +//----------------------------------------------------------------------------- +static int GetFirstPossibleDivisor(long max, long val ) +{ + for (int i = 2; i < 20; i++) {if (((val%i) == 0) && ((val/i) <= max)) return (val/i); } + return val; +} + +//------------------------------------------------------------------------ +static int IsPowerOfTwo( unsigned long n ) { return ((n & (n-1)) == 0); } + + +/******************************************************************* +* Determine size of native ASIO audio buffer size +* Input parameters : FramesPerUserBuffer, NumUserBuffers +* Output values : FramesPerHostBuffer, OutputBufferOffset or InputtBufferOffset +*/ + +static PaError PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + long requestedBufferSize; + long firstMultiple, firstDivisor; + + // Compute requestedBufferSize + if( past->past_NumUserBuffers < 1 ){ + requestedBufferSize = past->past_FramesPerUserBuffer; + }else{ + requestedBufferSize = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + } + + // Adjust FramesPerHostBuffer using requestedBufferSize, ASIO minSize and maxSize, + if (requestedBufferSize < asioDriverInfo.pahsc_minSize){ + + firstMultiple = GetFirstMultiple(asioDriverInfo.pahsc_minSize, requestedBufferSize); + + if (firstMultiple <= asioDriverInfo.pahsc_maxSize) + asioDriverInfo.past_FramesPerHostBuffer = firstMultiple; + else + asioDriverInfo.past_FramesPerHostBuffer = asioDriverInfo.pahsc_minSize; + + }else if (requestedBufferSize > asioDriverInfo.pahsc_maxSize){ + + firstDivisor = GetFirstPossibleDivisor(asioDriverInfo.pahsc_maxSize, requestedBufferSize); + + if ((firstDivisor >= asioDriverInfo.pahsc_minSize) && (firstDivisor <= asioDriverInfo.pahsc_maxSize)) + asioDriverInfo.past_FramesPerHostBuffer = firstDivisor; + else + asioDriverInfo.past_FramesPerHostBuffer = asioDriverInfo.pahsc_maxSize; + }else{ + asioDriverInfo.past_FramesPerHostBuffer = requestedBufferSize; + } + + // If ASIO buffer size needs to be a power of two + if( asioDriverInfo.pahsc_granularity < 0 ){ + // Needs to be a power of two. + + if( !IsPowerOfTwo( asioDriverInfo.past_FramesPerHostBuffer ) ) + { + int highestBit = GetHighestBitPosition(asioDriverInfo.past_FramesPerHostBuffer); + asioDriverInfo.past_FramesPerHostBuffer = 1 << (highestBit + 1); + } + } + + DBUG(("----------------------------------\n")); + DBUG(("PortAudio : minSize = %ld \n",asioDriverInfo.pahsc_minSize)); + DBUG(("PortAudio : preferredSize = %ld \n",asioDriverInfo.pahsc_preferredSize)); + DBUG(("PortAudio : maxSize = %ld \n",asioDriverInfo.pahsc_maxSize)); + DBUG(("PortAudio : granularity = %ld \n",asioDriverInfo.pahsc_granularity)); + DBUG(("PortAudio : User buffer size = %d\n", asioDriverInfo.past->past_FramesPerUserBuffer )); + DBUG(("PortAudio : ASIO buffer size = %d\n", asioDriverInfo.past_FramesPerHostBuffer )); + + if (asioDriverInfo.past_FramesPerHostBuffer > past->past_FramesPerUserBuffer){ + + // Computes the MINIMUM value of null frames shift for the output buffer alignement + asioDriverInfo.pahsc_OutputBufferOffset = Pa_ASIO_CalcFrameShift (asioDriverInfo.past_FramesPerHostBuffer,past->past_FramesPerUserBuffer); + asioDriverInfo.pahsc_InputBufferOffset = 0; + DBUG(("PortAudio : Minimum BufferOffset for Output = %d\n", asioDriverInfo.pahsc_OutputBufferOffset)); + }else{ + + //Computes the MINIMUM value of null frames shift for the input buffer alignement + asioDriverInfo.pahsc_InputBufferOffset = Pa_ASIO_CalcFrameShift (asioDriverInfo.past_FramesPerHostBuffer,past->past_FramesPerUserBuffer); + asioDriverInfo.pahsc_OutputBufferOffset = 0; + DBUG(("PortAudio : Minimum BufferOffset for Input = %d\n", asioDriverInfo.pahsc_InputBufferOffset)); + } + + return paNoError; +} + + +/***********************************************************************/ +int Pa_CountDevices() +{ + PaError err ; + + if( sNumDevices <= 0 ) + { + /* Force loading of ASIO drivers */ + err = Pa_ASIO_QueryDeviceInfo(sDevices); + if( err != paNoError ) goto error; + } + + return sNumDevices; + +error: + PaHost_Term(); + DBUG(("Pa_CountDevices: returns %d\n", err )); + return err; +} + +/***********************************************************************/ +PaError PaHost_Init( void ) +{ + /* Have we already initialized the device info? */ + return (Pa_CountDevices() < 0 ) ? paHostError : paNoError; +} + +/***********************************************************************/ +PaError PaHost_Term( void ) +{ + int i; + PaDeviceInfo *dev; + double *rates; + PaError result = paNoError; + + /* Free allocated sample rate arrays and names*/ + for( i=0; isampleRates; + if((rates != NULL)) PaHost_FreeFastMemory(rates, MAX_NUMSAMPLINGRATES * sizeof(double)); + dev->sampleRates = NULL; + if(dev->name != NULL) PaHost_FreeFastMemory((void *) dev->name, 32); + dev->name = NULL; + } + + sNumDevices = 0; + + /* Dispose : if not done by Pa_CloseStream */ + if(ASIODisposeBuffers() != ASE_OK) result = paHostError; + if(ASIOExit() != ASE_OK) result = paHostError; + + /* remove the loaded ASIO driver */ + asioDrivers->removeCurrentDriver(); + + return result; +} + +/***********************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + ASIOError err; + int32 device; + + /* TO DO : Check if a stream already runs */ + //if (asioDriverInfo.past != NULL) return paHostError; (does not work) + + /* Check the device number */ + if ((past->past_InputDeviceID != paNoDevice) + &&(past->past_OutputDeviceID != paNoDevice) + &&(past->past_InputDeviceID != past->past_OutputDeviceID)) + { + return paInvalidDeviceId; + } + + /* Allocation */ + memset(&asioDriverInfo, 0, sizeof(PaHostSoundControl)); + past->past_DeviceData = (void*) &asioDriverInfo; + + + /* FIXME */ + asioDriverInfo.past = past; + + /* load the ASIO device */ + device = (past->past_InputDeviceID < 0) ? past->past_OutputDeviceID : past->past_InputDeviceID; + result = Pa_ASIO_loadDevice(device); + if (result != paNoError) goto error; + + /* Check ASIO parameters and input parameters */ + if ((past->past_NumInputChannels > asioDriverInfo.pahsc_NumInputChannels) + || (past->past_NumOutputChannels > asioDriverInfo.pahsc_NumOutputChannels)) { + result = paInvalidChannelCount; + goto error; + } + + /* Set sample rate */ + if (ASIOSetSampleRate(past->past_SampleRate) != ASE_OK) { + result = paInvalidSampleRate; + goto error; + } + + /* if OK calc buffer size */ + result = PaHost_CalcNumHostBuffers( past ); + if (result != paNoError) goto error; + + + /* + Allocating input and output buffers number for the real past_NumInputChannels and past_NumOutputChannels + optimize the data transfer. + */ + + asioDriverInfo.pahsc_NumInputChannels = past->past_NumInputChannels; + asioDriverInfo.pahsc_NumOutputChannels = past->past_NumOutputChannels; + + /* Allocate ASIO buffers and callback*/ + err = Pa_ASIO_CreateBuffers(&asioDriverInfo, + asioDriverInfo.pahsc_NumInputChannels, + asioDriverInfo.pahsc_NumOutputChannels, + asioDriverInfo.past_FramesPerHostBuffer); + + if (err == ASE_OK) + return paNoError; + else if (err == ASE_NoMemory) + result = paInsufficientMemory; + else if (err == ASE_InvalidParameter) + result = paInvalidChannelCount; + else if (err == ASE_InvalidMode) + result = paBufferTooBig; + else + result = paHostError; + +error: + ASIOExit(); + return result; + +} + +/***********************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + #if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_CloseStream: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); + #endif + + /* Dispose */ + if(ASIODisposeBuffers() != ASE_OK) result = paHostError; + if(ASIOExit() != ASE_OK) result = paHostError; + + /* Free data and device for output. */ + past->past_DeviceData = NULL; + asioDriverInfo.past = NULL; + + return result; +} + +/***********************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + /* Clear the index 0 host output buffer */ + Pa_ASIO_Clear_Output(asioDriverInfo.bufferInfos, + asioDriverInfo.pahsc_channelInfos[0].type, + asioDriverInfo.pahsc_NumInputChannels, + asioDriverInfo.pahsc_NumOutputChannels, + 0, + 0, + asioDriverInfo.past_FramesPerHostBuffer); + + /* Clear the index 1 host output buffer */ + Pa_ASIO_Clear_Output(asioDriverInfo.bufferInfos, + asioDriverInfo.pahsc_channelInfos[0].type, + asioDriverInfo.pahsc_NumInputChannels, + asioDriverInfo.pahsc_NumOutputChannels, + 1, + 0, + asioDriverInfo.past_FramesPerHostBuffer); + + Pa_ASIO_Clear_User_Buffers(); + + Pa_ASIO_Adaptor_Init(); + + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + /* Nothing to do ?? */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + /* Nothing to do ?? */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + /* Nothing to do */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + // TO DO : count of samples + past->past_IsActive = 1; + return (ASIOStart() == ASE_OK) ? paNoError : paHostError; +} + +/***********************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + // TO DO : count of samples + past->past_IsActive = 0; + return (ASIOStop() == ASE_OK) ? paNoError : paHostError; +} + +/***********************************************************************/ +// TO BE CHECKED +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) past->past_IsActive; +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + return pahsc->pahsc_NumFramesDone; +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + #if MAC + void *addr = NewPtrClear( numBytes ); + if( (addr == NULL) || (MemError () != 0) ) return NULL; + + #if (CARBON_COMPATIBLE == 0) + if( HoldMemory( addr, numBytes ) != noErr ) + { + DisposePtr( (Ptr) addr ); + return NULL; + } + #endif + return addr; + #elif WINDOWS + void *addr = malloc( numBytes ); /* FIXME - do we need physical memory? */ + if( addr != NULL ) memset( addr, 0, numBytes ); + return addr; + #endif +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + #if MAC + if( addr == NULL ) return; + #if CARBON_COMPATIBLE + (void) numBytes; + #else + UnholdMemory( addr, numBytes ); + #endif + DisposePtr( (Ptr) addr ); + #elif WINDOWS + if( addr != NULL ) free( addr ); + #endif +} + + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + #if MAC + int32 sleepTime, endTime; + /* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */ + sleepTime = ((msec * 60) + 999) / 1000; + if( sleepTime < 1 ) sleepTime = 1; + endTime = TickCount() + sleepTime; + do{ + DBUGX(("Sleep for %d ticks.\n", sleepTime )); + WaitNextEvent( 0, NULL, sleepTime, NULL ); /* Use this just to sleep without getting events. */ + sleepTime = endTime - TickCount(); + } while( sleepTime > 0 ); + #elif WINDOWS + Sleep( msec ); + #endif +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + return &sDevices[id].pad_Info; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + return sDefaultInputDeviceID; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return sDefaultOutputDeviceID; +} + +/*************************************************************************/ +int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate ) +{ + // TO BE IMPLEMENTED : using the ASIOGetLatency call?? + return 2; +} + +/*************************************************************************/ +int32 Pa_GetHostError( void ) +{ + int32 err = sPaHostError; + sPaHostError = 0; + return err; +} + + +#ifdef MAC + +/**************************************************************************/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + UnsignedWide widePad; + if( pahsc == NULL ) return; +/* Query system timer for usage analysis and to prevent overuse of CPU. */ + Microseconds( &widePad ); + pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad ); +} +/**************************************************************************/ +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + UnsignedWide widePad; + UInt64 CurrentCount; + long InsideCount; + long TotalCount; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; +/* Measure CPU utilization during this callback. Note that this calculation +** assumes that we had the processor the whole time. +*/ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + Microseconds( &widePad ); + CurrentCount = UnsignedWideToUInt64( widePad ); + if( past->past_IfLastExitValid ) + { + InsideCount = (long) U64Subtract(CurrentCount, pahsc->pahsc_EntryCount); + TotalCount = (long) U64Subtract(CurrentCount, pahsc->pahsc_LastExitCount); +/* Low pass filter the result because sometimes we get called several times in a row. +* That can cause the TotalCount to be very low which can cause the usage to appear +* unnaturally high. So we must filter numerator and denominator separately!!! +*/ + past->past_AverageInsideCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * InsideCount)); + past->past_AverageTotalCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * TotalCount)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + } + pahsc->pahsc_LastExitCount = CurrentCount; + past->past_IfLastExitValid = 1; +} + +#elif WINDOWS + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; +/* Query system timer for usage analysis and to prevent overuse of CPU. */ + QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); +} + +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + LARGE_INTEGER CurrentCount = { 0, 0 }; + LONGLONG InsideCount; + LONGLONG TotalCount; +/* +** Measure CPU utilization during this callback. Note that this calculation +** assumes that we had the processor the whole time. +*/ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + + if( QueryPerformanceCounter( &CurrentCount ) ) + { + if( past->past_IfLastExitValid ) + { + InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart; + TotalCount = CurrentCount.QuadPart - pahsc->pahsc_LastExitCount.QuadPart; +/* Low pass filter the result because sometimes we get called several times in a row. + * That can cause the TotalCount to be very low which can cause the usage to appear + * unnaturally high. So we must filter numerator and denominator separately!!! + */ + past->past_AverageInsideCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * InsideCount)); + past->past_AverageTotalCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * TotalCount)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + } + pahsc->pahsc_LastExitCount = CurrentCount; + past->past_IfLastExitValid = 1; + } +} + +#endif + + + + diff --git a/pa_beos/PlaybackNode.cc b/pa_beos/PlaybackNode.cc new file mode 100644 index 0000000..84d5453 --- /dev/null +++ b/pa_beos/PlaybackNode.cc @@ -0,0 +1,538 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * 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. + * + * --- + * + * Significant portions of this file are based on sample code from Be. The + * Be Sample Code Licence follows: + * + * Copyright 1991-1999, Be Incorporated. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + + +PaPlaybackNode::PaPlaybackNode(uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback* callback, void *user_data) : + BMediaNode("PortAudio input node"), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BMediaEventLooper(), + mAborted(false), + mRunning(false), + mBufferGroup(NULL), + mDownstreamLatency(0), + mStartTime(0), + mCallback(callback), + mUserData(user_data), + mFramesPerBuffer(frames_per_buffer) +{ + DBUG(("Constructor called.\n")); + + mPreferredFormat.type = B_MEDIA_RAW_AUDIO; + mPreferredFormat.u.raw_audio.channel_count = channels; + mPreferredFormat.u.raw_audio.frame_rate = frame_rate; + mPreferredFormat.u.raw_audio.byte_order = + (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + mPreferredFormat.u.raw_audio.buffer_size = + media_raw_audio_format::wildcard.buffer_size; + + mOutput.destination = media_destination::null; + mOutput.format = mPreferredFormat; + + /* The amount of time it takes for this node to produce a buffer when + * asked. Essentially, it is how long the user's callback takes to run. + * We set this to be the length of the sound data each buffer of the + * requested size can hold. */ + //mInternalLatency = (bigtime_t)(1000000 * frames_per_buffer / frame_rate); + + /* ACK! it seems that the mixer (at least on my machine) demands that IT + * specify the buffer size, so for now I'll just make a generic guess here */ + mInternalLatency = 1000000 / 20; +} + + + +PaPlaybackNode::~PaPlaybackNode() +{ + DBUG(("Destructor called.\n")); + Quit(); /* Stop the BMediaEventLooper thread */ +} + + +/************************* + * + * Local methods + * + */ + +bool PaPlaybackNode::IsRunning() +{ + return mRunning; +} + + +PaTimestamp PaPlaybackNode::GetStreamTime() +{ + BTimeSource *timeSource = TimeSource(); + PaTimestamp time = (timeSource->Now() - mStartTime) * + mPreferredFormat.u.raw_audio.frame_rate / 1000000; + return time; +} + + +void PaPlaybackNode::SetSampleFormat(PaSampleFormat inFormat, + PaSampleFormat outFormat) +{ + uint32 beOutFormat; + + switch(outFormat) + { + case paFloat32: + beOutFormat = media_raw_audio_format::B_AUDIO_FLOAT; + mOutputSampleWidth = 4; + break; + + case paInt16: + beOutFormat = media_raw_audio_format::B_AUDIO_SHORT; + mOutputSampleWidth = 2; + break; + + case paInt32: + beOutFormat = media_raw_audio_format::B_AUDIO_INT; + mOutputSampleWidth = 4; + break; + + case paInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_CHAR; + mOutputSampleWidth = 1; + break; + + case paUInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_UCHAR; + mOutputSampleWidth = 1; + break; + + case paInt24: + case paPackedInt24: + case paCustomFormat: + DBUG(("Unsupported output format: %x\n", outFormat)); + break; + + default: + DBUG(("Unknown output format: %x\n", outFormat)); + } + + mPreferredFormat.u.raw_audio.format = beOutFormat; + mFramesPerBuffer * mPreferredFormat.u.raw_audio.channel_count * mOutputSampleWidth; +} + +BBuffer *PaPlaybackNode::FillNextBuffer(bigtime_t time) +{ + /* Get a buffer from the buffer group */ + BBuffer *buf = mBufferGroup->RequestBuffer( + mOutput.format.u.raw_audio.buffer_size, BufferDuration()); + unsigned long frames = mOutput.format.u.raw_audio.buffer_size / + mOutputSampleWidth / mOutput.format.u.raw_audio.channel_count; + bigtime_t start_time; + int ret; + + if( !buf ) + { + DBUG(("Unable to allocate a buffer\n")); + return NULL; + } + + start_time = mStartTime + + (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.frame_rate / + (double)mOutput.format.u.raw_audio.channel_count * + 1000000.0); + + /* Now call the user callback to get the data */ + ret = mCallback(NULL, /* Input buffer */ + buf->Data(), /* Output buffer */ + frames, /* Frames per buffer */ + mSamplesSent / mOutput.format.u.raw_audio.channel_count, /* timestamp */ + mUserData); + + if( ret ) + mAborted = true; + + media_header *hdr = buf->Header(); + + hdr->type = B_MEDIA_RAW_AUDIO; + hdr->size_used = mOutput.format.u.raw_audio.buffer_size; + hdr->time_source = TimeSource()->ID(); + hdr->start_time = start_time; + + return buf; +} + + + + +/************************* + * + * BMediaNode methods + * + */ + +BMediaAddOn *PaPlaybackNode::AddOn( int32 * ) const +{ + DBUG(("AddOn() called.\n")); + return NULL; /* we don't provide service to outside applications */ +} + + +status_t PaPlaybackNode::HandleMessage( int32 message, const void *data, + size_t size ) +{ + DBUG(("HandleMessage() called.\n")); + return B_ERROR; /* we don't define any custom messages */ +} + + + + +/************************* + * + * BMediaEventLooper methods + * + */ + +void PaPlaybackNode::NodeRegistered() +{ + DBUG(("NodeRegistered() called.\n")); + + /* Start the BMediaEventLooper thread */ + SetPriority(B_REAL_TIME_PRIORITY); + Run(); + + /* set up as much information about our output as we can */ + mOutput.source.port = ControlPort(); + mOutput.source.id = 0; + mOutput.node = Node(); + ::strcpy(mOutput.name, "PortAudio Playback"); +} + + +void PaPlaybackNode::HandleEvent( const media_timed_event *event, + bigtime_t lateness, bool realTimeEvent ) +{ + // DBUG(("HandleEvent() called.\n")); + status_t err; + + switch(event->type) + { + case BTimedEventQueue::B_START: + DBUG((" Handling a B_START event\n")); + if( RunState() != B_STARTED ) + { + mStartTime = event->event_time + EventLatency(); + mSamplesSent = 0; + mAborted = false; + mRunning = true; + media_timed_event firstEvent( mStartTime, + BTimedEventQueue::B_HANDLE_BUFFER ); + EventQueue()->AddEvent( firstEvent ); + } + break; + + case BTimedEventQueue::B_STOP: + DBUG((" Handling a B_STOP event\n")); + mRunning = false; + EventQueue()->FlushEvents( 0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER ); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: + //DBUG((" Handling a B_HANDLE_BUFFER event\n")); + + /* make sure we're started and connected */ + if( RunState() != BMediaEventLooper::B_STARTED || + mOutput.destination == media_destination::null ) + break; + + BBuffer *buffer = FillNextBuffer(event->event_time); + + /* make sure we weren't aborted while this routine was running. + * this can happen in one of two ways: either the callback returned + * nonzero (in which case mAborted is set in FillNextBuffer() ) or + * the client called AbortStream */ + if( mAborted ) + { + if( buffer ) + buffer->Recycle(); + Stop(0, true); + break; + } + + if( buffer ) + { + err = SendBuffer(buffer, mOutput.destination); + if( err != B_OK ) + buffer->Recycle(); + } + + mSamplesSent += mOutput.format.u.raw_audio.buffer_size / mOutputSampleWidth; + + /* Now schedule the next buffer event, so we can send another + * buffer when this one runs out. We calculate when it should + * happen by calculating when the data we just sent will finish + * playing. + * + * NOTE, however, that the event will actually get generated + * earlier than we specify, to account for the latency it will + * take to produce the buffer. It uses the latency value we + * specified in SetEventLatency() to determine just how early + * to generate it. */ + + /* totalPerformanceTime includes the time represented by the buffer + * we just sent */ + bigtime_t totalPerformanceTime = (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.channel_count / + (double)mOutput.format.u.raw_audio.frame_rate * 1000000.0); + + bigtime_t nextEventTime = mStartTime + totalPerformanceTime; + + media_timed_event nextBufferEvent(nextEventTime, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + + break; + + } +} + + + + +/************************* + * + * BBufferProducer methods + * + */ + +status_t PaPlaybackNode::FormatSuggestionRequested( media_type type, + int32 /*quality*/, media_format* format ) +{ + /* the caller wants to know this node's preferred format and provides + * a suggestion, asking if we support it */ + DBUG(("FormatSuggestionRequested() called.\n")); + + if(!format) + return B_BAD_VALUE; + + *format = mPreferredFormat; + + /* we only support raw audio (a wildcard is okay too) */ + if ( type == B_MEDIA_UNKNOWN_TYPE || type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatProposal( const media_source& output, + media_format* format ) +{ + /* This is similar to FormatSuggestionRequested(), but it is actually part + * of the negotiation process. We're given the opportunity to specify any + * properties that are wildcards (ie. properties that the other node doesn't + * care one way or another about) */ + DBUG(("FormatProposal() called.\n")); + + /* Make sure this proposal really applies to our output */ + if( output != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* We return two things: whether we support the proposed format, and our own + * preferred format */ + *format = mPreferredFormat; + + if( format->type == B_MEDIA_UNKNOWN_TYPE || format->type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ) +{ + /* we refuse to change formats, supporting only 1 */ + DBUG(("FormatChangeRequested() called.\n")); + + return B_ERROR; +} + + +status_t PaPlaybackNode::GetNextOutput( int32* cookie, media_output* out_output ) +{ + /* this is where we allow other to enumerate our outputs -- the cookie is + * an integer we can use to keep track of where we are in enumeration. */ + DBUG(("GetNextOutput() called.\n")); + + if( *cookie == 0 ) + { + *out_output = mOutput; + *cookie = 1; + return B_OK; + } + + return B_BAD_INDEX; +} + + +status_t PaPlaybackNode::DisposeOutputCookie( int32 cookie ) +{ + DBUG(("DisposeOutputCookie() called.\n")); + return B_OK; +} + + +void PaPlaybackNode::LateNoticeReceived( const media_source& what, + bigtime_t how_much, bigtime_t performance_time ) +{ + /* This function is called as notification that a buffer we sent wasn't + * received by the time we stamped it with -- it got there late. Basically, + * it means we underestimated our own latency, so we should increase it */ + DBUG(("LateNoticeReceived() called.\n")); + + if( what != mOutput.source ) + return; + + if( RunMode() == B_INCREASE_LATENCY ) + { + mInternalLatency += how_much; + SetEventLatency( mDownstreamLatency + mInternalLatency ); + DBUG(("Increasing latency to %Ld\n", mDownstreamLatency + mInternalLatency)); + } + else + DBUG(("I don't know what to do with this notice!")); +} + + +void PaPlaybackNode::EnableOutput( const media_source& what, bool enabled, + int32* ) +{ + DBUG(("EnableOutput() called.\n")); + /* stub -- we don't support this yet */ +} + + +status_t PaPlaybackNode::PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ) +{ + /* the final stage of format negotiations. here we _must_ make specific any + * remaining wildcards */ + DBUG(("PrepareToConnect() called.\n")); + + /* make sure this really refers to our source */ + if( what != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* make sure we're not already connected */ + if( mOutput.destination != media_destination::null ) + return B_MEDIA_ALREADY_CONNECTED; + + if( format->type != B_MEDIA_RAW_AUDIO ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.format != mPreferredFormat.u.raw_audio.format ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.buffer_size == + media_raw_audio_format::wildcard.buffer_size ) + { + DBUG(("We were left to decide buffer size: choosing 2048")); + format->u.raw_audio.buffer_size = 2048; + } + else + DBUG(("Using consumer specified buffer size of %lu.\n", + format->u.raw_audio.buffer_size)); + + /* Reserve the connection, return the information */ + mOutput.destination = where; + mOutput.format = *format; + *out_source = mOutput.source; + strncpy( out_name, mOutput.name, B_MEDIA_NAME_LENGTH ); + + return B_OK; +} + + +void PaPlaybackNode::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, char* io_name) +{ + DBUG(("Connect() called.\n")); + diff --git a/pa_beos/PlaybackNode.h b/pa_beos/PlaybackNode.h new file mode 100644 index 0000000..64ee5b3 --- /dev/null +++ b/pa_beos/PlaybackNode.h @@ -0,0 +1,108 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * 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" + +class PaPlaybackNode : + public BBufferProducer, + public BMediaEventLooper +{ + +public: + PaPlaybackNode( uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback *callback, void *user_data ); + ~PaPlaybackNode(); + + + /* Local methods ******************************************/ + + BBuffer *FillNextBuffer(bigtime_t time); + void SetSampleFormat(PaSampleFormat inFormat, PaSampleFormat outFormat); + bool IsRunning(); + PaTimestamp GetStreamTime(); + + /* BMediaNode methods *************************************/ + + BMediaAddOn* AddOn( int32 * ) const; + status_t HandleMessage( int32 message, const void *data, size_t size ); + + /* BMediaEventLooper methods ******************************/ + + void HandleEvent( const media_timed_event *event, bigtime_t lateness, + bool realTimeEvent ); + void NodeRegistered(); + + /* BBufferProducer methods ********************************/ + + status_t FormatSuggestionRequested( media_type type, int32 quality, + media_format* format ); + status_t FormatProposal( const media_source& output, media_format* format ); + status_t FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ); + + status_t GetNextOutput( int32* cookie, media_output* out_output ); + status_t DisposeOutputCookie( int32 cookie ); + + void LateNoticeReceived( const media_source& what, bigtime_t how_much, + bigtime_t performance_time ); + void EnableOutput( const media_source& what, bool enabled, int32* _deprecated_ ); + + status_t PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ); + void Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, + char* io_name); + void Disconnect(const media_source& what, const media_destination& where); + + status_t SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup); + + bool mAborted; + +private: + media_output mOutput; + media_format mPreferredFormat; + uint32 mOutputSampleWidth, mFramesPerBuffer; + BBufferGroup *mBufferGroup; + bigtime_t mDownstreamLatency, mInternalLatency, mStartTime; + uint64 mSamplesSent; + PortAudioCallback *mCallback; + void *mUserData; + bool mRunning; + +}; + diff --git a/pa_beos/pa_beos_mk.cc b/pa_beos/pa_beos_mk.cc new file mode 100644 index 0000000..287ec55 --- /dev/null +++ b/pa_beos/pa_beos_mk.cc @@ -0,0 +1,441 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * 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 +#include + +#include +#include + +#include "portaudio.h" +#include "pa_host.h" + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + +typedef struct PaHostSoundControl +{ + /* These members are common to all modes of operation */ + media_node pahsc_TimeSource; /* the sound card's DAC. */ + media_format pahsc_Format; + + /* These methods are specific to playing mode */ + media_node pahsc_OutputNode; /* output to the mixer */ + media_node pahsc_InputNode; /* reads data from user callback -- PA specific */ + + media_input pahsc_MixerInput; /* input jack on the soundcard's mixer. */ + media_output pahsc_PaOutput; /* output jack from the PA node */ + + PaPlaybackNode *pahsc_InputNodeInstance; + +} +PaHostSoundControl; + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + /* stub */ + return 0; +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + /* stub */ + return NULL; +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + /* stub */ + return 1; +} + +/*************************************************************************/ +PaError PaHost_Init( void ) +{ + /* we have to create this in order to use BMediaRoster. I hope it doesn't + * cause problems */ + be_app = new BApplication("application/x-vnd.portaudio-app"); + + return paNoError; +} + +PaError PaHost_Term( void ) +{ + delete be_app; + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + DBUG(("IsRunning returning: %s\n", + pahsc->pahsc_InputNodeInstance->IsRunning() ? "true" : "false")); + + return (PaError)pahsc->pahsc_InputNodeInstance->IsRunning(); +} + +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + bigtime_t very_soon, start_latency; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + /* for some reason, err indicates an error (though nothing it wrong) + * when the DBUG macro in pa_lib.c is enabled. It's reproducably + * linked. Weird. */ + if( !roster /* || err != B_OK */ ) + { + DBUG(("No media server! err=%d, roster=%x\n", err, roster)); + return paHostError; + } + + /* tell the node when to start -- since there aren't any other nodes + * starting that we have to wait for, just tell it to start now + */ + + BTimeSource *timeSource = roster->MakeTimeSourceFor(pahsc->pahsc_TimeSource); + very_soon = timeSource->PerformanceTimeFor( BTimeSource::RealTime() ); + timeSource->Release(); + + /* Add the latency of starting the network of nodes */ + err = roster->GetStartLatencyFor( pahsc->pahsc_TimeSource, &start_latency ); + very_soon += start_latency; + + err = roster->StartNode( pahsc->pahsc_InputNode, very_soon ); + /* No need to start the mixer -- it's always running */ + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + BMediaRoster *roster = BMediaRoster::Roster(); + + if( !roster ) + { + DBUG(("No media roster!\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* this crashes, and I don't know why yet */ + // if( abort ) + // pahsc->pahsc_InputNodeInstance->mAborted = true; + + roster->StopNode(pahsc->pahsc_InputNode, 0, /* immediate = */ true); + + return paNoError; +} + + +/*************************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + PaHostSoundControl *pahsc; + + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + + if( !roster /* || err != B_OK */ ) + { + /* no media server! */ + DBUG(("No media server.\n")); + goto error; + } + + if ( past->past_NumInputChannels > 0 && past->past_NumOutputChannels > 0 ) + { + /* filter -- not implemented yet */ + goto error; + } + else if ( past->past_NumInputChannels > 0 ) + { + /* recorder -- not implemented yet */ + goto error; + } + else + { + /* player ****************************************************************/ + + status_t err; + int32 num; + + /* First we need to create the three components (like components in a stereo + * system). The mixer component is our interface to the sound card, data + * we write there will get played. The BePA_InputNode component is the node + * which represents communication with the PA client (it is what calls the + * client's callbacks). The time source component is the sound card's DAC, + * which allows us to slave the other components to it instead of the system + * clock. */ + err = roster->GetAudioMixer( &pahsc->pahsc_OutputNode ); + if( err != B_OK ) + { + DBUG(("Couldn't get default mixer.\n")); + goto error; + } + + err = roster->GetTimeSource( &pahsc->pahsc_TimeSource ); + if( err != B_OK ) + { + DBUG(("Couldn't get time source.\n")); + goto error; + } + + pahsc->pahsc_InputNodeInstance = new PaPlaybackNode(2, 44100, + past->past_FramesPerUserBuffer, past->past_Callback, past->past_UserData ); + pahsc->pahsc_InputNodeInstance->SetSampleFormat(0, + past->past_OutputSampleFormat); + err = roster->RegisterNode( pahsc->pahsc_InputNodeInstance ); + if( err != B_OK ) + { + DBUG(("Unable to register node.\n")); + goto error; + } + + roster->GetNodeFor( pahsc->pahsc_InputNodeInstance->Node().node, + &pahsc->pahsc_InputNode ); + if( err != B_OK ) + { + DBUG(("Unable to get input node.\n")); + goto error; + } + + /* Now we have three components (nodes) sitting next to each other. The + * next step is to look at them and find their inputs and outputs so we can + * wire them together. */ + err = roster->GetFreeInputsFor( pahsc->pahsc_OutputNode, + &pahsc->pahsc_MixerInput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get the mixer input.\n")); + goto error; + } + + err = roster->GetFreeOutputsFor( pahsc->pahsc_InputNode, + &pahsc->pahsc_PaOutput, 1, &num, B_MEDIA_RAW_AUDIO ); + if( err != B_OK || num < 1 ) + { + DBUG(("Couldn't get PortAudio output.\n")); + goto error; + } + + + /* We've found the input and output -- the final step is to run a wire + * between them so they are connected. */ + + /* try to make the mixer input adapt to what PA sends it */ + pahsc->pahsc_Format = pahsc->pahsc_PaOutput.format; + roster->Connect( pahsc->pahsc_PaOutput.source, + pahsc->pahsc_MixerInput.destination, &pahsc->pahsc_Format, + &pahsc->pahsc_PaOutput, &pahsc->pahsc_MixerInput ); + + + /* Actually, there's one final step -- tell them all to sync to the + * sound card's DAC */ + roster->SetTimeSourceFor( pahsc->pahsc_InputNode.node, + pahsc->pahsc_TimeSource.node ); + roster->SetTimeSourceFor( pahsc->pahsc_OutputNode.node, + pahsc->pahsc_TimeSource.node ); + + } + + return paNoError; + +error: + PaHost_CloseStream( past ); + return paHostError; +} + +/*************************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + status_t err; + BMediaRoster *roster = BMediaRoster::Roster(&err); + + if( !roster ) + { + DBUG(("Couldn't get media roster\n")); + return paHostError; + } + + if( !pahsc ) + return paHostError; + + /* Disconnect all the connections we made when opening the stream */ + + roster->Disconnect(pahsc->pahsc_InputNode.node, pahsc->pahsc_PaOutput.source, + pahsc->pahsc_OutputNode.node, pahsc->pahsc_MixerInput.destination); + + DBUG(("Calling ReleaseNode()")); + roster->ReleaseNode(pahsc->pahsc_InputNode); + + /* deleting the node shouldn't be necessary -- it is reference counted, and will + * delete itself when its references drop to zero. the call to ReleaseNode() + * above should decrease its reference count */ + pahsc->pahsc_InputNodeInstance = NULL; + + return paNoError; +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc = (PaHostSoundControl *)past->past_DeviceData; + + return pahsc->pahsc_InputNodeInstance->GetStreamTime(); +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + /* snooze() takes microseconds */ + snooze( msec * 1000 ); +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + * Memory will be set to zero. + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + /* BeOS supports non-pagable memory through pools -- a pool is an area + * of physical memory that is locked. It would be best to pre-allocate + * that pool and then hand out memory from it, but we don't know in + * advance how much we'll need. So for now, we'll allocate a pool + * for every request we get, storing a pointer to the pool at the + * beginning of the allocated memory */ + rtm_pool *pool; + void *addr; + long size = numBytes + sizeof(rtm_pool *); + static int counter = 0; + char pool_name[100]; + + /* Every pool needs a unique name. */ + sprintf(pool_name, "PaPoolNumber%d", counter++); + + if( rtm_create_pool( &pool, size, pool_name ) != B_OK ) + return 0; + + addr = rtm_alloc( pool, size ); + if( addr == NULL ) + return 0; + + memset( addr, 0, numBytes ); + *((rtm_pool **)addr) = pool; // store the pointer to the pool + addr = (rtm_pool **)addr + 1; // and return the next location in memory + + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + rtm_pool *pool; + + if( addr == NULL ) + return; + + addr = (rtm_pool **)addr - 1; + pool = *((rtm_pool **)addr); + + rtm_free( addr ); + rtm_delete_pool( pool ); +} diff --git a/pa_common/pa_host.h b/pa_common/pa_host.h new file mode 100644 index 0000000..9a35261 --- /dev/null +++ b/pa_common/pa_host.h @@ -0,0 +1,142 @@ +#ifndef PA_HOST_H +#define PA_HOST_H + +/* + * $Id$ + * 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. + * + */ + +#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 + +#define PA_MAGIC (0x18273645) + +/************************************************************************************/ +/****************** Structures ******************************************************/ +/************************************************************************************/ + +typedef struct internalPortAudioStream +{ + uint32 past_Magic; /* ID for struct to catch bugs. */ + /* 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; + void *past_DeviceData; + PortAudioCallback *past_Callback; + void *past_UserData; + uint32 past_Flags; + /* 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; + 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; +} +internalPortAudioStream; + +/************************************************************************************/ +/****************** Prototypes ******************************************************/ +/************************************************************************************/ + +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 ); + +long Pa_CallConvertInt16( internalPortAudioStream *past, + short *nativeInputBuffer, + short *nativeOutputBuffer ); + +long Pa_CallConvertFloat32( internalPortAudioStream *past, + float *nativeInputBuffer, + float *nativeOutputBuffer ); + +void *PaHost_AllocateFastMemory( long numBytes ); +void PaHost_FreeFastMemory( void *addr, long numBytes ); + +PaError PaHost_ValidateSampleRate( PaDeviceID id, double requestedFrameRate, + double *closestFrameRatePtr ); +int PaHost_FindClosestTableEntry( double allowableError, const double *rateTable, + int numRates, double frameRate ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_HOST_H */ diff --git a/pa_common/pa_lib.c b/pa_common/pa_lib.c new file mode 100644 index 0000000..f32c635 --- /dev/null +++ b/pa_common/pa_lib.c @@ -0,0 +1,836 @@ +/* + * $Id$ + * Portable Audio I/O Library + * Host Independant Layer + * + * 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. + * + */ + +/* Modification History: + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + PLB20010820 - fix dither and shift for recording PaUInt8 format +*/ + +#include +#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() NMUST 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. */ + 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; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* 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; + long bytesEmpty = 0; + long bytesFilled = 0; + 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) ? + 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) + Pa_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; ipast_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + inputBuffer = nativeInputBuffer; /* FIXME */ + } + + /* 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 == paFloat32) ? + 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. */ /* FIXME */ + return userResult; +} + +/*************************************************************************/ +PaError Pa_Initialize( void ) +{ + if( gInitCount++ > 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; +} + +/*************************************************************************/ +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/pa_common/pa_trace.c b/pa_common/pa_trace.c new file mode 100644 index 0000000..08e68f5 --- /dev/null +++ b/pa_common/pa_trace.c @@ -0,0 +1,83 @@ +/* + * $Id$ + * 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 +#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; +} + +/*************************************************************************/ +DLL_API 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() NMUST 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. */ + 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; +} + + +/*************************************************************************/ +DLL_API 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 ); +} + +/*************************************************************************/ +DLL_API 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; +} + +/*************************************************************************/ +DLL_API 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; +} + +/*************************************************************************/ +DLL_API PaError Pa_StopStream( PortAudioStream *stream ) +{ + return Pa_KillStream( stream, 0 ); +} + +/*************************************************************************/ +DLL_API 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; +} + +/*************************************************************************/ +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ) +{ + internalPortAudioStream *past; + if( stream == NULL ) return paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return PaHost_StreamActive( past ); +} + +/*************************************************************************/ +DLL_API 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; + 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. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream) +{ + internalPortAudioStream *past; + if( stream == NULL ) return (double) paBadStreamPtr; + past = (internalPortAudioStream *) stream; + return past->past_Usage; +} + +/************************************************************* +** Calculate 2 LSB dither signal with a triangular distribution. +** Ranged properly for adding to a 32 bit integer prior to >>15. +*/ +#define DITHER_BITS (15) +#define DITHER_SCALE (1.0f / ((1<>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS)); + /* 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; + long bytesEmpty = 0; + long bytesFilled = 0; + 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> 7; + 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> 7; + temp = ((temp < -0x8000) ? -0x8000 : ((temp > 0x7FFF) ? 0x7FFF : temp)); + inBufPtr[i] = (unsigned char)(temp + 0x80); + } + } + 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) ? + 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) + Pa_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; ipast_NumInputChannels > 0) && (nativeInputBuffer != NULL) ) + { + inputBuffer = nativeInputBuffer; // FIXME + } + + /* 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 == paFloat32) ? + 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. */ // FIXME + return userResult; +} + +/*************************************************************************/ +DLL_API PaError Pa_Initialize( void ) +{ + if( gInitCount++ > 0 ) return paNoError; + ResetTraceMessages(); + return PaHost_Init(); +} + +DLL_API PaError Pa_Terminate( void ) +{ + PaError result = paNoError; + + if( gInitCount == 0 ) return paNoError; + else if( --gInitCount == 0 ) + { + result = PaHost_Term(); + DumpTraceMessages(); + } + return result; +} + +/*************************************************************************/ +DLL_API 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/pa_dll_switch/portaudio.h b/pa_dll_switch/portaudio.h new file mode 100644 index 0000000..9632521 --- /dev/null +++ b/pa_dll_switch/portaudio.h @@ -0,0 +1,439 @@ +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * 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. + * + */ + +// added by zplane.developement in order to generate a DLL + +#if defined(PA_MME_EXPORTS) || defined(PA_DX_EXPORTS) +#define DLL_API __declspec( dllexport ) +#elif defined(_LIB) || defined(_STATIC_LINK) || defined(_STATIC_APP) +#define DLL_API +#else +#define DLL_API __declspec(dllexport) +#endif + + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. +*/ + +DLL_API PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. +*/ + +DLL_API PaError Pa_Terminate( void ); + +/* + Return host specific error. + This can be called after receiving a paHostError. +*/ +DLL_API long Pa_GetHostError( void ); + +/* + Translate the error number into a human readable message. +*/ +DLL_API const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all devices. + + The floating point representation uses +1.0 and -1.0 as the respective + maximum and minimum. + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) /* unsigned 8 bit, 128 is "ground" */ +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. Device 0 is always the "default" + device and should support at least stereo in and out if that is available + on the taget platform _even_ if this involves kludging an input/output + device on platforms that usually separate input from output. Other platform + specific devices are specified by positive device ids. +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + + +DLL_API int Pa_CountDevices(); +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() + + Return the default device ID or paNoDevice if there is no devices. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "pa_devs". +*/ +DLL_API PaDeviceID Pa_GetDefaultInputDeviceID( void ); +DLL_API PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time useful for syncronisation. The type is used in the outTime + argument to the callback function and the result of Pa_StreamTime() +*/ + +typedef double PaTimestamp; + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + referring to the device specified by id. + If id is out of range the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must + not be manipulated or freed. The pointer is guaranteed to be valid until + between calls to Pa_Initialize() and Pa_Terminate(). +*/ + +DLL_API const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ); + +/* + PortAudioCallback is implemented by clients of the portable audio api. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a nonzero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + nonzero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + + [ suggestions? ] +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable defult clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + device input record for the device specified in the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the sound devices native formats (nativeSampleFormats in the device info + record) and additionally 16 and 32 bit integer and 32 bit floating point + formats. Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate for input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered + communication with the platform specific audio routines. This parameter is + provided only as a guide - and does not imply that an implementation must + use multibuffered i/o when reliable double buffering is available (such as + SndPlayDoubleBuffer() on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behavior of the + streaming process. Some flags may only be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Apon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a nonzero error code is returned (see + PAError above) and the value of stream is invalid. + +*/ + +DLL_API PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that + opens the default input and/or ouput devices. Most parameters have + identical meaning to their Pa_OpenStream() counterparts, with the following + exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened (same as passing paNoDevice in the device arguments to Pa_OpenStream() ) + + sampleFormat applies to both the input and output buffers. +*/ + +DLL_API PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. +*/ + +DLL_API PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + When Pa_StopStream() returns, all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. +*/ + +DLL_API PaError Pa_StartStream( PortAudioStream *stream ); + +DLL_API PaError Pa_StopStream( PortAudioStream *stream ); + +DLL_API PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one when the stream is playing audio, + zero when not playing, or a negative error number if the + stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. +*/ + +DLL_API PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time for the stream in samples. + This time may be used as a time reference (for example syncronising audio to + MIDI). +*/ + +DLL_API PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + The "CPU Load" is a fraction of total CPU time consumed by the + stream's audio processing. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. +*/ +DLL_API double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Use Pa_GetMinNumBuffers() to determine minimum number of buffers required for + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. +*/ + +DLL_API int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely + on this for accurate musical timing. +*/ +DLL_API void Pa_Sleep( long msec ); + +/* + Return size in bytes of a single sample in a given PaSampleFormat + or paSampleFormatNotSupported. +*/ +DLL_API PaError Pa_GetSampleSize( PaSampleFormat format ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/pa_mac/pa_mac.c b/pa_mac/pa_mac.c new file mode 100644 index 0000000..cde9a95 --- /dev/null +++ b/pa_mac/pa_mac.c @@ -0,0 +1,1567 @@ +/* + * $Id$ + * Portable Audio I/O Library for Macintosh + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2000 Phil Burk + * + * Special thanks to Chris Rolfe for his many helpful suggestions, bug fixes, + * and code contributions. + * Thanks also to Tue Haste Andersen, Alberto Ricci, Nico Wald, + * Roelf Toxopeus and Tom Erbe for testing the code and making + * numerous suggestions. + * + * 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. + */ +/* Modification History + PLB20010415 - ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID + PLB20010415 - Device Scan was crashing for anything other than siBadSoundInDevice, but some Macs may return other errors! + PLB20010420 - Fix TIMEOUT in record mode. + PLB20010420 - Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON + PLB20010907 - Pass unused event to WaitNextEvent to prevent Mac OSX crash. Thanks Dominic Mazzoni. + PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. + PLB20011009 - Use NewSndCallBackUPP() for CARBON +*/ +/* +COMPATIBILITY +This Macintosh implementation is designed for use with Mac OS 7, 8 and +9 on PowerMacs, and OS X if compiled with CARBON + +OUTPUT +A circular array of CmpSoundHeaders is used as a queue. For low latency situations +there will only be two small buffers used. For higher latency, more and larger buffers +may be used. +To play the sound we use SndDoCommand() with bufferCmd. Each buffer is followed +by a callbackCmd which informs us when the buffer has been processsed. + +INPUT +The SndInput Manager SPBRecord call is used for sound input. If only +input is used, then the PA user callback is called from the Input completion proc. +For full-duplex, or output only operation, the PA callback is called from the +HostBuffer output completion proc. In that case, input sound is passed to the +callback by a simple FIFO. + +TODO: +O- Add support for native sample data formats other than int16. +O- Review buffer sizing. Should it be based on result of siDeviceBufferInfo query? +O- Determine default devices somehow. +*/ +#include +#include +#include +#include +#include +/* Mac specific includes */ +#include "OSUtils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#ifndef FALSE + #define FALSE (0) + #define TRUE (!FALSE) +#endif + +/* Debugging output macros. */ +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) /**/ +#define DBUGX(x) /* PRINT(x) /**/ + +#define MAC_PHYSICAL_FRAMES_PER_BUFFER (512) /* Minimum number of stereo frames per SoundManager double buffer. */ +#define MAC_VIRTUAL_FRAMES_PER_BUFFER (4096) /* Need this many when using Virtual Memory for recording. */ +#define PA_MIN_NUM_HOST_BUFFERS (2) +#define PA_MAX_NUM_HOST_BUFFERS (16) /* Do not exceed!! */ +#define PA_MAX_DEVICE_INFO (32) + +/* Conversions for 16.16 fixed point code. */ +#define DoubleToUnsignedFixed(x) ((UnsignedFixed) ((x) * 65536.0)) +#define UnsignedFixedToDouble(fx) (((double)(fx)) * (1.0/(1<<16))) + +/************************************************************************************/ +/****************** Structures ******************************************************/ +/************************************************************************************/ +/* Use for passing buffers from input callback to output callback for processing. */ +typedef struct MultiBuffer +{ + char *buffers[PA_MAX_NUM_HOST_BUFFERS]; + int numBuffers; + int nextWrite; + int nextRead; +} +MultiBuffer; + +/* Define structure to contain all Macintosh specific data. */ +typedef struct PaHostSoundControl +{ + UInt64 pahsc_EntryCount; + UInt64 pahsc_LastExitCount; + /* Use char instead of Boolean for atomic operation. */ + volatile char pahsc_IsRecording; /* Recording in progress. Set by foreground. Cleared by background. */ + volatile char pahsc_StopRecording; /* Signal sent to background. */ + volatile char pahsc_IfInsideCallback; + /* Input */ + SPB pahsc_InputParams; + SICompletionUPP pahsc_InputCompletionProc; + MultiBuffer pahsc_InputMultiBuffer; + int32 pahsc_BytesPerInputHostBuffer; + int32 pahsc_InputRefNum; + /* Output */ + CmpSoundHeader pahsc_SoundHeaders[PA_MAX_NUM_HOST_BUFFERS]; + int32 pahsc_BytesPerOutputHostBuffer; + SndChannelPtr pahsc_Channel; + SndCallBackUPP pahsc_OutputCompletionProc; + int32 pahsc_NumOutsQueued; + int32 pahsc_NumOutsPlayed; + PaTimestamp pahsc_NumFramesDone; + /* Init Time -------------- */ + int32 pahsc_NumHostBuffers; + int32 pahsc_FramesPerHostBuffer; + int32 pahsc_UserBuffersPerHostBuffer; + int32 pahsc_MinFramesPerHostBuffer; /* Can vary depending on virtual memory usage. */ +} +PaHostSoundControl; + +/* Mac specific device information. */ +typedef struct internalPortAudioDevice +{ + long pad_DeviceRefNum; + long pad_DeviceBufferSize; + Component pad_Identifier; + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/************************************************************************************/ +/****************** Data ************************************************************/ +/************************************************************************************/ +static int sNumDevices = 0; +static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 }; +static int32 sPaHostError = 0; +static int sDefaultOutputDeviceID; +static int sDefaultInputDeviceID; + +/************************************************************************************/ +/****************** Prototypes ******************************************************/ +/************************************************************************************/ +static PaError PaMac_TimeSlice( internalPortAudioStream *past, int16 *macOutputBufPtr ); +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ); +static PaError PaMac_RecordNext( internalPortAudioStream *past );static void StartLoadCalculation( internalPortAudioStream *past ); +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerBuffer, double sampleRate ); +static double *PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ); +static PaError PaMac_ScanInputDevices( void ); +static PaError PaMac_ScanOutputDevices( void ); +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ); +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ); +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ); +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ); +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ); +static long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ); +static pascal void PaMac_InputCompletionProc(SPBPtr recParams); +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCmd); +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ); +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static int Mac_IsVirtualMemoryOn( void ); +static void PToCString(unsigned char* inString, char* outString); +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ); +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ); +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ); +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ); +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ); +int MultiBuffer_IsReadable( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ); +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ); + +/************************************************************************* +** Simple FIFO index control for multiple buffers. +** Read and Write indices range from 0 to 2N-1. +** This allows us to distinguish between full and empty. +*/ +char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextWrite % mbuf->numBuffers]; +} +char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf ) +{ + return mbuf->buffers[mbuf->nextRead % mbuf->numBuffers]; +} +int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextRead % mbuf->numBuffers; +} +int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf ) +{ + return mbuf->nextWrite % mbuf->numBuffers; +} + +int MultiBuffer_IsWriteable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull < mbuf->numBuffers); +} +int MultiBuffer_IsReadable( MultiBuffer *mbuf ) +{ + int bufsFull = mbuf->nextWrite - mbuf->nextRead; + if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers); + return (bufsFull > 0); +} +void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextRead + 1; + mbuf->nextRead = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} +void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf ) +{ + int temp = mbuf->nextWrite + 1; + mbuf->nextWrite = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp; +} + +/************************************************************************* +** String Utility by Chris Rolfe +*/ +static void PToCString(unsigned char* inString, char* outString) +{ + long i; + for(i=0; isampleRates; + if( (rates != NULL) ) free( rates ); /* MEM_011 */ + dev->sampleRates = NULL; + if( dev->name != NULL ) free( (void *) dev->name ); /* MEM_010 */ + dev->name = NULL; + } + sNumDevices = 0; + return paNoError; +} + +/************************************************************************* + PaHost_Init() is the library initialization function - call this before + using the library. +*/ +PaError PaHost_Init( void ) +{ + PaError err; + NumVersionVariant version; + + version.parts = SndSoundManagerVersion(); + DBUG(("SndSoundManagerVersion = 0x%x\n", version.whole)); + + /* Have we already initialized the device info? */ + err = (PaError) Pa_CountDevices(); + if( err < 0 ) return err; + else return paNoError; +} + +/************************************************************************* + PaMac_ScanOutputDevices() queries the properties of all output devices. +*/ +static PaError PaMac_ScanOutputDevices( void ) +{ + PaError err; + Component identifier=0; + ComponentDescription criteria = { kSoundOutputDeviceType, 0, 0, 0, 0 }; + long numComponents, i; + + /* Search the system linked list for output components */ + numComponents = CountComponents (&criteria); + identifier = 0; + sDefaultOutputDeviceID = sNumDevices; /* FIXME - query somehow */ + for (i = 0; i < numComponents; i++) + { + /* passing nil returns first matching component. */ + identifier = FindNextComponent( identifier, &criteria); + sDevices[sNumDevices].pad_Identifier = identifier; + + /* Set up for default OUTPUT devices. */ + err = PaMac_QueryOutputDeviceInfo( identifier, &sDevices[sNumDevices] ); + if( err < 0 ) return err; + else sNumDevices++; + + } + + return paNoError; +} + +/************************************************************************* + PaMac_ScanInputDevices() queries the properties of all input devices. +*/ +static PaError PaMac_ScanInputDevices( void ) +{ + Str255 deviceName; + int count; + Handle iconHandle; + PaError err; + OSErr oserr; + count = 1; + sDefaultInputDeviceID = sNumDevices; /* FIXME - query somehow */ /* PLB20010415 - was setting sDefaultOutputDeviceID */ + while(true) + { + /* Thanks Chris Rolfe and Alberto Ricci for this trick. */ + oserr = SPBGetIndexedDevice(count++, deviceName, &iconHandle); + DBUG(("PaMac_ScanInputDevices: SPBGetIndexedDevice returned %d\n", oserr )); +#if 1 + /* PLB20010415 - was giving error for anything other than siBadSoundInDevice, but some Macs may return other errors! */ + if(oserr != noErr) break; /* Some type of error is expected when count > devices */ +#else + if(oserr == siBadSoundInDevice) + { /* it's expected when count > devices */ + oserr = noErr; + break; + } + if(oserr != noErr) + { + ERR_RPT(("ERROR: SPBGetIndexedDevice(%d,,) returned %d\n", count-1, oserr )); + sPaHostError = oserr; + return paHostError; + } +#endif + DisposeHandle(iconHandle); /* Don't need the icon */ + + err = PaMac_QueryInputDeviceInfo( deviceName, &sDevices[sNumDevices] ); + DBUG(("PaMac_ScanInputDevices: PaMac_QueryInputDeviceInfo returned %d\n", err )); + if( err < 0 ) return err; + else if( err == 1 ) sNumDevices++; + } + + return paNoError; +} + +/* Sample rate info returned by using siSampleRateAvailable selector in SPBGetDeviceInfo() */ +/* Thanks to Chris Rolfe for help with this query. */ +#pragma options align=mac68k +typedef struct +{ + int16 numRates; + UnsignedFixed (**rates)[]; /* Handle created by SPBGetDeviceInfo */ +} +SRateInfo; +#pragma options align=reset + +/************************************************************************* +** PaMac_QueryOutputDeviceInfo() +** Query information about a named output device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad ) +{ + int len; + OSErr err; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + ComponentDescription tempD; + Handle nameH=nil, infoH=nil, iconH=nil; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + + dev->structVersion = 1; + dev->maxInputChannels = 0; + dev->maxOutputChannels = 2; + dev->nativeSampleFormats = paInt16; /* FIXME - query to see if 24 or 32 bit data can be handled. */ + + /* Get sample rates supported. */ + err = GetSoundOutputInfo(identifier, siSampleRateAvailable, (Ptr) &srinfo); + if(err != noErr) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetSoundOutputInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + numRates = srinfo.numRates; + DBUG(("PaMac_QueryOutputDeviceInfo: srinfo.numRates = 0x%x\n", srinfo.numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Device name */ + /* we pass an existing handle for the component name; + we don't care about the info (type, subtype, etc.) or icon, so set them to nil */ + infoH = nil; + iconH = nil; + nameH = NewHandle(0); + if(nameH == nil) return paInsufficientMemory; + err = GetComponentInfo(identifier, &tempD, nameH, infoH, iconH); + if (err) + { + ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetComponentInfo returned %d\n", err )); + goto error; + } + len = (*nameH)[0] + 1; + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + DisposeHandle(nameH); + return paInsufficientMemory; + } + else + { + PToCString((unsigned char *)(*nameH), (char *) dev->name); + DisposeHandle(nameH); + } + + DBUG(("PaMac_QueryOutputDeviceInfo: dev->name = %s\n", dev->name )); + return paNoError; + +error: + sPaHostError = err; + return paHostError; + +} + +/************************************************************************* +** PaMac_QueryInputDeviceInfo() +** Query information about a named input device. +** Clears contents of ipad and writes info based on queries. +** Return one if OK, +** zero if device cannot be used, +** or negative error. +*/ +static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad ) +{ + PaError result = paNoError; + int len; + OSErr err; + long mRefNum = 0; + long tempL; + int16 tempS; + Fixed tempF; + PaDeviceInfo *dev = &ipad->pad_Info; + SRateInfo srinfo = {0}; + int numRates; + + memset( ipad, 0, sizeof(internalPortAudioDevice) ); + dev->maxOutputChannels = 0; + + /* Open device based on name. If device is in use, it may not be able to open in write mode. */ + err = SPBOpenDevice( deviceName, siWritePermission, &mRefNum); + if (err) + { + /* If device is in use, it may not be able to open in write mode so try read mode. */ + err = SPBOpenDevice( deviceName, siReadPermission, &mRefNum); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBOpenDevice returned %d\n", err )); + sPaHostError = err; + return paHostError; + } + } + + /* Define macros for printing out device info. */ +#define PrintDeviceInfo(selector,var) \ + err = SPBGetDeviceInfo(mRefNum, selector, (Ptr) &var); \ + if (err) { \ + DBUG(("query %s failed\n", #selector )); \ + }\ + else { \ + DBUG(("query %s = 0x%x\n", #selector, var )); \ + } + + PrintDeviceInfo( siContinuous, tempS ); + PrintDeviceInfo( siAsync, tempS ); + PrintDeviceInfo( siNumberChannels, tempS ); + PrintDeviceInfo( siSampleSize, tempS ); + PrintDeviceInfo( siSampleRate, tempF ); + PrintDeviceInfo( siChannelAvailable, tempS ); + PrintDeviceInfo( siActiveChannels, tempL ); + PrintDeviceInfo( siDeviceBufferInfo, tempL ); + + err = SPBGetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err == 0) DBUG(("%s = 0x%x\n", "siActiveChannels", tempL )); + /* Can we use this device? */ + err = SPBGetDeviceInfo(mRefNum, siAsync, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siAsync returned %d\n", err )); + goto error; + } + if( tempS == 0 ) goto useless; /* Does not support async recording so forget about it. */ + + err = SPBGetDeviceInfo(mRefNum, siChannelAvailable, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siChannelAvailable returned %d\n", err )); + goto error; + } + dev->maxInputChannels = tempS; + + /* Get sample rates supported. */ + err = SPBGetDeviceInfo(mRefNum, siSampleRateAvailable, (Ptr) &srinfo); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleRateAvailable returned %d\n", err )); + goto error; + } + + numRates = srinfo.numRates; + DBUG(("numRates = 0x%x\n", numRates )); + if( numRates == 0 ) + { + dev->numSampleRates = -1; + numRates = 2; + } + else + { + dev->numSampleRates = numRates; + } + dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates ); + /* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */ + DisposeHandle((Handle) srinfo.rates); + + /* Get size of device buffer. */ + err = SPBGetDeviceInfo(mRefNum, siDeviceBufferInfo, (Ptr) &tempL); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siDeviceBufferInfo returned %d\n", err )); + goto error; + } + ipad->pad_DeviceBufferSize = tempL; + DBUG(("siDeviceBufferInfo = %d\n", tempL )); + + /* Set format based on sample size. */ + err = SPBGetDeviceInfo(mRefNum, siSampleSize, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleSize returned %d\n", err )); + goto error; + } + switch( tempS ) + { + case 0x0020: + dev->nativeSampleFormats = paInt32; /* FIXME - warning, code probably won't support this! */ + break; + case 0x0010: + default: /* FIXME - What about other formats? */ + dev->nativeSampleFormats = paInt16; + break; + } + DBUG(("nativeSampleFormats = %d\n", dev->nativeSampleFormats )); + + /* Device name */ + len = deviceName[0] + 1; /* Get length of Pascal string */ + dev->name = (char *) malloc(len); /* MEM_010 */ + if( dev->name == NULL ) + { + result = paInsufficientMemory; + goto cleanup; + } + PToCString(deviceName, (char *) dev->name); + DBUG(("deviceName = %s\n", dev->name )); + result = (PaError) 1; + /* All done so close up device. */ +cleanup: + if( mRefNum ) SPBCloseDevice(mRefNum); + return result; + +error: + if( mRefNum ) SPBCloseDevice(mRefNum); + sPaHostError = err; + return paHostError; + +useless: + if( mRefNum ) SPBCloseDevice(mRefNum); + return (PaError) 0; +} + +/************************************************************************* +** Allocate a double array and fill it with listed sample rates. +*/ +static double * PaMac_GetSampleRatesFromHandle ( int numRates, Handle h ) +{ + OSErr err = noErr; + SInt8 hState; + int i; + UnsignedFixed *fixedRates; + double *rates = (double *) malloc( numRates * sizeof(double) ); /* MEM_011 */ + if( rates == NULL ) return NULL; + /* Save and restore handle state as suggested by TechNote at: + http://developer.apple.com/technotes/tn/tn1122.html + */ + hState = HGetState (h); + if (!(err = MemError ())) + { + HLock (h); + if (!(err = MemError ( ))) + { + fixedRates = (UInt32 *) *h; + for( i=0; i= Pa_CountDevices()) ) return NULL; + return &sDevices[id].pad_Info; +} +/*************************************************************************/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + return sDefaultInputDeviceID; +} + +/*************************************************************************/ +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return sDefaultOutputDeviceID; +} + +/**************************************************************************/ +static void StartLoadCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + UnsignedWide widePad; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + Microseconds( &widePad ); + pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad ); +} + +/**************************************************************************/ +static void PaMac_EndLoadCalculation( internalPortAudioStream *past ) +{ + UnsignedWide widePad; + UInt64 CurrentCount; + long InsideCount; + long TotalCount; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + Microseconds( &widePad ); + CurrentCount = UnsignedWideToUInt64( widePad ); + if( past->past_IfLastExitValid ) + { + InsideCount = (long) U64Subtract(CurrentCount, pahsc->pahsc_EntryCount); + TotalCount = (long) U64Subtract(CurrentCount, pahsc->pahsc_LastExitCount); + /* Low pass filter the result because sometimes we get called several times in a row. + * That can cause the TotalCount to be very low which can cause the usage to appear + * unnaturally high. So we must filter numerator and denominator separately!!! + */ + past->past_AverageInsideCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * InsideCount)); + past->past_AverageTotalCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * TotalCount)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + } + pahsc->pahsc_LastExitCount = CurrentCount; + past->past_IfLastExitValid = 1; +} + +/*********************************************************************** +** Called by Pa_StartStream() +*/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + pahsc->pahsc_InputMultiBuffer.nextWrite = 0; + pahsc->pahsc_InputMultiBuffer.nextRead = 0; + return PaMac_RecordNext( past ); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaError result = paNoError; + OSErr err = 0; + long mRefNum; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + (void) abort; + + mRefNum = pahsc->pahsc_InputRefNum; + + DBUG(("PaHost_StopInput: mRefNum = %d\n", mRefNum )); + if( mRefNum ) + { + DBUG(("PaHost_StopInput: pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + if( pahsc->pahsc_IsRecording ) + { + /* PLB20010420 - Fix TIMEOUT in record mode. */ + pahsc->pahsc_StopRecording = 1; /* Request that we stop recording. */ + err = SPBStopRecording(mRefNum); + DBUG(("PaHost_StopInput: then pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording )); + + /* Calculate timeOut longer than longest time it could take to play one buffer. */ + timeOutMsec = (int32) ((1500.0 * pahsc->pahsc_FramesPerHostBuffer) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( !err && pahsc->pahsc_IsRecording && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopInput: timed out!\n")); + return paTimedOut; + } + } + } + if( err ) + { + sPaHostError = err; + result = paHostError; + } + + DBUG(("PaHost_StopInput: finished.\n", mRefNum )); + return result; +} + +/***********************************************************************/ +static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader ) +{ + sndHeader->numChannels = past->past_NumOutputChannels; + sndHeader->sampleRate = DoubleToUnsignedFixed(past->past_SampleRate); + sndHeader->loopStart = 0; + sndHeader->loopEnd = 0; + sndHeader->encode = cmpSH; + sndHeader->baseFrequency = kMiddleC; + sndHeader->markerChunk = nil; + sndHeader->futureUse2 = nil; + sndHeader->stateVars = nil; + sndHeader->leftOverSamples = nil; + sndHeader->compressionID = 0; + sndHeader->packetSize = 0; + sndHeader->snthID = 0; + sndHeader->sampleSize = 8 * sizeof(int16); // FIXME - might be 24 or 32 bits some day; + sndHeader->sampleArea[0] = 0; + sndHeader->format = kSoundNotCompressed; +} + +/***********************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + SndCommand pauseCommand; + SndCommand resumeCommand; + int i; + OSErr error; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + if( pahsc->pahsc_Channel == NULL ) return paInternalError; + + past->past_StopSoon = 0; + past->past_IsActive = 1; + pahsc->pahsc_NumOutsQueued = 0; + pahsc->pahsc_NumOutsPlayed = 0; + pahsc->pahsc_NumFramesDone = 0.0; + + /* Pause channel so it does not do back ground processing while we are still filling the queue. */ + pauseCommand.cmd = pauseCmd; + pauseCommand.param1 = pauseCommand.param2 = 0; + error = SndDoCommand (pahsc->pahsc_Channel, &pauseCommand, true); + if (noErr != error) goto exit; + + /* Queue all of the buffers so we start off full. */ + for (i = 0; ipahsc_NumHostBuffers; i++) + { + PaMac_PlayNext( past, i ); + } + + /* Resume channel now that the queue is full. */ + resumeCommand.cmd = resumeCmd; + resumeCommand.param1 = resumeCommand.param2 = 0; + error = SndDoImmediate( pahsc->pahsc_Channel, &resumeCommand ); + if (noErr != error) goto exit; + + return paNoError; +exit: + past->past_IsActive = 0; + sPaHostError = error; + ERR_RPT(("Error in PaHost_StartOutput: SndDoCommand returned %d\n", error )); + return paHostError; +} + +/*******************************************************************/ +long PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (long) (pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer); +} + +/*********************************************************************** +** Called by Pa_StopStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + int32 timeOutMsec; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + if( pahsc->pahsc_Channel == NULL ) return paNoError; + + DBUG(("PaHost_StopOutput()\n")); + if( past->past_IsActive == 0 ) return paNoError; + + /* Set flags for callback function to see. */ + if( abort ) past->past_StopNow = 1; + past->past_StopSoon = 1; + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + timeOutMsec = (int32) ((1500.0 * PaHost_GetTotalBufferFrames( past )) / past->past_SampleRate); + /* Keep querying sound channel until it is no longer busy playing. */ + while( past->past_IsActive && (timeOutMsec > 0)) + { + Pa_Sleep(20); + timeOutMsec -= 20; + } + if( timeOutMsec <= 0 ) + { + ERR_RPT(("PaHost_StopOutput: timed out!\n")); + return paTimedOut; + } + else return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + (void) past; /* Prevent unused variable warnings. */ + return paNoError; +} + +/***********************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + (void) past; /* Prevent unused variable warnings. */ + (void) abort; /* Prevent unused variable warnings. */ + return paNoError; +} +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return (PaError) ( past->past_IsActive + pahsc->pahsc_IsRecording ); +} +int Mac_IsVirtualMemoryOn( void ) +{ + long attr; + OSErr result = Gestalt( gestaltVMAttr, &attr ); + DBUG(("gestaltVMAttr : 0x%x\n", attr )); + return ((attr >> gestaltVMHasPagingControl ) & 1); +} + +/******************************************************************* +* Determine number of WAVE Buffers +* and how many User Buffers we can put into each WAVE buffer. +*/ +static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int32 minNumBuffers; + int32 minFramesPerHostBuffer; + int32 minTotalFrames; + int32 userBuffersPerHostBuffer; + int32 framesPerHostBuffer; + int32 numHostBuffers; + /* Calculate minimum and maximum sizes based on timing and sample rate. */ + minFramesPerHostBuffer = pahsc->pahsc_MinFramesPerHostBuffer; + minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); + /* Determine number of user buffers based on minimum latency. */ + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */ + if( (int32) past->past_FramesPerUserBuffer < minFramesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) / + past->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* Make sure we have anough WAVE buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else + { + /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + } + } + + pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer; + pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer; + pahsc->pahsc_NumHostBuffers = numHostBuffers; + DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + OSErr err; + PaError result = paHostError; + PaHostSoundControl *pahsc; + int i; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if( pahsc == NULL ) + { + return paInsufficientMemory; + } + past->past_DeviceData = (void *) pahsc; + + /* If recording, and virtual memory is turned on, then use bigger buffers to prevent glitches. */ + if( (past->past_NumInputChannels > 0) && Mac_IsVirtualMemoryOn() ) + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_VIRTUAL_FRAMES_PER_BUFFER; + } + else + { + pahsc->pahsc_MinFramesPerHostBuffer = MAC_PHYSICAL_FRAMES_PER_BUFFER; + } + + PaHost_CalcNumHostBuffers( past ); + + /* ------------------ OUTPUT */ + if( past->past_NumOutputChannels > 0 ) + { + /* Create sound channel to which we can send commands. */ + pahsc->pahsc_Channel = 0L; + err = SndNewChannel(&pahsc->pahsc_Channel, sampledSynth, 0, nil); /* FIXME - use kUseOptionalOutputDevice if not default. */ + if(err != 0) + { + ERR_RPT(("Error in PaHost_OpenStream: SndNewChannel returned 0x%x\n", err )); + goto error; + } + + /* Install our callback function pointer straight into the sound channel structure */ + /* Use new CARBON name for callback procedure. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_OutputCompletionProc = NewSndCallBackUPP(PaMac_OutputCompletionProc); +#else + pahsc->pahsc_OutputCompletionProc = NewSndCallBackProc(PaMac_OutputCompletionProc); +#endif + + pahsc->pahsc_Channel->callBack = pahsc->pahsc_OutputCompletionProc; + + pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumOutputChannels * sizeof(int16); + for (i = 0; ipahsc_NumHostBuffers; i++) + { + char *buf = (char *)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer); + if (buf == NULL) + { + ERR_RPT(("Error in PaHost_OpenStream: could not allocate output buffer. Size = \n", pahsc->pahsc_BytesPerOutputHostBuffer )); + goto memerror; + } + + PaMac_InitSoundHeader( past, &pahsc->pahsc_SoundHeaders[i] ); + pahsc->pahsc_SoundHeaders[i].samplePtr = buf; + pahsc->pahsc_SoundHeaders[i].numFrames = (unsigned long) pahsc->pahsc_FramesPerHostBuffer; + + } + } +#ifdef SUPPORT_AUDIO_CAPTURE + /* ------------------ INPUT */ + /* Use double buffer scheme that matches output. */ + if( past->past_NumInputChannels > 0 ) + { + int16 tempS; + long tempL; + Fixed tempF; + long mRefNum; + unsigned char noname = 0; /* FIXME - use real device names. */ +#if TARGET_API_MAC_CARBON + pahsc->pahsc_InputCompletionProc = NewSICompletionUPP((SICompletionProcPtr)PaMac_InputCompletionProc); +#else + pahsc->pahsc_InputCompletionProc = NewSICompletionProc((ProcPtr)PaMac_InputCompletionProc); +#endif + pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumInputChannels * sizeof(int16); + for (i = 0; ipahsc_NumHostBuffers; i++) + { + char *buf = (char *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer); + if ( buf == NULL ) + { + ERR_RPT(("PaHost_OpenStream: could not allocate input buffer. Size = \n", pahsc->pahsc_BytesPerInputHostBuffer )); + goto memerror; + } + pahsc->pahsc_InputMultiBuffer.buffers[i] = buf; + } + pahsc->pahsc_InputMultiBuffer.numBuffers = pahsc->pahsc_NumHostBuffers; + + err = SPBOpenDevice( (const unsigned char *) &noname, siWritePermission, &mRefNum); /* FIXME - use name so we get selected device */ + // FIXME err = SPBOpenDevice( (const unsigned char *) sDevices[past->past_InputDeviceID].pad_Info.name, siWritePermission, &mRefNum); + if (err) goto error; + pahsc->pahsc_InputRefNum = mRefNum; + DBUG(("PaHost_OpenStream: mRefNum = %d\n", mRefNum )); + + /* Set input device characteristics. */ + tempS = 1; + err = SPBSetDeviceInfo(mRefNum, siContinuous, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siContinuous returned %d\n", err )); + goto error; + } + + tempL = 0x03; + err = SPBSetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL); + if (err) + { + DBUG(("PaHost_OpenStream: setting siActiveChannels returned 0x%x. Error ignored.\n", err )); + } + + /* PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. */ + tempS = past->past_NumInputChannels; + err = SPBSetDeviceInfo(mRefNum, siNumberChannels, (Ptr) &tempS); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siNumberChannels returned %d\n", err )); + goto error; + } + + tempF = ((unsigned long)past->past_SampleRate) << 16; + err = SPBSetDeviceInfo(mRefNum, siSampleRate, (Ptr) &tempF); + if (err) + { + ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siSampleRate returned %d\n", err )); + goto error; + } + + /* Setup record-parameter block */ + pahsc->pahsc_InputParams.inRefNum = mRefNum; + pahsc->pahsc_InputParams.milliseconds = 0; // not used + pahsc->pahsc_InputParams.completionRoutine = pahsc->pahsc_InputCompletionProc; + pahsc->pahsc_InputParams.interruptRoutine = 0; + pahsc->pahsc_InputParams.userLong = (long) past; + pahsc->pahsc_InputParams.unused1 = 0; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + DBUG(("PaHost_OpenStream: complete.\n")); + return paNoError; + +error: + PaHost_CloseStream( past ); + ERR_RPT(("PaHost_OpenStream: sPaHostError = 0x%x.\n", err )); + sPaHostError = err; + return paHostError; + +memerror: + PaHost_CloseStream( past ); + return paInsufficientMemory; +} + +/*********************************************************************** +** Called by Pa_CloseStream(). +** May be called during error recovery or cleanup code +** so protect against NULL pointers. +*/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err = 0; + int i; + PaHostSoundControl *pahsc; + + DBUG(("PaHost_CloseStream( 0x%x )\n", past )); + + if( past == NULL ) return paBadStreamPtr; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + if( past->past_NumOutputChannels > 0 ) + { + /* TRUE means flush now instead of waiting for quietCmd to be processed. */ + if( pahsc->pahsc_Channel != NULL ) SndDisposeChannel(pahsc->pahsc_Channel, TRUE); + { + for (i = 0; ipahsc_NumHostBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_SoundHeaders[i].samplePtr; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerOutputHostBuffer ); + } + } + } + + if( past->past_NumInputChannels > 0 ) + { + if( pahsc->pahsc_InputRefNum ) + { + err = SPBCloseDevice(pahsc->pahsc_InputRefNum); + pahsc->pahsc_InputRefNum = 0; + if( err ) + { + sPaHostError = err; + result = paHostError; + } + } + { + for (i = 0; ipahsc_InputMultiBuffer.numBuffers; i++) + { + Ptr p = (Ptr) pahsc->pahsc_InputMultiBuffer.buffers[i]; + if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerInputHostBuffer ); + } + } + } + + past->past_DeviceData = NULL; + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); + + DBUG(("PaHost_CloseStream: complete.\n", past )); + return result; +} +/*************************************************************************/ +int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate ) +{ + return PaMac_GetMinNumBuffers( MAC_VIRTUAL_FRAMES_PER_BUFFER, framesPerUserBuffer, sampleRate ); +} +/*************************************************************************/ +static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerUserBuffer, double sampleRate ) +{ + int minUserPerHost = ( minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer; + int numBufs = PA_MIN_NUM_HOST_BUFFERS * minUserPerHost; + if( numBufs < PA_MIN_NUM_HOST_BUFFERS ) numBufs = PA_MIN_NUM_HOST_BUFFERS; + (void) sampleRate; + return numBufs; +} + +/*************************************************************************/ +void Pa_Sleep( int32 msec ) +{ + EventRecord event; + int32 sleepTime, endTime; + /* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */ + sleepTime = ((msec * 60) + 999) / 1000; + if( sleepTime < 1 ) sleepTime = 1; + endTime = TickCount() + sleepTime; + do + { + DBUGX(("Sleep for %d ticks.\n", sleepTime )); + /* Use WaitNextEvent() to sleep without getting events. */ + /* PLB20010907 - Pass unused event to WaitNextEvent instead of NULL to prevent + * Mac OSX crash. Thanks Dominic Mazzoni. */ + WaitNextEvent( 0, &event, sleepTime, NULL ); + sleepTime = endTime - TickCount(); + } + while( sleepTime > 0 ); +} +/*************************************************************************/ +int32 Pa_GetHostError( void ) +{ + int32 err = sPaHostError; + sPaHostError = 0; + return err; +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = NewPtrClear( numBytes ); + if( (addr == NULL) || (MemError () != 0) ) return NULL; + +#if (TARGET_API_MAC_CARBON == 0) + if( HoldMemory( addr, numBytes ) != noErr ) + { + DisposePtr( (Ptr) addr ); + return NULL; + } +#endif + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr == NULL ) return; +#if TARGET_API_MAC_CARBON + (void) numBytes; +#else + UnholdMemory( addr, numBytes ); +#endif + DisposePtr( (Ptr) addr ); +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + return pahsc->pahsc_NumFramesDone; +} + +/************************************************************************** +** Callback for Input, SPBRecord() +*/ +int gRecordCounter = 0; +int gPlayCounter = 0; +pascal void PaMac_InputCompletionProc(SPBPtr recParams) +{ + PaError result = paNoError; + int finished = 1; + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + + gRecordCounter += 1; /* debug hack to see if engine running */ + + /* Get our PA data from Mac structure. */ + past = (internalPortAudioStream *) recParams->userLong; + if( past == NULL ) return; + + if( past->past_Magic != PA_MAGIC ) + { + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, past", (long) past ); + AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, magic", (long) past->past_Magic ); + goto error; + } + pahsc = (PaHostSoundControl *) past->past_DeviceData; + past->past_NumCallbacks += 1; + + /* Have we been asked to stop recording? */ + if( (recParams->error == abortErr) || pahsc->pahsc_StopRecording ) goto error; + + /* If there are no output channels, then we need to call the user callback function from here. + * Otherwise we will call the user code during the output completion routine. + */ + if(past->past_NumOutputChannels == 0) + { + pahsc->pahsc_NumFramesDone += pahsc->pahsc_FramesPerHostBuffer; // Advance for recording + result = PaMac_CallUserLoop( past, NULL ); + } + + /* Did user code ask us to stop? If not, issue another recording request. */ + if( (result == paNoError) && (pahsc->pahsc_StopRecording == 0) ) + { + result = PaMac_RecordNext( past ); + if( result != paNoError ) pahsc->pahsc_IsRecording = 0; + } + else goto error; + + return; + +error: + pahsc->pahsc_IsRecording = 0; + pahsc->pahsc_StopRecording = 0; + return; +} + +/*********************************************************************** +** Called by either input or output completion proc. +** Grabs input data if any present, and calls PA conversion code, +** that in turn calls user code. +*/ +static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + int16 *inPtr = NULL; + int i; + + /* Advance read index for sound input FIFO here, independantly of record/write process. */ + if(past->past_NumInputChannels > 0) + { + if( MultiBuffer_IsReadable( &pahsc->pahsc_InputMultiBuffer ) ) + { + inPtr = (int16 *) MultiBuffer_GetNextReadBuffer( &pahsc->pahsc_InputMultiBuffer ); + MultiBuffer_AdvanceReadIndex( &pahsc->pahsc_InputMultiBuffer ); + } + } + + /* Call user code enough times to fill buffer. */ + if( (inPtr != NULL) || (outPtr != NULL) ) + { + StartLoadCalculation( past ); /* CPU usage */ + + for( i=0; ipahsc_UserBuffersPerHostBuffer; i++ ) + { + result = (PaError) Pa_CallConvertInt16( past, inPtr, outPtr ); + if( result != 0) + { + /* Recording might be in another process, so tell it to stop with a flag. */ + pahsc->pahsc_StopRecording = pahsc->pahsc_IsRecording; + break; + } + /* Advance sample pointers. */ + if(inPtr != NULL) inPtr += past->past_FramesPerUserBuffer * past->past_NumInputChannels; + if(outPtr != NULL) outPtr += past->past_FramesPerUserBuffer * past->past_NumOutputChannels; + } + + PaMac_EndLoadCalculation( past ); + } + return result; +} + +/*********************************************************************** +** Setup next recording buffer in FIFO and issue recording request to Snd Input Manager. +*/ +static PaError PaMac_RecordNext( internalPortAudioStream *past ) +{ + PaError result = paNoError; + OSErr err; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Get pointer to next buffer to record into. */ + pahsc->pahsc_InputParams.bufferPtr = MultiBuffer_GetNextWriteBuffer( &pahsc->pahsc_InputMultiBuffer ); + + /* Advance write index if there is room. Otherwise keep writing same buffer. */ + if( MultiBuffer_IsWriteable( &pahsc->pahsc_InputMultiBuffer ) ) + { + MultiBuffer_AdvanceWriteIndex( &pahsc->pahsc_InputMultiBuffer ); + } + + AddTraceMessage("PaMac_RecordNext: bufferPtr", (long) pahsc->pahsc_InputParams.bufferPtr ); + AddTraceMessage("PaMac_RecordNext: nextWrite", pahsc->pahsc_InputMultiBuffer.nextWrite ); + + /* Setup parameters and issue an asynchronous recording request. */ + pahsc->pahsc_InputParams.bufferLength = pahsc->pahsc_BytesPerInputHostBuffer; + pahsc->pahsc_InputParams.count = pahsc->pahsc_BytesPerInputHostBuffer; + err = SPBRecord(&pahsc->pahsc_InputParams, true); + if( err ) + { + AddTraceMessage("PaMac_RecordNext: SPBRecord error ", err ); + sPaHostError = err; + result = paHostError; + } + else + { + pahsc->pahsc_IsRecording = 1; + } + return result; +} + +/************************************************************************** +** Callback for Output Playback() +** Return negative error, 0 to continue, 1 to stop. +*/ +long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index ) +{ + PaHostSoundControl *pahsc; + long result = 0; + int finished = 1; + char *outPtr; + + gPlayCounter += 1; /* debug hack */ + + past->past_NumCallbacks += 1; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return -1; + /* Are we nested?! */ + if( pahsc->pahsc_IfInsideCallback ) return 0; + pahsc->pahsc_IfInsideCallback = 1; + /* Get pointer to buffer to fill. */ + outPtr = pahsc->pahsc_SoundHeaders[index].samplePtr; + /* Combine with any sound input, and call user callback. */ + result = PaMac_CallUserLoop( past, (int16 *) outPtr ); + + pahsc->pahsc_IfInsideCallback = 0; + return result; +} + +/************************************************************************************* +** Called by SoundManager when ready for another buffer. +*/ +static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCallBackCmd) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + (void) theChannel; + (void) theCallBackCmd; + + /* Get our data from Mac structure. */ + past = (internalPortAudioStream *) theCallBackCmd->param2; + if( past == NULL ) return; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + pahsc->pahsc_NumOutsPlayed += 1; + pahsc->pahsc_NumFramesDone += pahsc->pahsc_FramesPerHostBuffer; + + PaMac_BackgroundManager( past, theCallBackCmd->param1 ); +} + +/*******************************************************************/ +static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Has someone asked us to abort by calling Pa_AbortStream()? */ + if( past->past_StopNow ) + { + SndCommand command; + /* Clear the queue of any pending commands. */ + command.cmd = flushCmd; + command.param1 = command.param2 = 0; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + /* Then stop currently playing buffer, if any. */ + command.cmd = quietCmd; + SndDoImmediate( pahsc->pahsc_Channel, &command ); + past->past_IsActive = 0; + } + /* 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 ) + { + if( (pahsc->pahsc_NumOutsQueued - pahsc->pahsc_NumOutsPlayed) <= 0 ) + { + past->past_IsActive = 0; /* We're finally done. */ + } + } + else + { + PaMac_PlayNext( past, index ); + } + return result; +} + +/************************************************************************************* +** Fill next buffer with sound and queue it for playback. +*/ +static void PaMac_PlayNext ( internalPortAudioStream *past, int index ) +{ + OSErr error; + long result; + SndCommand playCmd; + SndCommand callbackCmd; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + /* If this was the last buffer, or abort requested, then just be done. */ + if ( past->past_StopSoon ) goto done; + /* Load buffer with sound. */ + result = PaMac_FillNextOutputBuffer ( past, index ); + if( result > 0 ) past->past_StopSoon = 1; /* Stop generating audio but wait until buffers play. */ + else if( result < 0 ) goto done; + /* Play the next buffer. */ + playCmd.cmd = bufferCmd; + playCmd.param1 = 0; + playCmd.param2 = (long) &pahsc->pahsc_SoundHeaders[ index ]; + error = SndDoCommand (pahsc->pahsc_Channel, &playCmd, true ); + if( error != noErr ) goto gotError; + /* Ask for a callback when it is done. */ + callbackCmd.cmd = callBackCmd; + callbackCmd.param1 = index; + callbackCmd.param2 = (long)past; + error = SndDoCommand (pahsc->pahsc_Channel, &callbackCmd, true ); + if( error != noErr ) goto gotError; + pahsc->pahsc_NumOutsQueued += 1; + + return; + +gotError: + sPaHostError = error; +done: + return; +} diff --git a/pa_mac_core/pa_mac_core.c b/pa_mac_core/pa_mac_core.c new file mode 100644 index 0000000..3dc4d74 --- /dev/null +++ b/pa_mac_core/pa_mac_core.c @@ -0,0 +1,1159 @@ +/* + * $Id$ + * pa_mac_core.c + * Implementation of PortAudio for Mac OS X Core Audio + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * 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. + * + * 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 + */ +#include +#include +#include +#include +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/************************************************* Constants ********/ +#define PA_USE_TIMER_CALLBACK (0) /* Select between two options for background task. 0=thread, 1=timer */ +#define PA_USE_HIGH_LATENCY (0) /* For debugging glitches. */ + +/* Switches for debugging. */ +#define PA_SIMULATE_UNDERFLOW (0) /* Set to one to force an underflow of the output buffer. */ + +/* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */ +#define PA_TRACE_RUN (0) +#define PA_TRACE_START_STOP (1) + +#define PA_MIN_MSEC_PER_HOST_BUFFER (10) +#define PA_MAX_MSEC_PER_HOST_BUFFER (100) /* Do not exceed unless user buffer exceeds */ +#define PA_MIN_NUM_HOST_BUFFERS (3) +#define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */ +#define PA_MIN_LATENCY_MSEC (200) + +#define MIN_TIMEOUT_MSEC (1000) + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +/************************************************* Definitions ********/ + +/************************************************************** + * Structure for internal host specific stream data. + * This is allocated on a per stream basis. + */ +typedef struct PaHostSoundControl +{ + /* Input -------------- */ + int pahsc_BytesPerHostInputBuffer; + int pahsc_BytesPerUserInputBuffer; /* native buffer size in bytes */ + /* Output -------------- */ + AudioDeviceID pahsc_OutputAudioDeviceID; + int pahsc_BytesPerHostOutputBuffer; + int pahsc_BytesPerUserOutputBuffer; /* native buffer size in bytes */ + /* Run Time -------------- */ + PaTimestamp pahsc_FramesPlayed; + long pahsc_LastPosition; /* used to track frames played. */ + /* For measuring CPU utilization. */ + // LARGE_INTEGER pahsc_EntryCount; + // LARGE_INTEGER pahsc_LastExitCount; + /* Init Time -------------- */ + int pahsc_NumHostBuffers; + int pahsc_FramesPerHostBuffer; + int pahsc_UserBuffersPerHostBuffer; + + // CRITICAL_SECTION pahsc_StreamLock; /* Mutext to prevent threads from colliding. */ + int pahsc_StreamLockInited; + +} +PaHostSoundControl; + +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumDevices = 0; +static PaDeviceInfo **sDevicePtrs = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sPaHostError = 0; +static AudioDeviceID *sDeviceIDList; // Array of AudioDeviceIDs + +static const char sMapperSuffixInput[] = " - Input"; +static const char sMapperSuffixOutput[] = " - Output"; + +/************************************************* Macros ********/ + +/* Convert external PA ID to an internal ID that includes WAVE_MAPPER */ +#define PaDeviceIdToWinId(id) (((id) < sNumInputDevices) ? (id - 1) : (id - sNumInputDevices - 1)) + +/************************************************* Prototypes **********/ + +static PaError Pa_QueryDevices( void ); +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + // QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); +} + +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ +#if 0 + // LARGE_INTEGER CurrentCount = { 0, 0 }; + // LONGLONG InsideCount; + // LONGLONG TotalCount; + /* + ** Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + + if( QueryPerformanceCounter( &CurrentCount ) ) + { + if( past->past_IfLastExitValid ) + { + InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart; + TotalCount = CurrentCount.QuadPart - pahsc->pahsc_LastExitCount.QuadPart; + /* Low pass filter the result because sometimes we get called several times in a row. + * That can cause the TotalCount to be very low which can cause the usage to appear + * unnaturally high. So we must filter numerator and denominator separately!!! + */ + past->past_AverageInsideCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * InsideCount)); + past->past_AverageTotalCount = (( LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * TotalCount)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + } + pahsc->pahsc_LastExitCount = CurrentCount; + past->past_IfLastExitValid = 1; + } +#endif +} + +static PaDeviceID Pa_QueryDefaultDevice( int deviceEnum ) +{ + OSStatus err = noErr; + UInt32 count; + int i; + AudioDeviceID tempDevice = kAudioDeviceUnknown; + PaDeviceID defaultDeviceID; + + // get the default output device for the HAL + // it is required to pass the size of the data to be returned + count = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty( deviceEnum, &count, (void *) &tempDevice); + if (err != noErr) goto Bail; + // find index of default device + defaultDeviceID = paNoDevice; + for( i=0; i= sNumDevices ) + return NULL; + + if( sDevicePtrs[ id ] != NULL ) + { + return sDevicePtrs[ id ]; + } + + deviceInfo = PaHost_AllocateFastMemory( sizeof(PaDeviceInfo) ); + if( deviceInfo == NULL ) return NULL; + + deviceInfo->structVersion = 1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->numSampleRates = -1; + + + // fill in format descriptor + if( id < sNumDevices ) + { + devID = sDeviceIDList[ id ]; + + // Get the device name + deviceInfo->name = deviceNameFromID( devID ); + + // Figure out supported sample rates + // Make room in case device supports all rates. + sampleRates = (double*)PaHost_AllocateFastMemory( MAX_NUMSAMPLINGRATES * sizeof(double) ); + deviceInfo->sampleRates = sampleRates; + deviceInfo->numSampleRates = 0; + + // Loop through the possible sampling rates and check each to see if the device supports it. + for (index = 0; index < MAX_NUMSAMPLINGRATES; index ++) + { + memset( &formatDesc, 0, sizeof(AudioStreamBasicDescription) ); + formatDesc.mSampleRate = possibleSampleRates[index]; + result = deviceDoesSupportFormat( devID, &formatDesc ); + + if (result == true) + { + deviceInfo->numSampleRates += 1; + *sampleRates = possibleSampleRates[index]; + sampleRates++; + } + } + + // Get data format info from the device. + deviceDataFormatFromID(devID, &formatDesc); + + deviceInfo->maxInputChannels = 0; // FIXME - Input and Output are separate devices! + deviceInfo->maxOutputChannels = formatDesc.mChannelsPerFrame; + + // FIXME - where to put current sample rate?: formatDesc.mSampleRate + + // Right now the Core Audio headers only define one formatID: LinearPCM + // Apparently LinearPCM must be Float32 for now. + // FIXME - + switch (formatDesc.mFormatID) + { + case kAudioFormatLinearPCM: + deviceInfo->nativeSampleFormats = paFloat32; + + // FIXME - details about the format are in these flags. + // formatDesc.mFormatFlags + + // here are the possibilities + // kLinearPCMFormatFlagIsFloat // set for floating point, clear for integer + // kLinearPCMFormatFlagIsBigEndian // set for big endian, clear for little + // kLinearPCMFormatFlagIsSignedInteger // set for signed integer, clear for unsigned integer, + // only valid if kLinearPCMFormatFlagIsFloat is clear + // kLinearPCMFormatFlagIsPacked // set if the sample bits are packed as closely together as possible, + // clear if they are high or low aligned within the channel + // kLinearPCMFormatFlagIsAlignedHigh // set if the sample bits are placed + + break; + default: + deviceInfo->nativeSampleFormats = paFloat32; // FIXME + break; + } + + } + + sDevicePtrs[ id ] = deviceInfo; + return deviceInfo; + + /* FIXME + error: + free( sampleRates ); + free( deviceInfo ); + */ + + return NULL; +} + +/************************************************************************* +** Returns recommended device ID. +** On the PC, the recommended device can be specified by the user by +** setting an environment variable. For example, to use device #1. +** +** set PA_RECOMMENDED_OUTPUT_DEVICE=1 +** +** The user should first determine the available device ID by using +** the supplied application "pa_devs". +*/ +#define PA_ENV_BUF_SIZE (32) +#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") + +static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) +{ +#if 0 + UInt32 hresult; + char envbuf[PA_ENV_BUF_SIZE]; + PaDeviceID recommendedID = paNoDevice; + + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + recommendedID = atoi( envbuf ); + } + + return recommendedID; +#endif + return paNoDevice; +} + +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sNumDevices == 0 ) + { + return Pa_QueryDevices(); + } + return 0; +} + +/********************************************************************** +** Check for environment variable, else query devices and use result. +*/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultInputDeviceID; + } + return result; +} + +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultOutputDeviceID; + } + return result; +} + +/********************************************************************** +** Initialize Host dependant part of API. +*/ + +PaError PaHost_Init( void ) +{ +#if PA_SIMULATE_UNDERFLOW + PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); +#endif + return Pa_MaybeQueryDevices(); +} + +/********************************************************************** +** Fill any available output buffers and use any available +** input buffers by calling user callback. +*/ +static PaError Pa_TimeSlice( internalPortAudioStream *past, const AudioBufferList* inInputData, + AudioBufferList* outOutputData ) +{ + PaError result = 0; + char *inBufPtr; + char *outBufPtr; + int gotInput = 0; + int gotOutput = 0; + int i; + int buffersProcessed = 0; + int done = 0; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + + past->past_NumCallbacks += 1; + +#if PA_SIMULATE_UNDERFLOW + if(gUnderCallbackCounter++ == UNDER_SLEEP_AT) + { + Sleep(UNDER_SLEEP_FOR); + } +#endif + +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", past->past_NumCallbacks ); +#endif + + Pa_StartUsageCalculation( past ); + + /* If we are using output, then we need an empty output buffer. */ + gotOutput = 0; + if( past->past_NumOutputChannels > 0 ) + { + outBufPtr = outOutputData->mBuffers[0].mData, + gotOutput = 1; + } + + /* If we are using input, then we need a full input buffer. */ + gotInput = 0; + inBufPtr = NULL; + if( past->past_NumInputChannels > 0 ) + { + inBufPtr = inInputData->mBuffers[0].mData, + gotInput = 1; + } + + buffersProcessed += 1; + + /* Each host buffer contains multiple user buffers so do them all now. */ + for( i=0; ipahsc_UserBuffersPerHostBuffer; i++ ) + { + if( done ) + { + if( gotOutput ) + { + /* Clear remainder of wave buffer if we are waiting for stop. */ + AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i ); + memset( outBufPtr, 0, pahsc->pahsc_BytesPerUserOutputBuffer ); + } + } + else + { + /* Convert 16 bit native data to user data and call user routine. */ + result = Pa_CallConvertFloat32( past, (float *) inBufPtr, (float *) outBufPtr ); + if( result != 0) done = 1; + } + if( gotInput ) inBufPtr += pahsc->pahsc_BytesPerUserInputBuffer; + if( gotOutput) outBufPtr += pahsc->pahsc_BytesPerUserOutputBuffer; + } + + Pa_EndUsageCalculation( past ); + +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed ); +#endif + + return (result != 0) ? result : done; +} + +OSStatus appIOProc (AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* contextPtr) +{ + + PaError result = 0; + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + past = (internalPortAudioStream *) contextPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + // Just Curious: printf("Num input Buffers: %d; Num output Buffers: %d.\n", inInputData->mNumberBuffers, outOutputData->mNumberBuffers); + + /* 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 + past->past_IsActive = 0; /* Will cause thread to return. */ + } + else + { + /* Process full input buffer and fill up empty output buffers. */ + if( (result = Pa_TimeSlice( past, inInputData, outOutputData )) != 0) + { + /* User callback has asked us to stop. */ +#if PA_TRACE_START_STOP + AddTraceMessage( "Pa_OutputThreadProc: TimeSlice() returned ", result ); +#endif + past->past_StopSoon = 1; /* Request that audio play out then stop. */ + result = paNoError; + } + } + + // FIXME PaHost_UpdateStreamTime( pahsc ); + + return result; +} + +static int PaHost_CalcTimeOut( internalPortAudioStream *past ) +{ + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + int timeOut = (UInt32) (1500.0 * PaHost_GetTotalBufferFrames( past ) / past->past_SampleRate); + if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC; + return timeOut; +} + +/*******************************************************************/ +PaError PaHost_OpenInputStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + int bytesPerInputFrame; + const PaDeviceInfo *pad; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_InputDeviceID)); + pad = Pa_GetDeviceInfo( past->past_InputDeviceID ); + if( pad == NULL ) return paInternalError; + + bytesPerInputFrame = Pa_GetSampleSize(pad->nativeSampleFormats) * past->past_NumInputChannels; + pahsc->pahsc_BytesPerUserInputBuffer = past->past_FramesPerUserBuffer * bytesPerInputFrame; + pahsc->pahsc_BytesPerHostInputBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * pahsc->pahsc_BytesPerUserInputBuffer; + // FIXME + return result; +} + +/*******************************************************************/ +PaError PaHost_OpenOutputStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + const PaDeviceInfo *pad; + UInt32 bytesPerHostBuffer; + UInt32 dataSize; + UInt32 hardwareLatency = -1; + Boolean writeable = false; + OSStatus err = noErr; + int bytesPerOutputFrame; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); + pad = Pa_GetDeviceInfo( past->past_OutputDeviceID ); + if( pad == NULL ) return paInternalError; + + pahsc->pahsc_OutputAudioDeviceID = sDeviceIDList[past->past_OutputDeviceID]; + + bytesPerOutputFrame = Pa_GetSampleSize(pad->nativeSampleFormats) * past->past_NumOutputChannels; + pahsc->pahsc_BytesPerUserOutputBuffer = past->past_FramesPerUserBuffer * bytesPerOutputFrame; + pahsc->pahsc_BytesPerHostOutputBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * pahsc->pahsc_BytesPerUserOutputBuffer; + + // FIXME - force host buffer to user size + + // change the bufferSize of the device + bytesPerHostBuffer = past->past_OutputBufferSize; // FIXME + dataSize = sizeof(UInt32); + err = AudioDeviceSetProperty( pahsc->pahsc_OutputAudioDeviceID, 0, 0, false, + kAudioDevicePropertyBufferSize, dataSize, &bytesPerHostBuffer); + if( err != noErr ) + { + ERR_RPT(("Could not force buffer size!")); + result = paHostError; + goto error; + } + + // FIXME -- this isn't working for some reason. I can't figure out what the error code is. + dataSize = sizeof(UInt32); + err = AudioDeviceGetProperty(pahsc->pahsc_OutputAudioDeviceID, 0, false, kAudioDevicePropertyLatency, &dataSize, &writeable); + if( err != noErr ) + { + PRINT(("A CoreAudio error occurred: %s.\n", coreAudioErrorString( err ) )); + } + + err = AudioDeviceGetProperty(pahsc->pahsc_OutputAudioDeviceID, 0, false, kAudioDevicePropertyLatency, &dataSize, (void *)&hardwareLatency); + if ( err != noErr ) + { + PRINT(("A CoreAudio error occurred: %s.\n", coreAudioErrorString( err ) )); + } + else + { + PRINT(("Output Stream Hardware Latency = %d frames.\n", hardwareLatency )); + } + + return result; + +error: + return result; +} + +/*******************************************************************/ +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer; +} + + +/******************************************************************* +* Determine number of WAVE Buffers +* and how many User Buffers we can put into each WAVE buffer. +*/ +void PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = past->past_DeviceData; + unsigned int minNumBuffers; + int minFramesPerHostBuffer; + int maxFramesPerHostBuffer; + int minTotalFrames; + int userBuffersPerHostBuffer; + int framesPerHostBuffer; + int numHostBuffers; + + /* Calculate minimum and maximum sizes based on timing and sample rate. */ + minFramesPerHostBuffer = (int) (PA_MIN_MSEC_PER_HOST_BUFFER * past->past_SampleRate / 1000.0); + minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); + + maxFramesPerHostBuffer = (int) (PA_MAX_MSEC_PER_HOST_BUFFER * past->past_SampleRate / 1000.0); + maxFramesPerHostBuffer = (maxFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: maxFramesPerHostBuffer = %d\n", maxFramesPerHostBuffer )); + + /* Determine number of user buffers based on minimum latency. */ + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + + minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + + /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */ + if( (int) past->past_FramesPerUserBuffer < minFramesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) / + past->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + + + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + + /* Make sure we have anough WAVE buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else if( (numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) && + ((int) past->past_FramesPerUserBuffer < (maxFramesPerHostBuffer/2) ) ) + { + /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* If we have gone too far, back up one. */ + if( (framesPerHostBuffer > maxFramesPerHostBuffer) || + (numHostBuffers < PA_MAX_NUM_HOST_BUFFERS) ) + { + userBuffersPerHostBuffer -= 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + break; + } + } + } + + pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer; + pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer; + pahsc->pahsc_NumHostBuffers = numHostBuffers; + + DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + + /* 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; + + /* Figure out how user buffers fit into WAVE buffers. */ + // FIXME - just force for now + + pahsc->pahsc_UserBuffersPerHostBuffer = 1; + pahsc->pahsc_FramesPerHostBuffer = past->past_FramesPerUserBuffer; + pahsc->pahsc_NumHostBuffers = 2; // FIXME - dunno?! + + { + int msecLatency = (int) ((PaHost_GetTotalBufferFrames(past) * 1000) / past->past_SampleRate); + PRINT(("PortAudio on OSX - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(past), msecLatency )); + } + + /* ------------------ OUTPUT */ + if( (past->past_OutputDeviceID != paNoDevice) && (past->past_NumOutputChannels > 0) ) + { + result = PaHost_OpenOutputStream( past ); + if( result < 0 ) goto error; + } + + /* ------------------ INPUT */ + if( (past->past_InputDeviceID != paNoDevice) && (past->past_NumInputChannels > 0) ) + { + result = PaHost_OpenInputStream( past ); + if( result < 0 ) goto error; + } + + return result; + +error: + PaHost_CloseStream( past ); + return result; +} + +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + OSStatus err = noErr; + + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( past->past_OutputDeviceID != paNoDevice ) + { + // Associate an IO proc with the device and pass a pointer to the audio data context + err = AudioDeviceAddIOProc(pahsc->pahsc_OutputAudioDeviceID, (AudioDeviceIOProc)appIOProc, past); + if (err != noErr) goto error; + + // start playing sound through the device + err = AudioDeviceStart(pahsc->pahsc_OutputAudioDeviceID, (AudioDeviceIOProc)appIOProc); + if (err != noErr) goto error; + } + +error: + return result; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( past->past_InputDeviceID != paNoDevice ) + { + // FIXME + } + return result; +} + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + +#if PA_USE_TIMER_CALLBACK + int resolution; + int bufsPerTimerCallback; + int msecPerBuffer; +#endif /* PA_USE_TIMER_CALLBACK */ + + past->past_StopSoon = 0; + past->past_StopNow = 0; + past->past_IsActive = 1; + pahsc->pahsc_FramesPlayed = 0.0; + pahsc->pahsc_LastPosition = 0; + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result ); +#endif + return result; +} + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int timeOut; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + /* 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; + + timeOut = PaHost_CalcTimeOut( past ); + past->past_IsActive = 0; + + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + + // FIXME + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopOutput: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + + // FIXME if( pahsc->pahsc_OutputAudioDeviceID != ??? ) + { + OSStatus err = noErr; + + err = AudioDeviceStop(pahsc->pahsc_OutputAudioDeviceID, (AudioDeviceIOProc)appIOProc); + if (err != noErr) goto Bail; + + err = AudioDeviceRemoveIOProc(pahsc->pahsc_OutputAudioDeviceID, (AudioDeviceIOProc)appIOProc); + if (err != noErr) goto Bail; + } +Bail: + return paNoError; +} + +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_CloseStream: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + /* Free data and device for output. */ + + /* Free data and device for input. */ + + free( pahsc ); + past->past_DeviceData = NULL; + + return paNoError; +} + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be determined +** based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") + +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) +{ + int minLatencyMsec = 0; + double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate; + int minBuffers; + + /* Let user determine minimal latency by setting environment variable. */ + { + minLatencyMsec = PA_MIN_LATENCY_MSEC; + } + + DBUG(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); + minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); + if( minBuffers < 2 ) minBuffers = 2; + + return minBuffers; +} + +/************************************************************************* +** Cleanup device info. +*/ +PaError PaHost_Term( void ) +{ + int i; + + if( sNumDevices > 0 ) + { + + if( sDevicePtrs != NULL ) + { + + for( i=0; iname ); + free( (void*)sDevicePtrs[i]->sampleRates ); + free( sDevicePtrs[i] ); + } + } + + free( sDevicePtrs ); + sDevicePtrs = NULL; + } + + sNumDevices = 0; + } + return paNoError; +} + +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + /* struct timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + select( 0, NULL, NULL, NULL, &timeout ); + */ + usleep( msec * 1000 ); +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = malloc( numBytes ); /* FIXME - do we need physical memory? */ + if( addr != NULL ) memset( addr, 0, numBytes ); + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr != NULL ) free( addr ); +} + + +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) past->past_IsActive; +} + +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + // FIXME PaHost_UpdateStreamTime( pahsc ); + return pahsc->pahsc_FramesPlayed; +} diff --git a/pa_sgi/Makefile b/pa_sgi/Makefile new file mode 100644 index 0000000..c631c9b --- /dev/null +++ b/pa_sgi/Makefile @@ -0,0 +1,52 @@ +# Make PortAudio for Silicon Graphics IRIX (6.2) +# Pieter suurmond, august 17, 2001. (v15 pa_sgi sub-version #0.04) + +# Instead of "-lpthread", as with linux, +# just the audio- and math-library for SGI: +LIBS = -laudio -lm + +CDEFINES = -I../pa_common + +# Possible CFLAGS with MIPSpro compiler are: -32, -o32, -n32, -64, +# -mips1, -mips2, -mips3, -mips4, etc. Use -g, -g2, -g3 for debugging. +# And use for example -O2 or -O3 for better optimization: +CFLAGS = -O2 +PASRC = ../pa_common/pa_lib.c pa_sgi.c +PAINC = ../pa_common/portaudio.h + +# Tests that work (SGI Indy with R5000 @ 180MHz running IRIX 6.2). +#TESTC = $(PASRC) ../pa_tests/patest_many.c # OK +TESTC = $(PASRC) ../pa_tests/patest_record.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_latency.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_longsine.c # OK but needs more than 4 buffers to do without glitches. +#TESTC = $(PASRC) ../pa_tests/patest_saw.c # Seems OK (does it gracefully exit?). +#TESTC = $(PASRC) ../pa_tests/patest_wire.c # OK +#TESTC = $(PASRC) ../pa_tests/pa_devs.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_sine.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_sine_time.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_sine8.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_leftright.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_pink.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_clip.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_stop.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_dither.c # OK +#TESTC = $(PASRC) ../pa_tests/patest_sync.c # BEEPS and delta's, no crashes anymore. + +# Tests that do not yet work. +#TESTC = $(PASRC) ../pa_tests/paqa_devs.c # Heavy crash with core-dump after PaHost_OpenStream: opening 1 output channel(s) on AL default +#TESTC = $(PASRC) ../pa_tests/paqa_errs.c # Heavy crash with core-dump after PaHost_OpenStream: opening 2 output channel(s) on AL default +#TESTC = $(PASRC) ../pa_tests/pa_fuzz.c # THIS CRASHED MY WHOLE IRIX SYSTEM after "ENTER"! :-( + # PROCESS IN ITSELF RUNS OK, WITH LARGER BUFFSIZES THOUGH. + # OH MAYBE IT WAS BECAUSE DEBUGGING WAS ON??? + # HMMM I'M NOT GONNA RUN THAT AGAIN... WAY TOO DANGEROUS! +TESTH = $(PAINC) + +all: patest + +#(cc may be changed to gcc) +patest: $(TESTC) $(TESTH) Makefile + cc $(CFLAGS) $(TESTC) $(CDEFINES) $(LIBS) -o patest + +run: patest + ./patest + diff --git a/pa_sgi/pa_sgi.c b/pa_sgi/pa_sgi.c new file mode 100644 index 0000000..094cb4d --- /dev/null +++ b/pa_sgi/pa_sgi.c @@ -0,0 +1,991 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * SGI IRIX implementation by Pieter Suurmond, aug 17, 2001. + * + * 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. + */ +/* +Modfication History: + 8/12/2001 - Pieter Suurmond - took the v15 pa_linux_oss.c file and started to adapt for IRIX 6.2. + 8/17/2001 - (v15 pa_sgi sub-version #0.04), wow, much is working now! Send this to Phil & Ross. + ID-stuff is nicer here now than in the linux_oss-alpha-version, v15. Was able to get + rid of a number of globals (like "sNumDevices" for instance). +TODO: + - Test under IRIX 6.5. + - Maybe use pthread instead of (platform-specific) poll/select/pollfd-calls. + (As Gary Scavone more or less suggested(?)... But can I get enough process-control then?) + - Dynamically switch to 32 bit float as native format when appropriate (let SGI do the conversion), + and maybe also the other natively supported formats? (might increase performance) + - Not sure whether CPU UTILIZATION MEASUREMENT (from OSS/linux) really works. Changed nothing yet, + seems ok, but I've not yet tested it thoroughly. (maybe utilization-code may be made _unix_common_ then?) + - Make sure that the fuzz-test doesn't let all IRIX crash (especially with higher buff-sizes). + - The minimal number of buffers setting... I do not yet fully understand it.. I now just take *4. +REFERENCES: + - IRIX 6.2 man pages regarding SGI AL library. + - IRIS Digital MediaProgramming Guide (online books as well as man-pages come with IRIX 6.2 and + may not be publically available on the internet). +*/ + +#include /* Standard libraries. */ +#include +#include +#include +#include + +#include "portaudio.h" /* Portaudio headers. */ +#include "pa_host.h" +#include "pa_trace.h" + +#include /* System specific (IRIX 6.2). */ +#include +#include +#include +#include /* fcntl.h needed. */ +#include /* For streams, ioctl(), etc. */ +#include +#include +#include +#include +#include + +/*--------------------------------------------*/ +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) PRINT(x) +#define DBUGX(x) /* PRINT(x) */ + +#define MAX_CHARS_DEVNAME (16) /* Was 32 in OSS (20 for AL but "in"/"out" is concat. */ +#define MAX_SAMPLE_RATES (8) /* Known from SGI AL there are 7 (was 10 in OSS v15). */ + +typedef struct internalPortAudioDevice /* IRIX specific device info: */ +{ + PaDeviceID /* NEW: */ pad_DeviceID; /* THIS "ID" IS NEW HERE (Pieter)! */ + long pad_ALdevice; /* SGI-number! */ + double pad_SampleRates[MAX_SAMPLE_RATES]; /* for pointing to from pad_Info */ + char pad_DeviceName[MAX_CHARS_DEVNAME+1]; /* +1 for \0, one more than OSS. */ + PaDeviceInfo pad_Info; /* pad_Info (v15) contains: */ + /* int structVersion; */ + /* const char* name; */ + /* int maxInputChannels, maxOutputChannels; */ + /* int numSampleRates; Num rates, or -1 if range supprtd. */ + /* const double* sampleRates; Array of supported sample rates, */ + /* PaSampleFormat nativeSampleFormats; or {min,max} if range supported. */ + struct internalPortAudioDevice* pad_Next; /* Singly linked list, (NULL=end). */ +} +internalPortAudioDevice; + +typedef struct PaHostSoundControl /* Structure to contain all SGI IRIX specific data. */ +{ + ALport pahsc_ALportIN, /* IRIX-audio-library-datatype. ALports can only be */ + pahsc_ALportOUT; /* unidirectional, so we sometimes need 2 of them. */ + int pahsc_threadPID; /* Sproc()-result, written by PaHost_StartEngine(). */ + short *pahsc_NativeInputBuffer, /* Allocated here, in this file, if necessary. */ + *pahsc_NativeOutputBuffer; + unsigned int pahsc_BytesPerInputBuffer, /* Native buffer sizes in bytes, really needed here */ + pahsc_BytesPerOutputBuffer; /* to free FAST memory, if buffs were alloctd FAST. */ + unsigned int pahsc_SamplesPerInputBuffer, /* These amounts are needed again and again in the */ + pahsc_SamplesPerOutputBuffer; /* audio-thread (don't need to be kept globally). */ + struct itimerval pahsc_EntryTime, /* For measuring CPU utilization (same as linux). */ + pahsc_LastExitTime; + long pahsc_InsideCountSum, + pahsc_TotalCountSum; +} +PaHostSoundControl; + +/*----------------------------- Shared Data ------------------------------------------------------*/ +static internalPortAudioDevice* sDeviceList = NULL; /* FIXME - put Mutex around this shared data. */ +static int sPaHostError = 0; /* Maybe more than one process writing errs!? */ + +/*----------------------------- BEGIN CPU UTILIZATION MEASUREMENT -----------------*/ +/* (copied from source pa_linux_oss/pa_linux_oss.c) */ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + struct itimerval itimer; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + getitimer( ITIMER_REAL, &pahsc->pahsc_EntryTime ); +} + +static long SubtractTime_AminusB( struct itimerval *timeA, struct itimerval *timeB ) +{ + long secs = timeA->it_value.tv_sec - timeB->it_value.tv_sec; + long usecs = secs * 1000000; + usecs += (timeA->it_value.tv_usec - timeB->it_value.tv_usec); + return usecs; +} + +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + struct itimerval currentTime; + long insideCount; + long totalCount; /* Measure CPU utilization during this callback. */ + +#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 (getitimer( ITIMER_REAL, ¤tTime ) == 0 ) + { + if (past->past_IfLastExitValid) + { + insideCount = SubtractTime_AminusB( &pahsc->pahsc_EntryTime, ¤tTime ); + pahsc->pahsc_InsideCountSum += insideCount; + totalCount = SubtractTime_AminusB( &pahsc->pahsc_LastExitTime, ¤tTime ); + pahsc->pahsc_TotalCountSum += totalCount; + /* DBUG(("insideCount = %d, totalCount = %d\n", insideCount, totalCount )); */ + /* Low pass filter the result because sometimes we get called several times in a row. */ + /* That can cause the TotalCount to be very low which can cause the usage to appear */ + /* unnaturally high. So we must filter numerator and denominator separately!!! */ + if (pahsc->pahsc_InsideCountSum > 0) + { + past->past_AverageInsideCount = ((LOWPASS_COEFFICIENT_0 * past->past_AverageInsideCount) + + (LOWPASS_COEFFICIENT_1 * pahsc->pahsc_InsideCountSum)); + past->past_AverageTotalCount = ((LOWPASS_COEFFICIENT_0 * past->past_AverageTotalCount) + + (LOWPASS_COEFFICIENT_1 * pahsc->pahsc_TotalCountSum)); + past->past_Usage = past->past_AverageInsideCount / past->past_AverageTotalCount; + pahsc->pahsc_InsideCountSum = 0; + pahsc->pahsc_TotalCountSum = 0; + } + } + past->past_IfLastExitValid = 1; + } + pahsc->pahsc_LastExitTime.it_value.tv_sec = 100; + pahsc->pahsc_LastExitTime.it_value.tv_usec = 0; + setitimer( ITIMER_REAL, &pahsc->pahsc_LastExitTime, NULL ); + past->past_IfLastExitValid = 1; +} /*----------- END OF CPU UTILIZATION CODE (from pa_linux_oss/pa_linux_oss.c v15)--------------------*/ + + +/*--------------------------------------------------------------------------------------*/ +PaError translateSGIerror(void) /* Calls oserror(), may be used after an SGI AL-library */ +{ /* call to report via ERR_RPT(), yields a PaError-num. */ + const char* a = "SGI AL "; /* (Not absolutely sure errno came from THIS thread! */ + switch(oserror()) /* Read IRIX man-pages about the _SGI_MP_SOURCE macro.) */ + { + case AL_BAD_OUT_OF_MEM: + ERR_RPT(("%sout of memory.\n", a)); + return paInsufficientMemory; /* Known PaError. */ + case AL_BAD_CONFIG: + ERR_RPT(("%sconfiguration invalid or NULL.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_CHANNELS: + ERR_RPT(("%schannels not 1,2 or 4.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_NO_PORTS: + ERR_RPT(("%sout of audio ports.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DEVICE: + ERR_RPT(("%swrong device number.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DEVICE_ACCESS: + ERR_RPT(("%swrong device access.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_DIRECTION: + ERR_RPT(("%sinvalid direction.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_SAMPFMT: + ERR_RPT(("%sdoesn't accept sampleformat.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_FLOATMAX: + ERR_RPT(("%smax float value is zero.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_WIDTH: + ERR_RPT(("%sunsupported samplewidth.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_QSIZE: + ERR_RPT(("%sinvalid queue size.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_PVBUFFER: + ERR_RPT(("%sPVbuffer null.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_BUFFERLENGTH_NEG: + ERR_RPT(("%snegative bufferlength.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_BUFFERLENGTH_ODD: + ERR_RPT(("%sodd bufferlength.\n", a)); + return paHostError; /* Generic PaError. */ + case AL_BAD_PARAM: + ERR_RPT(("%sparameter not valid for device.\n", a)); + return paHostError; /* Generic PaError. */ + default: + ERR_RPT(("%sunknown error.\n", a)); + return paHostError; /* Generic PaError. */ + } +} + +/*------------------------------------------------------------------------------------------*/ +/* Tries to set various rates and formats and fill in the device info structure. */ +static PaError Pa_sgiQueryDevice(long ALdev, /* (AL_DEFAULT_DEVICE) */ + PaDeviceID id, /* (DefaultI|ODeviceID()) */ + char* name, /* (for example "SGI AL") */ + internalPortAudioDevice* pad) /* Result written to pad. */ +{ + int format; + long min, max; /* To catch hardware characteristics. */ + ALseterrorhandler(0); /* 0 = turn off the default error handler. */ + /*--------------------------------------------------------------------------------------*/ + pad->pad_ALdevice = ALdev; /* Set the AL device number. */ + pad->pad_DeviceID = id; /* Set the PA device number. */ + if (strlen(name) > MAX_CHARS_DEVNAME) /* MAX_CHARS defined above. */ + { + ERR_RPT(("Pa_QueryDevice(): name too long (%s).\n", name)); + return paHostError; + } + strcpy(pad->pad_DeviceName, name); /* Write name-string. */ + pad->pad_Info.name = pad->pad_DeviceName; /* Set pointer,..hmmm. */ + /*--------------------------------- natively supported sample formats: -----------------*/ + pad->pad_Info.nativeSampleFormats = paInt16; /* Later also include paFloat32 | ..| etc. */ + /* Then also choose other CallConvertXX()! */ + /*--------------------------------- number of available i/o channels: ------------------*/ + if (ALgetminmax(ALdev, AL_INPUT_COUNT, &min, &max)) + return translateSGIerror(); + pad->pad_Info.maxInputChannels = max; + DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", pad->pad_Info.maxInputChannels)) + if (ALgetminmax(ALdev, AL_OUTPUT_COUNT, &min, &max)) + return translateSGIerror(); + pad->pad_Info.maxOutputChannels = max; + DBUG(("Pa_QueryDevice: maxOutputChannels = %d\n", pad->pad_Info.maxOutputChannels)) + /*--------------------------------- supported samplerates: ----------------------*/ + pad->pad_Info.numSampleRates = 7; + pad->pad_Info.sampleRates = pad->pad_SampleRates; + pad->pad_SampleRates[0] = (double)AL_RATE_8000; /* long -> double. */ + pad->pad_SampleRates[1] = (double)AL_RATE_11025; + pad->pad_SampleRates[2] = (double)AL_RATE_16000; + pad->pad_SampleRates[3] = (double)AL_RATE_22050; + pad->pad_SampleRates[4] = (double)AL_RATE_32000; + pad->pad_SampleRates[5] = (double)AL_RATE_44100; + pad->pad_SampleRates[6] = (double)AL_RATE_48000; + if (ALgetminmax(ALdev, AL_INPUT_RATE, &min, &max)) /* Ask INPUT rate-max. */ + return translateSGIerror(); /* double -> long. */ + if (max != (long)(0.5 + pad->pad_SampleRates[6])) /* FP-compare not recommndd. */ + goto weird; + if (ALgetminmax(ALdev, AL_OUTPUT_RATE, &min, &max)) /* Ask OUTPUT rate-max. */ + return translateSGIerror(); + if (max != (long)(0.5 + pad->pad_SampleRates[6])) + { +weird: ERR_RPT(("Pa_sgiQueryDevice() did not confirm max samplerate (%ld)\n",max)); + return paHostError; /* Or make it a warning and just carry on... */ + } + /*-------------------------------------------------------------------------------*/ + return paNoError; +} + + +/*--------------------------------------------------------------------------------*/ +int Pa_CountDevices() /* Name of this function suggests it only counts and */ +{ /* is NOT destructive, it however resets whole PA ! */ + int numDevices = 0; /* Let 's not do that here. */ + internalPortAudioDevice* currentDevice = sDeviceList; /* COPY GLOBAL VAR. */ +#if 0 /* Remains from linux_oss v15: Pa_Initialize(), on */ + if (!currentDevice) /* its turn, calls PaHost_Init() via file pa_lib.c. */ + Pa_Initialize(); /* Isn't that a bit too 'rude'? Don't be too */ +#endif /* friendly to clients that forgot to initialize PA. */ + while (currentDevice) /* Slower but more elegant than the sNumDevices-way: */ + { + numDevices++; + currentDevice = currentDevice->pad_Next; + } + return numDevices; +} + + + +/*-------------------------------------------------------------------------------*/ +static internalPortAudioDevice *Pa_GetInternalDevice(PaDeviceID id) +{ + int numDevices = 0; + internalPortAudioDevice *res = (internalPortAudioDevice*)NULL; + internalPortAudioDevice *pad = sDeviceList; /* COPY GLOBAL VAR. */ + while (pad) /* pad may be NULL, that's ok, return 0. */ + { /* (Added ->pad_DeviceID field to the pad-struct, Pieter, 2001.) */ + if (pad->pad_DeviceID == id) /* This the device we were looking for? */ + res = pad; /* But keep on(!) counting so we don't */ + numDevices++; /* have to call Pa_CountDevices() later. */ + pad = pad->pad_Next; /* Advance to the next device or NULL. */ + } /* No assumptions about order of ID's in */ + if (!res) /* the list. */ + ERR_RPT(("Pa_GetInternalDevice() could not find specified ID (%d).\n",id)); + if ((id < 0) || (id >= numDevices)) + { + ERR_RPT(("Pa_GetInternalDevice() supplied with an illegal ID (%d).\n",id)); +#if 1 /* Be strict, even when found, */ + res = (internalPortAudioDevice*)NULL; /* do not accept illegal ID's. */ +#endif + + } + return res; +} + +/*----------------------------------------------------------------------*/ +const PaDeviceInfo* Pa_GetDeviceInfo(PaDeviceID id) +{ + PaDeviceInfo* res = (PaDeviceInfo*)NULL; + internalPortAudioDevice* pad = Pa_GetInternalDevice(id); /* Call. */ + if (pad) + res = &pad->pad_Info; /* Not finding the specified ID is not */ + if (!res) /* the same as &pad->pad_Info == NULL. */ + ERR_RPT(("Pa_GetDeviceInfo() could not find it (ID=%d).\n", id)); + return res; /* So (maybe) a second/third ERR_RPT(). */ +} + +/*------------------------------------------------*/ +PaDeviceID Pa_GetDefaultInputDeviceID(void) +{ + return 0; /* 0 is the default device ID. */ +} +/*------------------------------------------------*/ +PaDeviceID Pa_GetDefaultOutputDeviceID(void) +{ + return 0; +} + +/*--------------------------------------------------------------------------------------*/ +/* Build linked list with all available audio devices on this SGI machine. */ +PaError PaHost_Init(void) /* Called by Pa_Initialize() from pa_lib.c. */ +{ + internalPortAudioDevice* pad; + PaError r = paNoError; + int audioLibFileID; + + if (sDeviceList) /* Allow re-init, only warn, no err. */ + { + ERR_RPT(("Warning: PaHost_Init() did not really re-init PA.\n")); + return r; + } + /*------------- ADD THE SGI DEFAULT DEVICES TO THE LIST: --------------------------------------*/ + audioLibFileID = open("/dev/hdsp/hdsp0master", O_RDONLY); /* Try to open Indigo style audio */ + if (audioLibFileID < 0) /* IO port. On failure, machine */ + { /* has no audio ability. */ + ERR_RPT(("PaHost_Init(): This machine has no (Indigo-style) audio abilities.\n")); + return paHostError; + } + close(audioLibFileID); /* Allocate fast mem to hold device info. */ + pad = PaHost_AllocateFastMemory(sizeof(internalPortAudioDevice)); + if (pad == NULL) + return paInsufficientMemory; + memset(pad, 0, sizeof(internalPortAudioDevice)); /* "pad->pad_Next = NULL" is more elegant. */ + r = Pa_sgiQueryDevice(AL_DEFAULT_DEVICE, /* Set AL device num (AL_DEFAULT_DEVICE). */ + Pa_GetDefaultOutputDeviceID(),/* Set PA device num (or InputDeviceID()). */ + "AL default", /* A suitable name. */ + pad); /* Write args and queried info into pad. */ + if (r != paNoError) + { + ERR_RPT(("Pa_QueryDevice for '%s' returned: %d\n", pad->pad_DeviceName, r)); + PaHost_FreeFastMemory(pad, sizeof(internalPortAudioDevice)); /* sDeviceList still NULL ! */ + } + else + sDeviceList = pad; /* First element in linked list (pad->pad_Next is already NULL). */ + /*------------- QUERY AND ADD MORE POSSIBLE SGI DEVICES TO THE LIST: --------------*/ + /*---------------------------------------------------------------------------------*/ + return r; +} + +/*------------------------------------------------------------------*/ +usema_t *SendSema, /* These variables are shared between the audio */ +*RcvSema; /* handling process and the main process. */ + +/*--------------------------------------------------------------------------------------------*/ +#define MIN(a,b) ((a)<(b)?(a):(b)) /* MIN()-function is used below. */ +#define kPollSEMA 0 /* To index the pollfd-array, reads nicer than just */ +#define kPollOUT 1 /* numbers. */ +#define kPollIN 2 +void Pa_SgiAudioProcess(void *v) /* This function is sproc-ed by PaHost_StartEngine() */ +{ /* as a separate thread. (Argument must be void*). */ + short evtLoop=1; /* Reset by parent indirectly, or at local errors. */ + PaError result; + struct pollfd PollFD[3]; /* To catch kPollSEMA-, kPollOUT- and kPollIN-events. */ + internalPortAudioStream *past = (internalPortAudioStream*)v; /* Copy void-ptr-argument.*/ + PaHostSoundControl *pahsc = (PaHostSoundControl*)past->past_DeviceData; + if (pahsc == NULL) + { + sPaHostError = paInternalError; /* The only way is to signal error to shared area?! */ + goto skipALL; /* Sproc-ed threads MAY NOT RETURN paInternalError. */ + } + past->past_IsActive = 1; /* Wasn't this already done by the calling parent?! */ + DBUG(("entering thread.\n")); + PollFD[kPollIN].fd = ALgetfd(pahsc->pahsc_ALportIN); /* ALgetfd returns -1 on failures */ + PollFD[kPollIN].events = POLLOUT; /* such as ALport not there. */ + /* .events = POLLOUT ??? ok? */ + PollFD[kPollOUT].fd = ALgetfd(pahsc->pahsc_ALportOUT); + PollFD[kPollOUT].events = POLLOUT; /* .events = POLLOUT is OK. */ + schedctl(NDPRI, NDPHIMIN); /* Sets non-degrading priority for this process. */ + PollFD[kPollSEMA].fd = usopenpollsema(SendSema, 0777); /* To communicate with parent. */ + PollFD[kPollSEMA].events = POLLIN; /* .events = POLLIN is OK. */ + uspsema(SendSema); /* Blocks until the next (first)signal is received???? */ + do + { + /*---------------------------- SET FILLPOINTS AND WAIT UNTIL SOMETHING HAPPENS: ----------*/ + if (pahsc->pahsc_NativeInputBuffer) /* Then pahsc_ALportIN should also be there! */ + /* For input port, fill point is number of locations in the sample queue that must be */ + /* filled in order to trigger a return from select(). (or poll()) */ + /* Notice IRIX docs mention number of samples as argument, not number of sampleframes.*/ + if (ALsetfillpoint(pahsc->pahsc_ALportIN, pahsc->pahsc_SamplesPerInputBuffer)) + { /* Same amount as transferred per time. */ + ERR_RPT(("ALsetfillpoint() for ALportIN failed.\n")); + sPaHostError = paInternalError; /* (Using exit(-1) would be a bit rude.) */ + goto skipIO; + } + if (pahsc->pahsc_NativeOutputBuffer) /* Then pahsc_ALportOUT should also be there! */ + /* For output port, fill point is number of locations that must be free in order to */ + /* wake up from select(). (or poll()) */ + if (ALsetfillpoint(pahsc->pahsc_ALportOUT, pahsc->pahsc_SamplesPerOutputBuffer)) + { + ERR_RPT(("ALsetfillpoint() for ALportOUT failed.\n")); + sPaHostError = paInternalError; /* (Using exit(-1) would be a bit rude.) */ + goto skipIO; + } /* poll() with timeout=-1 makes it block until a requested */ + poll(PollFD, 3, -1); /* event occurs or until call is interrupted. If fd-value in */ + /* array <0, events is ignored and revents is set to 0. */ + /*---------------------------- MESSAGE-EVENT FROM PARENT THREAD: -------------------------*/ + if (PollFD[kPollSEMA].revents & POLLIN) + { + if (past->past_StopNow || past->past_StopSoon) + evtLoop = 0; + uspsema(SendSema); /* Decrements count of previously allocated semaphore SendSema. If count */ + /* is then negative, semaphore will logically block calling process until */ + /* count is incremented due to an usvsema call made by another process. */ + usvsema(RcvSema); /* Increments the count associated with RcvSema. If there are any */ + /* processes queued waiting for the semaphore the first one is awakened. */ + /* Here usvsema sends an acknowledgement to the "SendAudioCommand()". */ + if (past->past_StopNow) + goto skipIO; /* Else go on for a little while... */ + } + /*------------------------------------- FREE-EVENT FROM INPUT BUFFER: ----------------------------*/ + if (PollFD[kPollIN].revents & POLLOUT) /* Don't need to check (pahsc->pahsc_NativeInputBuffer): */ + { /* if buffer was not there, ALport not there, no events! */ + if (ALreadsamps(pahsc->pahsc_ALportIN, (void*)pahsc->pahsc_NativeInputBuffer, + pahsc->pahsc_SamplesPerInputBuffer)) + { /* Here again: number of samples instead of number of frames. */ + ERR_RPT(("ALreadsamps() failed.\n")); + sPaHostError = paInternalError; + goto skipIO; /* (Using exit(-1) would be a bit rude.) */ + } + } + /*------------------------------------- USER-CALLBACK-ROUTINE: -----------------------------------*/ + if ((PollFD[kPollIN].revents & POLLOUT) || (PollFD[kPollOUT].revents & POLLOUT)) + { /* To be sure we that really DID input-transfer or gonna DO output-transfer, and that it is */ + /* not just "sema"- (i.e. user)-event, or some other system-event that awakened the poll(). */ + Pa_StartUsageCalculation(past); /* Convert 16 bit native data to */ + result = Pa_CallConvertInt16(past, /* user data and call user routine. */ + pahsc->pahsc_NativeInputBuffer, + pahsc->pahsc_NativeOutputBuffer); + Pa_EndUsageCalculation(past); + if (result) + { + DBUG(("Pa_CallConvertInt16() returned %d, stopping...\n", result)); + goto skipIO; /* But it is apparently NOT an error! */ + } /* Just letting the userCallBack stop us. */ + } + /*------------------------------------- FILLED-EVENT FROM OUTPUT BUFFER: -------------------------*/ + if (PollFD[kPollOUT].revents & POLLOUT) /* Don't need to check (pahsc->pahsc_NativeOutputBuffer) */ + { /* because if filedescriptor not there, no event for it. */ + if (ALwritesamps(pahsc->pahsc_ALportOUT, (void*)pahsc->pahsc_NativeOutputBuffer, + pahsc->pahsc_SamplesPerOutputBuffer)) + { + ERR_RPT(("ALwritesamps() failed.\n")); + sPaHostError = paInternalError; /* Better use SEMAS also for messaging back to parent! */ + goto skipIO; + } + } + /*------------------------------------------------------------------------------------------------*/ + } + while (evtLoop); /* Set at init-time, only reset indirectly via semaphore, */ +skipIO: /* or in case of error, only within this loop. */ + DBUG(("leaving thread.\n")); /* Don't we need to cleanup semaphore-stuff / thread itself? */ +skipALL: /* (make threadPID in the struct -1 again, for instance?) */ + past->past_IsActive = 0; + pahsc->pahsc_threadPID == -1; /* Signal back, so StopEngine() for example won't call KILL().... hmmm doesn't work yet! */ + /* SHIT: when this process ends, it also KILLS of the parent!!! because sigint = kill?? */ + exit(0); /* Unix process. SIGCLD is sent to parent (if prctl wa called with 0 by parent). */ +} /* if prctl was called with 9 by parent, then when this process ends, it also */ +/* KILLS off the parent!!! */ + +/*--------------------------------------------------------------------------------------*/ +PaError PaHost_OpenStream(internalPortAudioStream *past) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + unsigned int minNumBuffers; + internalPortAudioDevice *padIN, *padOUT; /* For looking up native AL-numbers. */ + ALconfig sgiALconfig = NULL; /* IRIX-datatype. */ + long pvbuf[8]; /* To get/set hardware configs. */ + long sr, ALqsize; + DBUG(("PaHost_OpenStream() called.\n")); /* Alloc FASTMEM and init host data. */ + if (!past) + { + ERR_RPT(("Streampointer NULL!\n")); + result = paBadStreamPtr; goto done; + } + pahsc = (PaHostSoundControl*)PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); + if (pahsc == NULL) + { + ERR_RPT(("FAST Memory allocation failed.\n")); /* Pass trough some ERR_RPT-exit- */ + result = paInsufficientMemory; goto done; /* code (nothing will be freed). */ + } + memset(pahsc, 0, sizeof(PaHostSoundControl)); /* Hmmm, not too elegant, besides, the */ + pahsc->pahsc_threadPID = -1; /* pahsc_threadPID must be inited to */ + past->past_DeviceData = (void*)pahsc; /* -1 instead of 0! ?? */ + /*--------------------------------------------------- Allocate native buffers: --------*/ + pahsc->pahsc_SamplesPerInputBuffer = past->past_FramesPerUserBuffer * /* Needed by the */ + past->past_NumInputChannels; /* audio-thread. */ + pahsc->pahsc_BytesPerInputBuffer = pahsc->pahsc_SamplesPerInputBuffer * sizeof(short); + if (past->past_NumInputChannels > 0) /* Assumes short = 16 bits! */ + { + pahsc->pahsc_NativeInputBuffer = (short *) malloc(pahsc->pahsc_BytesPerInputBuffer); + if( pahsc->pahsc_NativeInputBuffer == NULL ) + { + ERR_RPT(("(normal yet) Memory allocation failed.\n")); + result = paInsufficientMemory; + goto done; + } + } + pahsc->pahsc_SamplesPerOutputBuffer = past->past_FramesPerUserBuffer * /* Needed by the */ + past->past_NumOutputChannels; /* audio-thread. */ + pahsc->pahsc_BytesPerOutputBuffer = pahsc->pahsc_SamplesPerOutputBuffer * sizeof(short); + if (past->past_NumOutputChannels > 0) /* Assumes short = 16 bits! */ + { + pahsc->pahsc_NativeOutputBuffer = (short *) malloc(pahsc->pahsc_BytesPerOutputBuffer); + if (pahsc->pahsc_NativeOutputBuffer == NULL) + { + ERR_RPT(("(normal yet) Memory allocation failed.\n")); + result = paInsufficientMemory; goto done; + } + } + /*------------------------------------------ Manipulate hardware if necessary and allowed: --*/ + ALseterrorhandler(0); /* 0 = turn off the default error handler. */ + pvbuf[0] = AL_INPUT_RATE; + pvbuf[2] = AL_INPUT_COUNT; + pvbuf[4] = AL_OUTPUT_RATE; /* TO FIX: rates may be logically, not always in Hz! */ + pvbuf[6] = AL_OUTPUT_COUNT; + sr = (long)(past->past_SampleRate + 0.5); /* Common for input and output :-) */ + if (past->past_NumInputChannels > 0) /* We need to lookup the corre- */ + { /* sponding native AL-number(s). */ + padIN = Pa_GetInternalDevice(past->past_InputDeviceID); + if (!padIN) + { + ERR_RPT(("Pa_GetInternalDevice() for input failed.\n")); + result = paHostError; goto done; + } + if (ALgetparams(padIN->pad_ALdevice, &pvbuf[0], 4)) /* Although input and output will both be on */ + goto sgiError; /* the same AL-device, the AL-library might */ + if (pvbuf[1] != sr) /* contain more than AL_DEFAULT_DEVICE in */ + { /* Rate different from current harware-rate? the future. Therefore 2 seperate queries. */ + if (pvbuf[3] > 0) /* Means, there's other clients using AL-input-ports */ + { + ERR_RPT(("Sorry, not allowed to switch input-hardware to %ld Hz because \ + another process is currently using input at %ld kHz.\n", sr, pvbuf[1])); + result = paHostError; goto done; + } + pvbuf[1] = sr; /* Then set input-rate. */ + if (ALsetparams(padIN->pad_ALdevice, &pvbuf[0], 2)) + goto sgiError; /* WHETHER THIS SAMPLERATE WAS REALLY PRESENT IN OUR ARRAY OF RATES, */ + } + } + if (past->past_NumOutputChannels > 0) /* CARE: padOUT/IN may NOT be NULL if Channels<=0! */ + { /* We use padOUT/IN later on, or at least 1 of both. */ + padOUT = Pa_GetInternalDevice(past->past_OutputDeviceID); + if (!padOUT) + { + ERR_RPT(("Pa_GetInternalDevice() for output failed.\n")); + result = paHostError; goto done; + } + if (ALgetparams(padOUT->pad_ALdevice,&pvbuf[4], 4)) + goto sgiError; + if ((past->past_NumOutputChannels > 0) && (pvbuf[5] != sr)) + { /* Output needed and rate different from current harware-rate. */ + if (pvbuf[7] > 0) /* Means, there's other clients using AL-output-ports */ + { + ERR_RPT(("Sorry, not allowed to switch output-hardware to %ld Hz because \ + another process is currently using output at %ld kHz.\n", sr, pvbuf[5])); + result = paHostError; goto done; /* Will free again the inputbuffer */ + } /* that was just created above. */ + pvbuf[5] = sr; /* Then set output-rate. */ + if (ALsetparams(padOUT->pad_ALdevice, &pvbuf[4], 2)) + goto sgiError; + } + } + /*------------------------------------------ Construct an audio-port-configuration ----------*/ + sgiALconfig = ALnewconfig(); /* Change the SGI-AL-default-settings. */ + if (sgiALconfig == (ALconfig)0) /* sgiALconfig is released here after use! */ + goto sgiError; /* See that sgiALconfig is NOT released! */ + if (ALsetsampfmt(sgiALconfig, AL_SAMPFMT_TWOSCOMP)) /* Choose paInt16 as native i/o-format. */ + goto sgiError; + if (ALsetwidth (sgiALconfig, AL_SAMPLE_16)) /* Only meaningful when sample format for */ + goto sgiError; /* config is set to two's complement format. */ + /************************ Future versions might (dynamically) switch to 32-bit floats? ******* + if (ALsetsampfmt(sgiALconfig, AL_SAMPFMT_FLOAT)) (Then also call another CallConvert-func.) + goto sgiError; + if (ALsetfloatmax (sgiALconfig, 1.0)) Only meaningful when sample format for config + goto sgiError; is set to AL_SAMPFMT_FLOAT or AL_SAMPFMT_DOUBLE. */ + /*---------- ?? --------------------*/ + /* DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); */ + minNumBuffers = Pa_GetMinNumBuffers(past->past_FramesPerUserBuffer, past->past_SampleRate); + past->past_NumUserBuffers = (minNumBuffers > past->past_NumUserBuffers) ? + minNumBuffers : past->past_NumUserBuffers; + /*------------------------------------------------ Set internal AL queuesize (in samples) ----*/ + if (pahsc->pahsc_SamplesPerInputBuffer >= pahsc->pahsc_SamplesPerOutputBuffer) + ALqsize = (long)pahsc->pahsc_SamplesPerInputBuffer; + else /* Take the largest of the two amounts. */ + ALqsize = (long)pahsc->pahsc_SamplesPerOutputBuffer; + ALqsize *= 4; /* 4 times as large as amount per transfer! */ + if (ALsetqueuesize(sgiALconfig, ALqsize)) /* Or should we use past_NumUserBuffers here? */ + goto sgiError; /* Using 2 times may give glitches... */ + /* Have to work on ALsetqueuesize() above, fillpoints in the thread are ok now. */ + + /* Do ALsetchannels() later, apart per input and/or output. */ + /*----------------------------------------------- OPEN 1 OR 2 AL-DEVICES: --------------------*/ + if (past->past_OutputDeviceID == past->past_InputDeviceID) /* Who SETS these devive-numbers? */ + { + if ((past->past_NumOutputChannels > 0) && (past->past_NumInputChannels > 0)) + { + DBUG(("PaHost_OpenStream: opening both input and output channels.\n")); + /*------------------------- open output port: ----------------------------------*/ + if (ALsetchannels (sgiALconfig, (long)(past->past_NumOutputChannels))) + goto sgiError; /* Returns 0 on success, -1 on failure. */ + pahsc->pahsc_ALportOUT = ALopenport("PA sgi out", "w", sgiALconfig); + if (pahsc->pahsc_ALportOUT == (ALport)0) + goto sgiError; + /*------------------------- open input port: -----------------------------------*/ + if (ALsetchannels (sgiALconfig, (long)(past->past_NumInputChannels))) + goto sgiError; /* Returns 0 on success, -1 on failure. */ + pahsc->pahsc_ALportIN = ALopenport("PA sgi in", "r", sgiALconfig); + if (pahsc->pahsc_ALportIN == (ALport)0) + goto sgiError; /* For some reason the "patest_wire.c"-test crashes! */ + } /* Probably due to too small buffersizes?.... */ + else + { + ERR_RPT(("Cannot setup bidirectional stream between different devices.\n")); + result = paHostError; + goto done; + } + } + else /* (OutputDeviceID != InputDeviceID) */ + { + if (past->past_NumOutputChannels > 0) /* WRITE-ONLY: */ + { + /*------------------------- open output port: ----------------------------------*/ + DBUG(("PaHost_OpenStream: opening %d output channel(s) on %s.\n", + past->past_NumOutputChannels, padOUT->pad_DeviceName)); + if (ALsetchannels (sgiALconfig, (long)(past->past_NumOutputChannels))) + goto sgiError; /* Returns 0 on success, -1 on failure. */ + pahsc->pahsc_ALportOUT = ALopenport("PA sgi out", "w", sgiALconfig); + if (pahsc->pahsc_ALportOUT == (ALport)0) + goto sgiError; + } + if (past->past_NumInputChannels > 0) /* READ-ONLY: */ + { + /*------------------------- open input port: -----------------------------------*/ + DBUG(("PaHost_OpenStream: opening %d input channel(s) on %s.\n", + past->past_NumInputChannels, padIN->pad_DeviceName)); + if (ALsetchannels (sgiALconfig, (long)(past->past_NumInputChannels))) + goto sgiError; /* Returns 0 on success, -1 on failure. */ + pahsc->pahsc_ALportIN = ALopenport("PA sgi in", "r", sgiALconfig); + if (pahsc->pahsc_ALportIN == (ALport)0) + goto sgiError; + } + } + DBUG(("PaHost_OpenStream() succeeded.\n")); + goto done; /* (no errors occured) */ +sgiError: + result = translateSGIerror(); /* Translates SGI AL-code to PA-code and ERR_RPTs string. */ +done: + if (sgiALconfig) + ALfreeconfig(sgiALconfig); /* We don't need that struct anymore. */ + if (result != paNoError) + PaHost_CloseStream(past); /* Frees memory (only if really allocated!). */ + return result; +} + +/*-----------------------------------------------------*/ +PaError PaHost_StartOutput(internalPortAudioStream *past) +{ + return paNoError; /* Hmm, not implemented yet? */ +} +PaError PaHost_StartInput(internalPortAudioStream *past) +{ + return paNoError; +} + +/*------------------------------------------------------------------------------*/ +PaError PaHost_StartEngine(internalPortAudioStream *past) +{ + PaHostSoundControl *pahsc; + usptr_t *arena; + if (!past) /* Test argument. */ + { + ERR_RPT(("PaHost_StartEngine(NULL)!\n")); + return paBadStreamPtr; + } + pahsc = (PaHostSoundControl*)past->past_DeviceData; + if (!pahsc) + { + ERR_RPT(("PaHost_StartEngine(arg): arg->past_DeviceData = NULL!\n")); + return paHostError; + } + past->past_StopSoon = 0; /* Assume SGI ALport is already opened! */ + past->past_StopNow = 0; /* Why don't we check pahsc for NULL? */ + past->past_IsActive = 1; + + /* Although the pthread_create() function, as well as , may be */ + /* available in IRIX, use sproc() on SGI to create audio-background-thread. */ + /* (Linux/oss uses pthread_create() instead of __clone() because: */ + /* - pthread_create also works for other UNIX systems like Solaris, */ + /* - Java HotSpot VM crashes in pthread_setcanceltype() using __clone().) */ + + usconfig(CONF_ARENATYPE, US_SHAREDONLY); /* (From SGI-AL-examples, file */ + arena = usinit(tmpnam(0)); /* motifexample.c, function */ + SendSema = usnewpollsema(arena, 0); /* InitializeAudioProcess().) */ + RcvSema = usnewsema(arena, 1); /* 1= common mutual exclusion semaphore, where 1 and only 1 process + will be permitted through a semaphore at a time. Values > 1 + imply that up to val resources may be simultaneously used, but requests + for more than val resources cause the calling process to block until a + resource comes free (by a process holding a resource performing a + usvsema(). IS THIS usnewsema() TOO PLATFORM SPECIFIC? */ + prctl(PR_SETEXITSIG, 0);/* No not (void*)9, but 0, which doesn't kill the parent! */ + /* PR_SETEXITSIG controls whether all members of a share group will be + signaled if any one of them leaves the share group + (either via exit() or exec()). If second + argument, interpreted as an int is 0, then normal IRIX + process termination rules apply, namely that the parent + is sent a SIGCLD upon death of child, but no indication + of death of parent is given. If the second argument is + a valid signal number then if any member + of a share group leaves the share group, a signal is + sent to ALL surviving members of the share group. */ + /* SPAWN AUDIO-CHILD: */ + pahsc->pahsc_threadPID = sproc(Pa_SgiAudioProcess, /* Returns process ID of */ + PR_SALL, /* new process, or -1. */ + (void*)past); /* Pass past as optional */ + if (pahsc->pahsc_threadPID == -1) /* third void-ptr-arg. */ + { + ERR_RPT(("PaHost_StartEngine() failed to spawn audio-thread.\n")); + sPaHostError = oserror(); /* Pass native error-number to shared area. */ + return paHostError; /* But return the generic error-number. */ + } + return paNoError; /* Hmmm, errno may come from other threads in same group! */ +} /* ("man sproc" in IRIX6.2 to read about _SGI_MP_SOURCE.) */ + +/*------------------------------------------------------------------------------*/ +PaError PaHost_StopEngine(internalPortAudioStream *past, int abort) +{ + int hres; + long timeOut; + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl*)past->past_DeviceData; + + if (pahsc == NULL) + return result; /* paNoError */ + past->past_StopSoon = 1; /* Tell background thread to stop generating */ + if (abort) /* more and to let current data play out. If */ + past->past_StopNow = 1; /* aborting, tell backgrnd thread to stop NOW! */ + + /*---- USE SEMAPHORE LOCK TO COMMUNICATE: -----*/ + usvsema(SendSema); /* Increments count associated with SendSema. */ + /* Wait for the response. */ + uspsema(RcvSema); /* Decrements count of previously allocated */ + /* semaphore specified by RcvSema. */ + /* Must be sure sub-process is really there,... otherwise above semas block?! */ + if (pahsc->pahsc_threadPID != -1) /* Did we really init it to -1 somewhere? */ + { + /* How to JOIN a "sproc"-thread to recover memory resources????? */ + /* hres = pthread_join( pahsc->pahsc_ThreadPID, NULL ); */ + DBUG(("PaHost_StopEngine() is about to KILL(SIGKILL) audio-thread.\n")); + if (kill(pahsc->pahsc_threadPID, SIGKILL)) /* Or SIGTERM or SIGQUIT(core) */ + { /* Returns -1 in case of error. */ + result = paHostError; + sPaHostError = oserror(); /* Hmmm, other threads may also write here! */ + ERR_RPT(("PaHost_StopEngine() failed to kill audio-thread.\n")); + } + else + pahsc->pahsc_threadPID = -1; /* Notify that we've killed this thread. */ + } + past->past_IsActive = 0; /* Even when kill() failed and pahsc_threadPID still there??? */ + return result; +} + +/*---------------------------------------------------------------*/ +PaError PaHost_StopOutput(internalPortAudioStream *past, int abort) +{ + return paNoError; /* Not implemented yet? */ +} +PaError PaHost_StopInput(internalPortAudioStream *past, int abort ) +{ + return paNoError; +} + +/*******************************************************************/ +PaError PaHost_CloseStream(internalPortAudioStream *past) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + + if (past == NULL) + return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if (pahsc == NULL) /* If pahsc not NULL, past_DeviceData will be freed, and set to NULL. */ + return result; /* This test prevents from freeing NULL-pointers. */ + + if (pahsc->pahsc_ALportIN) + { + if (ALcloseport(pahsc->pahsc_ALportIN)) + result = translateSGIerror(); /* Translates SGI AL-code to PA-code and ERR_RPTs string. */ + else /* But go on anyway... to release other stuff... */ + pahsc->pahsc_ALportIN = (ALport)0; + } + if (pahsc->pahsc_ALportOUT) + { + if (ALcloseport(pahsc->pahsc_ALportOUT)) + result = translateSGIerror(); + else + pahsc->pahsc_ALportOUT = (ALport)0; + } + if (pahsc->pahsc_NativeInputBuffer) + { + free(pahsc->pahsc_NativeInputBuffer); + pahsc->pahsc_NativeInputBuffer = NULL; + } + if (pahsc->pahsc_NativeOutputBuffer) + { + free(pahsc->pahsc_NativeOutputBuffer); + pahsc->pahsc_NativeOutputBuffer = NULL; + } + PaHost_FreeFastMemory(pahsc, sizeof(PaHostSoundControl)); /* Not just free(pahsc) because */ + past->past_DeviceData = NULL; /* PaHost_OpenStream(); allocated FAST (thus mpinned?) MEM. */ + return result; +} + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** set PA_MIN_LATENCY_MSEC=200 +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be +** determined based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") + +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) +{ + return 2; +} +/* Hmmm, the note above isn't appropriate for SGI I'm afraid... */ +/* Do we HAVE to do it this way under IRIX???.... */ +/*--------------------------------------------------------------*/ + + +/*---------------------------------------------------------------------*/ +PaError PaHost_Term(void) /* Frees all of the linked devices. */ +{ /* Called by Pa_Terminate() from pa_lib.c. */ + internalPortAudioDevice *pad = sDeviceList, + *nxt; + while (pad) + { + DBUG(("PaHost_Term: freeing %s\n", pad->pad_DeviceName)); + nxt = pad->pad_Next; + PaHost_FreeFastMemory(pad, sizeof(internalPortAudioDevice)); + pad = nxt; /* PaHost_Init allocated this FAST MEM.*/ + } + sDeviceList = (internalPortAudioDevice*)NULL; + return 0; /* Got rid of sNumDevices=0; */ +} + +/***********************************************************************/ +void Pa_Sleep( long msec ) /* Sleep requested number of milliseconds. */ +{ +#if 0 + struct timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + select(0, NULL, NULL, NULL, &timeout); +#else + long usecs = msec * 1000; + usleep( usecs ); +#endif +} + +/*---------------------------------------------------------------------------------------*/ +/* Allocate memory that can be accessed in real-time. This may need to be held in physi- */ +/* cal memory so that it is not paged to virtual memory. This call MUST be balanced with */ +/* a call to PaHost_FreeFastMemory(). */ +void *PaHost_AllocateFastMemory(long numBytes) +{ + void *addr = malloc(numBytes); /* mpin() reads into memory all pages over the given */ + if (addr) /* range and locks the pages into memory. A counter */ + { /* is incremented each time the page is locked. The */ + if (mpin(addr, numBytes)) /* superuser can lock as many pages as it wishes, */ + { /* others are limited to the configurable PLOCK_MA. */ + ERR_RPT(("PaHost_AllocateFastMemory() failed to mpin() memory.\n")); +#if 1 + free(addr); /* You MAY cut out these 2 lines to be less strict, */ + addr = NULL; /* you then only get the warning but PA goes on... */ +#endif /* Only problem then may be corresponding munpin() */ + } /* call at PaHost_FreeFastMemory(), below. */ + memset(addr, 0, numBytes); /* Locks established with mlock are not inherited by */ + } /* a child process after a fork. Furthermore, IRIX- */ + return addr; /* man-pages warn against mixing both mpin and mlock */ +} /* in 1 piece of code, so stick to mpin()/munpin() ! */ + + +/*---------------------------------------------------------------------------------------*/ +/* Free memory that could be accessed in real-time. This call MUST be balanced with a */ +/* call to PaHost_AllocateFastMemory(). */ +void PaHost_FreeFastMemory(void *addr, long numBytes) +{ + if (addr) + { + if (munpin(addr, numBytes)) /* Will munpin() fail when it was never mpinned? */ + ERR_RPT(("WARNING: PaHost_FreeFastMemory() failed to munpin() memory.\n")); + free(addr); /* But go on, try to release it, just warn... */ + } +} + +/*----------------------------------------------------------*/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if (past == NULL) + return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if (pahsc == NULL) + return paInternalError; + return (PaError)(past->past_IsActive != 0); +} + +/*-------------------------------------------------------------------*/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + /* FIXME - return actual frames played, not frames generated. + ** Need to query the output device somehow. + */ + return past->past_FrameCount; +} diff --git a/pa_tests/debug_dual.c b/pa_tests/debug_dual.c new file mode 100644 index 0000000..35ba032 --- /dev/null +++ b/pa_tests/debug_dual.c @@ -0,0 +1,183 @@ +/* + * $Id$ + * debug_dual.c + * Try to open TWO streams on separate cards. + * Play a sine sweep using the Portable Audio api for several seconds. + * Hacked test for debugging PA. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define DEV_ID_1 (13) +#define DEV_ID_2 (15) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 0 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + return 0; +} + +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, + paTestData *data ); +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream1, *stream2; + PaError err; + paTestData DATA1, DATA2; + printf("PortAudio Test: DUAL sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = TestStart( &stream1, DEV_ID_1, &DATA1 ); + if( err != paNoError ) goto error; + err = TestStart( &stream2, DEV_ID_2, &DATA2 ); + if( err != paNoError ) goto error; + printf("Hit ENTER\n"); + getchar(); + err = Pa_StopStream( stream1 ); + if( err != paNoError ) goto error; + err = Pa_StopStream( stream2 ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} +PaError TestStart( PortAudioStream **streamPtr, PaDeviceID devID, paTestData *data ) +{ + PortAudioStream *stream; + PaError err; + int i; + /* initialise sinusoidal wavetable */ + for( i=0; isine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. ); + } + data->sine[TABLE_SIZE] = data->sine[0]; // set guard point + data->left_phase = data->right_phase = 0.0; + data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + printf("PortAudio Test: output device = %d\n", devID ); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + devID, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff|paDitherOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + *streamPtr = stream; + return 0; +error: + return err; +} diff --git a/pa_tests/debug_multi_in.c b/pa_tests/debug_multi_in.c new file mode 100644 index 0000000..def729c --- /dev/null +++ b/pa_tests/debug_multi_in.c @@ -0,0 +1,179 @@ +/* + * $Id$ + * debug_multi_in.c + * Pass output from each of multiple channels + * to a stereo output using the Portable Audio api. + * Hacked test for debugging PA. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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" +//#define INPUT_DEVICE_NAME ("EWS88 MT Interleaved Rec") +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (18) +#define SAMPLE_RATE (22050) +#define FRAMES_PER_BUFFER (256) +#define MIN_LATENCY_MSEC (400) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + int liveChannel; + int numChannels; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float *in = (float*)inputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( in == NULL ) return 0; + for( i=0; i<(int)framesPerBuffer; i++ ) + { + /* Copy one channel of input to output. */ + *out++ = in[data->liveChannel]; + *out++ = in[data->liveChannel]; + in += data->numChannels; + } + return 0; +} +/*******************************************************************/ +int PaFindDeviceByName( const char *name ) +{ + int i; + int numDevices; + const PaDeviceInfo *pdi; + int len = strlen( name ); + PaDeviceID result = paNoDevice; + numDevices = Pa_CountDevices(); + for( i=0; iname, len ) == 0 ) + { + result = i; + break; + } + } + return result; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + PaDeviceID inputDevice; + const PaDeviceInfo *pdi; + printf("PortAudio Test: input signal from each channel. %d buffers\n", NUM_BUFFERS ); + data.liveChannel = 0; + err = Pa_Initialize(); + if( err != paNoError ) goto error; +#ifdef INPUT_DEVICE_NAME + printf("Try to use device: %s\n", INPUT_DEVICE_NAME ); + inputDevice = PaFindDeviceByName(INPUT_DEVICE_NAME); + if( inputDevice == paNoDevice ) + { + printf("Could not find %s. Using default instead.\n", INPUT_DEVICE_NAME ); + inputDevice = Pa_GetDefaultInputDeviceID(); + } +#else + printf("Using default input device.\n"); + inputDevice = Pa_GetDefaultInputDeviceID(); +#endif + pdi = Pa_GetDeviceInfo( inputDevice ); + if( pdi == NULL ) + { + printf("Could not get device info!\n"); + goto error; + } + data.numChannels = pdi->maxInputChannels; + printf("Input Device name is %s\n", pdi->name ); + printf("Input Device has %d channels.\n", pdi->maxInputChannels); + err = Pa_OpenStream( + &stream, + inputDevice, + pdi->maxInputChannels, + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + data.liveChannel = 0; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + for( i=0; i +#include +#include "portaudio.h" +#define NUM_CHANNELS (8) +// #define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define OUTPUT_DEVICE (18) +#define NUM_SECONDS (NUM_CHANNELS*4) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_CHANNEL (SAMPLE_RATE/2) +#define FRAMES_PER_BUFFER (256) +#define MIN_LATENCY_MSEC (400) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (800) +typedef struct +{ + float sine[TABLE_SIZE]; + int phase; + int liveChannel; + int count; + unsigned int sampsToGo; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int i, j; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->sampsToGo < framesPerBuffer ) + { + finished = 1; + } + else + { + for( i=0; i<(int)framesPerBuffer; i++ ) + { + for( j=0; jliveChannel) ? data->sine[data->phase] : 0.0f; + /* Play each channel at a higher frequency. */ + data->phase += 1 + data->liveChannel; + if( data->phase >= TABLE_SIZE ) data->phase -= TABLE_SIZE; + } + /* Switch channels every so often. */ + if( --data->count <= 0 ) + { + data->count = FRAMES_PER_CHANNEL; + data->liveChannel += 1; + if( data->liveChannel >= NUM_CHANNELS ) data->liveChannel = 0; + } + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output sine wave. %d buffers\n", NUM_BUFFERS ); + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include +#include "portaudio.h" +#define SAMPLE_RATE (22050) +#define NUM_SECONDS (10) +#define SLEEP_DUR_MSEC (200) +#define REC_BUF_FRAMES (1<<10) +#define NUM_REC_BUFS (0) +#if 0 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif +typedef struct +{ + long frameIndex; /* Index into sample array. */ + long maxFrameIndex; + long samplesPerFrame; + SAMPLE *recordedSamples; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + unsigned long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; iframeIndex += framesToCalc; + return finished; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned long i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + if( outputBuffer == NULL ) return 0; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; iframeIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; iframeIndex += framesPerBuffer; + finished = 0; + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + long totalFrames; + long numSamples; + long numBytes; + long i; + printf("patest_record.c\n"); fflush(stdout); + + data.frameIndex = 0; + data.samplesPerFrame = 2; + data.maxFrameIndex = totalFrames = NUM_SECONDS*SAMPLE_RATE; + + printf("totalFrames = %d\n", totalFrames ); fflush(stdout); + numSamples = totalFrames * data.samplesPerFrame; + numBytes = numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i max ) max = posVal; + } + printf("Largest recorded sample = %d\n", max ); + } + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( data.recordedSamples, data.samplesPerFrame * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + free( data.recordedSamples ); + Pa_Terminate(); + 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 ) ); + if( err == paHostError ) + { + fprintf( stderr, "Host Error number: %d\n", Pa_GetHostError() ); + } + return -1; +} diff --git a/pa_tests/debug_sine.c b/pa_tests/debug_sine.c new file mode 100644 index 0000000..b79405d --- /dev/null +++ b/pa_tests/debug_sine.c @@ -0,0 +1,192 @@ +/* + * $Id$ + * debug_sine.c + * Play a sine sweep using the Portable Audio api for several seconds. + * Hacked test for debugging PA. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +//#define OUTPUT_DEVICE (11) +#define NUM_SECONDS (8) +#define SLEEP_DUR (800) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 1 +#define MIN_LATENCY_MSEC (200) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define MAX_FREQ (4000.0f) +#define FREQ_SCALAR (1.00002f) +#define CalcPhaseIncrement(freq) (freq/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; + unsigned int framesToGo; +} +paTestData; +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ); +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int framesToCalc; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + /* sweep frequency then start over. */ + data->phase_increment *= FREQ_SCALAR; + if( data->phase_increment > CalcPhaseIncrement(MAX_FREQ) ) data->phase_increment = CalcPhaseIncrement(MIN_FREQ); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + /* initialise sinusoidal wavetable */ + for( i=0; i + * Phil Burk + * + * 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 "portaudio.h" + +#define NUM_SECONDS (20) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) +#define NUM_BUFFERS (16) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i + Modifications: + April 5th, 2001 - PLB - Check for NULL inputBuffer. +*/ +#include +#include +#include "portaudio.h" +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long bufferFrames, + PaTimestamp outTime, void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = bufferFrames; + unsigned long i; + int finished = 0; + if(inputBuffer == NULL) return 0; + if( data->sampsToGo < bufferFrames ) + { + finished = 1; + } + for( i=0; iphase >= 100 ) + data->phase = 0; + } + data->sampsToGo -= bufferFrames; + /* zero remainder of final buffer if not already done */ + for( ; i +#include +#include "portaudio.h" +/*******************************************************************/ +int main(void); +int main(void) +{ + int i,j; + int numDevices; + const PaDeviceInfo *pdi; + PaError err; + Pa_Initialize(); + numDevices = Pa_CountDevices(); + if( numDevices < 0 ) + { + printf("ERROR: Pa_CountDevices returned 0x%x\n", numDevices ); + err = numDevices; + goto error; + } + printf("Number of devices = %d\n", numDevices ); + for( i=0; iname ); + printf("Max Inputs = %d", pdi->maxInputChannels ); + printf(", Max Outputs = %d\n", pdi->maxOutputChannels ); + if( pdi->numSampleRates == -1 ) + { + printf("Sample Rate Range = %f to %f\n", pdi->sampleRates[0], pdi->sampleRates[1] ); + } + else + { + printf("Sample Rates ="); + for( j=0; jnumSampleRates; j++ ) + { + printf(" %8.2f,", pdi->sampleRates[j] ); + } + printf("\n"); + } + printf("Native Sample Formats = "); + if( pdi->nativeSampleFormats & paInt8 ) printf("paInt8, "); + if( pdi->nativeSampleFormats & paUInt8 ) printf("paUInt8, "); + if( pdi->nativeSampleFormats & paInt16 ) printf("paInt16, "); + if( pdi->nativeSampleFormats & paInt32 ) printf("paInt32, "); + if( pdi->nativeSampleFormats & paFloat32 ) printf("paFloat32, "); + if( pdi->nativeSampleFormats & paInt24 ) printf("paInt24, "); + if( pdi->nativeSampleFormats & paPackedInt24 ) printf("paPackedInt24, "); + printf("\n"); + } + Pa_Terminate(); + + printf("----------------------------------------------\n"); + 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 err; +} diff --git a/pa_tests/pa_fuzz.c b/pa_tests/pa_fuzz.c new file mode 100644 index 0000000..3161c74 --- /dev/null +++ b/pa_tests/pa_fuzz.c @@ -0,0 +1,155 @@ +/* + * $Id$ + * pa_fuzz.c + * Distort input like a fuzz boz. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.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 PA_SAMPLE_TYPE paFloat32 +#define FRAMES_PER_BUFFER (64) + +typedef float SAMPLE; + +float CubicAmplifier( float input ); +static int fuzzCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + +/* Non-linear amplifier with soft distortion curve. */ +float CubicAmplifier( float input ) +{ + float output, temp; + if( input < 0.0 ) + { + temp = input + 1.0f; + output = (temp * temp * temp) - 1.0f; + } + else + { + temp = input - 1.0f; + output = (temp * temp * temp) + 1.0f; + } + + return output; +} +#define FUZZ(x) CubicAmplifier(CubicAmplifier(CubicAmplifier(CubicAmplifier(x)))) + +static int gNumNoInputs = 0; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int fuzzCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + SAMPLE *out = (SAMPLE*)outputBuffer; + SAMPLE *in = (SAMPLE*)inputBuffer; + unsigned int i; + (void) outTime; /* Prevent unused variable warnings. */ + (void) userData; + + if( inputBuffer == NULL ) + { + for( i=0; i +#include +#include +#include "portaudio.h" +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define DEFAULT_BUFFER_SIZE (128) +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; +/* Very simple synthesis routine to generate two sine waves. */ +static int paminlatCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +void main( int argc, char **argv ); +void main( int argc, char **argv ) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int go; + int numBuffers = 0; + int minBuffers = 0; + int framesPerBuffer; + double sampleRate = 44100.0; + char str[256]; + printf("paminlat - Determine minimum latency for your computer.\n"); + printf(" usage: paminlat {framesPerBuffer}\n"); + printf(" for example: paminlat 256\n"); + printf("Adjust your stereo until you hear a smooth tone in each speaker.\n"); + printf("Then try to find the smallest number of buffers that still sounds smooth.\n"); + printf("Note that the sound will stop momentarily when you change the number of buffers.\n"); + /* Get bufferSize from command line. */ + framesPerBuffer = ( argc > 1 ) ? atol( argv[1] ) : DEFAULT_BUFFER_SIZE; + printf("Frames per buffer = %d\n", framesPerBuffer ); + /* Initialise sinusoidal wavetable. */ + for( i=0; i +#include +#include "portaudio.h" +#include "pa_trace.h" +/****************************************** Definitions ***********/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; + short sawPhase; + PaSampleFormat format; +} +PaQaData; +/****************************************** Prototypes ***********/ +static void TestDevices( int mode ); +static void TestFormats( int mode, PaDeviceID deviceID, double sampleRate, + int numChannels ); +static int TestAdvance( int mode, PaDeviceID deviceID, double sampleRate, + int numChannels, PaSampleFormat format ); +static int QaCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +/****************************************** Globals ***********/ +static int gNumPassed = 0; +static int gNumFailed = 0; +/****************************************** Macros ***********/ +/* Print ERROR if it fails. Tally success or failure. */ +/* Odd do-while wrapper seems to be needed for some compilers. */ +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + /* printf("SUCCESS for %s\n", #_exp ); */ \ + gNumPassed++; \ + } \ + else { \ + printf("ERROR - 0x%x - %s for %s\n", result, \ + ((result == 0) ? "-" : Pa_GetErrorText(result)), \ + #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) +/*******************************************************************/ +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int QaCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + unsigned long i; + short phase; + PaQaData *data = (PaQaData *) userData; + (void) inputBuffer; + (void) outTime; + + /* Play simle sawtooth wave. */ + if( data->mode == MODE_OUTPUT ) + { + phase = data->sawPhase; + switch( data->format ) + { + case paFloat32: + { + float *out = (float *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = (float) (phase * (1.0 / 32768.0)); + } + } + } + break; + + case paInt32: + { + int *out = (int *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = ((int) phase ) << 16; + } + } + } + break; + case paInt16: + { + short *out = (short *) outputBuffer; + for( i=0; inumChannels == 2 ) + { + *out++ = phase; + } + } + } + break; + + default: + { + unsigned char *out = (unsigned char *) outputBuffer; + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; isawPhase = phase; + } + /* Are we through yet? */ + if( data->framesLeft > framesPerBuffer ) + { + AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft ); + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft ); + data->framesLeft = 0; + return 1; + } +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError result; + EXPECT( ((result=Pa_Initialize()) == 0) ); + printf("Test OUTPUT ---------------\n"); + TestDevices( MODE_OUTPUT ); + printf("Test INPUT ---------------\n"); + TestDevices( MODE_INPUT ); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed ); +} +/******************************************************************* +* Try each output device, through its full range of capabilities. */ +static void TestDevices( int mode ) +{ + int id,jc,kr; + int maxChannels; + const PaDeviceInfo *pdi; + int numDevices = Pa_CountDevices(); + /* Iterate through all devices. */ + for( id=0; idmaxInputChannels : pdi->maxOutputChannels; + for( jc=1; jc<=maxChannels; jc++ ) + { + printf("Name = %s\n", pdi->name ); + /* Try each legal sample rate. */ + if( pdi->numSampleRates == -1 ) + { + double low, high; + low = pdi->sampleRates[0]; + high = pdi->sampleRates[1]; + if( low < 8000.0 ) low = 8000.0; + TestFormats( mode, id, low, jc ); +#define TESTSR(sr) {if(((sr)>=low) && ((sr)<=high)) TestFormats( mode, id, (sr), jc ); } + + TESTSR(11025.0); + TESTSR(22050.0); + TESTSR(44100.0); + TestFormats( mode, id, high, jc ); + } + else + { + for( kr=0; krnumSampleRates; kr++ ) + { + TestFormats( mode, id, pdi->sampleRates[kr], jc ); + } + } + } + } +} +/*******************************************************************/ +static void TestFormats( int mode, PaDeviceID deviceID, double sampleRate, + int numChannels ) +{ + TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 ); /* */ + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 ); /* */ + TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 ); /* */ + /* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */ +} +/*******************************************************************/ +static int TestAdvance( int mode, PaDeviceID deviceID, double sampleRate, + int numChannels, PaSampleFormat format ) +{ + PortAudioStream *stream = NULL; + PaError result; + PaQaData myData; +#define FRAMES_PER_BUFFER (64) + printf("------ TestAdvance: %s, device = %d, rate = %g, numChannels = %d, format = %d -------\n", + ( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT", + deviceID, sampleRate, numChannels, format); + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (sampleRate * 100); /* 100 seconds */ + myData.numChannels = numChannels; + myData.mode = mode; + myData.format = format; + switch( format ) + { + case paFloat32: + case paInt32: + case paInt24: + myData.bytesPerSample = 4; + break; + case paPackedInt24: + myData.bytesPerSample = 3; + break; + default: + myData.bytesPerSample = 2; + break; + } + EXPECT( ((result = Pa_OpenStream( + &stream, + ( mode == MODE_INPUT ) ? deviceID : paNoDevice, + ( mode == MODE_INPUT ) ? numChannels : 0, + format, + NULL, + ( mode == MODE_OUTPUT ) ? deviceID : paNoDevice, + ( mode == MODE_OUTPUT ) ? numChannels : 0, + format, + NULL, + sampleRate, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + QaCallback, + &myData ) + ) == 0) ); + if( stream ) + { + PaTimestamp oldStamp, newStamp; + unsigned long oldFrames; + int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400; + int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); + int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate); + if( msec < minDelay ) msec = minDelay; + printf("msec = %d\n", msec); /**/ + EXPECT( ((result=Pa_StartStream( stream )) == 0) ); + /* Check to make sure PortAudio is advancing timeStamp. */ + result = paNoError; + oldStamp = Pa_StreamTime(stream); + Pa_Sleep(msec); + newStamp = Pa_StreamTime(stream); + printf("oldStamp = %g,newStamp = %g\n", oldStamp, newStamp ); /**/ + EXPECT( (oldStamp < newStamp) ); + /* Check to make sure callback is decrementing framesLeft. */ + oldFrames = myData.framesLeft; + Pa_Sleep(msec); + printf("oldFrames = %d, myData.framesLeft = %d\n", oldFrames, myData.framesLeft ); /**/ + EXPECT( (oldFrames > myData.framesLeft) ); + EXPECT( ((result=Pa_CloseStream( stream )) == 0) ); + stream = NULL; + } +error: + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} diff --git a/pa_tests/paqa_errs.c b/pa_tests/paqa_errs.c new file mode 100644 index 0000000..37e75d7 --- /dev/null +++ b/pa_tests/paqa_errs.c @@ -0,0 +1,305 @@ +/* + * $Id$ + * paqa_devs.c + * Self Testing Quality Assurance app for PortAudio + * Do lots of bad things to test error reporting. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +/****************************************** Definitions ***********/ +#define MODE_INPUT (0) +#define MODE_OUTPUT (1) +#define FRAMES_PER_BUFFER (64) +#define SAMPLE_RATE (44100.0) +#define NUM_BUFFERS (0) +typedef struct PaQaData +{ + unsigned long framesLeft; + int numChannels; + int bytesPerSample; + int mode; +} +PaQaData; +/****************************************** Prototypes ***********/ +static void TestDevices( int mode ); +static void TestFormats( int mode, PaDeviceID deviceID, double sampleRate, + int numChannels ); +static int TestBadOpens( void ); +static int TestBadActions( void ); +static int QaCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +/****************************************** Globals ***********/ +static int gNumPassed = 0; +static int gNumFailed = 0; +/****************************************** Macros ***********/ +/* Print ERROR if it fails. Tally success or failure. */ +/* Odd do-while wrapper seems to be needed for some compilers. */ +#define EXPECT(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + goto error; \ + } \ + } while(0) +#define HOPEFOR(_exp) \ + do \ + { \ + if ((_exp)) {\ + gNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + gNumFailed++; \ + } \ + } while(0) +/*******************************************************************/ +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int QaCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + unsigned long i; + unsigned char *out = (unsigned char *) outputBuffer; + PaQaData *data = (PaQaData *) userData; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) outTime; + + /* Zero out buffer so we don't hear terrible noise. */ + if( data->mode == MODE_OUTPUT ) + { + unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + for( i=0; iframesLeft > framesPerBuffer ) + { + data->framesLeft -= framesPerBuffer; + return 0; + } + else + { + data->framesLeft = 0; + return 1; + } +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError result; + EXPECT( ((result=Pa_Initialize()) == 0) ); + TestBadOpens(); + TestBadActions(); +error: + Pa_Terminate(); + printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed ); + return 0; +} +/*******************************************************************/ +static int TestBadOpens( void ) +{ + PortAudioStream *stream = NULL; + PaError result; + PaQaData myData; + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + HOPEFOR( (/* No devices specified. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + paNoDevice, 0, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidDeviceId) ); + HOPEFOR( ( /* Out of range input device specified. */ + (result = Pa_OpenStream( + &stream, + Pa_CountDevices(), 0, paFloat32, NULL, + paNoDevice, 0, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidDeviceId) ); + + HOPEFOR( ( /* Out of range output device specified. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_CountDevices(), 0, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidDeviceId) ); + HOPEFOR( ( /* Zero input channels. */ + (result = Pa_OpenStream( + &stream, + Pa_GetDefaultInputDeviceID(), 0, paFloat32, NULL, + paNoDevice, 0, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidChannelCount) ); + HOPEFOR( ( /* Zero output channels. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 0, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidChannelCount) ); + HOPEFOR( ( /* Nonzero input channels but no device. */ + (result = Pa_OpenStream( + &stream, + Pa_GetDefaultInputDeviceID(), 2, paFloat32, NULL, + paNoDevice, 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidChannelCount) ); + + HOPEFOR( ( /* Nonzero output channels but no device. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 2, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidChannelCount) ); + HOPEFOR( ( /* NULL stream pointer. */ + (result = Pa_OpenStream( + NULL, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paBadStreamPtr) ); + HOPEFOR( ( /* Low sample rate. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + 1.0, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidSampleRate) ); + HOPEFOR( ( /* High sample rate. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + 10000000.0, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == paInvalidSampleRate) ); + HOPEFOR( ( /* NULL callback. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + NULL, + &myData ) + ) == paNullCallback) ); + HOPEFOR( ( /* Bad flag. */ + (result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + (1<<3), + QaCallback, + &myData ) + ) == paInvalidFlag) ); + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} +/*******************************************************************/ +static int TestBadActions( void ) +{ + PortAudioStream *stream = NULL; + PaError result; + PaQaData myData; + /* Setup data for synthesis thread. */ + myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */ + myData.numChannels = 1; + myData.mode = MODE_OUTPUT; + /* Default output. */ + EXPECT( ((result = Pa_OpenStream( + &stream, + paNoDevice, 0, paFloat32, NULL, + Pa_GetDefaultOutputDeviceID(), 2, paFloat32, NULL, + SAMPLE_RATE, FRAMES_PER_BUFFER, NUM_BUFFERS, + paClipOff, + QaCallback, + &myData ) + ) == 0) ); + HOPEFOR( ((result = Pa_StartStream( NULL )) == paBadStreamPtr) ); + HOPEFOR( ((result = Pa_StopStream( NULL )) == paBadStreamPtr) ); + HOPEFOR( ((result = Pa_StreamActive( NULL )) == paBadStreamPtr) ); + HOPEFOR( ((result = Pa_CloseStream( NULL )) == paBadStreamPtr) ); + HOPEFOR( ((result = (PaError)Pa_StreamTime( NULL )) != 0) ); + HOPEFOR( ((result = (PaError)Pa_GetCPULoad( NULL )) != 0) ); +error: + if( stream != NULL ) Pa_CloseStream( stream ); + return result; +} diff --git a/pa_tests/patest1.c b/pa_tests/patest1.c new file mode 100644 index 0000000..5fddd51 --- /dev/null +++ b/pa_tests/patest1.c @@ -0,0 +1,114 @@ +/* + $Id$ + patest1.c + Ring modulate the audio input with a sine wave for 20 seconds + using the Portable Audio api + Author: Ross Bencina + Modifications: + April 5th, 2001 - PLB - Check for NULL inputBuffer. +*/ +#include +#include +#include "portaudio.h" +#ifndef M_PI +#define M_PI (3.14159265) +#endif +typedef struct +{ + float sine[100]; + int phase; + int sampsToGo; +} +patest1data; +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long bufferFrames, + PaTimestamp outTime, void *userData ) +{ + patest1data *data = (patest1data*)userData; + float *in = (float*)inputBuffer; + float *out = (float*)outputBuffer; + int framesToCalc = bufferFrames; + unsigned long i; + int finished = 0; + /* Check to see if any input data is available. */ + if(inputBuffer == NULL) return 0; + if( data->sampsToGo < bufferFrames ) + { + framesToCalc = data->sampsToGo; + finished = 1; + } + for( i=0; isine[data->phase]; /* left */ + *out++ = *in++ * data->sine[data->phase++]; /* right */ + if( data->phase >= 100 ) + data->phase = 0; + } + data->sampsToGo -= framesToCalc; + /* zero remainder of final buffer if not already done */ + for( ; i +#include +#include +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) + +#define BUFFER_TABLE 9 +long buffer_table[] = {200,256,500,512,600, 723, 1000, 1024, 2345}; + +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( int buffersize ); + +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) outTime; + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; isampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + PaError err; + printf("Test opening streams with different buffer sizes\n\n"); + + for (i = 0 ; i < BUFFER_TABLE; i++) + { + printf("Buffer size %d\n", buffer_table[i]); + err = TestOnce(buffer_table[i]); + if( err < 0 ) return 0; + + } +} + + +PaError TestOnce( int buffersize ) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include "portaudio.h" +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; +PaError PlaySine( paTestData *data, unsigned long flags, float amplitude ); +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int sineCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) outTime; + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData DATA; + int i; + printf("PortAudio Test: output sine wave with and without clipping.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; ileft_phase = data->right_phase = 0; + data->amplitude = amplitude; + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + 1024, + 0, /* number of buffers, if zero then use default minimum */ + flags, /* we won't output out of range samples so don't bother clipping them */ + sineCallback, + data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetCPULoad( stream ) ); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + return err; +} diff --git a/pa_tests/patest_dither.c b/pa_tests/patest_dither.c new file mode 100644 index 0000000..8878161 --- /dev/null +++ b/pa_tests/patest_dither.c @@ -0,0 +1,151 @@ +/* + * $Id$ + * patest_dither.c + * Attempt to hear difference between dithered and non-dithered signal. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct paTestData +{ + float sine[TABLE_SIZE]; + float amplitude; + int left_phase; + int right_phase; +} +paTestData; +PaError PlaySine( paTestData *data, PaStreamFlags flags, float amplitude ); +static int sineCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int sineCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float amplitude = data->amplitude; + unsigned int i; + (void) outTime; + (void) inputBuffer; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = amplitude * data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PaError err; + paTestData DATA; + int i; + float amplitude = 32.0 / (1<<15); + printf("PortAudio Test: output EXTREMELY QUIET sine wave with and without dithering.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; ileft_phase = data->right_phase = 0; + data->amplitude = amplitude; + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + 1024, + 0, /* number of buffers, if zero then use default minimum */ + flags, /* we won't output out of range samples so don't bother clipping them */ + sineCallback, + (void *)data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + Pa_Sleep( NUM_SECONDS * 1000 ); + printf("CPULoad = %8.6f\n", Pa_GetCPULoad( stream ) ); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + return paNoError; +error: + return err; +} diff --git a/pa_tests/patest_latency.c b/pa_tests/patest_latency.c new file mode 100644 index 0000000..2daf431 --- /dev/null +++ b/pa_tests/patest_latency.c @@ -0,0 +1,171 @@ +/* + * $Id$ + * Hear the latency caused by bug buffers. + * Play a sine wave and change frequency based on letter input. + * + * Author: Phil Burk + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#if 1 +#define MIN_LATENCY_MSEC (2000) +#define NUM_BUFFERS ((MIN_LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#else +#define NUM_BUFFERS (0) +#endif +#define MIN_FREQ (100.0f) +#define CalcPhaseIncrement(freq) ((freq)/SAMPLE_RATE) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float sine[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float left_phase; + float right_phase; +} +paTestData; +float LookupSine( paTestData *data, float phase ); +/* Convert phase between and 1.0 to sine value + * using linear interpolation. + */ +float LookupSine( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->sine[index]; + float hi = data->sine[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; ileft_phase); /* left */ + *out++ = LookupSine(data, data->right_phase); /* right */ + data->left_phase += data->phase_increment; + if( data->left_phase >= 1.0f ) data->left_phase -= 1.0f; + data->right_phase += (data->phase_increment * 1.5f); /* fifth above */ + if( data->right_phase >= 1.0f ) data->right_phase -= 1.0f; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int done = 0; + printf("PortAudio Test: output sine sweep. ask for %d buffers\n", NUM_BUFFERS ); + /* initialise sinusoidal wavetable */ + for( i=0; i + * Phil Burk + * + * 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 "portaudio.h" +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int toggle; + int countDown; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; itoggle ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = 0; /* right */ + } + else + { + *out++ = 0; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + } + + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + if( data->countDown < 0 ) + { + data->countDown = SAMPLE_RATE; + data->toggle = !data->toggle; + } + data->countDown -= framesPerBuffer; + + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int timeout; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i 0 ) + { + printf("Countdown = %d, Toggle = %d\n", data.countDown, data.toggle ); + fflush( stdout ); + Pa_Sleep( 1000/4 ); + timeout -= 1; + } + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} diff --git a/pa_tests/patest_longsine.c b/pa_tests/patest_longsine.c new file mode 100644 index 0000000..3d8bd4c --- /dev/null +++ b/pa_tests/patest_longsine.c @@ -0,0 +1,124 @@ +/* + * $Id$ + * patest_longsine.c + * Play a sine wave using the Portable Audio api forever. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave.\n"); + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include +#include "portaudio.h" +#define NUM_SECONDS (1) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + short sine[TABLE_SIZE]; + int left_phase; + int right_phase; + unsigned int sampsToGo; +} +paTestData; +PaError TestOnce( void ); +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patest1Callback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned int i; + int finished = 0; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) outTime; + + if( data->sampsToGo < framesPerBuffer ) + { + /* final buffer... */ + + for( i=0; isampsToGo; i++ ) + { + *out++ = data->sine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + data->sampsToGo -= framesPerBuffer; + } + return finished; +} +/*******************************************************************/ +#ifdef MACINTOSH +int main(void); +int main(void) +{ + int i; + PaError err; + int numLoops = 10; + printf("Loop %d times.\n", numLoops ); + for( i=0; i 1 ) + { + numLoops = atoi(argv[1]); + } + for( i=0; i + * Phil Burk + * + * 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 "portaudio.h" + +#define MAX_SINES (500) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TWOPI (M_PI * 2.0) + +typedef struct paTestData +{ + int numSines; + double phases[MAX_SINES]; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int j; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; inumSines; j++ ) + { + /* Advance phase of next oscillator. */ + phase = data->phases[j]; + phase += phaseInc; + if( phase > TWOPI ) phase -= TWOPI; + + phaseInc *= 1.02; + if( phaseInc > 0.5 ) phaseInc *= 0.5; + + /* This is not a very efficient way to calc sines. */ + output += (float) sin( phase ); + data->phases[j] = phase; + } + + + *out++ = (float) (output / data->numSines); + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data = {0}; + double load; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 1, /* mono output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + do + { + data.numSines++; + Pa_Sleep( 200 ); + + load = Pa_GetCPULoad( stream ); + printf("numSines = %d, CPU load = %f\n", data.numSines, load ); + } + while( load < 0.8 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} diff --git a/pa_tests/patest_pink.c b/pa_tests/patest_pink.c new file mode 100644 index 0000000..c1fdece --- /dev/null +++ b/pa_tests/patest_pink.c @@ -0,0 +1,245 @@ +/* + * $Id$ + patest_pink.c + Generate Pink Noise using Gardner method. + Optimization suggested by James McCartney uses a tree + to select which random value to replace. + x x x x x x x x x x x x x x x x + x x x x x x x x + x x x x + x x + x + Tree is generated by counting trailing zeros in an increasing index. + When the index is zero, no random number is selected. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define PINK_MAX_RANDOM_ROWS (30) +#define PINK_RANDOM_BITS (24) +#define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS) +typedef struct +{ + long pink_Rows[PINK_MAX_RANDOM_ROWS]; + long pink_RunningSum; /* Used to optimize summing of generators. */ + int pink_Index; /* Incremented each sample. */ + int pink_IndexMask; /* Index wrapped by ANDing with this mask. */ + float pink_Scalar; /* Used to scale within range of -1.0 to +1.0 */ +} +PinkNoise; +/* Prototypes */ +static unsigned long GenerateRandomNumber( void ); +void InitializePinkNoise( PinkNoise *pink, int numRows ); +float GeneratePinkNoise( PinkNoise *pink ); +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + /* Change this seed for different random sequences. */ + static unsigned long randSeed = 22222; + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} +/************************************************************/ +/* Setup PinkNoise structure for N rows of generators. */ +void InitializePinkNoise( PinkNoise *pink, int numRows ) +{ + int i; + long pmax; + pink->pink_Index = 0; + pink->pink_IndexMask = (1<pink_Scalar = 1.0f / pmax; + /* Initialize rows. */ + for( i=0; ipink_Rows[i] = 0; + pink->pink_RunningSum = 0; +} +#define PINK_MEASURE +#ifdef PINK_MEASURE +float pinkMax = -999.0; +float pinkMin = 999.0; +#endif +/* Generate Pink noise values between -1.0 and +1.0 */ +float GeneratePinkNoise( PinkNoise *pink ) +{ + long newRandom; + long sum; + float output; + /* Increment and mask index. */ + pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask; + /* If index is zero, don't update any random values. */ + if( pink->pink_Index != 0 ) + { + /* Determine how many trailing zeros in PinkIndex. */ + /* This algorithm will hang if n==0 so test first. */ + int numZeros = 0; + int n = pink->pink_Index; + while( (n & 1) == 0 ) + { + n = n >> 1; + numZeros++; + } + /* Replace the indexed ROWS random value. + * Subtract and add back to RunningSum instead of adding all the random + * values together. Only one changes each time. + */ + pink->pink_RunningSum -= pink->pink_Rows[numZeros]; + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + pink->pink_RunningSum += newRandom; + pink->pink_Rows[numZeros] = newRandom; + } + + /* Add extra white noise value. */ + newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT; + sum = pink->pink_RunningSum + newRandom; + /* Scale to range of -1.0 to 0.9999. */ + output = pink->pink_Scalar * sum; +#ifdef PINK_MEASURE + /* Check Min/Max */ + if( output > pinkMax ) pinkMax = output; + else if( output < pinkMin ) pinkMin = output; +#endif + return output; +} +/*******************************************************************/ +#define PINK_TEST +#ifdef PINK_TEST +/* Context for callback routine. */ +typedef struct +{ + PinkNoise leftPink; + PinkNoise rightPink; + unsigned int sampsToGo; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + int finished; + int i; + int numFrames; + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + (void) inputBuffer; /* Prevent "unused variable" warnings. */ + (void) outTime; + + /* Are we almost at end. */ + if( data->sampsToGo < framesPerBuffer ) + { + numFrames = data->sampsToGo; + finished = 1; + } + else + { + numFrames = framesPerBuffer; + finished = 0; + } + for( i=0; ileftPink ); + *out++ = GeneratePinkNoise( &data->rightPink ); + } + data->sampsToGo -= numFrames; + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int totalSamps; + /* Initialize two pink noise signals with different numbers of rows. */ + InitializePinkNoise( &data.leftPink, 12 ); + InitializePinkNoise( &data.rightPink, 16 ); + /* Look at a few values. */ + { + int i; + float pink; + for( i=0; i<20; i++ ) + { + pink = GeneratePinkNoise( &data.leftPink ); + printf("Pink = %f\n", pink ); + } + } + data.sampsToGo = totalSamps = 8*44100; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + /* Open a stereo PortAudio stream so we can hear the result. */ + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + 44100., + 2048, /* 46 msec buffers */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for sound to finish.\n"); + while( Pa_StreamActive( stream ) ) + { + Pa_Sleep(100); /* SPIN! */ + } + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; +#ifdef PINK_MEASURE + printf("Pink min = %f, max = %f\n", pinkMin, pinkMax ); +#endif + Pa_Terminate(); + 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 0; +} +#endif /* PINK_TEST */ diff --git a/pa_tests/patest_record.c b/pa_tests/patest_record.c new file mode 100644 index 0000000..47ccec1 --- /dev/null +++ b/pa_tests/patest_record.c @@ -0,0 +1,303 @@ +/* + * $Id$ + * patest_record.c + * Record input into an array. + * Save array to a file. + * Playback recorded data. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" + +/* #define SAMPLE_RATE (17932) /* Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define NUM_SECONDS (5) + +/* Select sample format. */ +#if 0 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#elif 0 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#endif + +typedef struct +{ + int frameIndex; /* Index into sample array. */ + int maxFrameIndex; + int samplesPerFrame; + SAMPLE *recordedSamples; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int recordCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = (SAMPLE*)inputBuffer; + SAMPLE *wptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + long framesToCalc; + long i; + int finished; + unsigned long framesLeft = data->maxFrameIndex - data->frameIndex; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + framesToCalc = framesLeft; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + finished = 0; + } + if( inputBuffer == NULL ) + { + for( i=0; iframeIndex += framesToCalc; + return finished; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int playCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE *rptr = &data->recordedSamples[data->frameIndex * data->samplesPerFrame]; + SAMPLE *wptr = (SAMPLE*)outputBuffer; + unsigned int i; + int finished; + unsigned int framesLeft = data->maxFrameIndex - data->frameIndex; + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) outTime; + + if( framesLeft < framesPerBuffer ) + { + /* final buffer... */ + for( i=0; iframeIndex += framesLeft; + finished = 1; + } + else + { + for( i=0; iframeIndex += framesPerBuffer; + finished = 0; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalFrames; + int numSamples; + int numBytes; + SAMPLE max, average, val; + printf("patest_record.c\n"); fflush(stdout); + + data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ + data.frameIndex = 0; + data.samplesPerFrame = 2; + numSamples = totalFrames * data.samplesPerFrame; + + numBytes = numSamples * sizeof(SAMPLE); + data.recordedSamples = (SAMPLE *) malloc( numBytes ); + if( data.recordedSamples == NULL ) + { + printf("Could not allocate record array.\n"); + exit(1); + } + for( i=0; i max ) + { + max = val; + } + average += val; + } + printf("sample max amplitude = %d\n", max ); + average = average / numSamples; + printf("sample average = %d\n", average ); + + /* Write recorded data to a file. */ +#if 0 + { + FILE *fid; + fid = fopen("recorded.raw", "wb"); + if( fid == NULL ) + { + printf("Could not open file."); + } + else + { + fwrite( data.recordedSamples, data.samplesPerFrame * sizeof(SAMPLE), totalFrames, fid ); + fclose( fid ); + printf("Wrote data to 'recorded.raw'\n"); + } + } +#endif + + /* Playback recorded data. -------------------------------------------- */ + data.frameIndex = 0; + printf("Begin playback.\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + paNoDevice, + 0, /* NO input */ + PA_SAMPLE_TYPE, + NULL, + Pa_GetDefaultOutputDeviceID(), + data.samplesPerFrame, /* stereo output */ + PA_SAMPLE_TYPE, + NULL, + SAMPLE_RATE, + 1024, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto error; + + if( stream ) + { + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + printf("Waiting for playback to finish.\n"); fflush(stdout); + + while( Pa_StreamActive( stream ) ) Pa_Sleep(100); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + printf("Done.\n"); fflush(stdout); + } + free( data.recordedSamples ); + + Pa_Terminate(); + 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/pa_tests/patest_ringmix.c b/pa_tests/patest_ringmix.c new file mode 100644 index 0000000..0bae220 --- /dev/null +++ b/pa_tests/patest_ringmix.c @@ -0,0 +1,41 @@ +/* $Id$ */ + +#include "stdio.h" +#include "portaudio.h" +/* This will be called asynchronously by the PortAudio engine. */ +static int myCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, PaTimestamp outTime, void *userData ) +{ + float *out = (float *) outputBuffer; + float *in = (float *) inputBuffer; + float leftInput, rightInput; + unsigned int i; + if( inputBuffer == NULL ) return 0; + /* Read input buffer, process data, and fill output buffer. */ + for( i=0; i +#include +#include "portaudio.h" +#define NUM_SECONDS (4) +#define SAMPLE_RATE (44100) +typedef struct +{ + float left_phase; + float right_phase; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + for( i=0; ileft_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + return 0; +} +/*******************************************************************/ +static paTestData data; +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + printf("PortAudio Test: output sawtooth wave.\n"); + /* Initialize our data for use by callback. */ + data.left_phase = data.right_phase = 0.0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + /* Open an audio I/O stream. */ + err = Pa_OpenDefaultStream( + &stream, + 0, /* no input channels */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + SAMPLE_RATE, + 256, /* frames per buffer */ + 0, /* number of buffers, if zero then use default minimum */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + /* Sleep for several seconds. */ + Pa_Sleep(NUM_SECONDS*1000); + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} diff --git a/pa_tests/patest_sine.c b/pa_tests/patest_sine.c new file mode 100644 index 0000000..f6d8f6a --- /dev/null +++ b/pa_tests/patest_sine.c @@ -0,0 +1,138 @@ +/* + * $Id$ + * $Id$ + * patest_sine.c + * Play a sine wave using the Portable Audio api for several seconds. + * + * Authors: + * Ross Bencina + * Phil Burk + * + * 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 "portaudio.h" + +#define NUM_SECONDS (5) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i + * + * 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 "portaudio.h" +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define TEST_UNSIGNED (1) +#if TEST_UNSIGNED +#define TEST_FORMAT paUInt8 +#else +#define TEST_FORMAT paInt8 +#endif +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ +#if TEST_UNSIGNED + unsigned char sine[TABLE_SIZE]; +#else + char sine[TABLE_SIZE]; +#endif + int left_phase; + int right_phase; + unsigned int framesToGo; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + char *out = (char*)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { +#if TEST_UNSIGNED + *out++ = (unsigned char) 0x80; /* left */ + *out++ = (unsigned char) 0x80; /* right */ +#else + *out++ = 0; /* left */ + *out++ = 0; /* right */ +#endif + + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; +#if TEST_UNSIGNED + printf("PortAudio Test: output UNsigned 8 bit sine wave.\n"); +#else + printf("PortAudio Test: output signed 8 bit sine wave.\n"); +#endif + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include "portaudio.h" + +#define NUM_SECONDS (20) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define LEFT_FREQ (SAMPLE_RATE/256.0) /* So we hit 1.0 */ +#define RIGHT_FREQ (500.0) +#define AMPLITUDE (1.0) + +/* Select ONE format for testing. */ +#define TEST_UINT8 (0) +#define TEST_INT8 (0) +#define TEST_INT16 (1) +#define TEST_FLOAT32 (0) + +#if TEST_UINT8 +#define TEST_FORMAT paUInt8 +typedef unsigned char SAMPLE_t; +#define SAMPLE_ZERO (0x80) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Unsigned 8 Bit" +#endif + +#if TEST_INT8 +#define TEST_FORMAT paInt8 +typedef char SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(127.0 * (x))) +#define FORMAT_NAME "Signed 8 Bit" +#endif + +#if TEST_INT16 +#define TEST_FORMAT paInt16 +typedef short SAMPLE_t; +#define SAMPLE_ZERO (0) +#define DOUBLE_TO_SAMPLE(x) (SAMPLE_ZERO + (SAMPLE_t)(32767.0 * (x))) +#define FORMAT_NAME "Signed 16 Bit" +#endif + +#if TEST_FLOAT32 +#define TEST_FORMAT paFloat32 +typedef float SAMPLE_t; +#define SAMPLE_ZERO (0.0) +#define DOUBLE_TO_SAMPLE(x) ((SAMPLE_t)(x)) +#define FORMAT_NAME "Float 32 Bit" +#endif + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + + +typedef struct +{ + double left_phase; + double right_phase; + unsigned int framesToGo; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + SAMPLE_t *out = (SAMPLE_t *)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; ileft_phase += (LEFT_FREQ / SAMPLE_RATE); + if( data->left_phase > 1.0) data->left_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->left_phase * M_PI * 2. ))); + + data->right_phase += (RIGHT_FREQ / SAMPLE_RATE); + if( data->right_phase > 1.0) data->right_phase -= 1.0; + *out++ = DOUBLE_TO_SAMPLE( AMPLITUDE * sin( (data->right_phase * M_PI * 2. ))); + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = SAMPLE_ZERO; /* left */ + *out++ = SAMPLE_ZERO; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int totalSamps; + + printf("PortAudio Test: output " FORMAT_NAME "\n"); + + + data.left_phase = data.right_phase = 0.0; + data.framesToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + TEST_FORMAT, + NULL, + Pa_GetDefaultOutputDeviceID(), /* default output device */ + 2, /* stereo output */ + TEST_FORMAT, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Waiting %d seconds for sound to finish.\n", NUM_SECONDS ); + while( Pa_StreamActive( stream ) ) Pa_Sleep(10); + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + + printf("PortAudio Test Finished: " FORMAT_NAME "\n"); + + return err; +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 err; +} diff --git a/pa_tests/patest_sine_time.c b/pa_tests/patest_sine_time.c new file mode 100644 index 0000000..9b9cf3b --- /dev/null +++ b/pa_tests/patest_sine_time.c @@ -0,0 +1,165 @@ +/* + * $Id$ + * patest_sine_time.c + * Play a sine wave using the Portable Audio api for several seconds. + * Pausing in the middle. + * use the Pa_StreamTime() and Pa_StreamActive() calls. + * + * Authors: + * Ross Bencina + * Phil Burk + * + * 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 "portaudio.h" +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (64) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int framesToGo; +} +paTestData; +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + int i; + int framesToCalc; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + if( data->framesToGo < framesPerBuffer ) + { + framesToCalc = data->framesToGo; + data->framesToGo = 0; + finished = 1; + } + else + { + framesToCalc = framesPerBuffer; + data->framesToGo -= framesPerBuffer; + } + + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + /* zero remainder of final buffer */ + for( ; i<(int)framesPerBuffer; i++ ) + { + *out++ = 0; /* left */ + *out++ = 0; /* right */ + } + return finished; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + int totalSamps; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define OUTPUT_DEVICE (Pa_GetDefaultOutputDeviceID()) +#define SLEEP_DUR (200) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define LATENCY_MSEC (3000) +#define NUM_BUFFERS ((LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#define FRAMES_PER_NOTE (SAMPLE_RATE/2) +#define MAX_REPEATS (2) +#define FUNDAMENTAL (400.0f / SAMPLE_RATE) +#define NOTE_0 (FUNDAMENTAL * 1.0f / 1.0f) +#define NOTE_1 (FUNDAMENTAL * 5.0f / 4.0f) +#define NOTE_2 (FUNDAMENTAL * 4.0f / 3.0f) +#define NOTE_3 (FUNDAMENTAL * 3.0f / 2.0f) +#define NOTE_4 (FUNDAMENTAL * 2.0f / 1.0f) +#define MODE_FINISH (0) +#define MODE_STOP (1) +#define MODE_ABORT (2) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (400) +typedef struct +{ + float waveform[TABLE_SIZE + 1]; // add one for guard point for interpolation + float phase_increment; + float phase; + float *tune; + int notesPerTune; + int frameCounter; + int noteCounter; + int repeatCounter; + PaTimestamp outTime; + int stopMode; + int done; +} +paTestData; +/************* Prototypes *****************************/ +int TestStopMode( paTestData *data ); +float LookupWaveform( paTestData *data, float phase ); +/****************************************************** + * Convert phase between 0.0 and 1.0 to waveform value + * using linear interpolation. + */ +float LookupWaveform( paTestData *data, float phase ) +{ + float fIndex = phase*TABLE_SIZE; + int index = (int) fIndex; + float fract = fIndex - index; + float lo = data->waveform[index]; + float hi = data->waveform[index+1]; + float val = lo + fract*(hi-lo); + return val; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + float value; + unsigned int i = 0; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + + data->outTime = outTime; + if( !data->done ) + { + for( i=0; iframeCounter >= FRAMES_PER_NOTE ) + { + data->noteCounter += 1; + data->frameCounter = 0; + /* Are we done with this tune? */ + if( data->noteCounter >= data->notesPerTune ) + { + data->noteCounter = 0; + data->repeatCounter += 1; + /* Are we totally done? */ + if( data->repeatCounter >= MAX_REPEATS ) + { + data->done = 1; + if( data->stopMode == MODE_FINISH ) + { + finished = 1; + break; + } + } + } + data->phase_increment = data->tune[data->noteCounter]; + } + value = LookupWaveform(data, data->phase); + *out++ = value; /* left */ + *out++ = value; /* right */ + data->phase += data->phase_increment; + if( data->phase >= 1.0f ) data->phase -= 1.0f; + + data->frameCounter += 1; + } + } + /* zero remainder of final buffer */ + for( ; idone = 0; + data->phase = 0.0; + data->frameCounter = 0; + data->noteCounter = 0; + data->repeatCounter = 0; + data->phase_increment = data->tune[data->noteCounter]; + err = Pa_Initialize(); + if( err != paNoError ) goto error; + err = Pa_OpenStream( + &stream, + paNoDevice,/* default input device */ + 0, /* no input */ + paFloat32, /* 32 bit floating point input */ + NULL, + OUTPUT_DEVICE, + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, /* frames per buffer */ + NUM_BUFFERS, /* number of buffers, if zero then use default minimum */ + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + patestCallback, + data ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + if( data->stopMode == MODE_FINISH ) + { + while( Pa_StreamActive( stream ) ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); /**/ + Pa_Sleep( SLEEP_DUR ); + } + } + else + { + while( data->repeatCounter < MAX_REPEATS ) + { + /*printf("outTime = %g, note# = %d, repeat# = %d\n", data->outTime, + data->noteCounter, data->repeatCounter ); + fflush(stdout); /**/ + Pa_Sleep( SLEEP_DUR ); + } + } + if( data->stopMode == MODE_ABORT ) + { + printf("Call Pa_AbortStream()\n"); + err = Pa_AbortStream( stream ); + } + else + { + printf("Call Pa_StopStream()\n"); + err = Pa_StopStream( stream ); + } + if( err != paNoError ) goto error; + printf("Call Pa_CloseStream()\n"); fflush(stdout); + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} diff --git a/pa_tests/patest_sync.c b/pa_tests/patest_sync.c new file mode 100644 index 0000000..e2eb88a --- /dev/null +++ b/pa_tests/patest_sync.c @@ -0,0 +1,227 @@ +/* + * $Id$ + * patest_sync.c + * Test time stamping and synchronization of audio and video. + * A high latency is used so we can hear the difference in time. + * Random durations are used so we know we are hearing the right beep + * and not the one before or after. + * + * Sequence of events: + * Foreground requests a beep. + * Background randomly schedules a beep. + * Foreground waits for the beep to be heard based on Pa_StreamTime(). + * Foreground outputs video (printf) in sync with audio. + * Repeat. + * + * Author: Phil Burk http://www.softsynth.com + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * 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 "portaudio.h" +#define NUM_BEEPS (6) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (256) +#define BEEP_DURATION (1000) +#define LATENCY_MSEC (2000) +#define SLEEP_MSEC (10) +#define TIMEOUT_MSEC ((3 * LATENCY_MSEC) / (2 * SLEEP_MSEC)) +#define NUM_BUFFERS ((LATENCY_MSEC * SAMPLE_RATE) / (FRAMES_PER_BUFFER * 1000)) +#define STATE_BKG_IDLE (0) +#define STATE_BKG_PENDING (1) +#define STATE_BKG_BEEPING (2) +typedef struct +{ + float left_phase; + float right_phase; + int state; + int requestBeep; /* Set by foreground, cleared by background. */ + PaTimestamp beepTime; + int beepCount; +} +paTestData; +static unsigned long GenerateRandomNumber( void ); +/************************************************************/ +/* Calculate pseudo-random 32 bit number based on linear congruential method. */ +static unsigned long GenerateRandomNumber( void ) +{ + static unsigned long randSeed = 22222; /* Change this for different random sequences. */ + randSeed = (randSeed * 196314165) + 907633515; + return randSeed; +} +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + /* Cast data passed through stream to our structure. */ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned int i; + (void) inputBuffer; + + for( i=0; istate ) + { + case STATE_BKG_IDLE: + /* Schedule beep at some random time in the future. */ + if( data->requestBeep ) + { + int random = GenerateRandomNumber() >> 14; + data->beepTime = outTime + (i + random + (SAMPLE_RATE/4)); + data->state = STATE_BKG_PENDING; + data->requestBeep = 0; + data->left_phase = data->right_phase = 0.0; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + case STATE_BKG_PENDING: + if( (outTime + i) >= data->beepTime ) + { + data->state = STATE_BKG_BEEPING; + data->beepCount = BEEP_DURATION; + } + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + break; + case STATE_BKG_BEEPING: + if( data->beepCount <= 0 ) + { + data->state = STATE_BKG_IDLE; + *out++ = 0.0; /* left */ + *out++ = 0.0; /* right */ + } + else + { + /* Play sawtooth wave. */ + *out++ = data->left_phase; /* left */ + *out++ = data->right_phase; /* right */ + /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */ + data->left_phase += 0.01f; + /* When signal reaches top, drop back down. */ + if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f; + /* higher pitch so we can distinguish left and right. */ + data->right_phase += 0.03f; + if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f; + } + data->beepCount -= 1; + break; + default: + data->state = STATE_BKG_IDLE; + break; + } + } + return 0; +} +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData DATA; + int i, timeout; + PaTimestamp previousTime; + printf("PortAudio Test: you should see BEEP at the same time you hear it.\n"); + printf("Wait for a few seconds random delay between BEEPs.\n"); + printf("BEEP %d times.\n", NUM_BEEPS ); + /* Initialize our DATA for use by callback. */ + DATA.left_phase = DATA.right_phase = 0.0; + DATA.state = STATE_BKG_IDLE; + DATA.requestBeep = 0; + /* Initialize library before making any other calls. */ + err = Pa_Initialize(); + if( err != paNoError ) goto error; + /* Open an audio I/O stream. */ + err = Pa_OpenDefaultStream( + &stream, + 0, /* no input channels */ + 2, /* stereo output */ + paFloat32, /* 32 bit floating point output */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + NUM_BUFFERS, + patestCallback, + &DATA ); + if( err != paNoError ) goto error; + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + previousTime = Pa_StreamTime( stream ); + for( i=0; i 0 ) ) Pa_Sleep(SLEEP_MSEC); + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for background to acknowledge request.\n" ); + goto error; + } + /* Wait for scheduled beep time. */ + timeout = TIMEOUT_MSEC + (10000/SLEEP_MSEC); + while( (Pa_StreamTime( stream ) < DATA.beepTime) && (timeout-- > 0 ) ) + { + Pa_Sleep(SLEEP_MSEC); + } + if( timeout <= 0 ) + { + fprintf( stderr, "Timed out waiting for time. Now = %g, Beep for %g.\n", + Pa_StreamTime( stream ), DATA.beepTime ); + goto error; + } + /* Beep should be sounding now so print synchronized BEEP. */ + printf("BEEP"); + fflush(stdout); + printf(" at %d, delta = %d\n", + (long) DATA.beepTime, (long) (DATA.beepTime - previousTime) ); + fflush(stdout); + previousTime = DATA.beepTime; + } + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + printf("Test finished.\n"); + return err; +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 err; +} diff --git a/pa_tests/patest_underflow.c b/pa_tests/patest_underflow.c new file mode 100644 index 0000000..ca07cf4 --- /dev/null +++ b/pa_tests/patest_underflow.c @@ -0,0 +1,151 @@ +/* + * $Id$ + * patest_underflow.c + * Simulate an output buffer underflow condition. + * Tests whether the stream can be stopped when underflowing buffers. + * + * Authors: + * Ross Bencina + * Phil Burk + * + * 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 "portaudio.h" + +#define NUM_SECONDS (20) +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (2048) +#define MSEC_PER_BUFFER ( (FRAMES_PER_BUFFER * 1000) / SAMPLE_RATE ) + +#ifndef M_PI +#define M_PI (3.14159265) +#endif + +#define TABLE_SIZE (200) +typedef struct +{ + float sine[TABLE_SIZE]; + int left_phase; + int right_phase; + int sleepTime; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + paTestData *data = (paTestData*)userData; + float *out = (float*)outputBuffer; + unsigned long i; + int finished = 0; + (void) outTime; /* Prevent unused variable warnings. */ + (void) inputBuffer; + for( i=0; isine[data->left_phase]; /* left */ + *out++ = data->sine[data->right_phase]; /* right */ + data->left_phase += 1; + if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; + data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ + if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; + } + + /* Cause underflow to occur. */ + if( data->sleepTime > 0 ) Pa_Sleep( data->sleepTime ); + data->sleepTime += 1; + + return finished; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PortAudioStream *stream; + PaError err; + paTestData data; + int i; + printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); + /* initialise sinusoidal wavetable */ + for( i=0; i +#include +#include "portaudio.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 FRAMES_PER_BUFFER (64) +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#else +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#endif +static int wireCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int wireCallback( void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ) +{ + SAMPLE *out = (SAMPLE*)outputBuffer; + SAMPLE *in = (SAMPLE*)inputBuffer; + unsigned int i; + int finished = 0; + (void) outTime; + + /* This may get called with NULL inputBuffer during initial setup. */ + if( inputBuffer == NULL ) + { + for( i=0; i ;-) + added 24k and 16k to ratesToTry[] + fixed Pa_GetInternalDevice() + changed DEVICE_NAME_BASE from /dev/audio to /dev/dsp + handled SNDCTL_DSP_SPEED in Pq_QueryDevice() more graceful + fixed Pa_StreamTime() for paqa_errs.c + fixed numCannel=2 oddity and error handling in Pa_SetupDeviceFormat() + grep also for HP20010922 ... + PLB20010924 - Phil - merged Heiko's changes + removed sNumDevices and potential related bugs, + use getenv("PA_MIN_LATENCY_MSEC") to set desired latency, + simplify CPU Load calculation by comparing real-time to framesPerBuffer, + always close device when querying even if error occurs, + PLB20010927 - Phil - Improved negotiation for numChannels. + SG20011005 - Stewart Greenhill - set numChannels back to reasonable value after query. + DH20010115 - David Herring - fixed uninitialized handle. + +TODO +O- change Pa_StreamTime() to query device (patest_sync.c) +O- put semaphore lock around shared data? +O- handle native formats better +O- handle stereo-only device better ??? +O- what if input and output of a device capabilities differ (e.g. es1371) ??? +*/ +/* + PROPOSED - should we add this to "portaudio.h". Problem with + Pa_QueryDevice() not having same driver name os Pa_OpenStream(). + + A PaDriverInfo structure can be passed to the underlying device + on the Pa_OpenStream() call. The contents and interpretation of + the structure is determined by the PA implementation. +*/ +typedef struct PaDriverInfo /* PROPOSED */ +{ + /* Size of structure. Allows driver to extend the structure without breaking existing applications. */ + int size; + /* Can be used to request a specific device name. */ + const char *name; + unsigned long data; +} +PaDriverInfo; + + + +#include +#include +#include +#include +#include +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#else +#include /* JH20010905 */ +#endif + +#include +#include + +/* Some versions of OSS do not define AFMT_S16_NE. Assume little endian. FIXME - check CPU*/ +#ifndef AFMT_S16_NE + #define AFMT_S16_NE AFMT_S16_LE +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define BAD_DEVICE_ID (-1) + +#define MIN_LATENCY_MSEC (100) +#define MIN_TIMEOUT_MSEC (100) +#define MAX_TIMEOUT_MSEC (1000) + +/************************************************* Definitions ********/ +#ifdef __linux__ + #define DEVICE_NAME_BASE "/dev/dsp" +#else + #define DEVICE_NAME_BASE "/dev/audio" +#endif + +#define MAX_CHARS_DEVNAME (32) +#define MAX_SAMPLE_RATES (10) +typedef struct internalPortAudioDevice +{ + struct internalPortAudioDevice *pad_Next; /* Singly linked list. */ + double pad_SampleRates[MAX_SAMPLE_RATES]; /* for pointing to from pad_Info */ + char pad_DeviceName[MAX_CHARS_DEVNAME]; + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/* Define structure to contain all OSS and Linux specific data. */ +typedef struct PaHostSoundControl +{ + int pahsc_OutputHandle; + int pahsc_InputHandle; + pthread_t pahsc_ThreadPID; + short *pahsc_NativeInputBuffer; + short *pahsc_NativeOutputBuffer; + unsigned int pahsc_BytesPerInputBuffer; /* native buffer size in bytes */ + unsigned int pahsc_BytesPerOutputBuffer; /* native buffer size in bytes */ + /* For measuring CPU utilization. */ + struct timeval pahsc_EntryTime; + double pahsc_InverseMicrosPerBuffer; /* 1/Microseconds of real-time audio per user buffer. */ +} +PaHostSoundControl; + +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sDeviceIndex = 0; +static internalPortAudioDevice *sDeviceList = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sEnumerationError; +static int sPaHostError = 0; + +/************************************************* Prototypes **********/ + +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ); +static Pa_QueryDevices( void ); +static PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ); +static PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ); + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + struct itimerval itimer; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + gettimeofday( &pahsc->pahsc_EntryTime, NULL ); +} + +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 timeval currentTime; + 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( gettimeofday( ¤tTime, NULL ) == 0 ) + { + usecsElapsed = SubtractTime_AminusB( ¤tTime, &pahsc->pahsc_EntryTime ); + /* Use inverse because it is faster than the divide. */ + newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerBuffer; + + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} +/****************************************** END CPU UTILIZATION *******/ + +/********************************************************************* + * Try to open the named device. + * If it opens, try to set various rates and formats and fill in + * the device info structure. + */ +static PaError Pa_QueryDevice( const char *deviceName, internalPortAudioDevice *pad ) +{ + int result = paHostError; + int numBytes; + int tempDevHandle; + int numChannels, maxNumChannels; + int format; + int numSampleRates; + int sampleRate; + int numRatesToTry; + int ratesToTry[9] = {96000, 48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; + int i; + + /* douglas: + we have to do this querying in a slightly different order. apparently + some sound cards will give you different info based on their settins. + e.g. a card might give you stereo at 22kHz but only mono at 44kHz. + the correct order for OSS is: format, channels, sample rate + + */ + if ( (tempDevHandle = open(deviceName,O_WRONLY)) == -1 ) + { + DBUG(("Pa_QueryDevice: could not open %s\n", deviceName )); + return paHostError; + } + + /* Ask OSS what formats are supported by the hardware. */ + pad->pad_Info.nativeSampleFormats = 0; + if (ioctl(tempDevHandle, SNDCTL_DSP_GETFMTS, &format) == -1) + { + ERR_RPT(("Pa_QueryDevice: could not get format info\n" )); + goto error; + } + if( format & AFMT_U8 ) pad->pad_Info.nativeSampleFormats |= paUInt8; + if( format & AFMT_S16_NE ) pad->pad_Info.nativeSampleFormats |= paInt16; + + /* Negotiate for the maximum number of channels for this device. PLB20010927 + * Consider up to 16 as the upper number of channels. + * Variable numChannels should contain the actual upper limit after the call. + * Thanks to John Lazzaro and Heiko Purnhagen for suggestions. + */ + maxNumChannels = 0; + for( numChannels = 1; numChannels <= 16; numChannels++ ) + { + int temp = numChannels; + DBUG(("Pa_QueryDevice: use SNDCTL_DSP_CHANNELS, numChannels = %d\n", numChannels )) + if(ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) + { + /* ioctl() failed so bail out if we already have stereo */ + if( numChannels > 2 ) break; + } + else + { + /* ioctl() worked but bail out if it does not support numChannels. + * We don't want to leave gaps in the numChannels supported. + */ + if( (numChannels > 2) && (temp != numChannels) ) break; + DBUG(("Pa_QueryDevice: temp = %d\n", temp )) + if( temp > maxNumChannels ) maxNumChannels = temp; /* Save maximum. */ + } + } + + /* The above negotiation may fail for an old driver so try this older technique. */ + if( maxNumChannels < 1 ) + { + int stereo = 1; + if(ioctl(tempDevHandle, SNDCTL_DSP_STEREO, &stereo) < 0) + { + maxNumChannels = 1; + } + else + { + maxNumChannels = (stereo) ? 2 : 1; + } + DBUG(("Pa_QueryDevice: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", maxNumChannels )) + } + + pad->pad_Info.maxOutputChannels = maxNumChannels; + DBUG(("Pa_QueryDevice: maxNumChannels = %d\n", maxNumChannels)) + + /* During channel negotiation, the last ioctl() may have failed. This can + * also cause sample rate negotiation to fail. Hence the following, to return + * to a supported number of channels. SG20011005 */ + { + int temp = maxNumChannels; + if( temp > 2 ) temp = 2; /* use most reasonable default value */ + ioctl(tempDevHandle, SNDCTL_DSP_CHANNELS, &temp); + } + + /* FIXME - for now, assume maxInputChannels = maxOutputChannels. + * Eventually do separate queries for O_WRONLY and O_RDONLY + */ + pad->pad_Info.maxInputChannels = pad->pad_Info.maxOutputChannels; + + DBUG(("Pa_QueryDevice: maxInputChannels = %d\n", + pad->pad_Info.maxInputChannels)) + + + /* Determine available sample rates by trying each one and seeing result. + */ + numSampleRates = 0; + numRatesToTry = sizeof(ratesToTry)/sizeof(int); + for (i = 0; i < numRatesToTry; i++) + { + sampleRate = ratesToTry[i]; + + if (ioctl(tempDevHandle, SNDCTL_DSP_SPEED, &sampleRate) >= 0 ) /* PLB20010817 */ + { + if (sampleRate == ratesToTry[i]) + { + DBUG(("Pa_QueryDevice: got sample rate: %d\n", sampleRate)) + pad->pad_SampleRates[numSampleRates] = (float)ratesToTry[i]; + numSampleRates++; + } + } + } + + DBUG(("Pa_QueryDevice: final numSampleRates = %d\n", numSampleRates)) + if (numSampleRates==0) /* HP20010922 */ + { + ERR_RPT(("Pa_QueryDevice: no supported sample rate (or SNDCTL_DSP_SPEED ioctl call failed).\n" )); + goto error; + } + + pad->pad_Info.numSampleRates = numSampleRates; + pad->pad_Info.sampleRates = pad->pad_SampleRates; + + pad->pad_Info.name = deviceName; + + result = paNoError; + +error: + /* We MUST close the handle here or we won't be able to reopen it later!!! */ + close(tempDevHandle); + + return result; +} + +/********************************************************************* + * Determines the number of available devices by trying to open + * each "/dev/dsp#" or "/dsp/audio#" in order until it fails. + * Add each working device to a singly linked list of devices. + */ +static PaError Pa_QueryDevices( void ) +{ + internalPortAudioDevice *pad, *lastPad; + int numBytes; + int go = 1; + int numDevices = 0; + PaError testResult; + PaError result = paNoError; + + sDefaultInputDeviceID = paNoDevice; + sDefaultOutputDeviceID = paNoDevice; + + lastPad = NULL; + + while( go ) + { + /* Allocate structure to hold device info. */ + pad = PaHost_AllocateFastMemory( sizeof(internalPortAudioDevice) ); + if( pad == NULL ) return paInsufficientMemory; + memset( pad, 0, sizeof(internalPortAudioDevice) ); + + /* Build name for device. */ + if( numDevices == 0 ) + { + sprintf( pad->pad_DeviceName, DEVICE_NAME_BASE); + } + else + { + sprintf( pad->pad_DeviceName, DEVICE_NAME_BASE "%d", numDevices ); + } + + DBUG(("Try device %s\n", pad->pad_DeviceName )); + testResult = Pa_QueryDevice( pad->pad_DeviceName, pad ); + DBUG(("Pa_QueryDevice returned %d\n", testResult )); + if( testResult != paNoError ) + { + if( lastPad == NULL ) + { + result = testResult; /* No good devices! */ + } + go = 0; + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + } + else + { + numDevices += 1; + /* Add to linked list of devices. */ + if( lastPad ) + { + lastPad->pad_Next = pad; + } + else + { + sDeviceList = pad; /* First element in linked list. */ + } + lastPad = pad; + } + } + + return result; + +} + +/*************************************************************************/ +int Pa_CountDevices() +{ + int numDevices = 0; + internalPortAudioDevice *pad; + + if( sDeviceList == NULL ) Pa_Initialize(); + /* Count devices in list. */ + pad = sDeviceList; + while( pad != NULL ) + { + pad = pad->pad_Next; + numDevices++; + } + + return numDevices; +} + +/*************************************************************************/ +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = sDeviceList; + while( id > 0 ) + { + pad = pad->pad_Next; + id--; + } + return pad; +} + +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = Pa_GetInternalDevice( id ); + return &pad->pad_Info ; +} + +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sDeviceList == NULL ) + { + return Pa_QueryDevices(); + } + return 0; +} + +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + /* return paNoDevice; */ + return 0; +} + +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + return 0; +} + +/********************************************************************** +** Make sure that we have queried the device capabilities. +*/ + +PaError PaHost_Init( void ) +{ + return Pa_MaybeQueryDevices(); +} + +/*******************************************************************************************/ +static PaError Pa_AudioThreadProc( internalPortAudioStream *past ) +{ + PaError result = 0; + PaHostSoundControl *pahsc; + short bytes_read = 0; + +#ifdef GNUSTEP + GSRegisterCurrentThread(); /* SB20010904 */ +#endif + +#if 1 + { +#define SCHEDULER_POLICY SCHED_RR + + struct sched_param schp; + memset(&schp, 0, sizeof(schp)); + schp.sched_priority = sched_get_priority_max(SCHEDULER_POLICY); + + if (sched_setscheduler(0, SCHEDULER_POLICY, &schp) != 0) + { + perror("sched_setscheduler"); + printf("PortAudio: only superuser can use real-time priority.\n"); + } + else + { + printf("PortAudio: callback priority set to level %d!\n", schp.sched_priority); + } + } +#endif + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + + past->past_IsActive = 1; + DBUG(("entering thread.\n")); + + while( (past->past_StopNow == 0) && (past->past_StopSoon == 0) ) + { + + DBUG(("go!\n")); + /* Read data from device */ + if(pahsc->pahsc_NativeInputBuffer) + { + bytes_read = read(pahsc->pahsc_InputHandle, + (void *)pahsc->pahsc_NativeInputBuffer, + pahsc->pahsc_BytesPerInputBuffer); + + DBUG(("bytes_read: %d\n", bytes_read)); + } + + /* Convert 16 bit native data to user data and call user routine. */ + DBUG(("converting...\n")); + Pa_StartUsageCalculation( past ); + result = Pa_CallConvertInt16( past, + pahsc->pahsc_NativeInputBuffer, + pahsc->pahsc_NativeOutputBuffer ); + Pa_EndUsageCalculation( past ); + if( result != 0) + { + DBUG(("hmm, Pa_CallConvertInt16() says: %d. i'm bailing.\n", + result)); + break; + } + + /* Write data to device. */ + if( pahsc->pahsc_NativeOutputBuffer ) + { + + write(pahsc->pahsc_OutputHandle, + (void *)pahsc->pahsc_NativeOutputBuffer, + pahsc->pahsc_BytesPerOutputBuffer); + } + } + + past->past_IsActive = 0; + DBUG(("leaving thread.\n")); + +#ifdef GNUSTEP + GSUnregisterCurrentThread(); /* SB20010904 */ +#endif + return 0; +} + +/*******************************************************************************************/ +static PaError Pa_SetupDeviceFormat( int devHandle, int numChannels, int sampleRate ) +{ + PaError result = paNoError; + int tmp; + + /* Set format, channels, and rate in this order to keep OSS happy. */ + /* Set data format. FIXME - handle more native formats. */ + tmp = AFMT_S16_NE; + if( ioctl(devHandle,SNDCTL_DSP_SETFMT,&tmp) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SETFMT\n" )); + return paHostError; + } + if( tmp != AFMT_S16_NE) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support AFMT_S16_NE\n" )); + return paHostError; + } + + + /* Set number of channels. */ + tmp = numChannels; + if (ioctl(devHandle, SNDCTL_DSP_CHANNELS, &numChannels) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_CHANNELS\n" )); + return paHostError; + } + if( tmp != numChannels) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d channels\n", numChannels )); + return paHostError; + } + + /* Set playing frequency. 44100, 22050 and 11025 are safe bets. */ + tmp = sampleRate; + if( ioctl(devHandle,SNDCTL_DSP_SPEED,&tmp) == -1) + { + ERR_RPT(("Pa_SetupDeviceFormat: could not SNDCTL_DSP_SPEED\n" )); + return paHostError; + } + if( tmp != sampleRate) + { + ERR_RPT(("Pa_SetupDeviceFormat: HW does not support %d Hz sample rate\n",sampleRate )); + return paHostError; + } + + return result; +} + +static int CalcHigherLogTwo( int n ) +{ + int log2 = 0; + while( (1< 8 ) + { + numBuffers = (numBuffers + 1) >> 1; + framesPerBuffer = framesPerBuffer << 1; + } + + /* calculate size of buffers in bytes */ + bufferSize = framesPerBuffer * channelsPerFrame * sizeof(short); /* FIXME */ + + /* Calculate next largest power of two */ + powerOfTwo = CalcHigherLogTwo( bufferSize ); + DBUG(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", + numBuffers, framesPerBuffer, powerOfTwo )); + + /* Encode info into a single int */ + tmp=(numBuffers<<16) + powerOfTwo; + + if(ioctl(devHandle,SNDCTL_DSP_SETFRAGMENT,&tmp) == -1) + { + ERR_RPT(("Pa_SetLatency: could not SNDCTL_DSP_SETFRAGMENT\n" )); + /* Don't return an error. Best to just continue and hope for the best. */ + ERR_RPT(("Pa_SetLatency: numBuffers = %d, framesPerBuffer = %d, powerOfTwo = %d\n", + numBuffers, framesPerBuffer, powerOfTwo )); + } +} + +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the cshrc file. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") + +int Pa_GetMinNumBuffers( int framesPerBuffer, double framesPerSecond ) +{ + int minBuffers; + int minLatencyMsec = MIN_LATENCY_MSEC; + char *minLatencyText = getenv(PA_LATENCY_ENV_NAME); + if( minLatencyText != NULL ) + { + PRINT(("PA_MIN_LATENCY_MSEC = %s\n", minLatencyText )); + minLatencyMsec = atoi( minLatencyText ); + if( minLatencyMsec < 1 ) minLatencyMsec = 1; + else if( minLatencyMsec > 5000 ) minLatencyMsec = 5000; + } + + minBuffers = (int) ((minLatencyMsec * framesPerSecond) / ( 1000.0 * framesPerBuffer )); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} + +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + int tmp; + int flags; + int numBytes, maxChannels; + unsigned int minNumBuffers; + internalPortAudioDevice *pad; + DBUG(("PaHost_OpenStream() called.\n" )); + + /* 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->pahsc_OutputHandle = BAD_DEVICE_ID; /* No device currently opened. */ + pahsc->pahsc_InputHandle = BAD_DEVICE_ID; + + /* Allocate native buffers. */ + pahsc->pahsc_BytesPerInputBuffer = past->past_FramesPerUserBuffer * + past->past_NumInputChannels * sizeof(short); + if( past->past_NumInputChannels > 0) + { + pahsc->pahsc_NativeInputBuffer = (short *) malloc(pahsc->pahsc_BytesPerInputBuffer); + if( pahsc->pahsc_NativeInputBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + pahsc->pahsc_BytesPerOutputBuffer = past->past_FramesPerUserBuffer * + past->past_NumOutputChannels * sizeof(short); + if( past->past_NumOutputChannels > 0) + { + pahsc->pahsc_NativeOutputBuffer = (short *) malloc(pahsc->pahsc_BytesPerOutputBuffer); + if( pahsc->pahsc_NativeOutputBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + + /* DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); */ + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + + pahsc->pahsc_InverseMicrosPerBuffer = past->past_SampleRate / (1000000.0 * past->past_FramesPerUserBuffer); + DBUG(("pahsc_InverseMicrosPerBuffer = %g\n", pahsc->pahsc_InverseMicrosPerBuffer )); + + /* ------------------------- OPEN DEVICE -----------------------*/ + + /* just output */ + if (past->past_OutputDeviceID == past->past_InputDeviceID) + { + + if ((past->past_NumOutputChannels > 0) && (past->past_NumInputChannels > 0) ) + { + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_RDWR\n", pad->pad_DeviceName )); + pahsc->pahsc_OutputHandle = pahsc->pahsc_InputHandle = + open(pad->pad_DeviceName,O_RDWR); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDWR\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_OutputHandle, + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumOutputChannels ); + result = Pa_SetupDeviceFormat( pahsc->pahsc_OutputHandle, + past->past_NumOutputChannels, (int)past->past_SampleRate ); + } + } + else + { + if (past->past_NumOutputChannels > 0) + { + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_WRONLY\n", pad->pad_DeviceName )); + pahsc->pahsc_OutputHandle = open(pad->pad_DeviceName,O_WRONLY); + if(pahsc->pahsc_OutputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_WRONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_OutputHandle, + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumOutputChannels ); + result = Pa_SetupDeviceFormat( pahsc->pahsc_OutputHandle, + past->past_NumOutputChannels, (int)past->past_SampleRate ); + } + + if (past->past_NumInputChannels > 0) + { + pad = Pa_GetInternalDevice( past->past_InputDeviceID ); + DBUG(("PaHost_OpenStream: attempt to open %s for O_RDONLY\n", pad->pad_DeviceName )); + pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDONLY); + if(pahsc->pahsc_InputHandle==-1) + { + ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDONLY\n", pad->pad_DeviceName )); + result = paHostError; + goto error; + } + Pa_SetLatency( pahsc->pahsc_InputHandle, /* DH20010115 - was OutputHandle! */ + past->past_NumUserBuffers, past->past_FramesPerUserBuffer, + past->past_NumInputChannels ); + result = Pa_SetupDeviceFormat( pahsc->pahsc_InputHandle, + past->past_NumInputChannels, (int)past->past_SampleRate ); + } + } + + + DBUG(("PaHost_OpenStream: SUCCESS - result = %d\n", result )); + return result; + +error: + ERR_RPT(("PaHost_OpenStream: ERROR - result = %d\n", result )); + PaHost_CloseStream( past ); + return result; +} + +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + int hres; + + pahsc = (PaHostSoundControl *) past->past_DeviceData; + + past->past_StopSoon = 0; + past->past_StopNow = 0; + past->past_IsActive = 1; + + /* Use pthread_create() instead of __clone() because: + * - pthread_create also works for other UNIX systems like Solaris, + * - the Java HotSpot VM crashes in pthread_setcanceltype() when using __clone() + */ + hres = pthread_create(&(pahsc->pahsc_ThreadPID), + NULL /*pthread_attr_t * attr*/, + (void*)Pa_AudioThreadProc, past); + if( hres != 0 ) + { + result = paHostError; + sPaHostError = hres; + goto error; + } + +error: + return result; +} + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int hres; + long timeOut; + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + + if( pahsc == NULL ) return paNoError; + + /* 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; + + /* Join thread to recover memory resources. */ + if( pahsc->pahsc_ThreadPID != -1 ) + { + /* This check is needed for GNUSTEP - SB20010904 */ + if ( !pthread_equal( pahsc->pahsc_ThreadPID, pthread_self() ) ) + { + hres = pthread_join( pahsc->pahsc_ThreadPID, NULL ); + } + else + { + DBUG(("Play thread was stopped from itself - can't do pthread_join()\n")); + hres = 0; + } + + if( hres != 0 ) + { + result = paHostError; + sPaHostError = hres; + } + pahsc->pahsc_ThreadPID = -1; + } + + past->past_IsActive = 0; + + return result; +} + +/*************************************************************************/ +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; + + if( pahsc->pahsc_OutputHandle != BAD_DEVICE_ID ) + { + int err; + DBUG(("PaHost_CloseStream: attempt to close output device handle = %d\n", + pahsc->pahsc_OutputHandle )); + err = close(pahsc->pahsc_OutputHandle); + if( err < 0 ) + { + ERR_RPT(("PaHost_CloseStream: warning, closing output device failed.\n")); + } + } + + if( (pahsc->pahsc_InputHandle != BAD_DEVICE_ID) && + (pahsc->pahsc_InputHandle != pahsc->pahsc_OutputHandle) ) + { + int err; + DBUG(("PaHost_CloseStream: attempt to close input device handle = %d\n", + pahsc->pahsc_InputHandle )); + err = close(pahsc->pahsc_InputHandle); + if( err < 0 ) + { + ERR_RPT(("PaHost_CloseStream: warning, closing input device failed.\n")); + } + } + pahsc->pahsc_OutputHandle = BAD_DEVICE_ID; + pahsc->pahsc_InputHandle = BAD_DEVICE_ID; + + if( pahsc->pahsc_NativeInputBuffer ) + { + free( pahsc->pahsc_NativeInputBuffer ); + pahsc->pahsc_NativeInputBuffer = NULL; + } + if( pahsc->pahsc_NativeOutputBuffer ) + { + free( pahsc->pahsc_NativeOutputBuffer ); + pahsc->pahsc_NativeOutputBuffer = NULL; + } + + free( pahsc ); + past->past_DeviceData = NULL; + return paNoError; +} + +/*************************************************************************/ +PaError PaHost_Term( void ) +{ + /* Free all of the linked devices. */ + internalPortAudioDevice *pad, *nextPad; + pad = sDeviceList; + while( pad != NULL ) + { + nextPad = pad->pad_Next; + DBUG(("PaHost_Term: freeing %s\n", pad->pad_DeviceName )); + PaHost_FreeFastMemory( pad, sizeof(internalPortAudioDevice) ); + pad = nextPad; + } + sDeviceList = NULL; + return 0; +} + +/************************************************************************* + * Sleep for the requested number of milliseconds. + */ +void Pa_Sleep( long msec ) +{ +#if 0 + struct timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + select( 0, NULL, NULL, NULL, &timeout ); +#else + long usecs = msec * 1000; + usleep( usecs ); +#endif +} + +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = malloc( numBytes ); /* FIXME - do we need physical memory? */ + if( addr != NULL ) memset( addr, 0, numBytes ); + return addr; +} + +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr != NULL ) free( addr ); +} + + +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) (past->past_IsActive != 0); +} + +/***********************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + internalPortAudioStream *past = (internalPortAudioStream *) stream; + /* FIXME - return actual frames played, not frames generated. + ** Need to query the output device somehow. + */ + if( past == NULL ) return paBadStreamPtr; + return past->past_FrameCount; +} + +/***********************************************************************/ +long Pa_GetHostError( void ) +{ + return (long) sPaHostError; +} diff --git a/pa_unix_oss/recplay.c b/pa_unix_oss/recplay.c new file mode 100644 index 0000000..9d4c78c --- /dev/null +++ b/pa_unix_oss/recplay.c @@ -0,0 +1,114 @@ +/* + * recplay.c + * Phil Burk + * Minimal record and playback test. + * + */ +#include +#include +#include +#ifndef __STDC__ +/* #include */ +#endif /* __STDC__ */ +#include +#ifdef __STDC__ +#include +#else /* __STDC__ */ +#include +#endif /* __STDC__ */ +#include + +#define NUM_BYTES (64*1024) +#define BLOCK_SIZE (4*1024) + +#define AUDIO "/dev/dsp" + +char buffer[NUM_BYTES]; + +int audioDev = 0; + +main (int argc, char *argv[]) +{ + int numLeft; + char *ptr; + int num; + int samplesize; + + /********** RECORD ********************/ + /* Open audio device. */ + audioDev = open (AUDIO, O_RDONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Record in blocks */ + printf("Begin recording.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = read (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Read %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); + + /********** PLAYBACK ********************/ + /* Open audio device for writing. */ + audioDev = open (AUDIO, O_WRONLY, 0); + if (audioDev == -1) + { + perror (AUDIO); + exit (-1); + } + + /* Set to 16 bit samples. */ + samplesize = 16; + ioctl(audioDev, SNDCTL_DSP_SAMPLESIZE, &samplesize); + if (samplesize != 16) + { + perror("Unable to set the sample size."); + exit(-1); + } + + /* Play in blocks */ + printf("Begin playing.\n"); + numLeft = NUM_BYTES; + ptr = buffer; + while( numLeft >= BLOCK_SIZE ) + { + if ( (num = write (audioDev, ptr, BLOCK_SIZE)) < 0 ) + { + perror (AUDIO); + exit (-1); + } + else + { + printf("Wrote %d bytes\n", num); + ptr += num; + numLeft -= num; + } + } + + close( audioDev ); +} diff --git a/pa_win_ds/dsound_wrapper.c b/pa_win_ds/dsound_wrapper.c new file mode 100644 index 0000000..e795525 --- /dev/null +++ b/pa_win_ds/dsound_wrapper.c @@ -0,0 +1,466 @@ +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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 +#define INITGUID // Needed to build IID_IDirectSoundNotify. See objbase.h for info. +#include +#include +#include "dsound_wrapper.h" +#include "pa_trace.h" + +/************************************************************************************/ +void DSW_Term( DSoundWrapper *dsw ) +{ + // Cleanup the sound buffers + if (dsw->dsw_OutputBuffer) + { + IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + IDirectSoundBuffer_Release( dsw->dsw_OutputBuffer ); + dsw->dsw_OutputBuffer = NULL; + } +#if SUPPORT_AUDIO_CAPTURE + if (dsw->dsw_InputBuffer) + { + IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + IDirectSoundCaptureBuffer_Release( dsw->dsw_InputBuffer ); + dsw->dsw_InputBuffer = NULL; + } + if (dsw->dsw_pDirectSoundCapture) + { + IDirectSoundCapture_Release( dsw->dsw_pDirectSoundCapture ); + dsw->dsw_pDirectSoundCapture = NULL; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + if (dsw->dsw_pDirectSound) + { + IDirectSound_Release( dsw->dsw_pDirectSound ); + dsw->dsw_pDirectSound = NULL; + } +} +/************************************************************************************/ +HRESULT DSW_Init( DSoundWrapper *dsw ) +{ + memset( dsw, 0, sizeof(DSoundWrapper) ); + return 0; +} +/************************************************************************************/ +HRESULT DSW_InitOutputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + // Create the DS object + HRESULT hr = DirectSoundCreate( lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer ) +{ + DWORD dwDataLen; + DWORD playCursor; + HRESULT result; + LPDIRECTSOUNDBUFFER pPrimaryBuffer; + HWND hWnd; + HRESULT hr; + WAVEFORMATEX wfFormat; + DSBUFFERDESC primaryDesc; + DSBUFFERDESC secondaryDesc; + unsigned char* pDSBuffData; + LARGE_INTEGER counterFrequency; + dsw->dsw_OutputSize = bytesPerBuffer; + dsw->dsw_OutputRunning = FALSE; + dsw->dsw_OutputUnderflows = 0; + dsw->dsw_FramesWritten = 0; + dsw->dsw_BytesPerFrame = nChannels * sizeof(short); + // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the + // applications's window. Also if that window is closed before the Buffer is closed + // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.) + // So we will use GetDesktopWindow() which was suggested by Miller Puckette. + // hWnd = GetForegroundWindow(); + hWnd = GetDesktopWindow(); + // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz. + // Exclusize also prevents unexpected sounds from other apps during a performance. + if ((hr = IDirectSound_SetCooperativeLevel( dsw->dsw_pDirectSound, + hWnd, DSSCL_EXCLUSIVE)) != DS_OK) + { + return hr; + } + // ----------------------------------------------------------------------- + // Create primary buffer and set format just so we can specify our custom format. + // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz. + // Setup the primary buffer description + ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC)); + primaryDesc.dwSize = sizeof(DSBUFFERDESC); + primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth + primaryDesc.dwBufferBytes = 0; + primaryDesc.lpwfxFormat = NULL; + // Create the buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = wfFormat.nChannels * wfFormat.wBitsPerSample / 8; + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + // Set the primary buffer's format + if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); + secondaryDesc.dwSize = sizeof(DSBUFFERDESC); + secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + secondaryDesc.dwBufferBytes = bytesPerBuffer; + secondaryDesc.lpwfxFormat = &wfFormat; + // Create the secondary buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &secondaryDesc, &dsw->dsw_OutputBuffer, NULL)) != DS_OK) return result; + // Lock the DS buffer + if ((result = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, 0, dsw->dsw_OutputSize, (LPVOID*)&pDSBuffData, + &dwDataLen, NULL, 0, 0)) != DS_OK) return result; + // Zero the DS buffer + ZeroMemory(pDSBuffData, dwDataLen); + // Unlock the DS buffer + if ((result = IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result; + if( QueryPerformanceFrequency( &counterFrequency ) ) + { + int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short)); + dsw->dsw_CounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate; + AddTraceMessage("dsw_CounterTicksPerBuffer = %d\n", dsw->dsw_CounterTicksPerBuffer.LowPart ); + } + else + { + dsw->dsw_CounterTicksPerBuffer.QuadPart = 0; + } + // Let DSound set the starting write position because if we set it to zero, it looks like the + // buffer is full to begin with. This causes a long pause before sound starts when using large buffers. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &dsw->dsw_WriteOffset ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_FramesWritten = dsw->dsw_WriteOffset / dsw->dsw_BytesPerFrame; + /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartOutput( DSoundWrapper *dsw ) +{ + HRESULT hr; + QueryPerformanceCounter( &dsw->dsw_LastPlayTime ); + dsw->dsw_LastPlayCursor = 0; + dsw->dsw_FramesPlayed = 0; + hr = IDirectSoundBuffer_SetCurrentPosition( dsw->dsw_OutputBuffer, 0 ); + if( hr != DS_OK ) + { + return hr; + } + // Start the buffer playback in a loop. + if( dsw->dsw_OutputBuffer != NULL ) + { + hr = IDirectSoundBuffer_Play( dsw->dsw_OutputBuffer, 0, 0, DSBPLAY_LOOPING ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_OutputRunning = TRUE; + } + + return 0; +} +/************************************************************************************/ +HRESULT DSW_StopOutput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_OutputBuffer != NULL ) + { + dsw->dsw_OutputRunning = FALSE; + return IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long numBytesEmpty; + long playWriteGap; + // Query to see how much room is in buffer. + // Note: Even though writeCursor is not used, it must be passed to prevent DirectSound from dieing + // under WinNT. The Microsoft documentation says we can pass NULL but apparently not. + // Thanks to Max Rheiner for the fix. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + AddTraceMessage("playCursor", playCursor); + AddTraceMessage("dsw_WriteOffset", dsw->dsw_WriteOffset); + // Determine size of gap between playIndex and WriteIndex that we cannot write into. + playWriteGap = writeCursor - playCursor; + if( playWriteGap < 0 ) playWriteGap += dsw->dsw_OutputSize; // unwrap + /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */ + /* Attempt to detect playCursor wrap-around and correct it. */ + if( dsw->dsw_OutputRunning && (dsw->dsw_CounterTicksPerBuffer.QuadPart != 0) ) + { + /* How much time has elapsed since last check. */ + LARGE_INTEGER currentTime; + LARGE_INTEGER elapsedTime; + long bytesPlayed; + long bytesExpected; + long buffersWrapped; + QueryPerformanceCounter( ¤tTime ); + elapsedTime.QuadPart = currentTime.QuadPart - dsw->dsw_LastPlayTime.QuadPart; + dsw->dsw_LastPlayTime = currentTime; + /* How many bytes does DirectSound say have been played. */ + bytesPlayed = playCursor - dsw->dsw_LastPlayCursor; + if( bytesPlayed < 0 ) bytesPlayed += dsw->dsw_OutputSize; // unwrap + dsw->dsw_LastPlayCursor = playCursor; + /* Calculate how many bytes we would have expected to been played by now. */ + bytesExpected = (long) ((elapsedTime.QuadPart * dsw->dsw_OutputSize) / dsw->dsw_CounterTicksPerBuffer.QuadPart); + buffersWrapped = (bytesExpected - bytesPlayed) / dsw->dsw_OutputSize; + if( buffersWrapped > 0 ) + { + AddTraceMessage("playCursor wrapped! bytesPlayed", bytesPlayed ); + AddTraceMessage("playCursor wrapped! bytesExpected", bytesExpected ); + playCursor += (buffersWrapped * dsw->dsw_OutputSize); + bytesPlayed += (buffersWrapped * dsw->dsw_OutputSize); + } + /* Maintain frame output cursor. */ + dsw->dsw_FramesPlayed += (bytesPlayed / dsw->dsw_BytesPerFrame); + } + numBytesEmpty = playCursor - dsw->dsw_WriteOffset; + if( numBytesEmpty < 0 ) numBytesEmpty += dsw->dsw_OutputSize; // unwrap offset + /* Have we underflowed? */ + if( numBytesEmpty > (dsw->dsw_OutputSize - playWriteGap) ) + { + if( dsw->dsw_OutputRunning ) + { + dsw->dsw_OutputUnderflows += 1; + AddTraceMessage("underflow detected! numBytesEmpty", numBytesEmpty ); + } + dsw->dsw_WriteOffset = writeCursor; + numBytesEmpty = dsw->dsw_OutputSize - playWriteGap; + } + *bytesEmpty = numBytesEmpty; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + long bytesEmpty; + hr = DSW_QueryOutputSpace( dsw, &bytesEmpty ); // updates dsw_FramesPlayed + if (hr != DS_OK) return hr; + if( bytesEmpty == 0 ) return DS_OK; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, bytesEmpty, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + ZeroMemory(lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + ZeroMemory(lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += bytesEmpty / dsw->dsw_BytesPerFrame; + } + return hr; +} + +/************************************************************************************/ +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + CopyMemory(lpbuf1, buf, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory(lpbuf2, buf+dwsize1, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += numBytes / dsw->dsw_BytesPerFrame; + } + return hr; +} + +/************************************************************************************/ +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ) +{ + DWORD status; + if (IDirectSoundBuffer_GetStatus( dsw->dsw_OutputBuffer, &status ) != DS_OK) + return( DSERR_INVALIDPARAM ); + else + return( status ); +} + +#if SUPPORT_AUDIO_CAPTURE +/* These routines are used to support audio input. + * Do NOT compile these calls when using NT4 because it does + * not support the entry points. + */ +/************************************************************************************/ +HRESULT DSW_InitInputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + HRESULT hr = DirectSoundCaptureCreate( lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} +/************************************************************************************/ +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer ) +{ + DSCBUFFERDESC captureDesc; + WAVEFORMATEX wfFormat; + HRESULT result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = wfFormat.nChannels * (wfFormat.wBitsPerSample / 8); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + dsw->dsw_InputSize = bytesPerBuffer; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); + captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerBuffer; + captureDesc.lpwfxFormat = &wfFormat; + // Create the capture buffer + if ((result = IDirectSoundCapture_CreateCaptureBuffer( dsw->dsw_pDirectSoundCapture, + &captureDesc, &dsw->dsw_InputBuffer, NULL)) != DS_OK) return result; + dsw->dsw_ReadOffset = 0; // reset last read position to start of buffer + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartInput( DSoundWrapper *dsw ) +{ + // Start the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Start( dsw->dsw_InputBuffer, DSCBSTART_LOOPING ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_StopInput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ) +{ + HRESULT hr; + DWORD capturePos; + DWORD readPos; + long filled; + // Query to see how much data is in buffer. + // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundCaptureBuffer_GetCurrentPosition( dsw->dsw_InputBuffer, &capturePos, &readPos ); + if( hr != DS_OK ) + { + return hr; + } + filled = readPos - dsw->dsw_ReadOffset; + if( filled < 0 ) filled += dsw->dsw_InputSize; // unwrap offset + *bytesFilled = filled; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, dsw->dsw_ReadOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy from DS to the buffer + CopyMemory( buf, lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory( buf+dwsize1, lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + dwsize1 + dwsize2) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock ( dsw->dsw_InputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + } + return hr; +} + +#endif /* SUPPORT_AUDIO_CAPTURE */ diff --git a/pa_win_ds/dsound_wrapper.h b/pa_win_ds/dsound_wrapper.h new file mode 100644 index 0000000..e926514 --- /dev/null +++ b/pa_win_ds/dsound_wrapper.h @@ -0,0 +1,106 @@ +#ifndef __DSOUND_WRAPPER_H +#define __DSOUND_WRAPPER_H +/* + * $Id$ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * For PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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 +#if !defined(BOOL) +#define BOOL short +#endif +#ifndef SUPPORT_AUDIO_CAPTURE +#define SUPPORT_AUDIO_CAPTURE (1) +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#define DSW_NUM_POSITIONS (4) +#define DSW_NUM_EVENTS (5) +#define DSW_TERMINATION_EVENT (DSW_NUM_POSITIONS) + +typedef struct +{ + /* Output */ + LPDIRECTSOUND dsw_pDirectSound; + LPDIRECTSOUNDBUFFER dsw_OutputBuffer; + DWORD dsw_WriteOffset; /* last write position */ + INT dsw_OutputSize; + INT dsw_BytesPerFrame; + /* Try to detect play buffer underflows. */ + LARGE_INTEGER dsw_CounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ + LARGE_INTEGER dsw_LastPlayTime; + UINT dsw_LastPlayCursor; + UINT dsw_OutputUnderflows; + BOOL dsw_OutputRunning; + /* use double which lets us can play for several thousand years with enough precision */ + double dsw_FramesWritten; + double dsw_FramesPlayed; +#if SUPPORT_AUDIO_CAPTURE + /* Input */ + LPDIRECTSOUNDCAPTURE dsw_pDirectSoundCapture; + LPDIRECTSOUNDCAPTUREBUFFER dsw_InputBuffer; + UINT dsw_ReadOffset; /* last read position */ + UINT dsw_InputSize; +#endif /* SUPPORT_AUDIO_CAPTURE */ + +} +DSoundWrapper; +HRESULT DSW_Init( DSoundWrapper *dsw ); +void DSW_Term( DSoundWrapper *dsw ); +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + int nChannels, int bufSize ); +HRESULT DSW_StartOutput( DSoundWrapper *dsw ); +HRESULT DSW_StopOutput( DSoundWrapper *dsw ); +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ); +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ); +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ); +HRESULT DSW_Enumerate( DSoundWrapper *dsw ); + +#if SUPPORT_AUDIO_CAPTURE +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + int nChannels, int bufSize ); +HRESULT DSW_StartInput( DSoundWrapper *dsw ); +HRESULT DSW_StopInput( DSoundWrapper *dsw ); +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ); +#endif /* SUPPORT_AUDIO_CAPTURE */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DSOUND_WRAPPER_H */ diff --git a/pa_win_ds/pa_dsound.c b/pa_win_ds/pa_dsound.c new file mode 100644 index 0000000..96f4753 --- /dev/null +++ b/pa_win_ds/pa_dsound.c @@ -0,0 +1,983 @@ +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.softsynth.com/portaudio/ + * DirectSound 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. + * + */ +/* Modifications + * 7/19/01 Mike Berry - casts for compiling with __MWERKS__ CodeWarrior + * 9/27/01 Phil Burk - use number of frames instead of real-time for CPULoad calculation. + */ +/* Compiler flags: + SUPPORT_AUDIO_CAPTURE - define this flag if you want to SUPPORT_AUDIO_CAPTURE + */ + +#include +#include +#ifndef __MWERKS__ +#include +#include +#endif //__MWERKS__ +#include +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" +#include "dsound_wrapper.h" +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ +#define PA_USE_HIGH_LATENCY (0) +#if PA_USE_HIGH_LATENCY +#define PA_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif +/* Trigger an underflow for testing purposes. Should normally be (0). */ +#define PA_SIMULATE_UNDERFLOW (0) +#if PA_SIMULATE_UNDERFLOW +static gUnderCallbackCounter = 0; +#define UNDER_START_GAP (10) +#define UNDER_STOP_GAP (UNDER_START_GAP + 4) +#endif +/************************************************* Definitions ********/ +typedef struct internalPortAudioStream internalPortAudioStream; +typedef struct internalPortAudioDevice +{ + GUID pad_GUID; + GUID *pad_lpGUID; + double pad_SampleRates[10]; /* for pointing to from pad_Info FIXME?!*/ + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; +/* Define structure to contain all DirectSound and Windows specific data. */ +typedef struct PaHostSoundControl +{ + DSoundWrapper pahsc_DSoundWrapper; + MMRESULT pahsc_TimerID; + BOOL pahsc_IfInsideCallback; /* Test for reentrancy. */ + short *pahsc_NativeBuffer; + unsigned int pahsc_BytesPerBuffer; /* native buffer size in bytes */ + double pahsc_ValidFramesWritten; + int pahsc_FramesPerDSBuffer; + /* For measuring CPU utilization. */ + LARGE_INTEGER pahsc_EntryCount; + double pahsc_InverseTicksPerUserBuffer; +} +PaHostSoundControl; +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumDevices = 0; +static int sDeviceIndex = 0; +static internalPortAudioDevice *sDevices = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sEnumerationError; +static int sPaHostError = 0; +/************************************************* Prototypes **********/ +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ); +static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); +static Pa_QueryDevices( void ); +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, + DWORD dwUser, DWORD dw1, DWORD dw2); +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); +} +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + LARGE_INTEGER CurrentCount = { 0, 0 }; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* + ** Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + if( QueryPerformanceCounter( &CurrentCount ) ) + { + LONGLONG InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart; + double newUsage = InsideCount * pahsc->pahsc_InverseTicksPerUserBuffer; + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} +/****************************************** END CPU UTILIZATION *******/ +static PaError Pa_QueryDevices( void ) +{ + int numBytes; + sDefaultInputDeviceID = paNoDevice; + sDefaultOutputDeviceID = paNoDevice; + /* Enumerate once just to count devices. */ + sNumDevices = 0; // for default device + DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); +#if SUPPORT_AUDIO_CAPTURE + DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Allocate structures to hold device info. */ + numBytes = sNumDevices * sizeof(internalPortAudioDevice); + sDevices = (internalPortAudioDevice *)PaHost_AllocateFastMemory( numBytes ); /* MEM */ + if( sDevices == NULL ) return paInsufficientMemory; + /* Enumerate again to fill in structures. */ + sDeviceIndex = 0; + sEnumerationError = 0; + DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)0 ); +#if SUPPORT_AUDIO_CAPTURE + if( sEnumerationError != paNoError ) return sEnumerationError; + sEnumerationError = 0; + DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)1 ); +#endif /* SUPPORT_AUDIO_CAPTURE */ + return sEnumerationError; +} +/************************************************************************************/ +long Pa_GetHostError() +{ + return sPaHostError; +} +/************************************************************************************ +** Just count devices so we know how much memory to allocate. +*/ +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + sNumDevices++; + return TRUE; +} +/************************************************************************************ +** Extract capabilities info from each device. +*/ +static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + HRESULT hr; + LPDIRECTSOUND lpDirectSound; +#if SUPPORT_AUDIO_CAPTURE + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; +#endif /* SUPPORT_AUDIO_CAPTURE */ + int isInput = (int) lpContext; /* Passed from Pa_CountDevices() */ + internalPortAudioDevice *pad; + + if( sDeviceIndex >= sNumDevices ) + { + sEnumerationError = paInternalError; + return FALSE; + } + pad = &sDevices[sDeviceIndex]; + /* Copy GUID to static array. Set pointer. */ + if( lpGUID == NULL ) + { + pad->pad_lpGUID = NULL; + } + else + { + memcpy( &pad->pad_GUID, lpGUID, sizeof(GUID) ); + pad->pad_lpGUID = &pad->pad_GUID; + } + pad->pad_Info.sampleRates = pad->pad_SampleRates; /* Point to array. */ + /* Allocate room for descriptive name. */ + if( lpszDesc != NULL ) + { + int len = strlen(lpszDesc); + pad->pad_Info.name = (char *)malloc( len+1 ); + if( pad->pad_Info.name == NULL ) + { + sEnumerationError = paInsufficientMemory; + return FALSE; + } + memcpy( (void *) pad->pad_Info.name, lpszDesc, len+1 ); + } +#if SUPPORT_AUDIO_CAPTURE + if( isInput ) + { + /********** Input ******************************/ + DSCCAPS caps; + if( lpGUID == NULL ) sDefaultInputDeviceID = sDeviceIndex; + hr = DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + pad->pad_Info.maxInputChannels = 0; + DBUG(("Cannot create Capture for %s. Result = 0x%x\n", lpszDesc, hr )); + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); + /* printf("caps.dwFormats = 0x%x\n", caps.dwFormats ); */ + pad->pad_Info.maxInputChannels = caps.dwChannels; + /* Determine sample rates from flags. */ + if( caps.dwChannels == 2 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1S16) pad->pad_SampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2S16) pad->pad_SampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4S16) pad->pad_SampleRates[index++] = 44100.0; + pad->pad_Info.numSampleRates = index; + } + else if( caps.dwChannels == 1 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1M16) pad->pad_SampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2M16) pad->pad_SampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4M16) pad->pad_SampleRates[index++] = 44100.0; + pad->pad_Info.numSampleRates = index; + } + else pad->pad_Info.numSampleRates = 0; + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + } + else +#endif /* SUPPORT_AUDIO_CAPTURE */ + + { + /********** Output ******************************/ + DSCAPS caps; + if( lpGUID == NULL ) sDefaultOutputDeviceID = sDeviceIndex; + /* Create interfaces for each object. */ + hr = DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); + if( hr != DS_OK ) + { + pad->pad_Info.maxOutputChannels = 0; + DBUG(("Cannot create dsound for %s. Result = 0x%x\n", lpszDesc, hr )); + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSound_GetCaps( lpDirectSound, &caps ); + pad->pad_Info.maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + /* Get sample rates. */ + pad->pad_SampleRates[0] = (double) caps.dwMinSecondarySampleRate; + pad->pad_SampleRates[1] = (double) caps.dwMaxSecondarySampleRate; + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) pad->pad_Info.numSampleRates = -1; + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) + { + /* + ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + ** But it supports continuous sampling. + ** So fake range of rates, and hope it really supports it. + */ + pad->pad_SampleRates[0] = 11025.0f; + pad->pad_SampleRates[1] = 48000.0f; + pad->pad_Info.numSampleRates = -1; /* continuous range */ + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex )); + } + else + { + pad->pad_Info.numSampleRates = 1; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + ** But we know that they really support a range of rates! + ** So when we see a ridiculous set of rates, assume it is a range. + */ + pad->pad_Info.numSampleRates = -1; + DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex )); + } + else pad->pad_Info.numSampleRates = 2; + IDirectSound_Release( lpDirectSound ); + } + } + pad->pad_Info.nativeSampleFormats = paInt16; + sDeviceIndex++; + return( TRUE ); +} +/*************************************************************************/ +int Pa_CountDevices() +{ + if( sNumDevices <= 0 ) Pa_Initialize(); + return sNumDevices; +} +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ) +{ + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + return &sDevices[id]; +} +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = Pa_GetInternalDevice( id ); + return &pad->pad_Info ; +} +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sNumDevices == 0 ) + { + return Pa_QueryDevices(); + } + return 0; +} +/************************************************************************* +** Returns recommended device ID. +** On the PC, the recommended device can be specified by the user by +** setting an environment variable. For example, to use device #1. +** +** set PA_RECOMMENDED_OUTPUT_DEVICE=1 +** +** The user should first determine the available device ID by using +** the supplied application "pa_devs". +*/ +#define PA_ENV_BUF_SIZE (32) +#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) +{ + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE]; + PaDeviceID recommendedID = paNoDevice; + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + recommendedID = atoi( envbuf ); + } + return recommendedID; +} +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultInputDeviceID; + } + return result; +} +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultOutputDeviceID; + } + return result; +} +/********************************************************************** +** Make sure that we have queried the device capabilities. +*/ +PaError PaHost_Init( void ) +{ +#if PA_SIMULATE_UNDERFLOW + PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); +#endif + return Pa_MaybeQueryDevices(); +} +static PaError Pa_TimeSlice( internalPortAudioStream *past ) +{ + PaError result = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long numChunks; + HRESULT hresult; + PaHostSoundControl *pahsc; + short *nativeBufPtr; + past->past_NumCallbacks += 1; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + /* How much input data is available? */ +#if SUPPORT_AUDIO_CAPTURE + if( past->past_NumInputChannels > 0 ) + { + DSW_QueryInputFilled( &pahsc->pahsc_DSoundWrapper, &bytesFilled ); + bytesToXfer = bytesFilled; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* How much output room is available? */ + if( past->past_NumOutputChannels > 0 ) + { + DSW_QueryOutputSpace( &pahsc->pahsc_DSoundWrapper, &bytesEmpty ); + bytesToXfer = bytesEmpty; + } + AddTraceMessage( "bytesEmpty ", bytesEmpty ); + /* Choose smallest value if both are active. */ + if( (past->past_NumInputChannels > 0) && (past->past_NumOutputChannels > 0) ) + { + bytesToXfer = ( bytesFilled < bytesEmpty ) ? bytesFilled : bytesEmpty; + } + /* printf("bytesFilled = %d, bytesEmpty = %d, bytesToXfer = %d\n", + bytesFilled, bytesEmpty, bytesToXfer); + */ + /* Quantize to multiples of a buffer. */ + numChunks = bytesToXfer / pahsc->pahsc_BytesPerBuffer; + if( numChunks > (long)(past->past_NumUserBuffers/2) ) + { + numChunks = (long)past->past_NumUserBuffers/2; + } + else if( numChunks < 0 ) + { + numChunks = 0; + } + AddTraceMessage( "numChunks ", numChunks ); + nativeBufPtr = pahsc->pahsc_NativeBuffer; + if( numChunks > 0 ) + { + while( numChunks-- > 0 ) + { + /* Measure usage based on time to process one user buffer. */ + Pa_StartUsageCalculation( past ); +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( past->past_NumInputChannels > 0 ) + { + hresult = DSW_ReadBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); + if( hresult < 0 ) + { + ERR_RPT(("DirectSound ReadBlock failed, hresult = 0x%x\n",hresult)); + sPaHostError = hresult; + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Convert 16 bit native data to user data and call user routine. */ + result = Pa_CallConvertInt16( past, nativeBufPtr, nativeBufPtr ); + if( result != 0) break; + /* Pass native data to DirectSound. */ + if( past->past_NumOutputChannels > 0 ) + { + /* static short DEBUGHACK = 0; + DEBUGHACK += 0x0049; + nativeBufPtr[0] = DEBUGHACK; /* Make buzz to see if DirectSound still running. */ + hresult = DSW_WriteBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); + if( hresult < 0 ) + { + ERR_RPT(("DirectSound WriteBlock failed, result = 0x%x\n",hresult)); + sPaHostError = hresult; + break; + } + } + Pa_EndUsageCalculation( past ); + } + } + return result; +} +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; +#if PA_SIMULATE_UNDERFLOW + gUnderCallbackCounter++; + if( (gUnderCallbackCounter >= UNDER_START_GAP) && + (gUnderCallbackCounter <= UNDER_STOP_GAP) ) + { + if( gUnderCallbackCounter == UNDER_START_GAP) + { + AddTraceMessage("Begin stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); + } + if( gUnderCallbackCounter == UNDER_STOP_GAP) + { + AddTraceMessage("End stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); + } + return; + } +#endif + past = (internalPortAudioStream *) dwUser; + if( past == NULL ) return; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + if( !pahsc->pahsc_IfInsideCallback && past->past_IsActive ) + { + if( past->past_StopNow ) + { + past->past_IsActive = 0; + } + else if( past->past_StopSoon ) + { + DSoundWrapper *dsw = &pahsc->pahsc_DSoundWrapper; + if( past->past_NumOutputChannels > 0 ) + { + DSW_ZeroEmptySpace( dsw ); + AddTraceMessage("Pa_TimerCallback: waiting - written ", (int) dsw->dsw_FramesWritten ); + AddTraceMessage("Pa_TimerCallback: waiting - played ", (int) dsw->dsw_FramesPlayed ); + /* clear past_IsActive when all sound played */ + if( dsw->dsw_FramesPlayed >= past->past_FrameCount ) + { + past->past_IsActive = 0; + } + } + else + { + past->past_IsActive = 0; + } + } + else + { + pahsc->pahsc_IfInsideCallback = 1; + if( Pa_TimeSlice( past ) != 0) /* Call time slice independant of timing method. */ + { + past->past_StopSoon = 1; + } + pahsc->pahsc_IfInsideCallback = 0; + } + } +} +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + HRESULT hr; + PaError result = paNoError; + PaHostSoundControl *pahsc; + int numBytes, maxChannels; + unsigned int minNumBuffers; + internalPortAudioDevice *pad; + DSoundWrapper *dsw; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); /* MEM */ + if( pahsc == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + pahsc->pahsc_TimerID = 0; + dsw = &pahsc->pahsc_DSoundWrapper; + DSW_Init( dsw ); + /* Allocate native buffer. */ + maxChannels = ( past->past_NumOutputChannels > past->past_NumInputChannels ) ? + past->past_NumOutputChannels : past->past_NumInputChannels; + pahsc->pahsc_BytesPerBuffer = past->past_FramesPerUserBuffer * maxChannels * sizeof(short); + if( maxChannels > 0 ) + { + pahsc->pahsc_NativeBuffer = (short *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerBuffer); /* MEM */ + if( pahsc->pahsc_NativeBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + else + { + result = paInvalidChannelCount; + goto error; + } + + DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + numBytes = pahsc->pahsc_BytesPerBuffer * past->past_NumUserBuffers; + if( numBytes < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + if( numBytes > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + pahsc->pahsc_FramesPerDSBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers; + { + int msecLatency = (int) ((pahsc->pahsc_FramesPerDSBuffer * 1000) / past->past_SampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", pahsc->pahsc_FramesPerDSBuffer, msecLatency )); + } + /* ------------------ OUTPUT */ + if( (past->past_OutputDeviceID >= 0) && (past->past_NumOutputChannels > 0) ) + { + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); + /* If this fails, then try each output device until we find one that works. */ + if( hr != DS_OK ) + { + int i; + ERR_RPT(("Creation of requested Audio Output device '%s' failed.\n", + ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); + for( i=0; ipad_Info.maxOutputChannels >= past->past_NumOutputChannels ) + { + DBUG(("Try device '%s' instead.\n", pad->pad_Info.name )); + hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr == DS_OK ) + { + ERR_RPT(("Using device '%s' instead.\n", pad->pad_Info.name )); + break; + } + } + } + } + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paHostError; + sPaHostError = hr; + goto error; + } + hr = DSW_InitOutputBuffer( dsw, + (unsigned long) (past->past_SampleRate + 0.5), + past->past_NumOutputChannels, numBytes ); + DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } + past->past_FrameCount = pahsc->pahsc_DSoundWrapper.dsw_FramesWritten; + } +#if SUPPORT_AUDIO_CAPTURE + /* ------------------ INPUT */ + if( (past->past_InputDeviceID >= 0) && (past->past_NumInputChannels > 0) ) + { + pad = Pa_GetInternalDevice( past->past_InputDeviceID ); + hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + /* If this fails, then try each input device until we find one that works. */ + if( hr != DS_OK ) + { + int i; + ERR_RPT(("Creation of requested Audio Capture device '%s' failed.\n", + ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); + for( i=0; ipad_Info.maxInputChannels >= past->past_NumInputChannels ) + { + PRINT(("Try device '%s' instead.\n", pad->pad_Info.name )); + hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr == DS_OK ) break; + } + } + } + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paHostError; + sPaHostError = hr; + goto error; + } + hr = DSW_InitInputBuffer( dsw, + (unsigned long) (past->past_SampleRate + 0.5), + past->past_NumInputChannels, numBytes ); + DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); + result = paHostError; + sPaHostError = hr; + goto error; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Calculate scalar used in CPULoad calculation. */ + { + LARGE_INTEGER frequency; + if( QueryPerformanceFrequency( &frequency ) == 0 ) + { + pahsc->pahsc_InverseTicksPerUserBuffer = 0.0; + } + else + { + pahsc->pahsc_InverseTicksPerUserBuffer = past->past_SampleRate / + ( (double)frequency.QuadPart * past->past_FramesPerUserBuffer ); + DBUG(("pahsc_InverseTicksPerUserBuffer = %g\n", pahsc->pahsc_InverseTicksPerUserBuffer )); + } + } + return result; +error: + PaHost_CloseStream( past ); + return result; +} +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + HRESULT hr; + PaHostSoundControl *pahsc; + PaError result = paNoError; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Give user callback a chance to pre-fill buffer. */ + result = Pa_TimeSlice( past ); + if( result != paNoError ) return result; // FIXME - what if finished? + hr = DSW_StartOutput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaError result = paNoError; +#if SUPPORT_AUDIO_CAPTURE + HRESULT hr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + hr = DSW_StartInput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("Pa_StartStream: DSW_StartInput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } +error: +#endif /* SUPPORT_AUDIO_CAPTURE */ + return result; +} +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + past->past_StopNow = 0; + past->past_StopSoon = 0; + past->past_IsActive = 1; + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int msecPerBuffer; + int resolution; + int bufsPerInterrupt = past->past_NumUserBuffers/4; + if( bufsPerInterrupt < 1 ) bufsPerInterrupt = 1; + msecPerBuffer = 1000 * (bufsPerInterrupt * past->past_FramesPerUserBuffer) / (int) past->past_SampleRate; + if( msecPerBuffer < 10 ) msecPerBuffer = 10; + else if( msecPerBuffer > 100 ) msecPerBuffer = 100; + resolution = msecPerBuffer/4; + pahsc->pahsc_TimerID = timeSetEvent( msecPerBuffer, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) past, TIME_PERIODIC ); + } + if( pahsc->pahsc_TimerID == 0 ) + { + past->past_IsActive = 0; + result = paHostError; + sPaHostError = 0; + goto error; + } +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int timeoutMsec; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + if( abort ) past->past_StopNow = 1; + past->past_StopSoon = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * pahsc->pahsc_FramesPerDSBuffer / past->past_SampleRate); + while( past->past_IsActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( pahsc->pahsc_TimerID != 0 ) + { + timeKillEvent(pahsc->pahsc_TimerID); /* Stop callback timer. */ + pahsc->pahsc_TimerID = 0; + } + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ +#if SUPPORT_AUDIO_CAPTURE + HRESULT hr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + hr = DSW_StopInput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("DSW_StopInput() result is %x\n", hr)); +#endif /* SUPPORT_AUDIO_CAPTURE */ + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + HRESULT hr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + hr = DSW_StopOutput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("DSW_StopOutput() result is %x\n", hr)); + return paNoError; +} +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + DSW_Term( &pahsc->pahsc_DSoundWrapper ); + if( pahsc->pahsc_NativeBuffer ) + { + PaHost_FreeFastMemory( pahsc->pahsc_NativeBuffer, pahsc->pahsc_BytesPerBuffer ); /* MEM */ + pahsc->pahsc_NativeBuffer = NULL; + } + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); /* MEM */ + past->past_DeviceData = NULL; + return paNoError; +} +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be determined +** based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hostVersion; + DWORD hresult; + int minLatencyMsec = 0; + double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate; + int minBuffers; + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); + } + else + { + /* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ + hostVersion = GetVersion(); + /* High bit clear if NT */ + minLatencyMsec = ( (hostVersion & 0x80000000) == 0 ) ? PA_WIN_NT_LATENCY : PA_WIN_9X_LATENCY ; +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} +/*************************************************************************/ +PaError PaHost_Term( void ) +{ + int i; + /* Free names allocated during enumeration. */ + for( i=0; ipast_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) (past->past_IsActive); +} +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + DSoundWrapper *dsw; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + dsw = &pahsc->pahsc_DSoundWrapper; + return dsw->dsw_FramesPlayed; +} diff --git a/pa_win_ds/portaudio.def b/pa_win_ds/portaudio.def new file mode 100644 index 0000000..8012b99 --- /dev/null +++ b/pa_win_ds/portaudio.def @@ -0,0 +1,28 @@ +LIBRARY PortAudio +DESCRIPTION 'PortAudio Portable interface to audio HW' + +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 + + ;123456789012345678901234567890123456 + ;000000000111111111122222222223333333 + + diff --git a/pa_win_wmme/Makefile.cygwin b/pa_win_wmme/Makefile.cygwin new file mode 100644 index 0000000..5cb4ace --- /dev/null +++ b/pa_win_wmme/Makefile.cygwin @@ -0,0 +1,34 @@ + +# Makefile for PortAudio on cygwin +# Contributed by Bill Eldridge on 6/13/2001 + +ARCH= pa_win_wmme + +TESTS:= $(wildcard pa_tests/pa*.c pa_tests/debug*.c) + +.c.o: + -gcc -c -I./pa_common $< -o $*.o + -gcc $*.o -o $*.exe -L/usr/local/lib -L$(ARCH) -lportaudio.dll -lwinmm + +all: sharedlib tests + +sharedlib: ./pa_common/pa_lib.c + gcc -c -I./pa_common pa_common/pa_lib.c -o pa_common/pa_lib.o + gcc -c -I./pa_common pa_win_wmme/pa_win_wmme.c -o pa_win_wmme/pa_win_wmme.o + dlltool --export-all --output-def pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o + gcc -shared -Wl,--enable-auto-image-base -o pa_win_wmme/portaudio.dll -Wl,--out-implib=pa_win_wmme/libportaudio.dll.a pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o -L/usr/lib/w32api -lwinmm + cp pa_win_wmme/portaudio.dll /usr/local/bin + +tests: $(TESTS:.c=.o) + +sine: + gcc -c -I./pa_common pa_tests/patest_sine.c -o pa_tests/patest_sine.o + gcc pa_tests/patest_sine.o -o pa_tests/patest_sine.exe -L/usr/local/lib -lportaudio.dll -lwinmm + +clean: + -rm ./pa_tests/*.exe + +nothing: + gcc pa_tests/patest_sine.o -L/usr/lib/w32api -L./pa_win_wmme -lportaudio.dll -lwinmm + + diff --git a/pa_win_wmme/pa_win_wmme.c b/pa_win_wmme/pa_win_wmme.c new file mode 100644 index 0000000..2b3dea2 --- /dev/null +++ b/pa_win_wmme/pa_win_wmme.c @@ -0,0 +1,1493 @@ +/* + * $Id$ + * pa_win_wmme.c + * Implementation of PortAudio for Windows MultiMedia Extensions (WMME) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Authors: Ross Bencina and Phil Burk + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtainingF + * 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. + * + */ +/* + All memory allocations and frees are marked with MEM for quick review. +*/ + +/* Modification History: + PLB = Phil Burk + JM = Julien Maillard + PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer) + PLB20010413 - check for excessive numbers of channels + PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC + including condition including of memory.h, + and explicit typecasting on memory allocation + PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory + PLB20010816 - pass process instead of thread to SetPriorityClass() + PLB20010927 - use number of frames instead of real-time for CPULoad calculation. + JM20020118 - prevent hung thread when buffers underflow. +*/ + +#include +#include +#include +#include +#include +#include +/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */ +#ifndef __MWERKS__ +#include +#include +#endif /* __MWERKS__ */ +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" + +/************************************************* Constants ********/ +#define PA_USE_TIMER_CALLBACK (0) /* Select between two options for background task. 0=thread, 1=timer */ +#define PA_USE_HIGH_LATENCY (0) /* For debugging glitches. */ +/* Switches for debugging. */ +#define PA_SIMULATE_UNDERFLOW (0) /* Set to one to force an underflow of the output buffer. */ +/* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */ +#define PA_TRACE_RUN (0) +#define PA_TRACE_START_STOP (1) +#if PA_USE_HIGH_LATENCY + #define PA_MIN_MSEC_PER_HOST_BUFFER (100) + #define PA_MAX_MSEC_PER_HOST_BUFFER (300) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS (4) + #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY (400) +#else + #define PA_MIN_MSEC_PER_HOST_BUFFER (10) + #define PA_MAX_MSEC_PER_HOST_BUFFER (100) /* Do not exceed unless user buffer exceeds */ + #define PA_MIN_NUM_HOST_BUFFERS (3) + #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */ + #define PA_WIN_9X_LATENCY (200) +#endif +#define MIN_TIMEOUT_MSEC (1000) +/* +** Use higher latency for NT because it is even worse at real-time +** operation than Win9x. +*/ +#define PA_WIN_NT_LATENCY (PA_WIN_9X_LATENCY * 2) + +#if PA_SIMULATE_UNDERFLOW +static gUnderCallbackCounter = 0; +#define UNDER_SLEEP_AT (40) +#define UNDER_SLEEP_FOR (500) +#endif + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ +/************************************************* Definitions ********/ +/************************************************************** + * Structure for internal host specific stream data. + * This is allocated on a per stream basis. + */ +typedef struct PaHostSoundControl +{ + /* Input -------------- */ + HWAVEIN pahsc_HWaveIn; + WAVEHDR *pahsc_InputBuffers; + int pahsc_CurrentInputBuffer; + int pahsc_BytesPerHostInputBuffer; + int pahsc_BytesPerUserInputBuffer; /* native buffer size in bytes */ + /* Output -------------- */ + HWAVEOUT pahsc_HWaveOut; + WAVEHDR *pahsc_OutputBuffers; + int pahsc_CurrentOutputBuffer; + int pahsc_BytesPerHostOutputBuffer; + int pahsc_BytesPerUserOutputBuffer; /* native buffer size in bytes */ + /* Run Time -------------- */ + PaTimestamp pahsc_FramesPlayed; + long pahsc_LastPosition; /* used to track frames played. */ + /* For measuring CPU utilization. */ + LARGE_INTEGER pahsc_EntryCount; + double pahsc_InverseTicksPerHostBuffer; + /* Init Time -------------- */ + int pahsc_NumHostBuffers; + int pahsc_FramesPerHostBuffer; + int pahsc_UserBuffersPerHostBuffer; + CRITICAL_SECTION pahsc_StreamLock; /* Mutext to prevent threads from colliding. */ + INT pahsc_StreamLockInited; +#if PA_USE_TIMER_CALLBACK + BOOL pahsc_IfInsideCallback; /* Test for reentrancy. */ + MMRESULT pahsc_TimerID; +#else + HANDLE pahsc_AbortEvent; + int pahsc_AbortEventInited; + HANDLE pahsc_BufferEvent; + int pahsc_BufferEventInited; + HANDLE pahsc_EngineThread; + DWORD pahsc_EngineThreadID; +#endif +} +PaHostSoundControl; +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumInputDevices = 0; +static int sNumOutputDevices = 0; +static int sNumDevices = 0; +static PaDeviceInfo **sDevicePtrs = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sPaHostError = 0; +static const char sMapperSuffixInput[] = " - Input"; +static const char sMapperSuffixOutput[] = " - Output"; +/************************************************* Macros ********/ +/* Convert external PA ID to an internal ID that includes WAVE_MAPPER */ +#define PaDeviceIdToWinId(id) (((id) < sNumInputDevices) ? (id - 1) : (id - sNumInputDevices - 1)) +/************************************************* Prototypes **********/ +static Pa_QueryDevices( void ); +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, + DWORD dwUser, DWORD dw1, DWORD dw2); +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ); +static PaError PaHost_UpdateStreamTime( PaHostSoundControl *pahsc ); +static PaError PaHost_BackgroundManager( internalPortAudioStream *past ); + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); +} +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + LARGE_INTEGER CurrentCount = { 0, 0 }; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* + ** Measure CPU utilization during this callback. Note that this calculation + ** assumes that we had the processor the whole time. + */ +#define LOWPASS_COEFFICIENT_0 (0.9) +#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0) + if( QueryPerformanceCounter( &CurrentCount ) ) + { + LONGLONG InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart; + double newUsage = InsideCount * pahsc->pahsc_InverseTicksPerHostBuffer; + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} +/****************************************** END CPU UTILIZATION *******/ + +static PaError Pa_QueryDevices( void ) +{ + int numBytes; + /* Count the devices and add one extra for the WAVE_MAPPER */ + sNumInputDevices = waveInGetNumDevs() + 1; + sDefaultInputDeviceID = 0; + sNumOutputDevices = waveOutGetNumDevs() + 1; + sDefaultOutputDeviceID = sNumInputDevices; + sNumDevices = sNumInputDevices + sNumOutputDevices; + /* Allocate structures to hold device info. */ + /* PLB20010402 - was allocating too much memory. */ + /* numBytes = sNumDevices * sizeof(PaDeviceInfo); // PLB20010402 */ + numBytes = sNumDevices * sizeof(PaDeviceInfo *); /* PLB20010402 */ + sDevicePtrs = (PaDeviceInfo **) GlobalAlloc( GPTR, numBytes ); /* MEM */ + if( sDevicePtrs == NULL ) return paInsufficientMemory; + return paNoError; +} +/************************************************************************************/ +long Pa_GetHostError() +{ + return sPaHostError; +} +/*************************************************************************/ +int Pa_CountDevices() +{ + if( sNumDevices <= 0 ) Pa_Initialize(); + return sNumDevices; +} +/************************************************************************* +** If a PaDeviceInfo structure has not already been created, +** then allocate one and fill it in for the selected device. +** +** We create one extra input and one extra output device for the WAVE_MAPPER. +** [Does anyone know how to query the default device and get its name?] +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ +#define NUM_STANDARDSAMPLINGRATES 3 /* 11.025, 22.05, 44.1 */ +#define NUM_CUSTOMSAMPLINGRATES 5 /* must be the same number of elements as in the array below */ +#define MAX_NUMSAMPLINGRATES (NUM_STANDARDSAMPLINGRATES+NUM_CUSTOMSAMPLINGRATES) + static DWORD customSamplingRates[] = { 32000, 48000, 64000, 88200, 96000 }; + PaDeviceInfo *deviceInfo; + double *sampleRates; /* non-const ptr */ + int i; + char *s; + + if( id < 0 || id >= sNumDevices ) + return NULL; + if( sDevicePtrs[ id ] != NULL ) + { + return sDevicePtrs[ id ]; + } + deviceInfo = (PaDeviceInfo *)GlobalAlloc( GPTR, sizeof(PaDeviceInfo) ); /* MEM */ + if( deviceInfo == NULL ) return NULL; + deviceInfo->structVersion = 1; + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = 0; + deviceInfo->numSampleRates = 0; + sampleRates = (double*)GlobalAlloc( GPTR, MAX_NUMSAMPLINGRATES * sizeof(double) ); /* MEM */ + deviceInfo->sampleRates = sampleRates; + deviceInfo->nativeSampleFormats = paInt16; /* should query for higher bit depths below */ + if( id < sNumInputDevices ) + { + /* input device */ + int inputMmID = id - 1; /* WAVE_MAPPER is -1 so we start with WAVE_MAPPER */ + WAVEINCAPS wic; + if( waveInGetDevCaps( inputMmID, &wic, sizeof( WAVEINCAPS ) ) != MMSYSERR_NOERROR ) + goto error; + + /* Append I/O suffix to WAVE_MAPPER device. */ + if( inputMmID == WAVE_MAPPER ) + { + s = (char *) GlobalAlloc( GMEM_FIXED, strlen( wic.szPname ) + 1 + sizeof(sMapperSuffixInput) ); /* MEM */ + strcpy( s, wic.szPname ); + strcat( s, sMapperSuffixInput ); + } + else + { + s = (char *) GlobalAlloc( GMEM_FIXED, strlen( wic.szPname ) + 1 ); /* MEM */ + strcpy( s, wic.szPname ); + } + deviceInfo->name = s; + deviceInfo->maxInputChannels = wic.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + ** This happened with an SBLive card on a Windows ME box. + ** If that happens, then force it to 2 channels. PLB20010413 + */ + if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) ) + { + ERR_RPT(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxInputChannels = 2; + } + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on the format flags. */ + if( wic.dwFormats & WAVE_FORMAT_1M16 ||wic.dwFormats & WAVE_FORMAT_1S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 11025.; + if( wic.dwFormats & WAVE_FORMAT_2M16 ||wic.dwFormats & WAVE_FORMAT_2S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 22050.; + if( wic.dwFormats & WAVE_FORMAT_4M16 ||wic.dwFormats & WAVE_FORMAT_4S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 44100.; + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on opening the device successfully. */ + for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ ) + { + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = customSamplingRates[i]; + wfx.wBitsPerSample = 16; + wfx.cbSize = 0; /* ignored */ + wfx.nChannels = (WORD)deviceInfo->maxInputChannels; + wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short); + wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short)); + if( waveInOpen( NULL, inputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR ) + { + sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i]; + } + } + + } + else if( id - sNumInputDevices < sNumOutputDevices ) + { + /* output device */ + int outputMmID = id - sNumInputDevices - 1; + WAVEOUTCAPS woc; + if( waveOutGetDevCaps( outputMmID, &woc, sizeof( WAVEOUTCAPS ) ) != MMSYSERR_NOERROR ) + goto error; + /* Append I/O suffix to WAVE_MAPPER device. */ + if( outputMmID == WAVE_MAPPER ) + { + s = (char *) GlobalAlloc( GMEM_FIXED, strlen( woc.szPname ) + 1 + sizeof(sMapperSuffixOutput) ); /* MEM */ + strcpy( s, woc.szPname ); + strcat( s, sMapperSuffixOutput ); + } + else + { + s = (char *) GlobalAlloc( GMEM_FIXED, strlen( woc.szPname ) + 1 ); /* MEM */ + strcpy( s, woc.szPname ); + } + deviceInfo->name = s; + deviceInfo->maxOutputChannels = woc.wChannels; + /* Sometimes a device can return a rediculously large number of channels. + ** This happened with an SBLive card on a Windows ME box. + ** If that happens, then force it to 2 channels. PLB20010413 + */ + if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) ) + { + ERR_RPT(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels )); + deviceInfo->maxOutputChannels = 2; + } + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on the format flags. */ + if( woc.dwFormats & WAVE_FORMAT_1M16 ||woc.dwFormats & WAVE_FORMAT_1S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 11025.; + if( woc.dwFormats & WAVE_FORMAT_2M16 ||woc.dwFormats & WAVE_FORMAT_2S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 22050.; + if( woc.dwFormats & WAVE_FORMAT_4M16 ||woc.dwFormats & WAVE_FORMAT_4S16 ) + sampleRates[ deviceInfo->numSampleRates++ ] = 44100.; + /* Add a sample rate to the list if we can do stereo 16 bit at that rate + * based on opening the device successfully. */ + for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ ) + { + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nSamplesPerSec = customSamplingRates[i]; + wfx.wBitsPerSample = 16; + wfx.cbSize = 0; /* ignored */ + wfx.nChannels = (WORD)deviceInfo->maxOutputChannels; + wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short); + wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short)); + if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR ) + { + sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i]; + } + } + } + sDevicePtrs[ id ] = deviceInfo; + return deviceInfo; +error: + GlobalFree( sampleRates ); /* MEM */ + GlobalFree( deviceInfo ); /* MEM */ + + return NULL; +} +/************************************************************************* +** Returns recommended device ID. +** On the PC, the recommended device can be specified by the user by +** setting an environment variable. For example, to use device #1. +** +** set PA_RECOMMENDED_OUTPUT_DEVICE=1 +** +** The user should first determine the available device ID by using +** the supplied application "pa_devs". +*/ +#define PA_ENV_BUF_SIZE (32) +#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE") +#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE") +static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName ) +{ + DWORD hresult; + char envbuf[PA_ENV_BUF_SIZE]; + PaDeviceID recommendedID = paNoDevice; + /* Let user determine default device by setting environment variable. */ + hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + recommendedID = atoi( envbuf ); + } + return recommendedID; +} +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sNumDevices == 0 ) + { + return Pa_QueryDevices(); + } + return 0; +} +/********************************************************************** +** Check for environment variable, else query devices and use result. +*/ +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultInputDeviceID; + } + return result; +} +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultOutputDeviceID; + } + return result; +} +/********************************************************************** +** Initialize Host dependant part of API. +*/ +PaError PaHost_Init( void ) +{ +#if PA_SIMULATE_UNDERFLOW + PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); +#endif + return Pa_MaybeQueryDevices(); +} + +/********************************************************************** +** Check WAVE buffers to see if they are done. +** Fill any available output buffers and use any available +** input buffers by calling user callback. +** +** This routine will loop until: +** user callback returns !=0 OR +** all output buffers are filled OR +** past->past_StopSoon is set OR +** an error occurs when calling WMME. +** +** Returns >0 when user requests a stop, <0 on error. +** +*/ +static PaError Pa_TimeSlice( internalPortAudioStream *past ) +{ + PaError result = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long buffersEmpty = 0; + MMRESULT mresult; + char *inBufPtr; + char *outBufPtr; + int gotInput = 0; + int gotOutput = 0; + int i; + int buffersProcessed = 0; + int done = 0; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + + past->past_NumCallbacks += 1; +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", past->past_NumCallbacks ); +#endif + + /* JM20020118 - prevent hung thread when buffers underflow. */ + /* while( !done ) /* BAD */ + while( !done && !past->past_StopSoon ) /* GOOD */ + { +#if PA_SIMULATE_UNDERFLOW + if(gUnderCallbackCounter++ == UNDER_SLEEP_AT) + { + Sleep(UNDER_SLEEP_FOR); + } +#endif + + /* If we are using output, then we need an empty output buffer. */ + gotOutput = 0; + outBufPtr = NULL; + if( past->past_NumOutputChannels > 0 ) + { + if((pahsc->pahsc_OutputBuffers[ pahsc->pahsc_CurrentOutputBuffer ].dwFlags & WHDR_DONE) == 0) + { + break; /* If none empty then bail and try again later. */ + } + else + { + outBufPtr = pahsc->pahsc_OutputBuffers[ pahsc->pahsc_CurrentOutputBuffer ].lpData; + gotOutput = 1; + } + } + /* Use an input buffer if one is available. */ + gotInput = 0; + inBufPtr = NULL; + if( ( past->past_NumInputChannels > 0 ) && + (pahsc->pahsc_InputBuffers[ pahsc->pahsc_CurrentInputBuffer ].dwFlags & WHDR_DONE) ) + { + inBufPtr = pahsc->pahsc_InputBuffers[ pahsc->pahsc_CurrentInputBuffer ].lpData; + gotInput = 1; +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: got input buffer at ", (int)inBufPtr ); + AddTraceMessage("Pa_TimeSlice: got input buffer # ", pahsc->pahsc_CurrentInputBuffer ); +#endif + + } + /* If we can't do anything then bail out. */ + if( !gotInput && !gotOutput ) break; + buffersProcessed += 1; + /* Each Wave buffer contains multiple user buffers so do them all now. */ + /* Base Usage on time it took to process one host buffer. */ + Pa_StartUsageCalculation( past ); + for( i=0; ipahsc_UserBuffersPerHostBuffer; i++ ) + { + if( done ) + { + if( gotOutput ) + { + /* Clear remainder of wave buffer if we are waiting for stop. */ + AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i ); + memset( outBufPtr, 0, pahsc->pahsc_BytesPerUserOutputBuffer ); + } + } + else + { + /* Convert 16 bit native data to user data and call user routine. */ + result = Pa_CallConvertInt16( past, (short *) inBufPtr, (short *) outBufPtr ); + if( result != 0) done = 1; + } + if( gotInput ) inBufPtr += pahsc->pahsc_BytesPerUserInputBuffer; + if( gotOutput) outBufPtr += pahsc->pahsc_BytesPerUserOutputBuffer; + } + Pa_EndUsageCalculation( past ); + /* Send WAVE buffer to Wave Device to be refilled. */ + if( gotInput ) + { + mresult = waveInAddBuffer( pahsc->pahsc_HWaveIn, + &pahsc->pahsc_InputBuffers[ pahsc->pahsc_CurrentInputBuffer ], + sizeof(WAVEHDR) ); + if( mresult != MMSYSERR_NOERROR ) + { + sPaHostError = mresult; + result = paHostError; + break; + } + pahsc->pahsc_CurrentInputBuffer = (pahsc->pahsc_CurrentInputBuffer+1 >= pahsc->pahsc_NumHostBuffers) ? + 0 : pahsc->pahsc_CurrentInputBuffer+1; + } + /* Write WAVE buffer to Wave Device. */ + if( gotOutput ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "Pa_TimeSlice: writing buffer ", pahsc->pahsc_CurrentOutputBuffer ); +#endif + mresult = waveOutWrite( pahsc->pahsc_HWaveOut, + &pahsc->pahsc_OutputBuffers[ pahsc->pahsc_CurrentOutputBuffer ], + sizeof(WAVEHDR) ); + if( mresult != MMSYSERR_NOERROR ) + { + sPaHostError = mresult; + result = paHostError; + break; + } + pahsc->pahsc_CurrentOutputBuffer = (pahsc->pahsc_CurrentOutputBuffer+1 >= pahsc->pahsc_NumHostBuffers) ? + 0 : pahsc->pahsc_CurrentOutputBuffer+1; + } + + } + +#if PA_TRACE_RUN + AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed ); +#endif + return (result != 0) ? result : done; +} + +/*******************************************************************/ +static PaError PaHost_BackgroundManager( internalPortAudioStream *past ) +{ + PaError result = 0; + int i; + int numQueuedOutputBuffers = 0; + PaHostSoundControl *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 ) + { + /* Poll buffer and when all have played then exit thread. */ + /* Count how many output buffers are queued. */ + numQueuedOutputBuffers = 0; + if( past->past_NumOutputChannels > 0 ) + { + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + if( !( pahsc->pahsc_OutputBuffers[ i ].dwFlags & WHDR_DONE) ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: waiting for buffer ", i ); +#endif + numQueuedOutputBuffers++; + } + } + } +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: numQueuedOutputBuffers ", numQueuedOutputBuffers ); +#endif + if( numQueuedOutputBuffers == 0 ) + { + past->past_IsActive = 0; /* Will cause thread to return. */ + } + } + else + { + /* Process full input buffer and fill up empty output buffers. */ + if( (result = Pa_TimeSlice( past )) != 0) + { + /* User callback has asked us to stop. */ +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: TimeSlice() returned ", result ); +#endif + past->past_StopSoon = 1; /* Request that audio play out then stop. */ + result = paNoError; + } + } + + PaHost_UpdateStreamTime( pahsc ); + return result; +} + +#if PA_USE_TIMER_CALLBACK +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + PaError result; + past = (internalPortAudioStream *) dwUser; + if( past == NULL ) return; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + if( pahsc->pahsc_IfInsideCallback ) + { + if( pahsc->pahsc_TimerID != 0 ) + { + timeKillEvent(pahsc->pahsc_TimerID); /* Stop callback timer. */ + pahsc->pahsc_TimerID = 0; + } + return; + } + pahsc->pahsc_IfInsideCallback = 1; + /* Manage flags and audio processing. */ + result = PaHost_BackgroundManager( past ); + if( result != paNoError ) + { + past->past_IsActive = 0; + } + pahsc->pahsc_IfInsideCallback = 0; +} +#else /* PA_USE_TIMER_CALLBACK */ +/*******************************************************************/ +static DWORD WINAPI WinMMPa_OutputThreadProc( void *pArg ) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; + void *inputBuffer=NULL; + HANDLE events[2]; + int numEvents = 0; + DWORD result = 0; + DWORD waitResult; + DWORD numTimeouts = 0; + DWORD timeOut; + past = (internalPortAudioStream *) pArg; + pahsc = (PaHostSoundControl *) past->past_DeviceData; +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: timeoutPeriod", timeoutPeriod ); + AddTraceMessage( "WinMMPa_OutputThreadProc: past_NumUserBuffers", past->past_NumUserBuffers ); +#endif + /* Calculate timeOut as half the time it would take to play all buffers. */ + timeOut = (DWORD) (500.0 * PaHost_GetTotalBufferFrames( past ) / past->past_SampleRate); + /* Get event(s) ready for wait. */ + events[numEvents++] = pahsc->pahsc_BufferEvent; + if( pahsc->pahsc_AbortEventInited ) events[numEvents++] = pahsc->pahsc_AbortEvent; + /* Stay in this thread as long as we are "active". */ + while( past->past_IsActive ) + { + /*******************************************************************/ + /******** WAIT here for an event from WMME or PA *******************/ + /*******************************************************************/ + waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeOut ); + /* Error? */ + if( waitResult == WAIT_FAILED ) + { + sPaHostError = GetLastError(); + result = paHostError; + past->past_IsActive = 0; + } + /* Timeout? Don't stop. Just keep polling for DONE.*/ + else if( waitResult == WAIT_TIMEOUT ) + { +#if PA_TRACE_START_STOP + AddTraceMessage( "WinMMPa_OutputThreadProc: timed out ", numQueuedOutputBuffers ); +#endif + numTimeouts += 1; + } + /* Manage flags and audio processing. */ + result = PaHost_BackgroundManager( past ); + if( result != paNoError ) + { + past->past_IsActive = 0; + } + } + return result; +} +#endif + +/*******************************************************************/ +PaError PaHost_OpenInputStream( internalPortAudioStream *past ) +{ + MMRESULT mr; + PaError result = paNoError; + PaHostSoundControl *pahsc; + int i; + int inputMmId; + int bytesPerInputFrame; + WAVEFORMATEX wfx; + const PaDeviceInfo *pad; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_InputDeviceID)); + pad = Pa_GetDeviceInfo( past->past_InputDeviceID ); + if( pad == NULL ) return paInternalError; + switch( pad->nativeSampleFormats ) + { + case paInt32: + case paFloat32: + bytesPerInputFrame = sizeof(float) * past->past_NumInputChannels; + break; + default: + bytesPerInputFrame = sizeof(short) * past->past_NumInputChannels; + break; + } + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = (WORD) past->past_NumInputChannels; + wfx.nSamplesPerSec = (DWORD) past->past_SampleRate; + wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * past->past_SampleRate); + wfx.nBlockAlign = (WORD)bytesPerInputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/past->past_NumInputChannels) * 8); + wfx.cbSize = 0; + inputMmId = PaDeviceIdToWinId( past->past_InputDeviceID ); +#if PA_USE_TIMER_CALLBACK + mr = waveInOpen( &pahsc->pahsc_HWaveIn, inputMmId, &wfx, + 0, 0, CALLBACK_NULL ); +#else + mr = waveInOpen( &pahsc->pahsc_HWaveIn, inputMmId, &wfx, + (DWORD)pahsc->pahsc_BufferEvent, (DWORD) past, CALLBACK_EVENT ); +#endif + if( mr != MMSYSERR_NOERROR ) + { + ERR_RPT(("PortAudio: PaHost_OpenInputStream() failed!\n")); + result = paHostError; + sPaHostError = mr; + goto error; + } + /* Allocate an array to hold the buffer pointers. */ + pahsc->pahsc_InputBuffers = (WAVEHDR *) GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, sizeof(WAVEHDR)*pahsc->pahsc_NumHostBuffers ); /* MEM */ + if( pahsc->pahsc_InputBuffers == NULL ) + { + result = paInsufficientMemory; + goto error; + } + /* Allocate each buffer. */ + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + pahsc->pahsc_InputBuffers[i].lpData = (char *)GlobalAlloc( GMEM_FIXED, pahsc->pahsc_BytesPerHostInputBuffer ); /* MEM */ + if( pahsc->pahsc_InputBuffers[i].lpData == NULL ) + { + result = paInsufficientMemory; + goto error; + } + pahsc->pahsc_InputBuffers[i].dwBufferLength = pahsc->pahsc_BytesPerHostInputBuffer; + pahsc->pahsc_InputBuffers[i].dwUser = i; + if( ( mr = waveInPrepareHeader( pahsc->pahsc_HWaveIn, &pahsc->pahsc_InputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + } + return result; +error: + return result; +} +/*******************************************************************/ +PaError PaHost_OpenOutputStream( internalPortAudioStream *past ) +{ + MMRESULT mr; + PaError result = paNoError; + PaHostSoundControl *pahsc; + int i; + int outputMmID; + int bytesPerOutputFrame; + WAVEFORMATEX wfx; + const PaDeviceInfo *pad; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); + pad = Pa_GetDeviceInfo( past->past_OutputDeviceID ); + if( pad == NULL ) return paInternalError; + switch( pad->nativeSampleFormats ) + { + case paInt32: + case paFloat32: + bytesPerOutputFrame = sizeof(float) * past->past_NumOutputChannels; + break; + default: + bytesPerOutputFrame = sizeof(short) * past->past_NumOutputChannels; + break; + } + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = (WORD) past->past_NumOutputChannels; + wfx.nSamplesPerSec = (DWORD) past->past_SampleRate; + wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * past->past_SampleRate); + wfx.nBlockAlign = (WORD)bytesPerOutputFrame; + wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/past->past_NumOutputChannels) * 8); + wfx.cbSize = 0; + outputMmID = PaDeviceIdToWinId( past->past_OutputDeviceID ); +#if PA_USE_TIMER_CALLBACK + mr = waveOutOpen( &pahsc->pahsc_HWaveOut, outputMmID, &wfx, + 0, 0, CALLBACK_NULL ); +#else + + pahsc->pahsc_AbortEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + if( pahsc->pahsc_AbortEvent == NULL ) + { + result = paHostError; + sPaHostError = GetLastError(); + goto error; + } + pahsc->pahsc_AbortEventInited = 1; + mr = waveOutOpen( &pahsc->pahsc_HWaveOut, outputMmID, &wfx, + (DWORD)pahsc->pahsc_BufferEvent, (DWORD) past, CALLBACK_EVENT ); +#endif + if( mr != MMSYSERR_NOERROR ) + { + ERR_RPT(("PortAudio: PaHost_OpenOutputStream() failed!\n")); + result = paHostError; + sPaHostError = mr; + goto error; + } + /* Allocate an array to hold the buffer pointers. */ + pahsc->pahsc_OutputBuffers = (WAVEHDR *) GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, sizeof(WAVEHDR)*pahsc->pahsc_NumHostBuffers ); /* MEM */ + if( pahsc->pahsc_OutputBuffers == NULL ) + { + result = paInsufficientMemory; + goto error; + } + /* Allocate each buffer. */ + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + pahsc->pahsc_OutputBuffers[i].lpData = (char *) GlobalAlloc( GMEM_FIXED, pahsc->pahsc_BytesPerHostOutputBuffer ); /* MEM */ + if( pahsc->pahsc_OutputBuffers[i].lpData == NULL ) + { + result = paInsufficientMemory; + goto error; + } + pahsc->pahsc_OutputBuffers[i].dwBufferLength = pahsc->pahsc_BytesPerHostOutputBuffer; + pahsc->pahsc_OutputBuffers[i].dwUser = i; + if( (mr = waveOutPrepareHeader( pahsc->pahsc_HWaveOut, &pahsc->pahsc_OutputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + } + return result; +error: + return result; +} +/*******************************************************************/ +PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + return pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer; +} +/******************************************************************* +* Determine number of WAVE Buffers +* and how many User Buffers we can put into each WAVE buffer. +*/ +static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + unsigned int minNumBuffers; + int minFramesPerHostBuffer; + int maxFramesPerHostBuffer; + int minTotalFrames; + int userBuffersPerHostBuffer; + int framesPerHostBuffer; + int numHostBuffers; + /* Calculate minimum and maximum sizes based on timing and sample rate. */ + minFramesPerHostBuffer = (int) (PA_MIN_MSEC_PER_HOST_BUFFER * past->past_SampleRate / 1000.0); + minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer )); + maxFramesPerHostBuffer = (int) (PA_MAX_MSEC_PER_HOST_BUFFER * past->past_SampleRate / 1000.0); + maxFramesPerHostBuffer = (maxFramesPerHostBuffer + 7) & ~7; + DBUG(("PaHost_CalcNumHostBuffers: maxFramesPerHostBuffer = %d\n", maxFramesPerHostBuffer )); + /* Determine number of user buffers based on minimum latency. */ + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); + minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer; + /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */ + if( (int) past->past_FramesPerUserBuffer < minFramesPerHostBuffer ) + { + userBuffersPerHostBuffer = + (minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) / + past->past_FramesPerUserBuffer; + } + else + { + userBuffersPerHostBuffer = 1; + } + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */ + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* Make sure we have anough WAVE buffers. */ + if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS) + { + numHostBuffers = PA_MIN_NUM_HOST_BUFFERS; + } + else if( (numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) && + ((int) past->past_FramesPerUserBuffer < (maxFramesPerHostBuffer/2) ) ) + { + /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */ + while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) + { + userBuffersPerHostBuffer += 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + /* If we have gone too far, back up one. */ + if( (framesPerHostBuffer > maxFramesPerHostBuffer) || + (numHostBuffers < PA_MAX_NUM_HOST_BUFFERS) ) + { + userBuffersPerHostBuffer -= 1; + framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer; + numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer; + break; + } + } + } + + pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer; + pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer; + pahsc->pahsc_NumHostBuffers = numHostBuffers; + DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers )); + DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer )); + DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers )); +} +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); /* MEM */ + if( pahsc == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + /* Figure out how user buffers fit into WAVE buffers. */ + PaHost_CalcNumHostBuffers( past ); + { + int msecLatency = (int) ((PaHost_GetTotalBufferFrames(past) * 1000) / past->past_SampleRate); + DBUG(("PortAudio on WMME - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(past), msecLatency )); + } + InitializeCriticalSection( &pahsc->pahsc_StreamLock ); + pahsc->pahsc_StreamLockInited = 1; + +#if (PA_USE_TIMER_CALLBACK == 0) + pahsc->pahsc_BufferEventInited = 0; + pahsc->pahsc_BufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( pahsc->pahsc_BufferEvent == NULL ) + { + result = paHostError; + sPaHostError = GetLastError(); + goto error; + } + pahsc->pahsc_BufferEventInited = 1; +#endif /* (PA_USE_TIMER_CALLBACK == 0) */ + /* ------------------ OUTPUT */ + pahsc->pahsc_BytesPerUserOutputBuffer = past->past_FramesPerUserBuffer * past->past_NumOutputChannels * sizeof(short); + pahsc->pahsc_BytesPerHostOutputBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * pahsc->pahsc_BytesPerUserOutputBuffer; + if( (past->past_OutputDeviceID != paNoDevice) && (past->past_NumOutputChannels > 0) ) + { + result = PaHost_OpenOutputStream( past ); + if( result < 0 ) goto error; + } + /* ------------------ INPUT */ + pahsc->pahsc_BytesPerUserInputBuffer = past->past_FramesPerUserBuffer * past->past_NumInputChannels * sizeof(short); + pahsc->pahsc_BytesPerHostInputBuffer = pahsc->pahsc_UserBuffersPerHostBuffer * pahsc->pahsc_BytesPerUserInputBuffer; + if( (past->past_InputDeviceID != paNoDevice) && (past->past_NumInputChannels > 0) ) + { + result = PaHost_OpenInputStream( past ); + if( result < 0 ) goto error; + } + /* Calculate scalar used in CPULoad calculation. */ + { + LARGE_INTEGER frequency; + if( QueryPerformanceFrequency( &frequency ) == 0 ) + { + pahsc->pahsc_InverseTicksPerHostBuffer = 0.0; + } + else + { + pahsc->pahsc_InverseTicksPerHostBuffer = past->past_SampleRate / + ( (double)frequency.QuadPart * past->past_FramesPerUserBuffer * pahsc->pahsc_UserBuffersPerHostBuffer ); + DBUG(("pahsc_InverseTicksPerHostBuffer = %g\n", pahsc->pahsc_InverseTicksPerHostBuffer )); + } + } + return result; +error: + PaHost_CloseStream( past ); + return result; +} +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + MMRESULT mr; + PaHostSoundControl *pahsc; + PaError result = paNoError; + int i; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( past->past_OutputDeviceID != paNoDevice ) + { + if( (mr = waveOutPause( pahsc->pahsc_HWaveOut )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + ZeroMemory( pahsc->pahsc_OutputBuffers[i].lpData, pahsc->pahsc_OutputBuffers[i].dwBufferLength ); + mr = waveOutWrite( pahsc->pahsc_HWaveOut, &pahsc->pahsc_OutputBuffers[i], sizeof(WAVEHDR) ); + if( mr != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + past->past_FrameCount += pahsc->pahsc_FramesPerHostBuffer; + } + pahsc->pahsc_CurrentOutputBuffer = 0; + if( (mr = waveOutRestart( pahsc->pahsc_HWaveOut )) != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + } + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaError result = paNoError; + MMRESULT mr; + int i; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( past->past_InputDeviceID != paNoDevice ) + { + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + mr = waveInAddBuffer( pahsc->pahsc_HWaveIn, &pahsc->pahsc_InputBuffers[i], sizeof(WAVEHDR) ); + if( mr != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + } + pahsc->pahsc_CurrentInputBuffer = 0; + mr = waveInStart( pahsc->pahsc_HWaveIn ); + DBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", hr)); + if( mr != MMSYSERR_NOERROR ) + { + result = paHostError; + sPaHostError = mr; + goto error; + } + } +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaError result = paNoError; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; +#if PA_USE_TIMER_CALLBACK + int resolution; + int bufsPerTimerCallback; + int msecPerBuffer; +#endif /* PA_USE_TIMER_CALLBACK */ + + past->past_StopSoon = 0; + past->past_StopNow = 0; + past->past_IsActive = 1; + pahsc->pahsc_FramesPlayed = 0.0; + pahsc->pahsc_LastPosition = 0; +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result ); +#endif +#if PA_USE_TIMER_CALLBACK + /* Create timer that will wake us up so we can fill the DSound buffer. */ + bufsPerTimerCallback = pahsc->pahsc_NumHostBuffers/4; + if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1; + if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1; + msecPerBuffer = (1000 * bufsPerTimerCallback * + pahsc->pahsc_UserBuffersPerHostBuffer * + past->past_FramesPerUserBuffer ) / (int) past->past_SampleRate; + if( msecPerBuffer < 10 ) msecPerBuffer = 10; + else if( msecPerBuffer > 100 ) msecPerBuffer = 100; + resolution = msecPerBuffer/4; + pahsc->pahsc_TimerID = timeSetEvent( msecPerBuffer, resolution, + (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) past, TIME_PERIODIC ); + if( pahsc->pahsc_TimerID == 0 ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#else /* PA_USE_TIMER_CALLBACK */ + ResetEvent( pahsc->pahsc_AbortEvent ); + /* Create thread that waits for audio buffers to be ready for processing. */ + pahsc->pahsc_EngineThread = CreateThread( 0, 0, WinMMPa_OutputThreadProc, past, 0, &pahsc->pahsc_EngineThreadID ); + if( pahsc->pahsc_EngineThread == NULL ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StartEngine: thread ", (int) pahsc->pahsc_EngineThread ); +#endif + /* I used to pass the thread which was failing. I now pass GetCurrentProcess(). + ** This fix could improve latency for some applications. It could also result in CPU + ** starvation if the callback did too much processing. + ** I also added result checks, so we might see more failures at initialization. + ** Thanks to Alberto di Bene for spotting this. + */ + if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */ + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } + if( !SetThreadPriority( pahsc->pahsc_EngineThread, THREAD_PRIORITY_HIGHEST ) ) + { + result = paHostError; + sPaHostError = GetLastError();; + goto error; + } +#endif +error: + return result; +} + +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int timeOut; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + + /* 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; + + /* Calculate timeOut longer than longest time it could take to play all buffers. */ + timeOut = (DWORD) (1500.0 * PaHost_GetTotalBufferFrames( past ) / past->past_SampleRate); + if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC; + +#if PA_USE_TIMER_CALLBACK + if( (past->past_OutputDeviceID != paNoDevice) && + past->past_IsActive && + (pahsc->pahsc_TimerID != 0) ) + { + /* Wait for IsActive to drop. */ + while( (past->past_IsActive) && (timeOut > 0) ) + { + Sleep(10); + timeOut -= 10; + } + timeKillEvent(pahsc->pahsc_TimerID); /* Stop callback timer. */ + pahsc->pahsc_TimerID = 0; + } +#else /* PA_USE_TIMER_CALLBACK */ +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopEngine: thread ", (int) pahsc->pahsc_EngineThread ); +#endif + if( (past->past_OutputDeviceID != paNoDevice) && + (past->past_IsActive) && + (pahsc->pahsc_EngineThread != NULL) ) + { + DWORD got; + /* Tell background thread to stop generating more data and to let current data play out. */ + DBUG(("PaHost_StopEngine: waiting for background thread.\n")); + got = WaitForSingleObject( pahsc->pahsc_EngineThread, timeOut ); + if( got == WAIT_TIMEOUT ) + { + ERR_RPT(("PaHost_StopEngine: timed out while waiting for background thread to finish.\n")); + return paTimedOut; + } + CloseHandle( pahsc->pahsc_EngineThread ); + pahsc->pahsc_EngineThread = NULL; + } +#endif /* PA_USE_TIMER_CALLBACK */ + + past->past_IsActive = 0; + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ + MMRESULT mr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + if( pahsc->pahsc_HWaveIn != NULL ) + { + mr = waveInReset( pahsc->pahsc_HWaveIn ); + if( mr != MMSYSERR_NOERROR ) + { + sPaHostError = mr; + return paHostError; + } + } + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + MMRESULT mr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_StopOutput: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + if( pahsc->pahsc_HWaveOut != NULL ) + { + mr = waveOutReset( pahsc->pahsc_HWaveOut ); + if( mr != MMSYSERR_NOERROR ) + { + sPaHostError = mr; + return paHostError; + } + } + return paNoError; +} +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + int i; + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; +#if PA_TRACE_START_STOP + AddTraceMessage( "PaHost_CloseStream: pahsc_HWaveOut ", (int) pahsc->pahsc_HWaveOut ); +#endif + /* Free data and device for output. */ + if( pahsc->pahsc_HWaveOut ) + { + if( pahsc->pahsc_OutputBuffers ) + { + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + waveOutUnprepareHeader( pahsc->pahsc_HWaveOut, &pahsc->pahsc_OutputBuffers[i], sizeof(WAVEHDR) ); + GlobalFree( pahsc->pahsc_OutputBuffers[i].lpData ); /* MEM */ + } + GlobalFree( pahsc->pahsc_OutputBuffers ); /* MEM */ + } + waveOutClose( pahsc->pahsc_HWaveOut ); + } + /* Free data and device for input. */ + if( pahsc->pahsc_HWaveIn ) + { + if( pahsc->pahsc_InputBuffers ) + { + for( i=0; ipahsc_NumHostBuffers; i++ ) + { + waveInUnprepareHeader( pahsc->pahsc_HWaveIn, &pahsc->pahsc_InputBuffers[i], sizeof(WAVEHDR) ); + GlobalFree( pahsc->pahsc_InputBuffers[i].lpData ); /* MEM */ + } + GlobalFree( pahsc->pahsc_InputBuffers ); /* MEM */ + } + waveInClose( pahsc->pahsc_HWaveIn ); + } +#if (PA_USE_TIMER_CALLBACK == 0) + if( pahsc->pahsc_AbortEventInited ) CloseHandle( pahsc->pahsc_AbortEvent ); + if( pahsc->pahsc_BufferEventInited ) CloseHandle( pahsc->pahsc_BufferEvent ); +#endif + if( pahsc->pahsc_StreamLockInited ) + DeleteCriticalSection( &pahsc->pahsc_StreamLock ); + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); /* MEM */ + past->past_DeviceData = NULL; + return paNoError; +} +/************************************************************************* +** Determine minimum number of buffers required for this host based +** on minimum latency. Latency can be optionally set by user by setting +** an environment variable. For example, to set latency to 200 msec, put: +** +** set PA_MIN_LATENCY_MSEC=200 +** +** in the AUTOEXEC.BAT file and reboot. +** If the environment variable is not set, then the latency will be determined +** based on the OS. Windows NT has higher latency than Win95. +*/ +#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC") +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hostVersion; + DWORD hresult; + int minLatencyMsec = 0; + double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate; + int minBuffers; + /* Let user determine minimal latency by setting environment variable. */ + hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE ); + if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) ) + { + minLatencyMsec = atoi( envbuf ); + } + else + { + /* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ + hostVersion = GetVersion(); + /* High bit clear if NT */ + minLatencyMsec = ( (hostVersion & 0x80000000) == 0 ) ? PA_WIN_NT_LATENCY : PA_WIN_9X_LATENCY ; +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + DBUG(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); + minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} +/************************************************************************* +** Cleanup device info. +*/ +PaError PaHost_Term( void ) +{ + int i; + if( sNumDevices > 0 ) + { + if( sDevicePtrs != NULL ) + { + for( i=0; iname ); /* MEM */ + GlobalFree( (void*)sDevicePtrs[i]->sampleRates ); /* MEM */ + GlobalFree( sDevicePtrs[i] ); /* MEM */ + } + } + GlobalFree( sDevicePtrs ); /* MEM */ + sDevicePtrs = NULL; + } + sNumDevices = 0; + } + return paNoError; +} +/*************************************************************************/ +void Pa_Sleep( long msec ) +{ + Sleep( msec ); +} +/************************************************************************* + * Allocate memory that can be accessed in real-time. + * This may need to be held in physical memory so that it is not + * paged to virtual memory. + * This call MUST be balanced with a call to PaHost_FreeFastMemory(). + * Memory will be set to zero. + */ +void *PaHost_AllocateFastMemory( long numBytes ) +{ + void *addr = GlobalAlloc( GPTR, numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */ + return addr; +} +/************************************************************************* + * Free memory that could be accessed in real-time. + * This call MUST be balanced with a call to PaHost_AllocateFastMemory(). + */ +void PaHost_FreeFastMemory( void *addr, long numBytes ) +{ + if( addr != NULL ) GlobalFree( addr ); /* MEM */ +} +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) past->past_IsActive; +} +/************************************************************************* + * This must be called periodically because mmtime.u.sample + * is a DWORD and can wrap and lose sync after a few hours. + */ +static PaError PaHost_UpdateStreamTime( PaHostSoundControl *pahsc ) +{ + MMRESULT mr; + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + if( pahsc->pahsc_HWaveOut != NULL ) + { + mr = waveOutGetPosition( pahsc->pahsc_HWaveOut, &mmtime, sizeof(mmtime) ); + } + else + { + mr = waveInGetPosition( pahsc->pahsc_HWaveIn, &mmtime, sizeof(mmtime) ); + } + if( mr != MMSYSERR_NOERROR ) + { + sPaHostError = mr; + return paHostError; + } + /* This data has two variables and is shared by foreground and background. */ + /* So we need to make it thread safe. */ + EnterCriticalSection( &pahsc->pahsc_StreamLock ); + pahsc->pahsc_FramesPlayed += ((long)mmtime.u.sample) - pahsc->pahsc_LastPosition; + pahsc->pahsc_LastPosition = (long)mmtime.u.sample; + LeaveCriticalSection( &pahsc->pahsc_StreamLock ); + return paNoError; +} +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + PaHostSoundControl *pahsc; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + PaHost_UpdateStreamTime( pahsc ); + return pahsc->pahsc_FramesPlayed; +} diff --git a/pablio/README.txt b/pablio/README.txt new file mode 100644 index 0000000..99c7d14 --- /dev/null +++ b/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/pablio/pablio.c b/pablio/pablio.c new file mode 100644 index 0000000..452eccc --- /dev/null +++ b/pablio/pablio.c @@ -0,0 +1,307 @@ +/* + * $Id$ + * 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. + * + */ +#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; + 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; + int bytesEmpty; + int byteSize = aStream->outFIFO.bufferSize; + + /* If we are writing data, make sure we play everything written. */ + if( byteSize > 0 ) + { + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + while( bytesEmpty < byteSize ) + { + Pa_Sleep( 10 ); + bytesEmpty = RingBuffer_GetWriteAvailable( &aStream->outFIFO ); + } + } + + err = Pa_StopStream( aStream->stream ); + if( err != paNoError ) goto error; + err = Pa_CloseStream( aStream->stream ); + if( err != paNoError ) goto error; + Pa_Terminate(); + +error: + PABLIO_TermFIFO( &aStream->inFIFO ); + PABLIO_TermFIFO( &aStream->outFIFO ); + free( aStream ); + return err; +} diff --git a/pablio/pablio.def b/pablio/pablio.def new file mode 100644 index 0000000..a10f952 --- /dev/null +++ b/pablio/pablio.def @@ -0,0 +1,35 @@ +LIBRARY PABLIO +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/pablio/pablio.h b/pablio/pablio.h new file mode 100644 index 0000000..c1ad84a --- /dev/null +++ b/pablio/pablio.h @@ -0,0 +1,109 @@ +#ifndef _PABLIO_H +#define _PABLIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * 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/pablio/ringbuffer.c b/pablio/ringbuffer.c new file mode 100644 index 0000000..16031fe --- /dev/null +++ b/pablio/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * $Id$ + * 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/pablio/ringbuffer.h b/pablio/ringbuffer.h new file mode 100644 index 0000000..dd769be --- /dev/null +++ b/pablio/ringbuffer.h @@ -0,0 +1,101 @@ +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id$ + * 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. */ + long writeIndex; /* Index of next writable byte. Set by RingBuffer_AdvanceWriteIndex. */ + 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/pablio/test_rw.c b/pablio/test_rw.c new file mode 100644 index 0000000..245e19c --- /dev/null +++ b/pablio/test_rw.c @@ -0,0 +1,99 @@ +/* + * $Id$ + * 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 (22050) +#define NUM_SECONDS (15) +#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 + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i; + SAMPLE samples[SAMPLES_PER_FRAME]; + 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++ ) + { + /* Read one frame of data into sample array from audio input. */ + ReadAudioStream( aStream, samples, 1 ); + /* Write that same frame of data to output. */ + /* samples[1] = (i&256) * (1.0f/256.0f); /* sawtooth */ + WriteAudioStream( aStream, samples, 1 ); + } + + 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/pablio/test_rw_echo.c b/pablio/test_rw_echo.c new file mode 100644 index 0000000..9ba8c8d --- /dev/null +++ b/pablio/test_rw_echo.c @@ -0,0 +1,123 @@ +/* + * $Id$ + * 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/pablio/test_w_saw.c b/pablio/test_w_saw.c new file mode 100644 index 0000000..d5d4e9b --- /dev/null +++ b/pablio/test_w_saw.c @@ -0,0 +1,108 @@ +/* + * $Id$ + * 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 (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 | 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/pablio/test_w_saw8.c b/pablio/test_w_saw8.c new file mode 100644 index 0000000..0a9d05a --- /dev/null +++ b/pablio/test_w_saw8.c @@ -0,0 +1,106 @@ +/* + * $Id$ + * 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