336 lines
15 KiB
C
336 lines
15 KiB
C
//( { file_desc:"Improved real-time audio processing engine." kw:[rtsys] }
|
|
//
|
|
// The audio system is composed a collection of independent sub-systems.
|
|
// Each sub-system maintains a thread which runs asynchrounsly
|
|
// from the application, the MIDI devices, and the audio devices.
|
|
// To faciliate communication between these components each sub-system maintains
|
|
// two thread-safe data buffers one for control information and a second
|
|
// for audio data.
|
|
//
|
|
// The audio devices are the primary driver for the system.
|
|
// Callbacks from the audio devices (See #cmApCallbackPtr_t)
|
|
// inserts incoming audio samples into the audio
|
|
// record buffers and extracts samples from the playback buffer.
|
|
// When sufficient incoming samples and outgoing empty buffer space exists
|
|
// a sub-system thread is waken up by the callback. This triggers a DSP audio
|
|
// processing cycle which empties/fills the audio buffers. During a DSP
|
|
// processing cycle control messages from the application and MIDI are blocked and
|
|
// buffered. Upon completetion of the DSP cycle a control message
|
|
// transfer cycles occurs - buffered incoming messages are passed to
|
|
// the DSP system and messages originating in the DSP system are
|
|
// buffered by the audio system for later pickup by the application
|
|
// or MIDI system.
|
|
//
|
|
// Note that control messages that arrive when the DSP cycle is not
|
|
// occurring can pass directly through to the DSP system.
|
|
//
|
|
// The DSP system sends messages back to the host by calling
|
|
// cmRtDspToHostFunc_t provided by cmRtSysCtx_t. These
|
|
// calls are always made from within an audio system call to
|
|
// audio or control update within cmRtCallback_t. cmRtDspToHostFunc_t
|
|
// simply stores the message in a message buffer. The host picks
|
|
// up the message at some later time when it notices that messages
|
|
// are waiting via polling cmRtSysIsMsgWaiting().
|
|
//
|
|
// Implementation: \n
|
|
// The audio sub-systems work by maintaining an internal thread
|
|
// which blocks on a mutex condition variable.
|
|
// While the thread is blocked the mutex is unlocked allowing messages
|
|
// to pass directly through to the DSP procedure via cmRtCallback().
|
|
//
|
|
// Periodic calls from running audio devices update the audio buffer.
|
|
// When the audio buffer has input samples waiting and output space
|
|
// available the condition variable is signaled, the mutex is
|
|
// then automatically locked by the system, and the DSP execution
|
|
// procedure is called via cmRtCallback().
|
|
//
|
|
// Messages arriving while the mutex is locked are queued and
|
|
// delivered to the DSP procedure at the end of the DSP execution
|
|
// procedure.
|
|
//)
|
|
|
|
#ifndef cmRtSys_h
|
|
#define cmRtSys_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
//(
|
|
// Audio system result codes
|
|
enum
|
|
{
|
|
kOkRtRC = cmOkRC,
|
|
kThreadErrRtRC,
|
|
kMutexErrRtRC,
|
|
kTsQueueErrRtRC,
|
|
kMsgEnqueueFailRtRC,
|
|
kAudioDevSetupErrRtRC,
|
|
kAudioBufSetupErrRtRC,
|
|
kAudioDevStartFailRtRC,
|
|
kAudioDevStopFailRtRC,
|
|
kBufTooSmallRtRC,
|
|
kNoMsgWaitingRtRC,
|
|
kMidiSysFailRtRC,
|
|
kMsgSerializeFailRtRC,
|
|
kStateBufFailRtRC,
|
|
kInvalidArgRtRC,
|
|
kNotInitRtRC,
|
|
kTimeOutErrRtRC,
|
|
kNetErrRtRC
|
|
};
|
|
|
|
enum
|
|
{
|
|
kAsDfltMsgQueueByteCnt = 0xffff,
|
|
kAsDfltDevFramesPerCycle = 512,
|
|
kAsDfltDspFramesPerCycle = 64,
|
|
kAsDfltBufCnt = 3,
|
|
kAsDfltSrate = 44100,
|
|
kAsDfltSyncToInputFl = 1,
|
|
kAsDfltMinMeterMs = 10,
|
|
kAsDfltMeterMs = 50,
|
|
kAsDfltMaxMeterMs = 1000
|
|
};
|
|
|
|
typedef cmHandle_t cmRtSysH_t; //< Audio system handle type
|
|
typedef unsigned cmRtRC_t; //< Audio system result code
|
|
|
|
struct cmRtSysCtx_str;
|
|
|
|
//
|
|
// DSP system callback function.
|
|
//
|
|
// This is the sole point of entry into the DSP system while the audio system is running.
|
|
//
|
|
// ctxPtr is pointer to a cmRtSysCtx_t record.
|
|
//
|
|
// This function is called under two circumstances:
|
|
//
|
|
// 1) To notify the DSP system that the audio input/output buffers need to be serviced.
|
|
// This is a perioidic request which the DSP system uses as its execution trigger.
|
|
// cmRtSysCtx_t.audioRateFl is set to true to indicate this type of callback.
|
|
//
|
|
// 2) To pass messages from the host application to the DSP system.
|
|
// The DSP system is asyncronous with the host because it executes in the
|
|
// audio system thread rather than the host thread. The cmRtSysDeliverMsg()
|
|
// function synchronizes incoming messages with the internal audio system
|
|
// thread to prevent thread collisions.
|
|
//
|
|
// Notes:
|
|
// This callback is always made with the internal audio system mutex locked.
|
|
//
|
|
// The signal time covered by the callback is from
|
|
// ctx->begSmpIdx to ctx->begSmpIdx+cfg->dspFramesPerCycle.
|
|
//
|
|
// The return value is currently not used.
|
|
typedef cmRC_t (*cmRtCallback_t)(void* ctxPtr, unsigned msgByteCnt, const void* msgDataPtr );
|
|
|
|
// Network nodes
|
|
typedef struct
|
|
{
|
|
const cmChar_t* label; // Remote node label or NULL if this is the local node.
|
|
const cmChar_t* ipAddr; // IP address in xxx.xxx.xxx.xxx form or NULL for 'localhost'.
|
|
cmUdpPort_t ipPort; // IP port
|
|
} cmRtSysNetNode_t;
|
|
|
|
// Local endpoints.
|
|
typedef struct
|
|
{
|
|
const cmChar_t* label; // Local endpoint label
|
|
unsigned id; // Local endpoint id
|
|
} cmRtSysNetEndpt_t;
|
|
|
|
// Audio device sub-sytem configuration record
|
|
typedef struct cmRtSysArgs_str
|
|
{
|
|
cmRpt_t* rpt; // system console object
|
|
unsigned inDevIdx; // input audio device
|
|
unsigned outDevIdx; // output audio device
|
|
bool syncInputFl; // true/false sync the DSP update callbacks with audio input/output
|
|
unsigned msgQueueByteCnt; // Size of the internal msg queue used to buffer msgs arriving via cmRtSysDeliverMsg().
|
|
unsigned devFramesPerCycle; // (512) Audio device samples per channel per device update buffer.
|
|
unsigned dspFramesPerCycle; // (64) Audio samples per channel per DSP cycle.
|
|
unsigned audioBufCnt; // (3) Audio device buffers.
|
|
double srate; // Audio sample rate.
|
|
} cmRtSysArgs_t;
|
|
|
|
// Audio sub-system configuration record.
|
|
// This record is provided by the host to configure the audio system
|
|
// via cmRtSystemAllocate() or cmRtSystemInitialize().
|
|
typedef struct cmRtSysSubSys_str
|
|
{
|
|
cmRtSysArgs_t args; // Audio device configuration
|
|
cmRtCallback_t cbFunc; // DSP system entry point function.
|
|
void* cbDataPtr; // Host provided data for the DSP system callback.
|
|
const cmChar_t* bcastAddr; // Network broadcast address.
|
|
const cmChar_t* localNodeLabel; // Network local node address.
|
|
const cmChar_t* localIpAddr; // Network local IP address (default:NULL to use any available address)
|
|
cmUdpPort_t localIpPort; // Network local socket port address
|
|
cmRtSysNetEndpt_t* endptArray; // Local end points
|
|
unsigned endptCnt; // Count of local endpoints.
|
|
} cmRtSysSubSys_t;
|
|
|
|
|
|
// Signature of a callback function provided by the audio system to receive messages
|
|
// from the DSP system for later dispatch to the host application.
|
|
// This declaration is used by the DSP system implementation and the audio system.
|
|
// Note that this function is intended to convey one message broken into multiple parts.
|
|
// See cmTsQueueEnqueueSegMsg() for the equivalent interface.
|
|
typedef cmRtRC_t (*cmRtDspToHostFunc_t)(struct cmRtSysCtx_str* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt);
|
|
|
|
// Record passed with each call to the DSP callback function cmRtCallback_t
|
|
typedef struct cmRtSysCtx_str
|
|
{
|
|
void* reserved; // used internally by the audio system
|
|
|
|
bool audioRateFl; // true if this is an audio update callback
|
|
|
|
unsigned srcNetNodeId; // Source net node if this is a msg callback originating from a remote network node.
|
|
unsigned rtSubIdx; // index of the sub-system this DSP process is serving
|
|
|
|
cmRtSysSubSys_t* ss; // ptr to a copy of the cfg recd used to initialize the audio system
|
|
unsigned begSmpIdx; // gives signal time as a sample count
|
|
|
|
cmRtDspToHostFunc_t dspToHostFunc; // Callback used by the DSP process to send messages to the host
|
|
// via the audio system. Returns a cmRtRC_t result code.
|
|
|
|
// output (playback) buffers
|
|
cmSample_t** oChArray; // each ele is a ptr to buffer with cfg.dspFramesPerCycle samples
|
|
unsigned oChCnt; // count of output channels (ele's in oChArray[])
|
|
cmTimeSpec_t oTimeStamp;
|
|
// input (recording) buffers
|
|
cmSample_t** iChArray; // each ele is a ptr to buffer with cfg.dspFramesPerCycle samples
|
|
unsigned iChCnt; // count of input channels (ele's in iChArray[])
|
|
cmTimeSpec_t iTimeStamp;
|
|
|
|
} cmRtSysCtx_t;
|
|
|
|
extern cmRtSysH_t cmRtSysNullHandle;
|
|
|
|
// Allocate and initialize an audio system as a collection of 'cfgCnt' sub-systems.
|
|
// Prior to call this function the audio audio ports system must be initalized
|
|
// (via cmApInitialize()) and the MIDI port system must be initialized
|
|
// (via cmMpInitialize()). Note also that cmApFinalize() and cmMpFinalize()
|
|
// cannot be called prior to cmRtSysFree().
|
|
// See cmRtSystemTest() for a complete example.
|
|
cmRtRC_t cmRtSysAllocate( cmRtSysH_t* hp, cmCtx_t* ctx );
|
|
|
|
// Finalize and release any resources held by the audio system.
|
|
cmRtRC_t cmRtSysFree( cmRtSysH_t* hp );
|
|
|
|
// Returns true if 'h' is a handle which was successfully allocated by
|
|
// cmRtSysAllocate().
|
|
bool cmRtSysHandleIsValid( cmRtSysH_t h );
|
|
|
|
// clientCbFunc is Called by cmRtSysReceiveMsg() to deliver internally generated msg's to the host.
|
|
// Set to NULL if msg's will be directly returned by buffers passed to cmRtSysReceiveMsg().
|
|
cmRtRC_t cmRtSysBeginCfg( cmRtSysH_t h, cmTsQueueCb_t clientCbFunc, void* clientCbArg, unsigned meterMs, unsigned ssCnt );
|
|
|
|
// Reinitialize a previously allocated audio system. This function
|
|
// begins with a call to cmRtSysFinalize().
|
|
// Use cmRtSysEnable(h,true) to begin processing audio following this call.
|
|
cmRtRC_t cmRtSysCfg( cmRtSysH_t h, const cmRtSysSubSys_t* ss, unsigned rtSubIdx );
|
|
|
|
cmRtRC_t cmRtSysEndCfg( cmRtSysH_t h );
|
|
|
|
// Complements cmRtSysInitialize(). In general there is no need to call this function
|
|
// since calls to cmRtSysInitialize() and cmRtSysFree() automaticatically call it.
|
|
cmRtRC_t cmRtSysFinalize( cmRtSysH_t h );
|
|
|
|
// Returns true if the audio system has been successfully initialized.
|
|
bool cmRtSysIsInitialized( cmRtSysH_t );
|
|
|
|
// Returns true if the audio system is enabled.
|
|
bool cmRtSysIsEnabled( cmRtSysH_t h );
|
|
|
|
// Enable/disable the audio system. Enabling the starts audio stream
|
|
// in/out of the system.
|
|
cmRtRC_t cmRtSysEnable( cmRtSysH_t h, bool enableFl );
|
|
|
|
//
|
|
// DSP to Host delivery function
|
|
//
|
|
|
|
// This function is used to pass messages from a DSP process to the HOST it
|
|
// is always called from within the real-time thread.
|
|
cmRtRC_t cmRtSysDspToHostSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt);
|
|
cmRtRC_t cmRtSysDspToHost( cmRtSysH_t h, const void* msgDataPtr, unsigned msgByteCnt);
|
|
|
|
|
|
//
|
|
// Host to DSP delivery functions
|
|
//
|
|
|
|
// Deliver a message from the host application to the DSP process. (host -> DSP);
|
|
// The message is formed as a concatenation of the bytes in each of the segments
|
|
// pointed to by 'msgDataPtrArrary[segCnt][msgByteCntArray[segCnt]'.
|
|
// This is the canonical msg delivery function in so far as the other host->DSP
|
|
// msg delivery function are written in terms of this function.
|
|
// The first 4 bytes in the first segment must contain the index of the audio sub-system
|
|
// which is to receive the message.
|
|
cmRtRC_t cmRtSysDeliverSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt, unsigned srcNetNodeId );
|
|
|
|
// Deliver a single message from the host to the DSP system.
|
|
cmRtRC_t cmRtSysDeliverMsg( cmRtSysH_t h, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId );
|
|
|
|
// Deliver a single message from the host to the DSP system.
|
|
// Prior to delivery the 'id' is prepended to the message.
|
|
cmRtRC_t cmRtSysDeliverIdMsg( cmRtSysH_t h, unsigned rtSubIdx, unsigned id, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId );
|
|
|
|
|
|
//
|
|
// DSP to Host message functions
|
|
//
|
|
|
|
// Is a msg from the DSP waiting to be picked up by the host? (host <- DSP)
|
|
// 0 = no msgs are waiting or the msg queue is locked by the DSP process.
|
|
// >0 = the size of the buffer required to hold the next msg returned via
|
|
// cmRtSysReceiveMsg().
|
|
unsigned cmRtSysIsMsgWaiting( cmRtSysH_t h );
|
|
|
|
// Copy the next available msg sent from the DSP process to the host into the host supplied msg buffer
|
|
// pointed to by 'msgBufPtr'. Set 'msgDataPtr' to NULL to receive msg by callback from cmRtSysCfg_t.clientCbFunc.
|
|
// Returns kBufTooSmallRtRC if msgDataPtr[msgByteCnt] is too small to hold the msg.
|
|
// Returns kNoMsgWaitingRtRC if no messages are waiting for delivery or the msg queue is locked by the DSP process.
|
|
// Returns kOkRtRC if a msg was delivered.
|
|
// Call cmRtSysIsMsgWaiting() prior to calling this function to get
|
|
// the size of the data buffer required to hold the next message.
|
|
cmRtRC_t cmRtSysReceiveMsg( cmRtSysH_t h, void* msgDataPtr, unsigned msgByteCnt );
|
|
|
|
|
|
// Fill an audio system status record.
|
|
void cmRtSysStatus( cmRtSysH_t h, unsigned rtSubIdx, cmRtSysStatus_t* statusPtr );
|
|
|
|
// Enable cmRtSysStatus_t notifications to be sent periodically to the host.
|
|
// Set rtSubIdx to cmInvalidIdx to enable/disable all sub-systems.
|
|
// The notifications occur approximately every cmRtSysCfg_t.meterMs milliseconds.
|
|
void cmRtSysStatusNotifyEnable( cmRtSysH_t, unsigned rtSubIdx, bool enableFl );
|
|
|
|
// Return a pointer the context record associated with a sub-system
|
|
cmRtSysCtx_t* cmRtSysContext( cmRtSysH_t h, unsigned rtSubIdx );
|
|
|
|
// Enable non-block mode. In this mode audio I/O is disabled
|
|
// and the DSP callback is made every noBlockSleepMs milliseconds.
|
|
cmRtRC_t cmRtSysEnableNoBlockMode( cmRtSysH_t h, unsigned rtSubIdx, bool enaFl, unsigned noBlockSleepMs );
|
|
|
|
// Return the count of audio sub-systems.
|
|
// This is the same as the count of cfg recds passed to cmRtSystemInitialize().
|
|
unsigned cmRtSysSubSystemCount( cmRtSysH_t h );
|
|
|
|
// Audio system test and example function.
|
|
void cmRtSysTest( cmCtx_t* ctx, int argc, const char* argv[] );
|
|
|
|
bool cmRtSysNetIsInitialized( cmRtSysH_t h );
|
|
cmRtRC_t cmRtSysNetDoSync( cmRtSysH_t h );
|
|
cmRtRC_t cmRtSysNetReport( cmRtSysH_t h );
|
|
cmRtRC_t cmRtSysNetReportSyncEnable( cmRtSysH_t h, bool enableFl );
|
|
cmRtRC_t cmRtSysNetGetHandle( cmRtSysH_t h, unsigned rtSubIdx, cmRtNetH_t* hp );
|
|
//)
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|