diff options
Diffstat (limited to 'pd/portaudio/pa_sgi')
-rw-r--r-- | pd/portaudio/pa_sgi/pa_sgi.c | 1417 |
1 files changed, 1417 insertions, 0 deletions
diff --git a/pd/portaudio/pa_sgi/pa_sgi.c b/pd/portaudio/pa_sgi/pa_sgi.c new file mode 100644 index 00000000..8b45d09d --- /dev/null +++ b/pd/portaudio/pa_sgi/pa_sgi.c @@ -0,0 +1,1417 @@ +/* + * $Id: pa_sgi.c,v 1.2.2.20 2004/01/03 19:20:09 pieter Exp $ + * PortAudio Portable Real-Time Audio Library. + * Latest Version at: http://www.portaudio.com. + * Silicon Graphics (SGI) IRIX implementation by Pieter Suurmond. + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + * + */ +/** @file + @brief SGI IRIX AL implementation (according to V19 API version 2.0). + + @note This file started as a copy of pa_skeleton.c (v 1.1.2.35 2003/09/20), it + has nothing to do with the old V18 pa_sgi version: this implementation uses the + newer IRIX AL calls and uses pthreads instead of sproc. + + On IRIX, one may type './configure' followed by 'gmake' from the portaudio root + directory to build the static and shared libraries, as well as all the tests. + + On IRIX 6.5, using 'make' instead of 'gmake' may cause Makefile to fail. (This + happens on my machine: make does not understand syntax with 2 colons on a line, + like this: + $(TESTS): bin/%: [snip] + + Maybe this is due to an old make version(?), my only solution is: use gmake. + Anyway, all the tests compile well now, with GCC 3.3, as well as with MIPSpro 7.2.1. + Tested: + - paqa_devs ok, but at a certain point digital i/o fails: + TestAdvance: INPUT, device = 2, rate = 32000, numChannels = 1, format = 1 + Possibly, this is an illegal sr or number of channels for digital i/o. + - paqa_errs 13 of the tests run ok, but 5 of them give weird results. + + patest1 ok. + + patest_buffer ok. + + patest_callbackstop ok. + - patest_clip ok, but hear no difference between dithering turned OFF and ON. + + patest_hang ok. + + patest_latency ok. + + patest_leftright ok. + + patest_maxsines ok. + + patest_many ok. + + patest_multi_sine ok. + + patest_pink ok. + + patest_prime ok. + - patest_read_record ok, but playback stops a little earlier than 5 seconds it seems(?). + + patest_record ok. + + patest_ringmix ok. + + patest_saw ok. + + patest_sine ok. + + patest_sine8 ok. + - patest_sine_formats ok, FLOAT32 + INT16 + INT18 are OK, but UINT8 IS NOT OK! + + patest_sine_time ok. + + patest_start_stop ok, but under/overflow errors of course in the AL queue monitor. + + patest_stop ok. + - patest_sync ok? + + patest_toomanysines ok. + - patest_underflow ok? (stopping after SleepTime = 91: err=Stream is stopped) + - patest_wire ok. + + patest_write_sine ok. + + pa_devs ok. + Ok on an Indy, in both stereo and quadrophonic mode. + + pa_fuzz ok. + + pa_minlat ok. + + Worked on (or checked) proposals: + + 003: Improve Latency Specification OK, but not 100% sure: plus or minus 1 host buffer? + 004 OK: Allow Callbacks to Accept Variable Number of Frames per Buffer. + Simply using a fixed host buffer size. Very roughly implemented now, the adaption + to limited-requested latencies and samplerate may be improved. At least this + implementation chooses its own internal host buffer size (no coredumps any longer). + 005 OK: Blocking Read/Write Interface. + 006: Non-interleaved buffers seems OK? Covered by the buffer-processor and such?.... + 009 OK: Host error reporting should now be. + 010 OK: State Machine and State Querying Functions. + 011 OK: Renaming done. + 014 Implementation Style Guidelines (sorry, my braces are not ANSI style). + 015 OK: Callback Timestamps (During priming, though, these are still null!). + 016 OK: Use Structs for Pa_OpenStream() Parameters. + 019: Notify Client When All Buffers Have Played (Ok, covered by the buffer processor?) + 020 OK: Allow Callback to prime output stream (StartStream() should do the priming) + Should be tested more thoroughly for full duplex streams. (patest_prime seems ok). + + + @todo Underrun or overflow flags at some more places. + + @todo Callback Timestamps during priming. + + @todo Improve adaption to number of channels, samplerate and such when inventing + some frames per host buffer (when client requests 0). + + @todo Make a complete new version to support 'sproc'-applications. + Or could we manage that with some clever if-defs? + It must be clear which version we use (especially when using pa as lib!): + an irix-sproc() version or pthread version. + + @todo In Makefile.in: 'make clean' does not remove lib/libportaudio.so.0.0.19. + + Note: Even when mono-output is requested, with ALv7, the audio library opens + a outputs stereo. One can observe this in SGI's 'Audio Queue Monitor'. +*/ + +#include <string.h> /* For strlen() but also for strerror()! */ +#include <stdio.h> /* printf() */ +#include <math.h> /* fabs() */ + +#include <dmedia/audio.h> /* IRIX AL (audio library). Link with -laudio. */ +#include <dmedia/dmedia.h> /* IRIX DL (digital media library), solely for */ + /* function dmGetUST(). Link with -ldmedia. */ +#include <errno.h> /* To catch 'oserror' after AL-calls. */ +#include <pthread.h> /* POSIX threads. */ +#include <unistd.h> + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + + /* Uncomment for diagnostics: */ +#define DBUG(x) /*{ printf x; fflush(stdout); }*/ + + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *ipp, + const PaStreamParameters *opp, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + + +/* + Apparently, we must use macros for reporting unanticipated host errors. + Only in case we return paUnanticipatedHostError from an Portaudio call, + we have to call one of the following three macro's. + (Constant paAL is defined in pa_common/portaudio.h. See also proposal 009.) + + After an AL error, use this to translate the AL error code to human text: +*/ +#define PA_SGI_SET_LAST_AL_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, alGetErrorString(ee));\ + } +/* + But after a generic IRIX error, let strerror() translate the error code from + the operating system and use this (strerror() gives the same as perror()): +*/ +#define PA_SGI_SET_LAST_IRIX_ERROR() \ + {\ + int ee = oserror();\ + PaUtil_SetLastHostErrorInfo(paAL, ee, strerror(ee));\ + } + +/* GOT RID OF calling PaUtil_SetLastHostErrorInfo() with 0 as error number. +- Weird samplerate difference became: paInvalidSampleRate. +- Failing to set AL queue size became: paInternalError + (Because I cannot decide between paBufferTooBig and paBufferTooSmall + because it may even the 'default AL queue size that failed... Or + should we introduce another error-code like 'paInvalidQueueSize'?... NO) +*/ + +/* PaSGIHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + PaUtilAllocationGroup* allocations; + /* implementation specific data goes here. */ + ALvalue* sgiDeviceIDs; /* Array of AL resource device numbers. */ + /* PaHostApiIndex hostApiIndex; Hu? As in the linux and oss files? */ +} +PaSGIHostApiRepresentation; + +/* + Initialises sgiDeviceIDs array. +*/ +PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int e, i, deviceCount, def_in, def_out; + PaSGIHostApiRepresentation* SGIHostApi; + PaDeviceInfo* deviceInfoArray; + static const short numParams = 4; /* Array with name, samplerate, channels */ + ALpv y[numParams]; /* and type. */ + static const short maxDevNameChars = 32; /* Including the terminating null char. */ + char devName[maxDevNameChars]; /* Too lazy for dynamic alloc. */ + + /* DBUG(("PaSGI_Initialize() started.\n")); */ + SGIHostApi = (PaSGIHostApiRepresentation*)PaUtil_AllocateMemory(sizeof(PaSGIHostApiRepresentation)); + if( !SGIHostApi ) + { result = paInsufficientMemory; goto cleanup; } + SGIHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !SGIHostApi->allocations ) + { result = paInsufficientMemory; goto cleanup; } + *hostApi = &SGIHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paAL; /* IRIX AL type id, was paInDevelopment. */ + (*hostApi)->info.name = "SGI IRIX AL"; + (*hostApi)->info.defaultInputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.defaultOutputDevice = paNoDevice; /* Set later. */ + (*hostApi)->info.deviceCount = 0; /* We 'll increment in the loop below. */ + + /* Determine the total number of input and output devices (thanks to Gary Scavone). */ + deviceCount = alQueryValues(AL_SYSTEM, AL_DEVICES, 0, 0, 0, 0); + if (deviceCount < 0) /* Returns -1 in case of failure. */ + { + DBUG(("Failed to count devices: alQueryValues()=%d; %s.\n", + deviceCount, alGetErrorString(oserror()))); + result = paDeviceUnavailable; /* Is this an appropriate error return code? */ + goto cleanup; + } + if (deviceCount > 0) + { + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount); + if (!(*hostApi)->deviceInfos) + { result = paInsufficientMemory; goto cleanup; } + + /* Allocate all device info structs in a contiguous block. */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + SGIHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount); + if (!deviceInfoArray) + { result = paInsufficientMemory; goto cleanup; } + /* Store all AL device IDs in an array. */ + SGIHostApi->sgiDeviceIDs = (ALvalue*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, + deviceCount * sizeof(ALvalue)); + if (!SGIHostApi->sgiDeviceIDs) + { result = paInsufficientMemory; goto cleanup; } + /* Same query again, but now store all IDs in array sgiDeviceIDs (still using no qualifiers).*/ + e = alQueryValues(AL_SYSTEM, AL_DEVICES, SGIHostApi->sgiDeviceIDs, deviceCount, 0, 0); + if (e != deviceCount) + { + if (e < 0) /* Sure an AL error really occurred. */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else /* Seems we lost some devices. */ + { DBUG(("Number of devices suddenly changed!\n")); result = paDeviceUnavailable; } + goto cleanup; + } + y[0].param = AL_DEFAULT_INPUT; + y[1].param = AL_DEFAULT_OUTPUT; + e = alGetParams(AL_SYSTEM, y, 2); /* Get params global to the AL system. */ + if (e != 2) + { + if (e < 0) + { + PA_SGI_SET_LAST_AL_ERROR() /* Calls oserror() and alGetErrorString(). */ + result = paUnanticipatedHostError; /* Sure an AL error really occurred. */ + } + else + { + DBUG(("Default input and/or output could not be found!\n")); + result = paDeviceUnavailable; /* FIX: What if only in or out are available? */ + } + goto cleanup; + } + def_in = y[0].value.i; /* Remember both AL devices for a while. */ + def_out = y[1].value.i; + y[0].param = AL_NAME; + y[0].value.ptr = devName; + y[0].sizeIn = maxDevNameChars; /* Including terminating null char. */ + y[1].param = AL_RATE; + y[2].param = AL_CHANNELS; + y[3].param = AL_TYPE; /* Subtype of AL_INPUT_DEVICE_TYPE or AL_OUTPUT_DEVICE_TYPE? */ + for (i=0; i < deviceCount; ++i) /* Fill allocated deviceInfo structs. */ + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; /* Retrieve name, samplerate, channels and type. */ + e = alGetParams(SGIHostApi->sgiDeviceIDs[i].i, y, numParams); + if (e != numParams) + { + if (e < 0) /* Calls oserror() and alGetErrorString(). */ + { PA_SGI_SET_LAST_AL_ERROR() result = paUnanticipatedHostError; } + else + { DBUG(("alGetParams() could not get all params!\n")); result = paInternalError; } + goto cleanup; + } + deviceInfo->name = (char*)PaUtil_GroupAllocateMemory(SGIHostApi->allocations, strlen(devName) + 1); + if (!deviceInfo->name) + { result = paInsufficientMemory; goto cleanup; } + strcpy((char*)deviceInfo->name, devName); + + /* Determine whether the received number of channels belongs to input or output device. */ + if (alIsSubtype(AL_INPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = y[2].value.i; + deviceInfo->maxOutputChannels = 0; + } + else if (alIsSubtype(AL_OUTPUT_DEVICE_TYPE, y[3].value.i)) + { + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = y[2].value.i; + } + else /* Should never occur. */ + { + DBUG(("AL device is neither input nor output!\n")); + result = paInternalError; + goto cleanup; + } + + /* Determine if this device is the default (in or out). If so, assign. */ + if (def_in == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultInputDevice != paNoDevice) + { + DBUG(("Default input already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultInputDevice = i; + /* DBUG(("Default input assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + else if (def_out == SGIHostApi->sgiDeviceIDs[i].i) + { + if ((*hostApi)->info.defaultOutputDevice != paNoDevice) + { + DBUG(("Default output already assigned!\n")); + result = paInternalError; + goto cleanup; + } + (*hostApi)->info.defaultOutputDevice = i; + /* DBUG(("Default output assigned to pa device %d (%s).\n", i, deviceInfo->name)); */ + } + /*---------------------------------------------- Default latencies set to 'reasonable' values. */ + deviceInfo->defaultLowInputLatency = 0.050; /* 50 milliseconds seems ok. */ + deviceInfo->defaultLowOutputLatency = 0.050; /* These are ALSO ABSOLUTE MINIMA in OpenStream(). */ + deviceInfo->defaultHighInputLatency = 0.500; /* 500 milliseconds a reasonable value? */ + deviceInfo->defaultHighOutputLatency = 0.500; /* Ten times these are ABSOLUTE MAX in OpenStream()). */ + + deviceInfo->defaultSampleRate = alFixedToDouble(y[1].value.ll); /* Read current sr. */ + (*hostApi)->deviceInfos[i] = deviceInfo; + ++(*hostApi)->info.deviceCount; + } + } + /* What if (deviceCount==0)? */ + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface(&SGIHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface(&SGIHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); +cleanup: + if (result != paNoError) + { + if (SGIHostApi) + { + if (SGIHostApi->allocations) + { + PaUtil_FreeAllAllocations(SGIHostApi->allocations); + PaUtil_DestroyAllocationGroup(SGIHostApi->allocations); + } + PaUtil_FreeMemory(SGIHostApi); + } + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSGIHostApiRepresentation *SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + + /* Clean up any resources not handled by the allocation group. */ + if( SGIHostApi->allocations ) + { + PaUtil_FreeAllAllocations( SGIHostApi->allocations ); + PaUtil_DestroyAllocationGroup( SGIHostApi->allocations ); + } + PaUtil_FreeMemory( SGIHostApi ); +} + +/* + Check if samplerate is supported for this output device. Called once + or twice by function IsFormatSupported() and one time by OpenStream(). + When paUnanticipatedHostError is returned, the caller does NOT have + to call PA_SGI_SET_LAST_AL_ERROR() or such. +*/ +static PaError sr_supported(int al_device, double sr) +{ + int e; + PaError result; + ALparamInfo pinfo; + long long lsr; /* 64 bit fixed point internal AL samplerate. */ + + if (alGetParamInfo(al_device, AL_RATE, &pinfo)) + { + e = oserror(); + DBUG(("alGetParamInfo(AL_RATE) failed: %s.\n", alGetErrorString(e))); + if (e == AL_BAD_RESOURCE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() /* Sure an AL error occured. */ + result = paUnanticipatedHostError; + } + } + else + { + lsr = alDoubleToFixed(sr); /* Within the range? */ + if ((pinfo.min.ll <= lsr) && (lsr <= pinfo.max.ll)) + result = paFormatIsSupported; + else + result = paInvalidSampleRate; + } + /* DBUG(("sr_supported()=%d.\n", result)); */ + return result; +} + + +/* + See common/portaudio.h (suggestedLatency field is ignored). +*/ +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + int inputChannelCount, outputChannelCount, result; + PaSampleFormat inputSampleFormat, outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + /* Unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification. */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[inputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + } + else + { + inputChannelCount = 0; + } + if( outputParameters ) /* As with input above. */ + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[outputParameters->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + } + else + { + outputChannelCount = 0; + } + /* IMPLEMENT ME: + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + /* suppress unused variable warnings */ + (void) inputSampleFormat; + (void) outputSampleFormat; + return paFormatIsSupported; +} + +/** Auxilary struct, embedded twice in the struct below, for inputs and outputs. */ +typedef struct PaSGIhostPortBuffer +{ + /** NULL means IRIX AL port closed. */ + ALport port; + /** NULL means memory not allocated. */ + void* buffer; +} + PaSGIhostPortBuffer; + +/** Stream data structure specifically for this IRIX AL implementation. */ +typedef struct PaSGIStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + unsigned long framesPerHostCallback; + /** Allocated host buffers and AL ports. */ + PaSGIhostPortBuffer hostPortBuffIn, + hostPortBuffOut; + /** Copy of stream flags given to OpenStream(). */ + PaStreamFlags streamFlags; + /** Stream state may be 0 or 1 or 2, but never 3. */ + unsigned char state; + /** Requests to stop or abort may come from the parent, + or from the child itself (user callback result). */ + unsigned char stopAbort; + pthread_t thread; +} + PaSGIStream; + +/** Stream can be in only one of the following three states: stopped (1), active (2), or + callback finshed (0). To prevent 'state 3' from occurring, Setting and testing of the + state bits is done atomically. +*/ +#define PA_SGI_STREAM_FLAG_FINISHED_ (0) /* After callback finished or cancelled queued buffers. */ +#define PA_SGI_STREAM_FLAG_STOPPED_ (1) /* Set by OpenStream(), StopStream() and AbortStream(). */ +#define PA_SGI_STREAM_FLAG_ACTIVE_ (2) /* Set by StartStream. Reset by OpenStream(), */ + /* StopStream() and AbortStream(). */ + +/** Stop requests, via the 'stopAbort' field can be either 1, meaning 'stop' or 2, meaning 'abort'. + When both occur at the same time, 'abort' takes precedence, even after a first 'stop'. +*/ +#define PA_SGI_REQ_CONT_ (0) /* Reset by OpenStream(), StopStream and AbortStream. */ +#define PA_SGI_REQ_STOP_ (1) /* Set by StopStream(). */ +#define PA_SGI_REQ_ABORT_ (2) /* Set by AbortStream(). */ + + +/** Called by OpenStream() once or twice. First, the number of channels, sampleformat, and + queue size are configured. The configuration is then bound to the specified AL device. + Then an AL port is opened. Finally, the samplerate of the device is altered (or at least + set again). + + After successful return, actual latency is written in *latency, and actual samplerate + in *samplerate. + + @param pa_params may be NULL and pa_params->channelCount may also be null, in both + cases the function immediately returns. + @return paNoError if configuration was skipped or if it succeeded. +*/ +static PaError set_sgi_device(ALvalue* sgiDeviceIDs, /* Array built by PaSGI_Initialize(). */ + const PaStreamParameters* pa_params, /* read device and channels. */ + double* latency, /* Read and write in seconds. */ + + PaSampleFormat pasfmt, /* Don't read from pa_params!. */ + char* direction, /* "r" or "w". */ + char* name, + long framesPerHostBuffer, + double* samplerate, /* Also writes back here. */ + PaSGIhostPortBuffer* hostPortBuff) /* Receive pointers here. */ +{ + int bytesPerFrame, sgiDevice, alErr, d, dd, iq_size, default_iq_size; + ALpv pvs[2]; + ALconfig alc = NULL; + PaError result = paNoError; + + if (!pa_params) + goto cleanup; /* Not errors, just not full duplex, skip all. */ + if (!pa_params->channelCount) + goto cleanup; + alc = alNewConfig(); /* Create default config. This defaults to stereo, 16-bit integer data. */ + if (!alc) /* Call alFreeConfig() later, when done with it. */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- CONFIGURE NUMBER OF CHANNELS: ---------------------------*/ + if (alSetChannels(alc, pa_params->channelCount)) /* Returns 0 on success. */ + { + if (oserror() == AL_BAD_CHANNELS) + result = paInvalidChannelCount; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame = pa_params->channelCount; /* Is multiplied by width below. */ + /*----------------------- CONFIGURE SAMPLE FORMAT: --------------------------------*/ + if (pasfmt == paFloat32) + { + if (alSetSampFmt(alc, AL_SAMPFMT_FLOAT)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 4; /* No need to set width for floats. */ + } + else + { + if (alSetSampFmt(alc, AL_SAMPFMT_TWOSCOMP)) + { + if (oserror() == AL_BAD_SAMPFMT) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + if (pasfmt == paInt8) + { + if (alSetWidth(alc, AL_SAMPLE_8)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /* bytesPerFrame *= 1; */ + } + else if (pasfmt == paInt16) + { + if (alSetWidth(alc, AL_SAMPLE_16)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 2; + } + else if (pasfmt == paInt24) + { + if (alSetWidth(alc, AL_SAMPLE_24)) + { + if (oserror() == AL_BAD_WIDTH) + result = paSampleFormatNotSupported; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + bytesPerFrame *= 3; /* OR 4 ???????! */ + } + else return paSampleFormatNotSupported; + } + /*----------------------- SET INTERNAL AL QUEUE SIZE: -------------------------------*/ + /* The AL API doesn't provide a means for querying minimum and maximum buffer sizes. + So, if the requested size fails, try again with a value that is closer to the AL's + default queue size. In this implementation, 'Portaudio latency' corresponds to + the AL queue size minus one buffersize: + AL queue size - framesPerHostBuffer + PA latency = ----------------------------------- + sample rate */ + default_iq_size = alGetQueueSize(alc); + if (default_iq_size < 0) /* So let's first get that 'default size'. */ + { /* Default internal queue size could not be determined. */ + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + /* AL buffer becomes somewhat bigger than the suggested latency, notice this is */ + /* based on requsted samplerate, not in the actual rate, which is measured later. */ + /* Do NOT read pa_params->suggestedLatency, but use the limited *latency param! */ + + iq_size = (int)(0.5 + ((*latency) * (*samplerate))) + (int)framesPerHostBuffer; + /* The AL buffer becomes somewhat */ + /* bigger than the suggested latency. */ + if (iq_size < (framesPerHostBuffer << 1)) /* Make sure the minimum is twice */ + { /* framesPerHostBuffer. */ + DBUG(("Setting minimum queue size.\n")); + iq_size = (framesPerHostBuffer << 1); + } + d = iq_size - default_iq_size; /* Determine whether we'll decrease */ + while (alSetQueueSize(alc, iq_size)) /* or increase after failing. */ + { /* Size in sample frames. */ + if (oserror() != AL_BAD_QSIZE) /* Stop at AL_BAD_CONFIG. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } + dd = iq_size - default_iq_size; /* Stop when even the default size failed */ + if (((d >= 0) && (dd <= 0)) || /* (dd=0) or when difference flipped sign. */ + ((d <= 0) && (dd >= 0)) || + (iq_size <= framesPerHostBuffer)) /* Also guarentee that framesPerHostBuffer */ + { /* can be subtracted (res>0) after return. */ + DBUG(("Could not set AL queue size to %d sample frames!\n", iq_size)); + result = paInternalError; /* FIX: PROBABLY AN INAPROPRIATE ERROR CODE HERE. */ + goto cleanup; /* As inapropriate as paUnanticipatedHostError was? */ + } + DBUG(("Failed to set internal queue size to %d frames, ", iq_size)); + if (d > 0) + iq_size -= framesPerHostBuffer; /* Try lesser multiple. */ + else + iq_size += framesPerHostBuffer; /* Try larger multiple. */ + DBUG(("trying %d frames now...\n", iq_size)); + } + /* Note: Actual latency is written back to *latency after meausuring actual (not + the requested) samplerate. See below. + */ + /*----------------------- ALLOCATE HOST BUFFER: --------------------------------------*/ + hostPortBuff->buffer = PaUtil_AllocateMemory((long)bytesPerFrame * framesPerHostBuffer); + if (!hostPortBuff->buffer) /* Caller is responsible for cleanup+close after failures! */ + { result = paInsufficientMemory; goto cleanup; } + /*----------------------- BIND CONFIGURATION TO DEVICE: ------------------------------*/ + sgiDevice = sgiDeviceIDs[pa_params->device].i; + if (alSetDevice(alc, sgiDevice)) /* Try to switch the hardware. */ + { + if (oserror() == AL_BAD_DEVICE) + result = paInvalidDevice; + else + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + } + goto cleanup; + } + /*----------------------- OPEN PORT: ----------------------------------------------*/ + hostPortBuff->port = alOpenPort(name, direction, alc); /* Caller is responsible */ + if (!hostPortBuff->port) /* for closing after fail. */ + { + PA_SGI_SET_LAST_AL_ERROR() + result = paUnanticipatedHostError; + goto cleanup; + } /* Maybe set SR earlier? */ + /*----------------------- SET SAMPLERATE: -----------------------------------------*/ + pvs[0].param = AL_MASTER_CLOCK; /* Attempt to set a crystal-based sample- */ + pvs[0].value.i = AL_CRYSTAL_MCLK_TYPE; /* rate on input or output device. */ + pvs[1].param = AL_RATE; + pvs[1].value.ll = alDoubleToFixed(*samplerate); + if (2 != alSetParams(sgiDevice, pvs, 2)) + { + DBUG(("alSetParams() failed to set samplerate to %.4f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- GET ACTUAL SAMPLERATE: ---------------------------*/ + alErr = alGetParams(sgiDevice, &pvs[1], 1); /* SEE WHAT WE REALY SET IT TO. */ + if (alErr != 1) /* And return that to caller. */ + { + DBUG(("alGetParams() failed to read samplerate!\n")); + result = paInvalidSampleRate; + goto cleanup; + } + *samplerate = alFixedToDouble(pvs[1].value.ll); /* Between 1 Hz and 1 MHz. */ + if ((*samplerate < 1.0) || (*samplerate > 1000000.0)) + { + DBUG(("alFixedToDouble() resulted a weird samplerate: %.6f Hz!\n", *samplerate)); + result = paInvalidSampleRate; + goto cleanup; + } + /*----------------------- CALC ACTUAL LATENCY (based on actual SR): -----------------------*/ + *latency = (iq_size - framesPerHostBuffer) / (*samplerate); /* FIX: SURE > 0!???? */ +cleanup: + if (alc) + alFreeConfig(alc); /* We no longer need configuration. */ + return result; +} + +/** + Called by OpenStream() if it fails and by CloseStream. Only used here, in this file. + Fields MUST be set to NULL or to a valid value, prior to call. +*/ +static void streamCleanupAndClose(PaSGIStream* stream) +{ + if (stream->hostPortBuffIn.port) alClosePort(stream->hostPortBuffIn.port); /* Close AL ports. */ + if (stream->hostPortBuffIn.buffer) PaUtil_FreeMemory(stream->hostPortBuffIn.buffer); /* Release buffers. */ + if (stream->hostPortBuffOut.port) alClosePort(stream->hostPortBuffOut.port); + if (stream->hostPortBuffOut.buffer) PaUtil_FreeMemory(stream->hostPortBuffOut.buffer); +} + + +/* See pa_hostapi.h for a list of validity guarantees made about OpenStream parameters. */ +static PaError OpenStream(struct PaUtilHostApiRepresentation* hostApi, + PaStream** s, + const PaStreamParameters* ipp, + const PaStreamParameters* opp, + double sampleRate, /* Common to both i and o. */ + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback* streamCallback, + void* userData) +{ + PaError result = paNoError; + PaSGIHostApiRepresentation* SGIHostApi = (PaSGIHostApiRepresentation*)hostApi; + PaSGIStream* stream = 0; + unsigned long framesPerHostBuffer; /* Not necessarily the same as framesPerBuffer. */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat, + hostInputSampleFormat, hostOutputSampleFormat; + double sr_in, sr_out, + latency_in, latency_out; + static const PaSampleFormat irixFormats = (paInt8 | paInt16 | paInt24 | paFloat32); + /* Constant used by PaUtil_SelectClosestAvailableFormat(). Because IRIX AL does not + provide a way to query for possible formats for a given device, interface or port, + just add together the formats we know that are supported in general by IRIX AL + (at the end of the year 2003): AL_SAMPFMT_TWOSCOMP with AL_SAMPLE_8(=paInt8), + AL_SAMPLE_16(=paInt16) or AL_SAMPLE_24(=paInt24); AL_SAMPFMT_FLOAT(=paFloat32); + AL_SAMPFMT_DOUBLE(=paFloat64); IRIX misses unsigned 8 and 32 bit signed ints. + */ + DBUG(("OpenStream() started.\n")); + if (ipp) + { + inputChannelCount = ipp->channelCount; + inputSampleFormat = ipp->sampleFormat; + /* Unless alternate device specification is supported, reject the use of paUseHostApiSpecificDeviceSpecification. */ + if (ipp->device == paUseHostApiSpecificDeviceSpecification) /* DEVICE CHOOSEN BY CLIENT. */ + return paInvalidDevice; + /* Check that input device can support inputChannelCount. */ + if (inputChannelCount > hostApi->deviceInfos[ipp->device]->maxInputChannels) + return paInvalidChannelCount; + /* Validate inputStreamInfo. */ + if (ipp->hostApiSpecificStreamInfo) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, inputSampleFormat); + /* Check if samplerate is supported for this input device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[ipp->device].i, sampleRate); + if (result != paFormatIsSupported) /* PA_SGI_SET_LAST_AL_ERROR() may already be called. */ + return result; + /* Validate input latency. Use defaults if necessary. */ + if (ipp->suggestedLatency < hostApi->deviceInfos[ipp->device]->defaultLowInputLatency) + latency_in = hostApi->deviceInfos[ipp->device]->defaultLowInputLatency; /* Low = minimum. */ + else if (ipp->suggestedLatency > 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency) + latency_in = 10.0 * hostApi->deviceInfos[ipp->device]->defaultHighInputLatency; /* 10*High = max. */ + else + latency_in = ipp->suggestedLatency; + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + latency_in = 0.0; /* Necessary? */ + } + if (opp) + { + outputChannelCount = opp->channelCount; + outputSampleFormat = opp->sampleFormat; + if (opp->device == paUseHostApiSpecificDeviceSpecification) /* Like input (above). */ + return paInvalidDevice; + if (outputChannelCount > hostApi->deviceInfos[opp->device]->maxOutputChannels) + return paInvalidChannelCount; + if (opp->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(irixFormats, outputSampleFormat); + /* Check if samplerate is supported for this output device. */ + result = sr_supported(SGIHostApi->sgiDeviceIDs[opp->device].i, sampleRate); + if (result != paFormatIsSupported) + return result; + /* Validate output latency. Use defaults if necessary. */ + if (opp->suggestedLatency < hostApi->deviceInfos[opp->device]->defaultLowOutputLatency) + latency_out = hostApi->deviceInfos[opp->device]->defaultLowOutputLatency; /* Low = minimum. */ + else if (opp->suggestedLatency > 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency) + latency_out = 10.0 * hostApi->deviceInfos[opp->device]->defaultHighOutputLatency; /* 10*High = max. */ + else + latency_out = opp->suggestedLatency; + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialised var' warning. */ + latency_out = 0.0; + } + /* Sure that ipp and opp will never be both NULL. */ + + if( (streamFlags & paPlatformSpecificFlags) != 0 ) /* Validate platform specific flags. */ + return paInvalidFlag; /* Unexpected platform specific flag. */ + + stream = (PaSGIStream*)PaUtil_AllocateMemory( sizeof(PaSGIStream) ); + if (!stream) + { result = paInsufficientMemory; goto cleanup; } + + stream->hostPortBuffIn.port = (ALport)NULL; /* Ports closed. */ + stream->hostPortBuffIn.buffer = NULL; /* No buffers yet. */ + stream->hostPortBuffOut.port = (ALport)NULL; + stream->hostPortBuffOut.buffer = NULL; + + if (streamCallback) + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->callbackStreamInterface, streamCallback, userData); + else + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &SGIHostApi->blockingStreamInterface, streamCallback, userData); + /* (NULL.) */ + if (framesPerBuffer == paFramesPerBufferUnspecified) /* Proposal 004. */ + { /* Keep framesPerBuffer zero but come up with some fixed host buffer size. */ + double lowest_lat = 0.0; /* 0.0 to surpress uninit warning, we're sure it will end up higher. */ + if (ipp) + lowest_lat = latency_in; /* Sure > 0.0! */ + if (opp && (latency_out < lowest_lat)) + lowest_lat = latency_out; + /* So that queue size becomes approximately 5 times framesPerHostBuffer: */ + framesPerHostBuffer = (unsigned long)((lowest_lat * sampleRate) / 4.0); + /* But always limit: */ + if (framesPerHostBuffer < 64L) + framesPerHostBuffer = 64L; + else if (framesPerHostBuffer > 32768L) + framesPerHostBuffer = 32768L; + DBUG(("Decided to use a fixed host buffer size of %ld frames.\n", framesPerHostBuffer)); + } + else + framesPerHostBuffer = framesPerBuffer; /* Then just take the requested amount. No buffer-adaption yet? */ + + sr_in = sr_out = sampleRate; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, /* Needed by alSetDevice and other functs. */ + ipp, /* Reads channelCount, device but NOT latency. */ + &latency_in, /* Read limited requested latency but also WRITE actual. */ + hostInputSampleFormat, /* For alSetSampFmt and alSetWidth. */ + "r", /* "r" for reading from input port. */ + "portaudio in", /* Name string. */ + framesPerHostBuffer, /* As calculated or as requested by the client. */ + &sr_in, /* Receive actual s.rate after setting it. */ + &stream->hostPortBuffIn); /* Receives ALport and input host buffer. */ + if (result != paNoError) goto cleanup; + /*-------------------------------------------------------------------------------------------*/ + result = set_sgi_device(SGIHostApi->sgiDeviceIDs, + opp, + &latency_out, + hostOutputSampleFormat, + "w", /* "w" for writing. */ + "portaudio out", + framesPerHostBuffer, + &sr_out, + &stream->hostPortBuffOut); + if (result != paNoError) goto cleanup; + /*------------------------------------------------------------------------------------------*/ + if (fabs(sr_in - sr_out) > 0.001) /* Make sure both are the 'same'. */ + { + DBUG(("Weird samplerate difference between input and output!\n")); + result = paInvalidSampleRate; /* Could not come up with a better error code. */ + goto cleanup; + } /* sr_in '==' sr_out. */ + sampleRate = sr_in; /* Following fields set to estimated or actual values: */ + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + stream->streamRepresentation.streamInfo.inputLatency = latency_in; /* 0.0 if output only. */ + stream->streamRepresentation.streamInfo.outputLatency = latency_out; /* 0.0 if input only. */ + + PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); + result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, + framesPerBuffer, /* As requested by OpenStream(), may be zero! */ + framesPerHostBuffer, /* Use fixed number of frames per host buffer */ + paUtilFixedHostBufferSize, /* to keep things simple. See pa_common/pa_ */ + streamCallback, userData); /* process.h for more hostbuffersize options. */ + if (result != paNoError) + goto cleanup; + + stream->framesPerHostCallback = framesPerHostBuffer; + stream->streamFlags = streamFlags; /* Remember priming request. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; /* After opening, the stream */ + stream->stopAbort = PA_SGI_REQ_CONT_; /* is in the stopped state. */ + *s = (PaStream*)stream; /* Pass object to caller. */ +cleanup: + if (result != paNoError) /* Always set result when jumping to cleanup after failure. */ + { + if (stream) + { + streamCleanupAndClose(stream); /* Frees i/o buffers and closes AL ports. */ + PaUtil_FreeMemory(stream); + } + } + return result; +} + +/** POSIX thread that performs the actual i/o and calls the client's callback, + spawned by StartStream(). +*/ +static void* PaSGIpthread(void *userData) +{ + PaSGIStream* stream = (PaSGIStream*)userData; + int callbackResult = paContinue; + double nanosec_per_frame; + PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 }; + + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Parent thread also sets active flag, but we + make no assumption about who does this first. */ + nanosec_per_frame = 1000000000.0 / stream->streamRepresentation.streamInfo.sampleRate; + /*----------------------------------------------- OUTPUT PRIMING: -----------------------------*/ + if (stream->hostPortBuffOut.port) /* Somewhat less than AL queue size so the next */ + { /* output buffer will (probably) not block. */ + unsigned long frames_to_prime = (long)(0.5 + + (stream->streamRepresentation.streamInfo.outputLatency + * stream->streamRepresentation.streamInfo.sampleRate)); + if (stream->streamFlags & paPrimeOutputBuffersUsingStreamCallback) + { + PaStreamCallbackFlags cbflags = paPrimingOutput; + if (stream->hostPortBuffIn.port) /* Only set this flag in case of full duplex. */ + cbflags |= paInputUnderflow; + DBUG(("Prime with client's callback: < %ld frames.\n", frames_to_prime)); + while (frames_to_prime >= stream->framesPerHostCallback) /* We will not do less (yet). */ + { /* TODO: Timestamps and CPU load */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, /* measurement during priming. */ + &timeInfo, + cbflags); /* Pass underflow + priming flags. */ + if (stream->hostPortBuffIn.port) /* Does that provide client's call- */ + PaUtil_SetNoInput(&stream->bufferProcessor); /* back with silent inputbuffers? */ + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0=take host buffer size. */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + stream->hostPortBuffOut.buffer, 0); + callbackResult = paContinue; /* Call the client's callback. */ + frames_to_prime -= PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + if (callbackResult == paAbort) + { /* What should we do in other cases */ + stream->stopAbort = PA_SGI_REQ_ABORT_; /* where (callbackResult!=paContinue). */ + break; /* Don't even output the samples just returned (also skip following while). */ + } + else /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, + stream->framesPerHostCallback); + } + } + else /* Prime with silence. */ + { + DBUG(("Prime with silence: %ld frames.\n", frames_to_prime)); + alZeroFrames(stream->hostPortBuffOut.port, frames_to_prime); + } + } + /*------------------------------------------------------ I/O: ---------------------------------*/ + while (!stream->stopAbort) /* Exit loop immediately when 'stop' or 'abort' are raised. */ + { + unsigned long framesProcessed; + stamp_t fn, t, fnd, td; /* Unsigned 64 bit. */ + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + /* IMPLEMENT ME: - handle buffer slips. */ + if (stream->hostPortBuffIn.port) + { + /* Get device sample frame number associated with next audio sample frame + we're going to read from this port. */ + alGetFrameNumber(stream->hostPortBuffIn.port, &fn); + /* Get some recent pair of (frame number, time) from the audio device to + which our port is connected. time is 'UST' which is given in nanoseconds + and shared with the other audio devices and with other media. */ + alGetFrameTime(stream->hostPortBuffIn.port, &fnd, &td); + /* Calculate UST associated with fn, the next sample frame we're going to read or + write. Because this is signed arithmetic, code works for both inputs and outputs. */ + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + /* If port is not in underflow or overflow state, we can alReadFrames() or + alWriteFrames() here and know that t is the time associated with the first + sample frame of the buffer we read or write. */ + timeInfo.inputBufferAdcTime = ((PaTime)t) / 1000000000.0; + /* Read interleaved samples from AL port (I think it will block only the first time). */ + alReadFrames(stream->hostPortBuffIn.port, stream->hostPortBuffIn.buffer, + stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) + { + alGetFrameNumber(stream->hostPortBuffOut.port, &fn); + alGetFrameTime(stream->hostPortBuffOut.port, &fnd, &td); + t = td + (stamp_t) ((double)(fn - fnd) * nanosec_per_frame); + timeInfo.outputBufferDacTime = ((PaTime)t) / 1000000000.0; + } + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + timeInfo.currentTime = ((PaTime)t) / 1000000000.0; + + /* If you need to byte swap or shift inputBuffer to convert it into a pa format, do it here. */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, + 0 /* IMPLEMENT ME: pass underflow/overflow flags when necessary */); + + if (stream->hostPortBuffIn.port) /* Equivalent to (inputChannelCount > 0) */ + { /* We are sure about the amount to transfer (PaUtil_Set before alRead). */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ + } + if (stream->hostPortBuffOut.port) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0 /* 0 means take host buffer size */); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of outputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ + } + /* + You must pass a valid value of callback result to PaUtil_EndBufferProcessing() + in general you would pass paContinue for normal operation, and + paComplete to drain the buffer processor's internal output buffer. + You can check whether the buffer processor's output buffer is empty + using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) + */ + callbackResult = paContinue; /* Whoops, lost this somewhere, back again in v 1.2.2.16! */ + framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor, &callbackResult); + /* If you need to byte swap or shift outputBuffer to convert it to host format, do it here. */ + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + if (callbackResult != paContinue) + { /* Once finished, call the finished callback. */ + DBUG(("SGI callbackResult = %d.\n", callbackResult)); + if (stream->streamRepresentation.streamFinishedCallback) + stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData); + if (callbackResult == paAbort) + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + break; /* Don't play the last buffer returned. */ + } + else /* paComplete or some other non-zero value. */ + stream->stopAbort = PA_SGI_REQ_STOP_; + } + if (stream->hostPortBuffOut.port) /* Write interleaved samples to SGI device */ + alWriteFrames(stream->hostPortBuffOut.port, /* (like unix_oss, AFTER checking callback result). */ + stream->hostPortBuffOut.buffer, stream->framesPerHostCallback); + } + if (stream->hostPortBuffOut.port) /* Drain output buffer(s), as long as we don't see an 'abort' request. */ + { + while ((!(stream->stopAbort & PA_SGI_REQ_ABORT_)) && /* Assume _STOP_ is set (or meant). */ + (alGetFilled(stream->hostPortBuffOut.port) > 1)) /* In case of ABORT we quickly leave (again). */ + ; /* Don't provide any new (not even silent) samples, but let an underrun [almost] occur. */ + } + if (callbackResult != paContinue) + stream->state = PA_SGI_STREAM_FLAG_FINISHED_; + return NULL; +} + + +/* + When CloseStream() is called, the multi-api layer ensures + that the stream has already been stopped or aborted. +*/ +static PaError CloseStream(PaStream* s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("SGI CloseStream() started.\n")); + streamCleanupAndClose(stream); /* Releases i/o buffers and closes AL ports. */ + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); + PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); + return result; +} + + +static PaError StartStream(PaStream *s) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + DBUG(("StartStream() started.\n")); + PaUtil_ResetBufferProcessor(&stream->bufferProcessor); /* See pa_common/pa_process.h. */ + if (stream->bufferProcessor.streamCallback) + { /* only when callback is used */ + if (pthread_create(&stream->thread, + NULL, /* pthread_attr_t * attr */ + PaSGIpthread, /* Function to spawn. */ + (void*)stream)) /* Pass stream as arg. */ + { + PA_SGI_SET_LAST_IRIX_ERROR() /* Let's hope oserror() tells something useful. */ + result = paUnanticipatedHostError; + } + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; + } /* Set active before returning from this function. */ + else + stream->state = PA_SGI_STREAM_FLAG_ACTIVE_; /* Apparently, setting active for blocking i/o is */ + return result; /* necessary (for patest_write_sine for example). */ +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream* stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_STOP_; /* Signal and wait for the thread to drain outputs. */ + if (pthread_join(stream->thread, NULL)) /* When succesful, stream->state */ + { /* is still ACTIVE, or FINISHED. */ + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaSGIStream *stream = (PaSGIStream*)s; + + if (stream->bufferProcessor.streamCallback) /* Only for callback streams. */ + { + stream->stopAbort = PA_SGI_REQ_ABORT_; + if (pthread_join(stream->thread, NULL)) + { + PA_SGI_SET_LAST_IRIX_ERROR() + result = paUnanticipatedHostError; + } + else /* Transition from ACTIVE or FINISHED to STOPPED. */ + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; + stream->stopAbort = PA_SGI_REQ_CONT_; /* For possible next start. */ + } +/* else + stream->state = PA_SGI_STREAM_FLAG_STOPPED_; Is this necessary for blocking i/o? */ + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) /* Not just the opposite of IsStreamActive(): */ +{ /* in the 'callback finished' state, it */ + /* returns zero instead of nonzero. */ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_STOPPED_) + return 1; + return 0; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + if (((PaSGIStream*)s)->state & PA_SGI_STREAM_FLAG_ACTIVE_) + return 1; + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + stamp_t t; + + (void) s; /* Suppress unused argument warning. */ + dmGetUST((unsigned long long*)(&t)); /* Receive time in nanoseconds in t. */ + return (PaTime)t / 1000000000.0; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSGIStream *stream = (PaSGIStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + int n; + +printf("stream->framesPerHostCallback=%ld.\n", stream->framesPerHostCallback); +fflush(stdout); + + while (frames) + { + if (frames > stream->framesPerHostCallback) n = stream->framesPerHostCallback; + else n = frames; + /* Read interleaved samples from SGI device. */ + alReadFrames(stream->hostPortBuffIn.port, /* Port already opened by OpenStream(). */ + stream->hostPortBuffIn.buffer, n); /* Already allocated by OpenStream(). */ + /* alReadFrames() always returns 0. */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffIn.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from host input channels set up by the PaUtil_SetInterleavedInputChannels + to a user supplied buffer. */ +printf("frames=%ld, buffer=%ld\n", frames, (long)buffer); +fflush(stdout); + PaUtil_CopyInput(&stream->bufferProcessor, &buffer, n); + frames -= n; + } +printf("DONE: frames=%ld, buffer=%ld\n", frames, (long)buffer); + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSGIStream* stream = (PaSGIStream*)s; + unsigned long n; + while (frames) + { + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, 0); /* 0 means take host buffer size */ + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, + 0, /* first channel of inputBuffer is channel 0 */ + stream->hostPortBuffOut.buffer, + 0 ); /* 0 means use inputChannelCount passed at init. */ + /* Copy samples from user supplied buffer to host input channels set up by + PaUtil_SetInterleavedOutputChannels. Copies the minimum of the number of user frames + (specified by the frameCount parameter) and the number of host frames (specified in + a previous call to SetOutputFrameCount()). */ + n = PaUtil_CopyOutput(&stream->bufferProcessor, &buffer, frames); + /* Write interleaved samples to SGI device. */ + alWriteFrames(stream->hostPortBuffOut.port, stream->hostPortBuffOut.buffer, n); + frames -= n; /* alWriteFrames always returns 0. */ + } + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + return (signed long)alGetFilled(((PaSGIStream*)s)->hostPortBuffIn.port); +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + return (signed long)alGetFillable(((PaSGIStream*)s)->hostPortBuffOut.port); +} + + +/* CVS reminder: + To download the 'v19-devel' branch from portaudio's CVS server for the first time, type: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs checkout -r v19-devel portaudio + Then 'cd' to the 'portaudio' directory that should have been created. + To commit changes: + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs login + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs commit -m 'blabla.' -r v19-devel pa_sgi/pa_sgi.c + cvs -d:pserver:pieter@www.portaudio.com:/home/cvs logout + To see if someone else worked on something: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -r v19-devel + To get an older revision of a certain file (without sticky business): + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs update -p -r 1.1.1.1.2.4 pa_tests/patest1.c >pa_tests/patest1.c-OLD + To see logs: + cvs -d:pserver:anonymous@www.portaudio.com:/home/cvs log pa_common/pa_skeleton.c +*/ |