mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-19 10:32:02 -05:00
8d353a5b3b
git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/trunk@189 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
1657 lines
60 KiB
C
1657 lines
60 KiB
C
/*
|
|
* $Id$
|
|
* Portable Audio I/O Library for Macintosh
|
|
*
|
|
* Based on the Open Source API proposed by Ross Bencina
|
|
* Copyright (c) 1999-2000 Phil Burk
|
|
*
|
|
* Special thanks to Chris Rolfe for his many helpful suggestions, bug fixes,
|
|
* and code contributions.
|
|
* Thanks also to Tue Haste Andersen, Alberto Ricci, Nico Wald,
|
|
* Roelf Toxopeus and Tom Erbe for testing the code and making
|
|
* numerous suggestions.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* Any person wishing to distribute modifications to the Software is
|
|
* requested to send the modifications to the original developer so that
|
|
* they can be incorporated into the canonical version.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
/* Modification History
|
|
PLB20010415 - ScanInputDevices was setting sDefaultOutputDeviceID instead of sDefaultInputDeviceID
|
|
PLB20010415 - Device Scan was crashing for anything other than siBadSoundInDevice, but some Macs may return other errors!
|
|
PLB20010420 - Fix TIMEOUT in record mode.
|
|
PLB20010420 - Change CARBON_COMPATIBLE to TARGET_API_MAC_CARBON
|
|
PLB20010907 - Pass unused event to WaitNextEvent to prevent Mac OSX crash. Thanks Dominic Mazzoni.
|
|
PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni.
|
|
PLB20011009 - Use NewSndCallBackUPP() for CARBON
|
|
PLB20020417 - I used to call Pa_GetMinNumBuffers() which doesn't take into account the
|
|
variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will
|
|
give lower latency when virtual memory is turned off.
|
|
Thanks Kristoffer Jensen and Georgios Marentakis for spotting this bug.
|
|
PLB20020423 - Use new method to calculate CPU load similar to other ports. Based on num frames calculated.
|
|
Fixed Pa_StreamTime(). Now estimates how many frames have played based on MicroSecond timer.
|
|
Added PA_MAX_USAGE_ALLOWED to prevent Mac form hanging when CPU load approaches 100%.
|
|
PLB20020424 - Fixed return value in Pa_StreamTime
|
|
*/
|
|
|
|
/*
|
|
COMPATIBILITY
|
|
This Macintosh implementation is designed for use with Mac OS 7, 8 and
|
|
9 on PowerMacs, and OS X if compiled with CARBON
|
|
|
|
OUTPUT
|
|
A circular array of CmpSoundHeaders is used as a queue. For low latency situations
|
|
there will only be two small buffers used. For higher latency, more and larger buffers
|
|
may be used.
|
|
To play the sound we use SndDoCommand() with bufferCmd. Each buffer is followed
|
|
by a callbackCmd which informs us when the buffer has been processsed.
|
|
|
|
INPUT
|
|
The SndInput Manager SPBRecord call is used for sound input. If only
|
|
input is used, then the PA user callback is called from the Input completion proc.
|
|
For full-duplex, or output only operation, the PA callback is called from the
|
|
HostBuffer output completion proc. In that case, input sound is passed to the
|
|
callback by a simple FIFO.
|
|
|
|
TODO:
|
|
O- Add support for native sample data formats other than int16.
|
|
O- Review buffer sizing. Should it be based on result of siDeviceBufferInfo query?
|
|
O- Determine default devices somehow.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <memory.h>
|
|
#include <math.h>
|
|
|
|
/* Mac specific includes */
|
|
#include "OSUtils.h"
|
|
#include <MacTypes.h>
|
|
#include <Math64.h>
|
|
#include <Errors.h>
|
|
#include <Sound.h>
|
|
#include <SoundInput.h>
|
|
#include <SoundComponents.h>
|
|
#include <Devices.h>
|
|
#include <DateTimeUtils.h>
|
|
#include <Timer.h>
|
|
#include <Gestalt.h>
|
|
|
|
#include "portaudio.h"
|
|
#include "pa_host.h"
|
|
#include "pa_trace.h"
|
|
|
|
#ifndef FALSE
|
|
#define FALSE (0)
|
|
#define TRUE (!FALSE)
|
|
#endif
|
|
|
|
/* #define TARGET_API_MAC_CARBON (1) */
|
|
|
|
/*
|
|
* Define maximum CPU load that will be allowed. User callback will
|
|
* be skipped if load exceeds this limit. This is to prevent the Mac
|
|
* from hanging when the CPU is hogged by the sound thread.
|
|
* On my PowerBook G3, the mac hung when I used 94% of CPU ( usage = 0.94 ).
|
|
*/
|
|
#define PA_MAX_USAGE_ALLOWED (0.92)
|
|
|
|
/* Debugging output macros. */
|
|
#define PRINT(x) { printf x; fflush(stdout); }
|
|
#define ERR_RPT(x) PRINT(x)
|
|
#define DBUG(x) /* PRINT(x) /**/
|
|
#define DBUGX(x) /* PRINT(x) /**/
|
|
|
|
#define MAC_PHYSICAL_FRAMES_PER_BUFFER (512) /* Minimum number of stereo frames per SoundManager double buffer. */
|
|
#define MAC_VIRTUAL_FRAMES_PER_BUFFER (4096) /* Need this many when using Virtual Memory for recording. */
|
|
#define PA_MIN_NUM_HOST_BUFFERS (2)
|
|
#define PA_MAX_NUM_HOST_BUFFERS (16) /* Do not exceed!! */
|
|
#define PA_MAX_DEVICE_INFO (32)
|
|
|
|
/* Conversions for 16.16 fixed point code. */
|
|
#define DoubleToUnsignedFixed(x) ((UnsignedFixed) ((x) * 65536.0))
|
|
#define UnsignedFixedToDouble(fx) (((double)(fx)) * (1.0/(1<<16)))
|
|
|
|
/************************************************************************************/
|
|
/****************** Structures ******************************************************/
|
|
/************************************************************************************/
|
|
/* Use for passing buffers from input callback to output callback for processing. */
|
|
typedef struct MultiBuffer
|
|
{
|
|
char *buffers[PA_MAX_NUM_HOST_BUFFERS];
|
|
int numBuffers;
|
|
int nextWrite;
|
|
int nextRead;
|
|
}
|
|
MultiBuffer;
|
|
|
|
/* Define structure to contain all Macintosh specific data. */
|
|
typedef struct PaHostSoundControl
|
|
{
|
|
UInt64 pahsc_EntryCount;
|
|
double pahsc_InverseMicrosPerHostBuffer; /* 1/Microseconds of real-time audio per user buffer. */
|
|
|
|
/* Use char instead of Boolean for atomic operation. */
|
|
volatile char pahsc_IsRecording; /* Recording in progress. Set by foreground. Cleared by background. */
|
|
volatile char pahsc_StopRecording; /* Signal sent to background. */
|
|
volatile char pahsc_IfInsideCallback;
|
|
/* Input */
|
|
SPB pahsc_InputParams;
|
|
SICompletionUPP pahsc_InputCompletionProc;
|
|
MultiBuffer pahsc_InputMultiBuffer;
|
|
int32 pahsc_BytesPerInputHostBuffer;
|
|
int32 pahsc_InputRefNum;
|
|
/* Output */
|
|
CmpSoundHeader pahsc_SoundHeaders[PA_MAX_NUM_HOST_BUFFERS];
|
|
int32 pahsc_BytesPerOutputHostBuffer;
|
|
SndChannelPtr pahsc_Channel;
|
|
SndCallBackUPP pahsc_OutputCompletionProc;
|
|
int32 pahsc_NumOutsQueued;
|
|
int32 pahsc_NumOutsPlayed;
|
|
PaTimestamp pahsc_NumFramesDone;
|
|
UInt64 pahsc_WhenFramesDoneIncremented;
|
|
/* Init Time -------------- */
|
|
int32 pahsc_NumHostBuffers;
|
|
int32 pahsc_FramesPerHostBuffer;
|
|
int32 pahsc_UserBuffersPerHostBuffer;
|
|
int32 pahsc_MinFramesPerHostBuffer; /* Can vary depending on virtual memory usage. */
|
|
}
|
|
PaHostSoundControl;
|
|
|
|
/* Mac specific device information. */
|
|
typedef struct internalPortAudioDevice
|
|
{
|
|
long pad_DeviceRefNum;
|
|
long pad_DeviceBufferSize;
|
|
Component pad_Identifier;
|
|
PaDeviceInfo pad_Info;
|
|
}
|
|
internalPortAudioDevice;
|
|
|
|
/************************************************************************************/
|
|
/****************** Data ************************************************************/
|
|
/************************************************************************************/
|
|
static int sNumDevices = 0;
|
|
static internalPortAudioDevice sDevices[PA_MAX_DEVICE_INFO] = { 0 };
|
|
static int32 sPaHostError = 0;
|
|
static int sDefaultOutputDeviceID;
|
|
static int sDefaultInputDeviceID;
|
|
|
|
/************************************************************************************/
|
|
/****************** Prototypes ******************************************************/
|
|
/************************************************************************************/
|
|
static PaError PaMac_TimeSlice( internalPortAudioStream *past, int16 *macOutputBufPtr );
|
|
static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr );
|
|
static PaError PaMac_RecordNext( internalPortAudioStream *past );
|
|
static void PaMac_StartLoadCalculation( internalPortAudioStream *past );
|
|
static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerBuffer, double sampleRate );
|
|
static double *PaMac_GetSampleRatesFromHandle ( int numRates, Handle h );
|
|
static PaError PaMac_ScanInputDevices( void );
|
|
static PaError PaMac_ScanOutputDevices( void );
|
|
static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad );
|
|
static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad );
|
|
static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader );
|
|
static void PaMac_EndLoadCalculation( internalPortAudioStream *past );
|
|
static void PaMac_PlayNext ( internalPortAudioStream *past, int index );
|
|
static long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index );
|
|
static pascal void PaMac_InputCompletionProc(SPBPtr recParams);
|
|
static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCmd);
|
|
static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index );
|
|
long PaHost_GetTotalBufferFrames( internalPortAudioStream *past );
|
|
static int Mac_IsVirtualMemoryOn( void );
|
|
static void PToCString(unsigned char* inString, char* outString);
|
|
char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf );
|
|
char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf );
|
|
int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf );
|
|
int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf );
|
|
int MultiBuffer_IsWriteable( MultiBuffer *mbuf );
|
|
int MultiBuffer_IsReadable( MultiBuffer *mbuf );
|
|
void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf );
|
|
void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf );
|
|
|
|
/*************************************************************************
|
|
** Simple FIFO index control for multiple buffers.
|
|
** Read and Write indices range from 0 to 2N-1.
|
|
** This allows us to distinguish between full and empty.
|
|
*/
|
|
char *MultiBuffer_GetNextWriteBuffer( MultiBuffer *mbuf )
|
|
{
|
|
return mbuf->buffers[mbuf->nextWrite % mbuf->numBuffers];
|
|
}
|
|
char *MultiBuffer_GetNextReadBuffer( MultiBuffer *mbuf )
|
|
{
|
|
return mbuf->buffers[mbuf->nextRead % mbuf->numBuffers];
|
|
}
|
|
int MultiBuffer_GetNextReadIndex( MultiBuffer *mbuf )
|
|
{
|
|
return mbuf->nextRead % mbuf->numBuffers;
|
|
}
|
|
int MultiBuffer_GetNextWriteIndex( MultiBuffer *mbuf )
|
|
{
|
|
return mbuf->nextWrite % mbuf->numBuffers;
|
|
}
|
|
|
|
int MultiBuffer_IsWriteable( MultiBuffer *mbuf )
|
|
{
|
|
int bufsFull = mbuf->nextWrite - mbuf->nextRead;
|
|
if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers);
|
|
return (bufsFull < mbuf->numBuffers);
|
|
}
|
|
int MultiBuffer_IsReadable( MultiBuffer *mbuf )
|
|
{
|
|
int bufsFull = mbuf->nextWrite - mbuf->nextRead;
|
|
if( bufsFull < 0 ) bufsFull += (2 * mbuf->numBuffers);
|
|
return (bufsFull > 0);
|
|
}
|
|
void MultiBuffer_AdvanceReadIndex( MultiBuffer *mbuf )
|
|
{
|
|
int temp = mbuf->nextRead + 1;
|
|
mbuf->nextRead = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp;
|
|
}
|
|
void MultiBuffer_AdvanceWriteIndex( MultiBuffer *mbuf )
|
|
{
|
|
int temp = mbuf->nextWrite + 1;
|
|
mbuf->nextWrite = (temp >= (2 * mbuf->numBuffers)) ? 0 : temp;
|
|
}
|
|
|
|
/*************************************************************************
|
|
** String Utility by Chris Rolfe
|
|
*/
|
|
static void PToCString(unsigned char* inString, char* outString)
|
|
{
|
|
long i;
|
|
for(i=0; i<inString[0]; i++) /* convert Pascal to C string */
|
|
outString[i] = inString[i+1];
|
|
outString[i]=0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
PaError PaHost_Term( void )
|
|
{
|
|
int i;
|
|
PaDeviceInfo *dev;
|
|
double *rates;
|
|
/* Free any allocated sample rate arrays. */
|
|
for( i=0; i<sNumDevices; i++ )
|
|
{
|
|
dev = &sDevices[i].pad_Info;
|
|
rates = (double *) dev->sampleRates;
|
|
if( (rates != NULL) ) free( rates ); /* MEM_011 */
|
|
dev->sampleRates = NULL;
|
|
if( dev->name != NULL ) free( (void *) dev->name ); /* MEM_010 */
|
|
dev->name = NULL;
|
|
}
|
|
sNumDevices = 0;
|
|
return paNoError;
|
|
}
|
|
|
|
/*************************************************************************
|
|
PaHost_Init() is the library initialization function - call this before
|
|
using the library.
|
|
*/
|
|
PaError PaHost_Init( void )
|
|
{
|
|
PaError err;
|
|
NumVersionVariant version;
|
|
|
|
version.parts = SndSoundManagerVersion();
|
|
DBUG(("SndSoundManagerVersion = 0x%x\n", version.whole));
|
|
|
|
/* Have we already initialized the device info? */
|
|
err = (PaError) Pa_CountDevices();
|
|
if( err < 0 ) return err;
|
|
else return paNoError;
|
|
}
|
|
|
|
/*************************************************************************
|
|
PaMac_ScanOutputDevices() queries the properties of all output devices.
|
|
*/
|
|
static PaError PaMac_ScanOutputDevices( void )
|
|
{
|
|
PaError err;
|
|
Component identifier=0;
|
|
ComponentDescription criteria = { kSoundOutputDeviceType, 0, 0, 0, 0 };
|
|
long numComponents, i;
|
|
|
|
/* Search the system linked list for output components */
|
|
numComponents = CountComponents (&criteria);
|
|
identifier = 0;
|
|
sDefaultOutputDeviceID = sNumDevices; /* FIXME - query somehow */
|
|
for (i = 0; i < numComponents; i++)
|
|
{
|
|
/* passing nil returns first matching component. */
|
|
identifier = FindNextComponent( identifier, &criteria);
|
|
sDevices[sNumDevices].pad_Identifier = identifier;
|
|
|
|
/* Set up for default OUTPUT devices. */
|
|
err = PaMac_QueryOutputDeviceInfo( identifier, &sDevices[sNumDevices] );
|
|
if( err < 0 ) return err;
|
|
else sNumDevices++;
|
|
|
|
}
|
|
|
|
return paNoError;
|
|
}
|
|
|
|
/*************************************************************************
|
|
PaMac_ScanInputDevices() queries the properties of all input devices.
|
|
*/
|
|
static PaError PaMac_ScanInputDevices( void )
|
|
{
|
|
Str255 deviceName;
|
|
int count;
|
|
Handle iconHandle;
|
|
PaError err;
|
|
OSErr oserr;
|
|
count = 1;
|
|
sDefaultInputDeviceID = sNumDevices; /* FIXME - query somehow */ /* PLB20010415 - was setting sDefaultOutputDeviceID */
|
|
while(true)
|
|
{
|
|
/* Thanks Chris Rolfe and Alberto Ricci for this trick. */
|
|
oserr = SPBGetIndexedDevice(count++, deviceName, &iconHandle);
|
|
DBUG(("PaMac_ScanInputDevices: SPBGetIndexedDevice returned %d\n", oserr ));
|
|
#if 1
|
|
/* PLB20010415 - was giving error for anything other than siBadSoundInDevice, but some Macs may return other errors! */
|
|
if(oserr != noErr) break; /* Some type of error is expected when count > devices */
|
|
#else
|
|
if(oserr == siBadSoundInDevice)
|
|
{ /* it's expected when count > devices */
|
|
oserr = noErr;
|
|
break;
|
|
}
|
|
if(oserr != noErr)
|
|
{
|
|
ERR_RPT(("ERROR: SPBGetIndexedDevice(%d,,) returned %d\n", count-1, oserr ));
|
|
sPaHostError = oserr;
|
|
return paHostError;
|
|
}
|
|
#endif
|
|
DisposeHandle(iconHandle); /* Don't need the icon */
|
|
|
|
err = PaMac_QueryInputDeviceInfo( deviceName, &sDevices[sNumDevices] );
|
|
DBUG(("PaMac_ScanInputDevices: PaMac_QueryInputDeviceInfo returned %d\n", err ));
|
|
if( err < 0 ) return err;
|
|
else if( err == 1 ) sNumDevices++;
|
|
}
|
|
|
|
return paNoError;
|
|
}
|
|
|
|
/* Sample rate info returned by using siSampleRateAvailable selector in SPBGetDeviceInfo() */
|
|
/* Thanks to Chris Rolfe for help with this query. */
|
|
#pragma options align=mac68k
|
|
typedef struct
|
|
{
|
|
int16 numRates;
|
|
UnsignedFixed (**rates)[]; /* Handle created by SPBGetDeviceInfo */
|
|
}
|
|
SRateInfo;
|
|
#pragma options align=reset
|
|
|
|
/*************************************************************************
|
|
** PaMac_QueryOutputDeviceInfo()
|
|
** Query information about a named output device.
|
|
** Clears contents of ipad and writes info based on queries.
|
|
** Return one if OK,
|
|
** zero if device cannot be used,
|
|
** or negative error.
|
|
*/
|
|
static PaError PaMac_QueryOutputDeviceInfo( Component identifier, internalPortAudioDevice *ipad )
|
|
{
|
|
int len;
|
|
OSErr err;
|
|
PaDeviceInfo *dev = &ipad->pad_Info;
|
|
SRateInfo srinfo = {0};
|
|
int numRates;
|
|
ComponentDescription tempD;
|
|
Handle nameH=nil, infoH=nil, iconH=nil;
|
|
|
|
memset( ipad, 0, sizeof(internalPortAudioDevice) );
|
|
|
|
dev->structVersion = 1;
|
|
dev->maxInputChannels = 0;
|
|
dev->maxOutputChannels = 2;
|
|
dev->nativeSampleFormats = paInt16; /* FIXME - query to see if 24 or 32 bit data can be handled. */
|
|
|
|
/* Get sample rates supported. */
|
|
err = GetSoundOutputInfo(identifier, siSampleRateAvailable, (Ptr) &srinfo);
|
|
if(err != noErr)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetSoundOutputInfo siSampleRateAvailable returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
numRates = srinfo.numRates;
|
|
DBUG(("PaMac_QueryOutputDeviceInfo: srinfo.numRates = 0x%x\n", srinfo.numRates ));
|
|
if( numRates == 0 )
|
|
{
|
|
dev->numSampleRates = -1;
|
|
numRates = 2;
|
|
}
|
|
else
|
|
{
|
|
dev->numSampleRates = numRates;
|
|
}
|
|
dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates );
|
|
/* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */
|
|
DisposeHandle((Handle) srinfo.rates);
|
|
|
|
/* Device name */
|
|
/* we pass an existing handle for the component name;
|
|
we don't care about the info (type, subtype, etc.) or icon, so set them to nil */
|
|
infoH = nil;
|
|
iconH = nil;
|
|
nameH = NewHandle(0);
|
|
if(nameH == nil) return paInsufficientMemory;
|
|
err = GetComponentInfo(identifier, &tempD, nameH, infoH, iconH);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryOutputDeviceInfo: GetComponentInfo returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
len = (*nameH)[0] + 1;
|
|
dev->name = (char *) malloc(len); /* MEM_010 */
|
|
if( dev->name == NULL )
|
|
{
|
|
DisposeHandle(nameH);
|
|
return paInsufficientMemory;
|
|
}
|
|
else
|
|
{
|
|
PToCString((unsigned char *)(*nameH), (char *) dev->name);
|
|
DisposeHandle(nameH);
|
|
}
|
|
|
|
DBUG(("PaMac_QueryOutputDeviceInfo: dev->name = %s\n", dev->name ));
|
|
return paNoError;
|
|
|
|
error:
|
|
sPaHostError = err;
|
|
return paHostError;
|
|
|
|
}
|
|
|
|
/*************************************************************************
|
|
** PaMac_QueryInputDeviceInfo()
|
|
** Query information about a named input device.
|
|
** Clears contents of ipad and writes info based on queries.
|
|
** Return one if OK,
|
|
** zero if device cannot be used,
|
|
** or negative error.
|
|
*/
|
|
static PaError PaMac_QueryInputDeviceInfo( Str255 deviceName, internalPortAudioDevice *ipad )
|
|
{
|
|
PaError result = paNoError;
|
|
int len;
|
|
OSErr err;
|
|
long mRefNum = 0;
|
|
long tempL;
|
|
int16 tempS;
|
|
Fixed tempF;
|
|
PaDeviceInfo *dev = &ipad->pad_Info;
|
|
SRateInfo srinfo = {0};
|
|
int numRates;
|
|
|
|
memset( ipad, 0, sizeof(internalPortAudioDevice) );
|
|
dev->maxOutputChannels = 0;
|
|
|
|
/* Open device based on name. If device is in use, it may not be able to open in write mode. */
|
|
err = SPBOpenDevice( deviceName, siWritePermission, &mRefNum);
|
|
if (err)
|
|
{
|
|
/* If device is in use, it may not be able to open in write mode so try read mode. */
|
|
err = SPBOpenDevice( deviceName, siReadPermission, &mRefNum);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBOpenDevice returned %d\n", err ));
|
|
sPaHostError = err;
|
|
return paHostError;
|
|
}
|
|
}
|
|
|
|
/* Define macros for printing out device info. */
|
|
#define PrintDeviceInfo(selector,var) \
|
|
err = SPBGetDeviceInfo(mRefNum, selector, (Ptr) &var); \
|
|
if (err) { \
|
|
DBUG(("query %s failed\n", #selector )); \
|
|
}\
|
|
else { \
|
|
DBUG(("query %s = 0x%x\n", #selector, var )); \
|
|
}
|
|
|
|
PrintDeviceInfo( siContinuous, tempS );
|
|
PrintDeviceInfo( siAsync, tempS );
|
|
PrintDeviceInfo( siNumberChannels, tempS );
|
|
PrintDeviceInfo( siSampleSize, tempS );
|
|
PrintDeviceInfo( siSampleRate, tempF );
|
|
PrintDeviceInfo( siChannelAvailable, tempS );
|
|
PrintDeviceInfo( siActiveChannels, tempL );
|
|
PrintDeviceInfo( siDeviceBufferInfo, tempL );
|
|
|
|
err = SPBGetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL);
|
|
if (err == 0) DBUG(("%s = 0x%x\n", "siActiveChannels", tempL ));
|
|
/* Can we use this device? */
|
|
err = SPBGetDeviceInfo(mRefNum, siAsync, (Ptr) &tempS);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siAsync returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
if( tempS == 0 ) goto useless; /* Does not support async recording so forget about it. */
|
|
|
|
err = SPBGetDeviceInfo(mRefNum, siChannelAvailable, (Ptr) &tempS);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siChannelAvailable returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
dev->maxInputChannels = tempS;
|
|
|
|
/* Get sample rates supported. */
|
|
err = SPBGetDeviceInfo(mRefNum, siSampleRateAvailable, (Ptr) &srinfo);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleRateAvailable returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
|
|
numRates = srinfo.numRates;
|
|
DBUG(("numRates = 0x%x\n", numRates ));
|
|
if( numRates == 0 )
|
|
{
|
|
dev->numSampleRates = -1;
|
|
numRates = 2;
|
|
}
|
|
else
|
|
{
|
|
dev->numSampleRates = numRates;
|
|
}
|
|
dev->sampleRates = PaMac_GetSampleRatesFromHandle( numRates, (Handle) srinfo.rates );
|
|
/* SPBGetDeviceInfo created the handle, but it's OUR job to release it. */
|
|
DisposeHandle((Handle) srinfo.rates);
|
|
|
|
/* Get size of device buffer. */
|
|
err = SPBGetDeviceInfo(mRefNum, siDeviceBufferInfo, (Ptr) &tempL);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siDeviceBufferInfo returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
ipad->pad_DeviceBufferSize = tempL;
|
|
DBUG(("siDeviceBufferInfo = %d\n", tempL ));
|
|
|
|
/* Set format based on sample size. */
|
|
err = SPBGetDeviceInfo(mRefNum, siSampleSize, (Ptr) &tempS);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaMac_QueryInputDeviceInfo: SPBGetDeviceInfo siSampleSize returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
switch( tempS )
|
|
{
|
|
case 0x0020:
|
|
dev->nativeSampleFormats = paInt32; /* FIXME - warning, code probably won't support this! */
|
|
break;
|
|
case 0x0010:
|
|
default: /* FIXME - What about other formats? */
|
|
dev->nativeSampleFormats = paInt16;
|
|
break;
|
|
}
|
|
DBUG(("nativeSampleFormats = %d\n", dev->nativeSampleFormats ));
|
|
|
|
/* Device name */
|
|
len = deviceName[0] + 1; /* Get length of Pascal string */
|
|
dev->name = (char *) malloc(len); /* MEM_010 */
|
|
if( dev->name == NULL )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto cleanup;
|
|
}
|
|
PToCString(deviceName, (char *) dev->name);
|
|
DBUG(("deviceName = %s\n", dev->name ));
|
|
result = (PaError) 1;
|
|
/* All done so close up device. */
|
|
cleanup:
|
|
if( mRefNum ) SPBCloseDevice(mRefNum);
|
|
return result;
|
|
|
|
error:
|
|
if( mRefNum ) SPBCloseDevice(mRefNum);
|
|
sPaHostError = err;
|
|
return paHostError;
|
|
|
|
useless:
|
|
if( mRefNum ) SPBCloseDevice(mRefNum);
|
|
return (PaError) 0;
|
|
}
|
|
|
|
/*************************************************************************
|
|
** Allocate a double array and fill it with listed sample rates.
|
|
*/
|
|
static double * PaMac_GetSampleRatesFromHandle ( int numRates, Handle h )
|
|
{
|
|
OSErr err = noErr;
|
|
SInt8 hState;
|
|
int i;
|
|
UnsignedFixed *fixedRates;
|
|
double *rates = (double *) malloc( numRates * sizeof(double) ); /* MEM_011 */
|
|
if( rates == NULL ) return NULL;
|
|
/* Save and restore handle state as suggested by TechNote at:
|
|
http://developer.apple.com/technotes/tn/tn1122.html
|
|
*/
|
|
hState = HGetState (h);
|
|
if (!(err = MemError ()))
|
|
{
|
|
HLock (h);
|
|
if (!(err = MemError ( )))
|
|
{
|
|
fixedRates = (UInt32 *) *h;
|
|
for( i=0; i<numRates; i++ )
|
|
{
|
|
rates[i] = UnsignedFixedToDouble(fixedRates[i]);
|
|
}
|
|
|
|
HSetState (h,hState);
|
|
err = MemError ( );
|
|
}
|
|
}
|
|
if( err )
|
|
{
|
|
free( rates );
|
|
ERR_RPT(("Error in PaMac_GetSampleRatesFromHandle = %d\n", err ));
|
|
}
|
|
return rates;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
int Pa_CountDevices()
|
|
{
|
|
PaError err;
|
|
DBUG(("Pa_CountDevices()\n"));
|
|
/* If no devices, go find some. */
|
|
if( sNumDevices <= 0 )
|
|
{
|
|
err = PaMac_ScanOutputDevices();
|
|
if( err != paNoError ) goto error;
|
|
err = PaMac_ScanInputDevices();
|
|
if( err != paNoError ) goto error;
|
|
}
|
|
return sNumDevices;
|
|
|
|
error:
|
|
PaHost_Term();
|
|
DBUG(("Pa_CountDevices: returns %d\n", err ));
|
|
return err;
|
|
|
|
}
|
|
|
|
/*************************************************************************/
|
|
const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id )
|
|
{
|
|
if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL;
|
|
return &sDevices[id].pad_Info;
|
|
}
|
|
/*************************************************************************/
|
|
PaDeviceID Pa_GetDefaultInputDeviceID( void )
|
|
{
|
|
return sDefaultInputDeviceID;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
PaDeviceID Pa_GetDefaultOutputDeviceID( void )
|
|
{
|
|
return sDefaultOutputDeviceID;
|
|
}
|
|
|
|
/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/
|
|
static void PaMac_StartLoadCalculation( internalPortAudioStream *past )
|
|
{
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
UnsignedWide widePad;
|
|
if( pahsc == NULL ) return;
|
|
/* Query system timer for usage analysis and to prevent overuse of CPU. */
|
|
Microseconds( &widePad );
|
|
pahsc->pahsc_EntryCount = UnsignedWideToUInt64( widePad );
|
|
}
|
|
|
|
/******************************************************************************
|
|
** Measure fractional CPU load based on real-time it took to calculate
|
|
** buffers worth of output.
|
|
*/
|
|
/**************************************************************************/
|
|
static void PaMac_EndLoadCalculation( internalPortAudioStream *past )
|
|
{
|
|
UnsignedWide widePad;
|
|
UInt64 currentCount;
|
|
long usecsElapsed;
|
|
double newUsage;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return;
|
|
|
|
/* Measure CPU utilization during this callback. Note that this calculation
|
|
** assumes that we had the processor the whole time.
|
|
*/
|
|
#define LOWPASS_COEFFICIENT_0 (0.95)
|
|
#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0)
|
|
Microseconds( &widePad );
|
|
currentCount = UnsignedWideToUInt64( widePad );
|
|
|
|
usecsElapsed = (long) U64Subtract(currentCount, pahsc->pahsc_EntryCount);
|
|
|
|
/* Use inverse because it is faster than the divide. */
|
|
newUsage = usecsElapsed * pahsc->pahsc_InverseMicrosPerHostBuffer;
|
|
|
|
past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) +
|
|
(LOWPASS_COEFFICIENT_1 * newUsage);
|
|
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Called by Pa_StartStream()
|
|
*/
|
|
PaError PaHost_StartInput( internalPortAudioStream *past )
|
|
{
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
pahsc->pahsc_IsRecording = 0;
|
|
pahsc->pahsc_StopRecording = 0;
|
|
pahsc->pahsc_InputMultiBuffer.nextWrite = 0;
|
|
pahsc->pahsc_InputMultiBuffer.nextRead = 0;
|
|
return PaMac_RecordNext( past );
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Called by Pa_StopStream().
|
|
** May be called during error recovery or cleanup code
|
|
** so protect against NULL pointers.
|
|
*/
|
|
PaError PaHost_StopInput( internalPortAudioStream *past, int abort )
|
|
{
|
|
int32 timeOutMsec;
|
|
PaError result = paNoError;
|
|
OSErr err = 0;
|
|
long mRefNum;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return paNoError;
|
|
|
|
(void) abort;
|
|
|
|
mRefNum = pahsc->pahsc_InputRefNum;
|
|
|
|
DBUG(("PaHost_StopInput: mRefNum = %d\n", mRefNum ));
|
|
if( mRefNum )
|
|
{
|
|
DBUG(("PaHost_StopInput: pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording ));
|
|
if( pahsc->pahsc_IsRecording )
|
|
{
|
|
/* PLB20010420 - Fix TIMEOUT in record mode. */
|
|
pahsc->pahsc_StopRecording = 1; /* Request that we stop recording. */
|
|
err = SPBStopRecording(mRefNum);
|
|
DBUG(("PaHost_StopInput: then pahsc_IsRecording = %d\n", pahsc->pahsc_IsRecording ));
|
|
|
|
/* Calculate timeOut longer than longest time it could take to play one buffer. */
|
|
timeOutMsec = (int32) ((1500.0 * pahsc->pahsc_FramesPerHostBuffer) / past->past_SampleRate);
|
|
/* Keep querying sound channel until it is no longer busy playing. */
|
|
while( !err && pahsc->pahsc_IsRecording && (timeOutMsec > 0))
|
|
{
|
|
Pa_Sleep(20);
|
|
timeOutMsec -= 20;
|
|
}
|
|
if( timeOutMsec <= 0 )
|
|
{
|
|
ERR_RPT(("PaHost_StopInput: timed out!\n"));
|
|
return paTimedOut;
|
|
}
|
|
}
|
|
}
|
|
if( err )
|
|
{
|
|
sPaHostError = err;
|
|
result = paHostError;
|
|
}
|
|
|
|
DBUG(("PaHost_StopInput: finished.\n", mRefNum ));
|
|
return result;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
static void PaMac_InitSoundHeader( internalPortAudioStream *past, CmpSoundHeader *sndHeader )
|
|
{
|
|
sndHeader->numChannels = past->past_NumOutputChannels;
|
|
sndHeader->sampleRate = DoubleToUnsignedFixed(past->past_SampleRate);
|
|
sndHeader->loopStart = 0;
|
|
sndHeader->loopEnd = 0;
|
|
sndHeader->encode = cmpSH;
|
|
sndHeader->baseFrequency = kMiddleC;
|
|
sndHeader->markerChunk = nil;
|
|
sndHeader->futureUse2 = nil;
|
|
sndHeader->stateVars = nil;
|
|
sndHeader->leftOverSamples = nil;
|
|
sndHeader->compressionID = 0;
|
|
sndHeader->packetSize = 0;
|
|
sndHeader->snthID = 0;
|
|
sndHeader->sampleSize = 8 * sizeof(int16); // FIXME - might be 24 or 32 bits some day;
|
|
sndHeader->sampleArea[0] = 0;
|
|
sndHeader->format = kSoundNotCompressed;
|
|
}
|
|
|
|
static void SetFramesDone( PaHostSoundControl *pahsc, PaTimestamp framesDone )
|
|
{
|
|
UnsignedWide now;
|
|
Microseconds( &now );
|
|
pahsc->pahsc_NumFramesDone = framesDone;
|
|
pahsc->pahsc_WhenFramesDoneIncremented = UnsignedWideToUInt64( now );
|
|
}
|
|
|
|
/***********************************************************************/
|
|
PaError PaHost_StartOutput( internalPortAudioStream *past )
|
|
{
|
|
SndCommand pauseCommand;
|
|
SndCommand resumeCommand;
|
|
int i;
|
|
OSErr error;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return paInternalError;
|
|
if( pahsc->pahsc_Channel == NULL ) return paInternalError;
|
|
|
|
past->past_StopSoon = 0;
|
|
past->past_IsActive = 1;
|
|
pahsc->pahsc_NumOutsQueued = 0;
|
|
pahsc->pahsc_NumOutsPlayed = 0;
|
|
|
|
SetFramesDone( pahsc, 0.0 );
|
|
|
|
/* Pause channel so it does not do back ground processing while we are still filling the queue. */
|
|
pauseCommand.cmd = pauseCmd;
|
|
pauseCommand.param1 = pauseCommand.param2 = 0;
|
|
error = SndDoCommand (pahsc->pahsc_Channel, &pauseCommand, true);
|
|
if (noErr != error) goto exit;
|
|
|
|
/* Queue all of the buffers so we start off full. */
|
|
for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
|
|
{
|
|
PaMac_PlayNext( past, i );
|
|
}
|
|
|
|
/* Resume channel now that the queue is full. */
|
|
resumeCommand.cmd = resumeCmd;
|
|
resumeCommand.param1 = resumeCommand.param2 = 0;
|
|
error = SndDoImmediate( pahsc->pahsc_Channel, &resumeCommand );
|
|
if (noErr != error) goto exit;
|
|
|
|
return paNoError;
|
|
exit:
|
|
past->past_IsActive = 0;
|
|
sPaHostError = error;
|
|
ERR_RPT(("Error in PaHost_StartOutput: SndDoCommand returned %d\n", error ));
|
|
return paHostError;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
long PaHost_GetTotalBufferFrames( internalPortAudioStream *past )
|
|
{
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
return (long) (pahsc->pahsc_NumHostBuffers * pahsc->pahsc_FramesPerHostBuffer);
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Called by Pa_StopStream().
|
|
** May be called during error recovery or cleanup code
|
|
** so protect against NULL pointers.
|
|
*/
|
|
PaError PaHost_StopOutput( internalPortAudioStream *past, int abort )
|
|
{
|
|
int32 timeOutMsec;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return paNoError;
|
|
if( pahsc->pahsc_Channel == NULL ) return paNoError;
|
|
|
|
DBUG(("PaHost_StopOutput()\n"));
|
|
if( past->past_IsActive == 0 ) return paNoError;
|
|
|
|
/* Set flags for callback function to see. */
|
|
if( abort ) past->past_StopNow = 1;
|
|
past->past_StopSoon = 1;
|
|
/* Calculate timeOut longer than longest time it could take to play all buffers. */
|
|
timeOutMsec = (int32) ((1500.0 * PaHost_GetTotalBufferFrames( past )) / past->past_SampleRate);
|
|
/* Keep querying sound channel until it is no longer busy playing. */
|
|
while( past->past_IsActive && (timeOutMsec > 0))
|
|
{
|
|
Pa_Sleep(20);
|
|
timeOutMsec -= 20;
|
|
}
|
|
if( timeOutMsec <= 0 )
|
|
{
|
|
ERR_RPT(("PaHost_StopOutput: timed out!\n"));
|
|
return paTimedOut;
|
|
}
|
|
else return paNoError;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
PaError PaHost_StartEngine( internalPortAudioStream *past )
|
|
{
|
|
(void) past; /* Prevent unused variable warnings. */
|
|
return paNoError;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
PaError PaHost_StopEngine( internalPortAudioStream *past, int abort )
|
|
{
|
|
(void) past; /* Prevent unused variable warnings. */
|
|
(void) abort; /* Prevent unused variable warnings. */
|
|
return paNoError;
|
|
}
|
|
/***********************************************************************/
|
|
PaError PaHost_StreamActive( internalPortAudioStream *past )
|
|
{
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
return (PaError) ( past->past_IsActive + pahsc->pahsc_IsRecording );
|
|
}
|
|
int Mac_IsVirtualMemoryOn( void )
|
|
{
|
|
long attr;
|
|
OSErr result = Gestalt( gestaltVMAttr, &attr );
|
|
DBUG(("gestaltVMAttr : 0x%x\n", attr ));
|
|
return ((attr >> gestaltVMHasPagingControl ) & 1);
|
|
}
|
|
|
|
/*******************************************************************
|
|
* Determine number of host Buffers
|
|
* and how many User Buffers we can put into each host buffer.
|
|
*/
|
|
static void PaHost_CalcNumHostBuffers( internalPortAudioStream *past )
|
|
{
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
int32 minNumBuffers;
|
|
int32 minFramesPerHostBuffer;
|
|
int32 minTotalFrames;
|
|
int32 userBuffersPerHostBuffer;
|
|
int32 framesPerHostBuffer;
|
|
int32 numHostBuffers;
|
|
|
|
minFramesPerHostBuffer = pahsc->pahsc_MinFramesPerHostBuffer;
|
|
minFramesPerHostBuffer = (minFramesPerHostBuffer + 7) & ~7;
|
|
DBUG(("PaHost_CalcNumHostBuffers: minFramesPerHostBuffer = %d\n", minFramesPerHostBuffer ));
|
|
|
|
/* Determine number of user buffers based on minimum latency. */
|
|
/* PLB20020417 I used to call Pa_GetMinNumBuffers() which doesn't take into account the
|
|
** variable minFramesPerHostBuffer. Now I call PaMac_GetMinNumBuffers() which will
|
|
** gove lower latency when virtual memory is turned off. */
|
|
/* minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); WRONG */
|
|
minNumBuffers = PaMac_GetMinNumBuffers( minFramesPerHostBuffer, past->past_FramesPerUserBuffer, past->past_SampleRate );
|
|
|
|
past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers;
|
|
DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", past->past_NumUserBuffers ));
|
|
minTotalFrames = past->past_NumUserBuffers * past->past_FramesPerUserBuffer;
|
|
|
|
/* We cannot make the buffers too small because they may not get serviced quickly enough. */
|
|
if( (int32) past->past_FramesPerUserBuffer < minFramesPerHostBuffer )
|
|
{
|
|
userBuffersPerHostBuffer =
|
|
(minFramesPerHostBuffer + past->past_FramesPerUserBuffer - 1) /
|
|
past->past_FramesPerUserBuffer;
|
|
}
|
|
else
|
|
{
|
|
userBuffersPerHostBuffer = 1;
|
|
}
|
|
framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
|
|
|
|
/* Calculate number of host buffers needed. Round up to cover minTotalFrames. */
|
|
numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
|
|
/* Make sure we have enough host buffers. */
|
|
if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS)
|
|
{
|
|
numHostBuffers = PA_MIN_NUM_HOST_BUFFERS;
|
|
}
|
|
else
|
|
{
|
|
/* If we have too many host buffers, try to put more user buffers in a host buffer. */
|
|
while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS)
|
|
{
|
|
userBuffersPerHostBuffer += 1;
|
|
framesPerHostBuffer = past->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
|
|
numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
|
|
}
|
|
}
|
|
|
|
pahsc->pahsc_UserBuffersPerHostBuffer = userBuffersPerHostBuffer;
|
|
pahsc->pahsc_FramesPerHostBuffer = framesPerHostBuffer;
|
|
pahsc->pahsc_NumHostBuffers = numHostBuffers;
|
|
DBUG(("PaHost_CalcNumHostBuffers: pahsc_UserBuffersPerHostBuffer = %d\n", pahsc->pahsc_UserBuffersPerHostBuffer ));
|
|
DBUG(("PaHost_CalcNumHostBuffers: pahsc_NumHostBuffers = %d\n", pahsc->pahsc_NumHostBuffers ));
|
|
DBUG(("PaHost_CalcNumHostBuffers: pahsc_FramesPerHostBuffer = %d\n", pahsc->pahsc_FramesPerHostBuffer ));
|
|
DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", past->past_NumUserBuffers ));
|
|
}
|
|
|
|
/*******************************************************************/
|
|
PaError PaHost_OpenStream( internalPortAudioStream *past )
|
|
{
|
|
OSErr err;
|
|
PaError result = paHostError;
|
|
PaHostSoundControl *pahsc;
|
|
int i;
|
|
/* Allocate and initialize host data. */
|
|
pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl));
|
|
if( pahsc == NULL )
|
|
{
|
|
return paInsufficientMemory;
|
|
}
|
|
past->past_DeviceData = (void *) pahsc;
|
|
|
|
/* If recording, and virtual memory is turned on, then use bigger buffers to prevent glitches. */
|
|
if( (past->past_NumInputChannels > 0) && Mac_IsVirtualMemoryOn() )
|
|
{
|
|
pahsc->pahsc_MinFramesPerHostBuffer = MAC_VIRTUAL_FRAMES_PER_BUFFER;
|
|
}
|
|
else
|
|
{
|
|
pahsc->pahsc_MinFramesPerHostBuffer = MAC_PHYSICAL_FRAMES_PER_BUFFER;
|
|
}
|
|
|
|
PaHost_CalcNumHostBuffers( past );
|
|
|
|
/* Setup constants for CPU load measurement. */
|
|
pahsc->pahsc_InverseMicrosPerHostBuffer = past->past_SampleRate / (1000000.0 * pahsc->pahsc_FramesPerHostBuffer);
|
|
|
|
/* ------------------ OUTPUT */
|
|
if( past->past_NumOutputChannels > 0 )
|
|
{
|
|
/* Create sound channel to which we can send commands. */
|
|
pahsc->pahsc_Channel = 0L;
|
|
err = SndNewChannel(&pahsc->pahsc_Channel, sampledSynth, 0, nil); /* FIXME - use kUseOptionalOutputDevice if not default. */
|
|
if(err != 0)
|
|
{
|
|
ERR_RPT(("Error in PaHost_OpenStream: SndNewChannel returned 0x%x\n", err ));
|
|
goto error;
|
|
}
|
|
|
|
/* Install our callback function pointer straight into the sound channel structure */
|
|
/* Use new CARBON name for callback procedure. */
|
|
#if TARGET_API_MAC_CARBON
|
|
pahsc->pahsc_OutputCompletionProc = NewSndCallBackUPP(PaMac_OutputCompletionProc);
|
|
#else
|
|
pahsc->pahsc_OutputCompletionProc = NewSndCallBackProc(PaMac_OutputCompletionProc);
|
|
#endif
|
|
|
|
pahsc->pahsc_Channel->callBack = pahsc->pahsc_OutputCompletionProc;
|
|
|
|
pahsc->pahsc_BytesPerOutputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumOutputChannels * sizeof(int16);
|
|
for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
|
|
{
|
|
char *buf = (char *)PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerOutputHostBuffer);
|
|
if (buf == NULL)
|
|
{
|
|
ERR_RPT(("Error in PaHost_OpenStream: could not allocate output buffer. Size = \n", pahsc->pahsc_BytesPerOutputHostBuffer ));
|
|
goto memerror;
|
|
}
|
|
|
|
PaMac_InitSoundHeader( past, &pahsc->pahsc_SoundHeaders[i] );
|
|
pahsc->pahsc_SoundHeaders[i].samplePtr = buf;
|
|
pahsc->pahsc_SoundHeaders[i].numFrames = (unsigned long) pahsc->pahsc_FramesPerHostBuffer;
|
|
|
|
}
|
|
}
|
|
#ifdef SUPPORT_AUDIO_CAPTURE
|
|
/* ------------------ INPUT */
|
|
/* Use double buffer scheme that matches output. */
|
|
if( past->past_NumInputChannels > 0 )
|
|
{
|
|
int16 tempS;
|
|
long tempL;
|
|
Fixed tempF;
|
|
long mRefNum;
|
|
unsigned char noname = 0; /* FIXME - use real device names. */
|
|
#if TARGET_API_MAC_CARBON
|
|
pahsc->pahsc_InputCompletionProc = NewSICompletionUPP((SICompletionProcPtr)PaMac_InputCompletionProc);
|
|
#else
|
|
pahsc->pahsc_InputCompletionProc = NewSICompletionProc((ProcPtr)PaMac_InputCompletionProc);
|
|
#endif
|
|
pahsc->pahsc_BytesPerInputHostBuffer = pahsc->pahsc_FramesPerHostBuffer * past->past_NumInputChannels * sizeof(int16);
|
|
for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
|
|
{
|
|
char *buf = (char *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerInputHostBuffer);
|
|
if ( buf == NULL )
|
|
{
|
|
ERR_RPT(("PaHost_OpenStream: could not allocate input buffer. Size = \n", pahsc->pahsc_BytesPerInputHostBuffer ));
|
|
goto memerror;
|
|
}
|
|
pahsc->pahsc_InputMultiBuffer.buffers[i] = buf;
|
|
}
|
|
pahsc->pahsc_InputMultiBuffer.numBuffers = pahsc->pahsc_NumHostBuffers;
|
|
|
|
err = SPBOpenDevice( (const unsigned char *) &noname, siWritePermission, &mRefNum); /* FIXME - use name so we get selected device */
|
|
// FIXME err = SPBOpenDevice( (const unsigned char *) sDevices[past->past_InputDeviceID].pad_Info.name, siWritePermission, &mRefNum);
|
|
if (err) goto error;
|
|
pahsc->pahsc_InputRefNum = mRefNum;
|
|
DBUG(("PaHost_OpenStream: mRefNum = %d\n", mRefNum ));
|
|
|
|
/* Set input device characteristics. */
|
|
tempS = 1;
|
|
err = SPBSetDeviceInfo(mRefNum, siContinuous, (Ptr) &tempS);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siContinuous returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
|
|
tempL = 0x03;
|
|
err = SPBSetDeviceInfo(mRefNum, siActiveChannels, (Ptr) &tempL);
|
|
if (err)
|
|
{
|
|
DBUG(("PaHost_OpenStream: setting siActiveChannels returned 0x%x. Error ignored.\n", err ));
|
|
}
|
|
|
|
/* PLB20010908 - Use requested number of input channels. Thanks Dominic Mazzoni. */
|
|
tempS = past->past_NumInputChannels;
|
|
err = SPBSetDeviceInfo(mRefNum, siNumberChannels, (Ptr) &tempS);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siNumberChannels returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
|
|
tempF = ((unsigned long)past->past_SampleRate) << 16;
|
|
err = SPBSetDeviceInfo(mRefNum, siSampleRate, (Ptr) &tempF);
|
|
if (err)
|
|
{
|
|
ERR_RPT(("Error in PaHost_OpenStream: SPBSetDeviceInfo siSampleRate returned %d\n", err ));
|
|
goto error;
|
|
}
|
|
|
|
/* Setup record-parameter block */
|
|
pahsc->pahsc_InputParams.inRefNum = mRefNum;
|
|
pahsc->pahsc_InputParams.milliseconds = 0; // not used
|
|
pahsc->pahsc_InputParams.completionRoutine = pahsc->pahsc_InputCompletionProc;
|
|
pahsc->pahsc_InputParams.interruptRoutine = 0;
|
|
pahsc->pahsc_InputParams.userLong = (long) past;
|
|
pahsc->pahsc_InputParams.unused1 = 0;
|
|
}
|
|
#endif /* SUPPORT_AUDIO_CAPTURE */
|
|
DBUG(("PaHost_OpenStream: complete.\n"));
|
|
return paNoError;
|
|
|
|
error:
|
|
PaHost_CloseStream( past );
|
|
ERR_RPT(("PaHost_OpenStream: sPaHostError = 0x%x.\n", err ));
|
|
sPaHostError = err;
|
|
return paHostError;
|
|
|
|
memerror:
|
|
PaHost_CloseStream( past );
|
|
return paInsufficientMemory;
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Called by Pa_CloseStream().
|
|
** May be called during error recovery or cleanup code
|
|
** so protect against NULL pointers.
|
|
*/
|
|
PaError PaHost_CloseStream( internalPortAudioStream *past )
|
|
{
|
|
PaError result = paNoError;
|
|
OSErr err = 0;
|
|
int i;
|
|
PaHostSoundControl *pahsc;
|
|
|
|
DBUG(("PaHost_CloseStream( 0x%x )\n", past ));
|
|
|
|
if( past == NULL ) return paBadStreamPtr;
|
|
|
|
pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return paNoError;
|
|
|
|
if( past->past_NumOutputChannels > 0 )
|
|
{
|
|
/* TRUE means flush now instead of waiting for quietCmd to be processed. */
|
|
if( pahsc->pahsc_Channel != NULL ) SndDisposeChannel(pahsc->pahsc_Channel, TRUE);
|
|
{
|
|
for (i = 0; i<pahsc->pahsc_NumHostBuffers; i++)
|
|
{
|
|
Ptr p = (Ptr) pahsc->pahsc_SoundHeaders[i].samplePtr;
|
|
if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerOutputHostBuffer );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( past->past_NumInputChannels > 0 )
|
|
{
|
|
if( pahsc->pahsc_InputRefNum )
|
|
{
|
|
err = SPBCloseDevice(pahsc->pahsc_InputRefNum);
|
|
pahsc->pahsc_InputRefNum = 0;
|
|
if( err )
|
|
{
|
|
sPaHostError = err;
|
|
result = paHostError;
|
|
}
|
|
}
|
|
{
|
|
for (i = 0; i<pahsc->pahsc_InputMultiBuffer.numBuffers; i++)
|
|
{
|
|
Ptr p = (Ptr) pahsc->pahsc_InputMultiBuffer.buffers[i];
|
|
if( p != NULL ) PaHost_FreeFastMemory( p, pahsc->pahsc_BytesPerInputHostBuffer );
|
|
}
|
|
}
|
|
}
|
|
|
|
past->past_DeviceData = NULL;
|
|
PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) );
|
|
|
|
DBUG(("PaHost_CloseStream: complete.\n", past ));
|
|
return result;
|
|
}
|
|
/*************************************************************************/
|
|
int Pa_GetMinNumBuffers( int framesPerUserBuffer, double sampleRate )
|
|
{
|
|
/* We use the MAC_VIRTUAL_FRAMES_PER_BUFFER because we might be recording.
|
|
** This routine doesn't have enough information to determine the best value
|
|
** and is being depracated. */
|
|
return PaMac_GetMinNumBuffers( MAC_VIRTUAL_FRAMES_PER_BUFFER, framesPerUserBuffer, sampleRate );
|
|
}
|
|
/*************************************************************************/
|
|
static int PaMac_GetMinNumBuffers( int minFramesPerHostBuffer, int framesPerUserBuffer, double sampleRate )
|
|
{
|
|
int minUserPerHost = ( minFramesPerHostBuffer + framesPerUserBuffer - 1) / framesPerUserBuffer;
|
|
int numBufs = PA_MIN_NUM_HOST_BUFFERS * minUserPerHost;
|
|
if( numBufs < PA_MIN_NUM_HOST_BUFFERS ) numBufs = PA_MIN_NUM_HOST_BUFFERS;
|
|
(void) sampleRate;
|
|
return numBufs;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
void Pa_Sleep( int32 msec )
|
|
{
|
|
EventRecord event;
|
|
int32 sleepTime, endTime;
|
|
/* Convert to ticks. Round up so we sleep a MINIMUM of msec time. */
|
|
sleepTime = ((msec * 60) + 999) / 1000;
|
|
if( sleepTime < 1 ) sleepTime = 1;
|
|
endTime = TickCount() + sleepTime;
|
|
do
|
|
{
|
|
DBUGX(("Sleep for %d ticks.\n", sleepTime ));
|
|
/* Use WaitNextEvent() to sleep without getting events. */
|
|
/* PLB20010907 - Pass unused event to WaitNextEvent instead of NULL to prevent
|
|
* Mac OSX crash. Thanks Dominic Mazzoni. */
|
|
WaitNextEvent( 0, &event, sleepTime, NULL );
|
|
sleepTime = endTime - TickCount();
|
|
}
|
|
while( sleepTime > 0 );
|
|
}
|
|
/*************************************************************************/
|
|
int32 Pa_GetHostError( void )
|
|
{
|
|
int32 err = sPaHostError;
|
|
sPaHostError = 0;
|
|
return err;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* Allocate memory that can be accessed in real-time.
|
|
* This may need to be held in physical memory so that it is not
|
|
* paged to virtual memory.
|
|
* This call MUST be balanced with a call to PaHost_FreeFastMemory().
|
|
*/
|
|
void *PaHost_AllocateFastMemory( long numBytes )
|
|
{
|
|
void *addr = NewPtrClear( numBytes );
|
|
if( (addr == NULL) || (MemError () != 0) ) return NULL;
|
|
|
|
#if (TARGET_API_MAC_CARBON == 0)
|
|
if( HoldMemory( addr, numBytes ) != noErr )
|
|
{
|
|
DisposePtr( (Ptr) addr );
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return addr;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* Free memory that could be accessed in real-time.
|
|
* This call MUST be balanced with a call to PaHost_AllocateFastMemory().
|
|
*/
|
|
void PaHost_FreeFastMemory( void *addr, long numBytes )
|
|
{
|
|
if( addr == NULL ) return;
|
|
#if TARGET_API_MAC_CARBON
|
|
(void) numBytes;
|
|
#else
|
|
UnholdMemory( addr, numBytes );
|
|
#endif
|
|
DisposePtr( (Ptr) addr );
|
|
}
|
|
|
|
/*************************************************************************/
|
|
PaTimestamp Pa_StreamTime( PortAudioStream *stream )
|
|
{
|
|
PaTimestamp framesDone1;
|
|
PaTimestamp framesDone2;
|
|
UInt64 whenIncremented;
|
|
UnsignedWide now;
|
|
UInt64 now64;
|
|
long microsElapsed;
|
|
long framesElapsed;
|
|
|
|
PaHostSoundControl *pahsc;
|
|
internalPortAudioStream *past = (internalPortAudioStream *) stream;
|
|
if( past == NULL ) return paBadStreamPtr;
|
|
pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
|
|
/* Capture information from audio thread.
|
|
* We have to be careful that we don't get interrupted in the middle.
|
|
* So we grab the pahsc_NumFramesDone twice and make sure it didn't change.
|
|
*/
|
|
do
|
|
{
|
|
framesDone1 = pahsc->pahsc_NumFramesDone;
|
|
whenIncremented = pahsc->pahsc_WhenFramesDoneIncremented;
|
|
framesDone2 = pahsc->pahsc_NumFramesDone;
|
|
} while( framesDone1 != framesDone2 );
|
|
|
|
/* Calculate how many microseconds have elapsed and convert to frames. */
|
|
Microseconds( &now );
|
|
now64 = UnsignedWideToUInt64( now );
|
|
microsElapsed = U64Subtract( now64, whenIncremented );
|
|
framesElapsed = microsElapsed * past->past_SampleRate * 0.000001;
|
|
|
|
return framesDone1 + framesElapsed;
|
|
}
|
|
|
|
/**************************************************************************
|
|
** Callback for Input, SPBRecord()
|
|
*/
|
|
int gRecordCounter = 0;
|
|
int gPlayCounter = 0;
|
|
pascal void PaMac_InputCompletionProc(SPBPtr recParams)
|
|
{
|
|
PaError result = paNoError;
|
|
int finished = 1;
|
|
internalPortAudioStream *past;
|
|
PaHostSoundControl *pahsc;
|
|
|
|
gRecordCounter += 1; /* debug hack to see if engine running */
|
|
|
|
/* Get our PA data from Mac structure. */
|
|
past = (internalPortAudioStream *) recParams->userLong;
|
|
if( past == NULL ) return;
|
|
|
|
if( past->past_Magic != PA_MAGIC )
|
|
{
|
|
AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, past", (long) past );
|
|
AddTraceMessage("PaMac_InputCompletionProc: bad MAGIC, magic", (long) past->past_Magic );
|
|
goto error;
|
|
}
|
|
pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
past->past_NumCallbacks += 1;
|
|
|
|
/* Have we been asked to stop recording? */
|
|
if( (recParams->error == abortErr) || pahsc->pahsc_StopRecording ) goto error;
|
|
|
|
/* If there are no output channels, then we need to call the user callback function from here.
|
|
* Otherwise we will call the user code during the output completion routine.
|
|
*/
|
|
if(past->past_NumOutputChannels == 0)
|
|
{
|
|
SetFramesDone( pahsc,
|
|
pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer );
|
|
result = PaMac_CallUserLoop( past, NULL );
|
|
}
|
|
|
|
/* Did user code ask us to stop? If not, issue another recording request. */
|
|
if( (result == paNoError) && (pahsc->pahsc_StopRecording == 0) )
|
|
{
|
|
result = PaMac_RecordNext( past );
|
|
if( result != paNoError ) pahsc->pahsc_IsRecording = 0;
|
|
}
|
|
else goto error;
|
|
|
|
return;
|
|
|
|
error:
|
|
pahsc->pahsc_IsRecording = 0;
|
|
pahsc->pahsc_StopRecording = 0;
|
|
return;
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Called by either input or output completion proc.
|
|
** Grabs input data if any present, and calls PA conversion code,
|
|
** that in turn calls user code.
|
|
*/
|
|
static PaError PaMac_CallUserLoop( internalPortAudioStream *past, int16 *outPtr )
|
|
{
|
|
PaError result = paNoError;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
int16 *inPtr = NULL;
|
|
int i;
|
|
|
|
|
|
/* Advance read index for sound input FIFO here, independantly of record/write process. */
|
|
if(past->past_NumInputChannels > 0)
|
|
{
|
|
if( MultiBuffer_IsReadable( &pahsc->pahsc_InputMultiBuffer ) )
|
|
{
|
|
inPtr = (int16 *) MultiBuffer_GetNextReadBuffer( &pahsc->pahsc_InputMultiBuffer );
|
|
MultiBuffer_AdvanceReadIndex( &pahsc->pahsc_InputMultiBuffer );
|
|
}
|
|
}
|
|
|
|
/* Call user code enough times to fill buffer. */
|
|
if( (inPtr != NULL) || (outPtr != NULL) )
|
|
{
|
|
PaMac_StartLoadCalculation( past ); /* CPU usage */
|
|
|
|
#ifdef PA_MAX_USAGE_ALLOWED
|
|
/* If CPU usage exceeds limit, skip user callback to prevent hanging CPU. */
|
|
if( past->past_Usage > PA_MAX_USAGE_ALLOWED )
|
|
{
|
|
past->past_FrameCount += (PaTimestamp) pahsc->pahsc_FramesPerHostBuffer;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
|
|
for( i=0; i<pahsc->pahsc_UserBuffersPerHostBuffer; i++ )
|
|
{
|
|
result = (PaError) Pa_CallConvertInt16( past, inPtr, outPtr );
|
|
if( result != 0)
|
|
{
|
|
/* Recording might be in another process, so tell it to stop with a flag. */
|
|
pahsc->pahsc_StopRecording = pahsc->pahsc_IsRecording;
|
|
break;
|
|
}
|
|
/* Advance sample pointers. */
|
|
if(inPtr != NULL) inPtr += past->past_FramesPerUserBuffer * past->past_NumInputChannels;
|
|
if(outPtr != NULL) outPtr += past->past_FramesPerUserBuffer * past->past_NumOutputChannels;
|
|
}
|
|
}
|
|
|
|
PaMac_EndLoadCalculation( past );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Setup next recording buffer in FIFO and issue recording request to Snd Input Manager.
|
|
*/
|
|
static PaError PaMac_RecordNext( internalPortAudioStream *past )
|
|
{
|
|
PaError result = paNoError;
|
|
OSErr err;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
/* Get pointer to next buffer to record into. */
|
|
pahsc->pahsc_InputParams.bufferPtr = MultiBuffer_GetNextWriteBuffer( &pahsc->pahsc_InputMultiBuffer );
|
|
|
|
/* Advance write index if there is room. Otherwise keep writing same buffer. */
|
|
if( MultiBuffer_IsWriteable( &pahsc->pahsc_InputMultiBuffer ) )
|
|
{
|
|
MultiBuffer_AdvanceWriteIndex( &pahsc->pahsc_InputMultiBuffer );
|
|
}
|
|
|
|
AddTraceMessage("PaMac_RecordNext: bufferPtr", (long) pahsc->pahsc_InputParams.bufferPtr );
|
|
AddTraceMessage("PaMac_RecordNext: nextWrite", pahsc->pahsc_InputMultiBuffer.nextWrite );
|
|
|
|
/* Setup parameters and issue an asynchronous recording request. */
|
|
pahsc->pahsc_InputParams.bufferLength = pahsc->pahsc_BytesPerInputHostBuffer;
|
|
pahsc->pahsc_InputParams.count = pahsc->pahsc_BytesPerInputHostBuffer;
|
|
err = SPBRecord(&pahsc->pahsc_InputParams, true);
|
|
if( err )
|
|
{
|
|
AddTraceMessage("PaMac_RecordNext: SPBRecord error ", err );
|
|
sPaHostError = err;
|
|
result = paHostError;
|
|
}
|
|
else
|
|
{
|
|
pahsc->pahsc_IsRecording = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**************************************************************************
|
|
** Callback for Output Playback()
|
|
** Return negative error, 0 to continue, 1 to stop.
|
|
*/
|
|
long PaMac_FillNextOutputBuffer( internalPortAudioStream *past, int index )
|
|
{
|
|
PaHostSoundControl *pahsc;
|
|
long result = 0;
|
|
int finished = 1;
|
|
char *outPtr;
|
|
|
|
gPlayCounter += 1; /* debug hack */
|
|
|
|
past->past_NumCallbacks += 1;
|
|
pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
if( pahsc == NULL ) return -1;
|
|
/* Are we nested?! */
|
|
if( pahsc->pahsc_IfInsideCallback ) return 0;
|
|
pahsc->pahsc_IfInsideCallback = 1;
|
|
/* Get pointer to buffer to fill. */
|
|
outPtr = pahsc->pahsc_SoundHeaders[index].samplePtr;
|
|
/* Combine with any sound input, and call user callback. */
|
|
result = PaMac_CallUserLoop( past, (int16 *) outPtr );
|
|
|
|
pahsc->pahsc_IfInsideCallback = 0;
|
|
return result;
|
|
}
|
|
|
|
/*************************************************************************************
|
|
** Called by SoundManager when ready for another buffer.
|
|
*/
|
|
static pascal void PaMac_OutputCompletionProc (SndChannelPtr theChannel, SndCommand * theCallBackCmd)
|
|
{
|
|
internalPortAudioStream *past;
|
|
PaHostSoundControl *pahsc;
|
|
(void) theChannel;
|
|
(void) theCallBackCmd;
|
|
|
|
/* Get our data from Mac structure. */
|
|
past = (internalPortAudioStream *) theCallBackCmd->param2;
|
|
if( past == NULL ) return;
|
|
|
|
pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
pahsc->pahsc_NumOutsPlayed += 1;
|
|
|
|
SetFramesDone( pahsc,
|
|
pahsc->pahsc_NumFramesDone + pahsc->pahsc_FramesPerHostBuffer );
|
|
|
|
PaMac_BackgroundManager( past, theCallBackCmd->param1 );
|
|
}
|
|
|
|
/*******************************************************************/
|
|
static PaError PaMac_BackgroundManager( internalPortAudioStream *past, int index )
|
|
{
|
|
PaError result = paNoError;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
/* Has someone asked us to abort by calling Pa_AbortStream()? */
|
|
if( past->past_StopNow )
|
|
{
|
|
SndCommand command;
|
|
/* Clear the queue of any pending commands. */
|
|
command.cmd = flushCmd;
|
|
command.param1 = command.param2 = 0;
|
|
SndDoImmediate( pahsc->pahsc_Channel, &command );
|
|
/* Then stop currently playing buffer, if any. */
|
|
command.cmd = quietCmd;
|
|
SndDoImmediate( pahsc->pahsc_Channel, &command );
|
|
past->past_IsActive = 0;
|
|
}
|
|
/* Has someone asked us to stop by calling Pa_StopStream()
|
|
* OR has a user callback returned '1' to indicate finished.
|
|
*/
|
|
else if( past->past_StopSoon )
|
|
{
|
|
if( (pahsc->pahsc_NumOutsQueued - pahsc->pahsc_NumOutsPlayed) <= 0 )
|
|
{
|
|
past->past_IsActive = 0; /* We're finally done. */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PaMac_PlayNext( past, index );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*************************************************************************************
|
|
** Fill next buffer with sound and queue it for playback.
|
|
*/
|
|
static void PaMac_PlayNext ( internalPortAudioStream *past, int index )
|
|
{
|
|
OSErr error;
|
|
long result;
|
|
SndCommand playCmd;
|
|
SndCommand callbackCmd;
|
|
PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
|
|
|
|
/* If this was the last buffer, or abort requested, then just be done. */
|
|
if ( past->past_StopSoon ) goto done;
|
|
|
|
/* Load buffer with sound. */
|
|
result = PaMac_FillNextOutputBuffer ( past, index );
|
|
if( result > 0 ) past->past_StopSoon = 1; /* Stop generating audio but wait until buffers play. */
|
|
else if( result < 0 ) goto done;
|
|
|
|
/* Play the next buffer. */
|
|
playCmd.cmd = bufferCmd;
|
|
playCmd.param1 = 0;
|
|
playCmd.param2 = (long) &pahsc->pahsc_SoundHeaders[ index ];
|
|
error = SndDoCommand (pahsc->pahsc_Channel, &playCmd, true );
|
|
if( error != noErr ) goto gotError;
|
|
|
|
/* Ask for a callback when it is done. */
|
|
callbackCmd.cmd = callBackCmd;
|
|
callbackCmd.param1 = index;
|
|
callbackCmd.param2 = (long)past;
|
|
error = SndDoCommand (pahsc->pahsc_Channel, &callbackCmd, true );
|
|
if( error != noErr ) goto gotError;
|
|
pahsc->pahsc_NumOutsQueued += 1;
|
|
|
|
return;
|
|
|
|
gotError:
|
|
sPaHostError = error;
|
|
done:
|
|
return;
|
|
}
|