From cfc2f7d280ae57ef563dd69bad27c61a148d6ded Mon Sep 17 00:00:00 2001
From: Miller Puckette <millerpuckette@users.sourceforge.net>
Date: Mon, 6 Sep 2004 20:44:42 +0000
Subject: ... more changes to try to upload 0.38 test 5 to CVS

svn path=/trunk/; revision=2011
---
 pd/portaudio/pa_unix_oss/low_latency_tip.txt |  Bin 0 -> 3111 bytes
 pd/portaudio/pa_unix_oss/pa_unix_oss.c       | 1196 ++++++++++++++++++++++++++
 pd/portaudio/pa_unix_oss/recplay.c           |  114 +++
 3 files changed, 1310 insertions(+)
 create mode 100644 pd/portaudio/pa_unix_oss/low_latency_tip.txt
 create mode 100644 pd/portaudio/pa_unix_oss/pa_unix_oss.c
 create mode 100644 pd/portaudio/pa_unix_oss/recplay.c

(limited to 'pd/portaudio/pa_unix_oss')

diff --git a/pd/portaudio/pa_unix_oss/low_latency_tip.txt b/pd/portaudio/pa_unix_oss/low_latency_tip.txt
new file mode 100644
index 00000000..2d982b79
Binary files /dev/null and b/pd/portaudio/pa_unix_oss/low_latency_tip.txt differ
diff --git a/pd/portaudio/pa_unix_oss/pa_unix_oss.c b/pd/portaudio/pa_unix_oss/pa_unix_oss.c
new file mode 100644
index 00000000..b2e6d5a4
--- /dev/null
+++ b/pd/portaudio/pa_unix_oss/pa_unix_oss.c
@@ -0,0 +1,1196 @@
+/*
+ * $Id: pa_unix_oss.c,v 1.6.2.7 2003/09/23 21:17:22 rossbencina Exp $
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ * OSS implementation by:
+ *   Douglas Repetto
+ *   Phil Burk
+ *   Dominic Mazzoni
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#ifdef __linux__
+# include <linux/soundcard.h>
+# define DEVICE_NAME_BASE            "/dev/dsp"
+#else
+# include <machine/soundcard.h> /* JH20010905 */
+# define DEVICE_NAME_BASE            "/dev/audio"
+#endif
+
+#include "portaudio.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"
+
+/* TODO: add error text handling
+#define PA_UNIX_OSS_ERROR( errorCode, errorText ) \
+    PaUtil_SetLastHostErrorInfo( paInDevelopment, errorCode, errorText )
+*/
+
+#define PRINT(x)   { printf x; fflush(stdout); }
+#define DBUG(x)    /* PRINT(x) */
+
+/* PaOSSHostApiRepresentation - host api datastructure specific to this implementation */
+
+typedef struct
+{
+    PaUtilHostApiRepresentation inheritedHostApiRep;
+    PaUtilStreamInterface callbackStreamInterface;
+    PaUtilStreamInterface blockingStreamInterface;
+
+    PaUtilAllocationGroup *allocations;
+
+    PaHostApiIndex hostApiIndex;
+}
+PaOSSHostApiRepresentation;
+
+typedef struct PaOSS_DeviceList {
+    PaDeviceInfo *deviceInfo;
+    struct PaOSS_DeviceList *next;
+}
+PaOSS_DeviceList;
+
+/* prototypes for functions declared in this file */
+
+PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );
+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 *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           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 );
+static PaError BuildDeviceList( PaOSSHostApiRepresentation *hostApi );
+
+
+PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+    PaError result = paNoError;
+    PaOSSHostApiRepresentation *ossHostApi;
+
+    DBUG(("PaOSS_Initialize\n"));
+
+    ossHostApi = (PaOSSHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaOSSHostApiRepresentation) );
+    if( !ossHostApi )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    ossHostApi->allocations = PaUtil_CreateAllocationGroup();
+    if( !ossHostApi->allocations )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    *hostApi = &ossHostApi->inheritedHostApiRep;
+    (*hostApi)->info.structVersion = 1;
+    (*hostApi)->info.type = paOSS;
+    (*hostApi)->info.name = "OSS";
+    ossHostApi->hostApiIndex = hostApiIndex;
+
+    BuildDeviceList( ossHostApi );
+
+    (*hostApi)->Terminate = Terminate;
+    (*hostApi)->OpenStream = OpenStream;
+    (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+    PaUtil_InitializeStreamInterface( &ossHostApi->callbackStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, GetStreamCpuLoad,
+                                      PaUtil_DummyRead, PaUtil_DummyWrite,
+                                      PaUtil_DummyGetReadAvailable,
+                                      PaUtil_DummyGetWriteAvailable );
+
+    PaUtil_InitializeStreamInterface( &ossHostApi->blockingStreamInterface, CloseStream, StartStream,
+                                      StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+                                      GetStreamTime, PaUtil_DummyGetCpuLoad,
+                                      ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+    return result;
+
+error:
+    if( ossHostApi )
+    {
+        if( ossHostApi->allocations )
+        {
+            PaUtil_FreeAllAllocations( ossHostApi->allocations );
+            PaUtil_DestroyAllocationGroup( ossHostApi->allocations );
+        }
+                
+        PaUtil_FreeMemory( ossHostApi );
+    }
+    return result;
+}
+
+#ifndef AFMT_S16_NE
+#define AFMT_S16_NE  Get_AFMT_S16_NE()
+/*********************************************************************
+ * Some versions of OSS do not define AFMT_S16_NE. So check CPU.
+ * PowerPC is Big Endian. X86 is Little Endian.
+ */
+static int Get_AFMT_S16_NE( void )
+{
+    long testData = 1; 
+    char *ptr = (char *) &testData;
+    int isLittle = ( *ptr == 1 ); /* Does address point to least significant byte? */
+    return isLittle ? AFMT_S16_LE : AFMT_S16_BE;
+}
+#endif
+
+PaError PaOSS_SetFormat(const char *callingFunctionName, int deviceHandle,
+                        char *deviceName, int inputChannelCount, int outputChannelCount,
+                        double *sampleRate)
+{
+    int format;
+    int rate;
+    int temp;
+
+    /* Attempt to set format to 16-bit */
+    
+    format = AFMT_S16_NE;
+    if (ioctl(deviceHandle, SNDCTL_DSP_SETFMT, &format) == -1) {
+       DBUG(("%s: could not set format: %s\n", callingFunctionName, deviceName ));
+       return paSampleFormatNotSupported;
+    }
+    if (format != AFMT_S16_NE) {
+       DBUG(("%s: device does not support AFMT_S16_NE: %s\n", callingFunctionName, deviceName ));
+       return paSampleFormatNotSupported;
+    }
+
+    /* try to set the number of channels */
+
+    if (inputChannelCount > 0) {
+       temp = inputChannelCount;
+       
+       if( ioctl(deviceHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) {
+          DBUG(("%s: Couldn't set device %s to %d channels\n", callingFunctionName, deviceName, inputChannelCount ));
+          return paSampleFormatNotSupported;
+       }
+    }
+    
+    if (outputChannelCount > 0) {
+       temp = outputChannelCount;
+       
+       if( ioctl(deviceHandle, SNDCTL_DSP_CHANNELS, &temp) < 0 ) {
+          DBUG(("%s: Couldn't set device %s to %d channels\n", callingFunctionName, deviceName, outputChannelCount ));          
+          return paSampleFormatNotSupported;
+       }
+    }
+
+    /* try to set the sample rate */
+
+    rate = (int)(*sampleRate);
+    if (ioctl(deviceHandle, SNDCTL_DSP_SPEED, &rate) == -1)
+    {
+        DBUG(("%s: Device %s, couldn't set sample rate to %d\n",
+              callingFunctionName, deviceName, (int)*sampleRate ));
+        return paInvalidSampleRate;
+    }
+
+    /* reject if there's no sample rate within 1% of the one requested */
+    if ((fabs(*sampleRate - rate) / *sampleRate) > 0.01)
+    {
+        DBUG(("%s: Device %s, wanted %d, closest sample rate was %d\n",
+              callingFunctionName, deviceName, (int)*sampleRate, rate ));                 
+        return paInvalidSampleRate;
+    }
+
+    *sampleRate = rate;
+
+    return paNoError;
+}
+
+static PaError PaOSS_QueryDevice(char *deviceName, PaDeviceInfo *deviceInfo)
+{
+    PaError result = paNoError;
+    int tempDevHandle;
+    int numChannels, maxNumChannels;
+    int sampleRate;
+    int format;
+
+    /* 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|O_NONBLOCK))  == -1 )
+    {
+        DBUG(("PaOSS_QueryDevice: could not open %s\n", deviceName ));
+        return paDeviceUnavailable;
+    }
+
+    /* Attempt to set format to 16-bit */
+    format = AFMT_S16_NE;
+    if (ioctl(tempDevHandle, SNDCTL_DSP_SETFMT, &format) == -1) {
+       DBUG(("PaOSS_QueryDevice: could not set format: %s\n", deviceName ));
+       result = paSampleFormatNotSupported;
+       goto error;
+    }
+    if (format != AFMT_S16_NE) {
+       DBUG(("PaOSS_QueryDevice: device does not support AFMT_S16_NE: %s\n", deviceName ));
+       result = paSampleFormatNotSupported;
+       goto error;
+    }
+
+    /* Negotiate for the maximum number of channels for this device. PLB20010927
+     * Consider up to 16 as the upper number of channels.
+     * Variable maxNumChannels 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(("PaOSS_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(("PaOSS_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(("PaOSS_QueryDevice: use SNDCTL_DSP_STEREO, maxNumChannels = %d\n", maxNumChannels ))
+    }
+
+    DBUG(("PaOSS_QueryDevice: maxNumChannels = %d\n", maxNumChannels))
+
+    deviceInfo->maxOutputChannels = maxNumChannels;
+    /* FIXME - for now, assume maxInputChannels = maxOutputChannels.
+     *    Eventually do separate queries for O_WRONLY and O_RDONLY
+    */
+    deviceInfo->maxInputChannels = deviceInfo->maxOutputChannels;
+
+    /* 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);
+    }
+
+    /* Get supported sample rate closest to 44100 Hz */
+    sampleRate = 44100;
+    if (ioctl(tempDevHandle, SNDCTL_DSP_SPEED, &sampleRate) == -1)
+    {
+        result = paUnanticipatedHostError;
+        goto error;
+    }
+
+    deviceInfo->defaultSampleRate = sampleRate;
+
+    deviceInfo->structVersion = 2;
+
+    /* TODO */
+    deviceInfo->defaultLowInputLatency = 128.0 / sampleRate;
+    deviceInfo->defaultLowOutputLatency = 128.0 / sampleRate;
+    deviceInfo->defaultHighInputLatency = 16384.0 / sampleRate;
+    deviceInfo->defaultHighOutputLatency = 16384.0 / sampleRate;
+
+    result = paNoError;
+
+error:
+    /* We MUST close the handle here or we won't be able to reopen it later!!!  */
+    close(tempDevHandle);
+
+    return result;
+}
+
+static PaError BuildDeviceList( PaOSSHostApiRepresentation *ossApi )
+{
+    PaUtilHostApiRepresentation *commonApi = &ossApi->inheritedHostApiRep;
+    PaOSS_DeviceList *head = NULL, *tail = NULL, *entry;
+    int i;
+    int numDevices;
+
+    /* Find devices by calling PaOSS_QueryDevice on each
+       potential device names.  When we find a valid one,
+       add it to a linked list. */
+
+    for(i=0; i<10; i++) {
+       char deviceName[32];
+       PaDeviceInfo deviceInfo;
+       int testResult;
+
+       if (i==0)
+          sprintf(deviceName, "%s", DEVICE_NAME_BASE);
+       else
+          sprintf(deviceName, "%s%d", DEVICE_NAME_BASE, i);
+
+       DBUG(("PaOSS BuildDeviceList: trying device %s\n", deviceName ));
+       testResult = PaOSS_QueryDevice(deviceName, &deviceInfo);
+       DBUG(("PaOSS BuildDeviceList: PaOSS_QueryDevice returned %d\n", testResult ));
+
+       if (testResult == paNoError) {
+          DBUG(("PaOSS BuildDeviceList: Adding device %s to list\n", deviceName));
+          deviceInfo.hostApi = ossApi->hostApiIndex;
+          deviceInfo.name = PaUtil_GroupAllocateMemory(
+              ossApi->allocations, strlen(deviceName)+1);
+          strcpy((char *)deviceInfo.name, deviceName);
+          entry = (PaOSS_DeviceList *)PaUtil_AllocateMemory(sizeof(PaOSS_DeviceList));
+          entry->deviceInfo = (PaDeviceInfo*)PaUtil_GroupAllocateMemory(
+              ossApi->allocations, sizeof(PaDeviceInfo) );
+          entry->next = NULL;
+          memcpy(entry->deviceInfo, &deviceInfo, sizeof(PaDeviceInfo));
+          if (tail)
+             tail->next = entry;
+          else {
+             head = entry;
+             tail = entry;
+          }
+       }
+    }
+
+    /* Make an array of PaDeviceInfo pointers out of the linked list */
+
+    numDevices = 0;
+    entry = head;
+    while(entry) {
+       numDevices++;
+       entry = entry->next;
+    }
+
+    DBUG(("PaOSS BuildDeviceList: Total number of devices found: %d\n", numDevices));
+
+    commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
+        ossApi->allocations, sizeof(PaDeviceInfo*) *numDevices );
+
+    entry = head;
+    i = 0;
+    while(entry) {
+       commonApi->deviceInfos[i] = entry->deviceInfo;
+       i++;
+       entry = entry->next;
+    }
+
+    commonApi->info.deviceCount = numDevices;
+    commonApi->info.defaultInputDevice = 0;
+    commonApi->info.defaultOutputDevice = 0;
+
+    return paNoError;
+}
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+    PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi;
+
+    if( ossHostApi->allocations )
+    {
+        PaUtil_FreeAllAllocations( ossHostApi->allocations );
+        PaUtil_DestroyAllocationGroup( ossHostApi->allocations );
+    }
+
+    PaUtil_FreeMemory( ossHostApi );
+}
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+                                  const PaStreamParameters *inputParameters,
+                                  const PaStreamParameters *outputParameters,
+                                  double sampleRate )
+{
+    PaDeviceIndex device;
+    PaDeviceInfo *deviceInfo;
+    PaError result = paNoError;
+    char *deviceName;
+    int inputChannelCount, outputChannelCount;
+    int tempDevHandle = 0;
+    int flags;
+    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 */
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+        
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that output device can support inputChannelCount */
+        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate outputStreamInfo */
+        if( outputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+    }
+    else
+    {
+        outputChannelCount = 0;
+    }
+
+    if (inputChannelCount == 0 && outputChannelCount == 0)
+        return paInvalidChannelCount;
+
+    /* if full duplex, make sure that they're the same device */
+
+    if (inputChannelCount > 0 && outputChannelCount > 0 &&
+        inputParameters->device != outputParameters->device)
+        return paInvalidDevice;
+
+    /* if full duplex, also make sure that they're the same number of channels */
+
+    if (inputChannelCount > 0 && outputChannelCount > 0 &&
+        inputChannelCount != outputChannelCount)
+       return paInvalidChannelCount;
+
+    /* open the device so we can do more tests */
+    
+    if (inputChannelCount > 0) {
+        result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, inputParameters->device, hostApi);
+        if (result != paNoError)
+            return result;
+    }
+    else {
+        result = PaUtil_DeviceIndexToHostApiDeviceIndex(&device, outputParameters->device, hostApi);
+        if (result != paNoError)
+            return result;
+    }
+
+    deviceInfo = hostApi->deviceInfos[device];
+    deviceName = (char *)deviceInfo->name;
+    
+    flags = O_NONBLOCK;
+    if (inputChannelCount > 0 && outputChannelCount > 0)
+       flags |= O_RDWR;
+    else if (inputChannelCount > 0)
+       flags |= O_RDONLY;
+    else
+       flags |= O_WRONLY;
+
+    if ( (tempDevHandle = open(deviceInfo->name, flags))  == -1 )
+    {
+        DBUG(("PaOSS IsFormatSupported: could not open %s\n", deviceName ));
+        return paDeviceUnavailable;
+    }
+
+    /* PaOSS_SetFormat will do the rest of the checking for us */
+
+    if ((result = PaOSS_SetFormat("PaOSS IsFormatSupported", tempDevHandle,
+                                  deviceName, inputChannelCount, outputChannelCount,
+                                  &sampleRate)) != paNoError)
+    {
+       goto error;
+    }
+
+    /* everything succeeded! */
+
+    close(tempDevHandle);             
+
+    return paFormatIsSupported;
+
+ error:
+    if (tempDevHandle)
+        close(tempDevHandle);         
+
+    return paSampleFormatNotSupported;
+}
+
+/* PaOSSStream - a stream data structure specifically for this implementation */
+
+typedef struct PaOSSStream
+{
+    PaUtilStreamRepresentation streamRepresentation;
+    PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+    PaUtilBufferProcessor bufferProcessor;
+
+    int deviceHandle;
+
+    int stopSoon;
+    int stopNow;
+    int isActive;
+
+    int inputChannelCount;
+    int outputChannelCount;
+
+    pthread_t thread;
+
+    void *inputBuffer;
+    void *outputBuffer;
+
+    int lastPosPtr;
+    double lastStreamBytes;
+
+    int framesProcessed;
+
+    double sampleRate;
+
+    unsigned long framesPerHostCallback;
+}
+PaOSSStream;
+
+/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */
+
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+                           PaStream** s,
+                           const PaStreamParameters *inputParameters,
+                           const PaStreamParameters *outputParameters,
+                           double sampleRate,
+                           unsigned long framesPerBuffer,
+                           PaStreamFlags streamFlags,
+                           PaStreamCallback *streamCallback,
+                           void *userData )
+{
+    PaError result = paNoError;
+    PaOSSHostApiRepresentation *ossHostApi = (PaOSSHostApiRepresentation*)hostApi;
+    PaOSSStream *stream = 0;
+    PaDeviceIndex device;
+    PaDeviceInfo *deviceInfo;
+    audio_buf_info bufinfo;
+    int bytesPerHostBuffer;
+    int flags;
+    int deviceHandle = 0;
+    char *deviceName;
+    unsigned long framesPerHostBuffer;
+    int inputChannelCount, outputChannelCount;
+    PaSampleFormat inputSampleFormat = paInt16, outputSampleFormat = paInt16;
+    PaSampleFormat hostInputSampleFormat = paInt16, hostOutputSampleFormat = paInt16;
+
+    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 */
+
+        hostInputSampleFormat =
+            PaUtil_SelectClosestAvailableFormat( paInt16, inputSampleFormat );
+    }
+    else
+    {
+        inputChannelCount = 0;
+    }
+
+    if( outputParameters )
+    {
+        outputChannelCount = outputParameters->channelCount;
+        outputSampleFormat = outputParameters->sampleFormat;
+        
+        /* unless alternate device specification is supported, reject the use of
+            paUseHostApiSpecificDeviceSpecification */
+
+        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+            return paInvalidDevice;
+
+        /* check that output device can support inputChannelCount */
+        if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+            return paInvalidChannelCount;
+
+        /* validate outputStreamInfo */
+        if( outputParameters->hostApiSpecificStreamInfo )
+            return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+
+        hostOutputSampleFormat =
+            PaUtil_SelectClosestAvailableFormat( paInt16, outputSampleFormat );
+    }
+    else
+    {
+        outputChannelCount = 0;
+    }
+
+    if( inputChannelCount == 0 && outputChannelCount == 0 )
+    {
+        DBUG(("Both inputChannelCount and outputChannelCount are zero!\n"));
+        return paUnanticipatedHostError;
+    }
+
+    /* validate platform specific flags */
+    if( (streamFlags & paPlatformSpecificFlags) != 0 )
+        return paInvalidFlag; /* unexpected platform specific flag */
+
+    /*
+     * open the device and set parameters here
+     */
+
+    if (inputChannelCount == 0 && outputChannelCount == 0)
+        return paInvalidChannelCount;
+
+    /* if full duplex, make sure that they're the same device */
+
+    if (inputChannelCount > 0 && outputChannelCount > 0 &&
+        inputParameters->device != outputParameters->device)
+        return paInvalidDevice;
+
+    /* if full duplex, also make sure that they're the same number of channels */
+
+    if (inputChannelCount > 0 && outputChannelCount > 0 &&
+        inputChannelCount != outputChannelCount)
+       return paInvalidChannelCount;
+
+    /* note that inputParameters and outputParameters device indicies are
+     * already in host format */
+    device = (inputChannelCount > 0 )
+            ? inputParameters->device
+            : outputParameters->device;
+
+    deviceInfo = hostApi->deviceInfos[device];
+    deviceName = (char *)deviceInfo->name;
+
+    flags = O_NONBLOCK;
+    if (inputChannelCount > 0 && outputChannelCount > 0)
+       flags |= O_RDWR;
+    else if (inputChannelCount > 0)
+       flags |= O_RDONLY;
+    else
+       flags |= O_WRONLY;
+
+    /* open first in nonblocking mode, in case it's busy... */
+    if ( (deviceHandle = open(deviceInfo->name, flags))  == -1 )
+    {
+        DBUG(("PaOSS OpenStream: could not open %s\n", deviceName ));
+        return paDeviceUnavailable;
+    }
+
+    /* if that succeeded, immediately open it again in blocking mode */
+    close(deviceHandle);
+    flags -= O_NONBLOCK;
+    if ( (deviceHandle = open(deviceInfo->name, flags))  == -1 )
+    {
+        DBUG(("PaOSS OpenStream: could not open %s in blocking mode\n", deviceName ));
+        return paDeviceUnavailable;
+    }
+
+    if ((result = PaOSS_SetFormat("PaOSS OpenStream", deviceHandle,
+                                  deviceName, inputChannelCount, outputChannelCount,
+                                  &sampleRate)) != paNoError)
+    {
+       goto error;
+    }
+
+    /* Compute number of frames per host buffer - if we can't retrieve the
+     * value, use the user's value instead 
+     */
+    
+    if ( ioctl(deviceHandle, SNDCTL_DSP_GETBLKSIZE, &bytesPerHostBuffer) == 0)
+    {
+        framesPerHostBuffer = bytesPerHostBuffer / 2 / (inputChannelCount>0? inputChannelCount: outputChannelCount);
+    }
+    else
+        framesPerHostBuffer = framesPerBuffer;
+
+    /* Allocate stream and fill in structure */
+
+    stream = (PaOSSStream*)PaUtil_AllocateMemory( sizeof(PaOSSStream) );
+    if( !stream )
+    {
+        result = paInsufficientMemory;
+        goto error;
+    }
+
+    if( streamCallback )
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &ossHostApi->callbackStreamInterface, streamCallback, userData );
+    }
+    else
+    {
+        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+                                               &ossHostApi->blockingStreamInterface, streamCallback, userData );
+    }    
+
+    stream->streamRepresentation.streamInfo.inputLatency = 0.;
+    stream->streamRepresentation.streamInfo.outputLatency = 0.;
+
+    if (inputChannelCount > 0) {
+        if (ioctl( deviceHandle, SNDCTL_DSP_GETISPACE, &bufinfo) == 0)
+            stream->streamRepresentation.streamInfo.inputLatency =
+                (bufinfo.fragsize * bufinfo.fragstotal) / sampleRate;
+    }
+
+    if (outputChannelCount > 0) {
+        if (ioctl( deviceHandle, SNDCTL_DSP_GETOSPACE, &bufinfo) == 0)
+            stream->streamRepresentation.streamInfo.outputLatency =
+                (bufinfo.fragsize * bufinfo.fragstotal) / sampleRate;
+    }    
+
+    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+    PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+    /* we assume a fixed host buffer size in this example, but the buffer processor
+        can also support bounded and unknown host buffer sizes by passing 
+        paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of
+        paUtilFixedHostBufferSize below. */
+        
+    result =  PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+              inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+              outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+              sampleRate, streamFlags, framesPerBuffer,
+              framesPerHostBuffer, paUtilFixedHostBufferSize,
+              streamCallback, userData );
+    if( result != paNoError )
+        goto error;
+
+    stream->framesPerHostCallback = framesPerHostBuffer;
+
+    stream->stopSoon = 0;
+    stream->stopNow = 0;
+    stream->isActive = 0;
+    stream->thread = 0;
+    stream->lastPosPtr = 0;
+    stream->lastStreamBytes = 0;
+    stream->sampleRate = sampleRate;
+    stream->framesProcessed = 0;
+    stream->deviceHandle = deviceHandle;
+
+    if (inputChannelCount > 0)
+        stream->inputBuffer = PaUtil_AllocateMemory( 2 * framesPerHostBuffer * inputChannelCount );
+    else
+        stream->inputBuffer = NULL;
+
+    if (outputChannelCount > 0)
+        stream->outputBuffer = PaUtil_AllocateMemory( 2 * framesPerHostBuffer * outputChannelCount );
+    else
+        stream->outputBuffer = NULL;
+
+    stream->inputChannelCount = inputChannelCount;
+    stream->outputChannelCount = outputChannelCount;
+
+    *s = (PaStream*)stream;
+
+    result = paNoError;
+
+    return result;
+
+error:
+    if( stream )
+        PaUtil_FreeMemory( stream );
+
+    if( deviceHandle )
+        close( deviceHandle );
+
+    return result;
+}
+
+static void *PaOSS_AudioThreadProc(void *userData)
+{
+    PaOSSStream *stream = (PaOSSStream*)userData;
+
+    DBUG(("PaOSS AudioThread: %d in, %d out\n", stream->inputChannelCount, stream->outputChannelCount));
+
+    while( (stream->stopNow == 0) && (stream->stopSoon == 0) ) {
+        PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* TODO: IMPLEMENT ME */
+        int callbackResult;
+        unsigned long framesProcessed;
+        int bytesRequested;
+        int bytesRead, bytesWritten;
+        int delta;
+        int result;
+        count_info info;
+
+        PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+    
+        PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo,
+                0 /* @todo pass underflow/overflow flags when necessary */ );
+        
+        /*
+          depending on whether the host buffers are interleaved, non-interleaved
+          or a mixture, you will want to call PaUtil_SetInterleaved*Channels(),
+          PaUtil_SetNonInterleaved*Channel() or PaUtil_Set*Channel() here.
+        */
+
+        if ( stream->inputChannelCount > 0 )
+        {
+            bytesRequested = stream->framesPerHostCallback * 2 * stream->inputChannelCount;
+            bytesRead = read( stream->deviceHandle, stream->inputBuffer, bytesRequested );
+            
+            PaUtil_SetInputFrameCount( &stream->bufferProcessor, bytesRead/(2*stream->inputChannelCount));
+            PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor,
+                                                0, /* first channel of inputBuffer is channel 0 */
+                                                stream->inputBuffer,
+                                                0 ); /* 0 - use inputChannelCount passed to init buffer processor */
+        }
+
+        if ( stream->outputChannelCount > 0 )
+        {
+           PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+           PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor,
+                                                0, /* first channel of outputBuffer is channel 0 */
+                                                stream->outputBuffer,
+                                                0 ); /* 0 - use outputChannelCount passed to init buffer processor */
+        }
+
+        callbackResult = paContinue;
+        framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+
+        PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+        
+        if( callbackResult == paContinue )
+        {
+           /* nothing special to do */
+        }
+        else if( callbackResult == paAbort )
+        {
+            /* once finished, call the finished callback */
+            if( stream->streamRepresentation.streamFinishedCallback != 0 )
+                stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+            return NULL; /* return from the loop */
+        }
+        else if ( callbackResult == paComplete )
+        {
+            /* User callback has asked us to stop with paComplete or other non-zero value */
+           
+            /* once finished, call the finished callback */
+            if( stream->streamRepresentation.streamFinishedCallback != 0 )
+                stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+            stream->stopSoon = 1;
+        }
+
+        if ( stream->outputChannelCount > 0 ) {
+            /* write output samples AFTER we've checked the callback result code */
+
+            bytesRequested = stream->framesPerHostCallback * 2 * stream->outputChannelCount;
+            bytesWritten = write( stream->deviceHandle, stream->outputBuffer, bytesRequested );
+
+            /* TODO: handle bytesWritten != bytesRequested (slippage?) */
+        }
+
+        /* Update current stream time (using a double so that
+           we don't wrap around like info.bytes does) */
+        if( stream->outputChannelCount > 0 )
+            result = ioctl( stream->deviceHandle, SNDCTL_DSP_GETOPTR, &info);
+        else
+            result = ioctl( stream->deviceHandle, SNDCTL_DSP_GETIPTR, &info);
+
+        if (result == 0) {
+            delta = ( info.bytes - stream->lastPosPtr ) & 0x000FFFFF;
+            stream->lastStreamBytes += delta;
+            stream->lastPosPtr = info.bytes;
+        }
+
+        stream->framesProcessed += stream->framesPerHostCallback;
+    }
+
+    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;
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    close(stream->deviceHandle);
+
+    if ( stream->inputBuffer )
+        PaUtil_FreeMemory( stream->inputBuffer );
+    if ( stream->outputBuffer )
+        PaUtil_FreeMemory( stream->outputBuffer );
+
+    PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+    PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+    PaUtil_FreeMemory( stream );
+
+    return result;
+}
+
+
+static PaError StartStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaOSSStream *stream = (PaOSSStream*)s;
+    int presult;
+
+    stream->isActive = 1;
+    stream->lastPosPtr = 0;
+    stream->lastStreamBytes = 0;
+    stream->framesProcessed = 0;
+
+    DBUG(("PaOSS StartStream\n"));
+
+    /* only use the thread for callback streams */
+    if( stream->bufferProcessor.streamCallback ) {
+            presult = pthread_create(&stream->thread,
+                             NULL /*pthread_attr_t * attr*/,
+                             (void*)PaOSS_AudioThreadProc, (void *)stream);
+    }
+    
+    return result;
+}
+
+
+static PaError StopStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    stream->stopSoon = 1;
+
+    /* only use the thread for callback streams */
+    if( stream->bufferProcessor.streamCallback )
+        pthread_join( stream->thread, NULL );
+
+    stream->stopSoon = 0;
+    stream->stopNow = 0;
+    stream->isActive = 0;
+
+    DBUG(("PaOSS StopStream: Stopped stream\n"));
+
+    return result;
+}
+
+
+static PaError AbortStream( PaStream *s )
+{
+    PaError result = paNoError;
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    stream->stopNow = 1;
+
+    /* only use the thread for callback streams */
+    if( stream->bufferProcessor.streamCallback )
+        pthread_join( stream->thread, NULL );
+
+    stream->stopSoon = 0;
+    stream->stopNow = 0;
+    stream->isActive = 0;
+
+    DBUG(("PaOSS AbortStream: Stopped stream\n"));
+
+    return result;
+}
+
+
+static PaError IsStreamStopped( PaStream *s )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    return (!stream->isActive);
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    return (stream->isActive);
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+    count_info info;
+    int delta;
+
+    if( stream->outputChannelCount > 0 ) {
+        if (ioctl( stream->deviceHandle, SNDCTL_DSP_GETOPTR, &info) == 0) {
+            delta = ( info.bytes - stream->lastPosPtr ) & 0x000FFFFF;
+            return ( stream->lastStreamBytes + delta) / ( stream->outputChannelCount * 2 ) / stream->sampleRate;
+        }
+    }
+    else {
+        if (ioctl( stream->deviceHandle, SNDCTL_DSP_GETIPTR, &info) == 0) {
+            delta = (info.bytes - stream->lastPosPtr) & 0x000FFFFF;
+            return ( stream->lastStreamBytes + delta) / ( stream->inputChannelCount * 2 ) / stream->sampleRate;
+        }
+    }
+
+    /* the ioctl failed, but we can still give a coarse estimate */
+
+    return stream->framesProcessed / stream->sampleRate;
+}
+
+
+static double GetStreamCpuLoad( PaStream* s )
+{
+    PaOSSStream *stream = (PaOSSStream*)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 )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+    int bytesRequested, bytesRead;
+
+    bytesRequested = frames * 2 * stream->inputChannelCount;
+    bytesRead = read( stream->deviceHandle, stream->inputBuffer, bytesRequested );
+
+    if ( bytesRequested != bytesRead )
+        return paUnanticipatedHostError;
+    else
+        return paNoError;
+}
+
+
+static PaError WriteStream( PaStream* s,
+                            const void *buffer,
+                            unsigned long frames )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+    int bytesRequested, bytesWritten;
+
+    bytesRequested = frames * 2 * stream->outputChannelCount;
+    bytesWritten = write( stream->deviceHandle, buffer, bytesRequested );
+
+    if ( bytesRequested != bytesWritten )
+        return paUnanticipatedHostError;
+    else
+        return paNoError;
+}
+
+
+static signed long GetStreamReadAvailable( PaStream* s )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+    audio_buf_info info;
+
+    if ( ioctl(stream->deviceHandle, SNDCTL_DSP_GETISPACE, &info) == 0)
+    {
+        int bytesAvailable = info.fragments * info.fragsize;
+        return ( bytesAvailable / 2 / stream->inputChannelCount );
+    }
+    else
+        return 0; /* TODO: is this right for "don't know"? */
+}
+
+
+static signed long GetStreamWriteAvailable( PaStream* s )
+{
+    PaOSSStream *stream = (PaOSSStream*)s;
+
+    audio_buf_info info;
+
+    if ( ioctl(stream->deviceHandle, SNDCTL_DSP_GETOSPACE, &info) == 0)
+    {
+        int bytesAvailable = info.fragments * info.fragsize;
+        return ( bytesAvailable / 2 / stream->outputChannelCount );
+    }
+    else
+        return 0; /* TODO: is this right for "don't know"? */
+}
+
diff --git a/pd/portaudio/pa_unix_oss/recplay.c b/pd/portaudio/pa_unix_oss/recplay.c
new file mode 100644
index 00000000..9d4c78cf
--- /dev/null
+++ b/pd/portaudio/pa_unix_oss/recplay.c
@@ -0,0 +1,114 @@
+/*
+ * recplay.c
+ * Phil Burk
+ * Minimal record and playback test.
+ * 
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#ifndef __STDC__
+/* #include <getopt.h> */
+#endif /* __STDC__ */
+#include <fcntl.h>
+#ifdef __STDC__
+#include <string.h>
+#else /* __STDC__ */
+#include <strings.h>
+#endif /* __STDC__ */
+#include <sys/soundcard.h>
+
+#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 );
+}
-- 
cgit v1.2.1