#include #include #include #include "globals.h" #include "ERROR.hpp" #include "asiolist.h" // Object to list sound drivers #include "iasiodrv.h" // ASIO driver interface... defines available function calls #define _LAYLA_IMPLEMENTATION_ // Tells layla.h that we want real mapping tables not external references #include "layla.h" #define SLEEP(msToSleep) _sleep(msToSleep) // ======================================== TYPE DEFINITIONS typedef struct _BUFDESC { // Description of the sound buffer long lMinSZ; // Minimum buffer size long lMaxSZ; // Max buffer size long lPrefSZ; // Preferred buffer size long lGrans; // Buffer granularity } BUFDESC; #define MAX_CLK 16 #define MAX_CHNS ( 2 * LAYLA_CHNS ) typedef struct _OPDESC { // Current operation description of the driver ASIOSampleRate asioSR; // Current sample rate ASIOClockSource asioCLK[ MAX_CLK ]; // Current clock source long lNumClocks; // Number of clocks available to the driver ASIOChannelInfo asioCHAN[ 2*MAX_CHNS+2 ]; // Channel information } OPDESC; typedef struct _ASIOINFO { char szName[ 1024 ]; // Driver name long lVer; // version long lInChans; // Number of input channels long lOutChans; // Number of output channels long lInLatency; // Input channel latency long lOutLatency; // Output channel latency BUFDESC bfd; // Buffer description OPDESC opd; // Current operation description } ASIOINFO; typedef union _ASIOsr { // Holds the ASIO sample rate unsigned char cSR[ 8 ]; // Various sizes and formats of the sample rate defined unsigned int nSR[ 4 ]; unsigned long lSR[ 2 ]; double dSR; ASIOSampleRate asr; } ASIOsr; // ======================================== GLOBAL DATA STRUCTURES (to this file anyway) //static AsioDriverList g_ADL; // Construction records all ASIO compiant drivers. They can now be selected by name static AsioDriverList *g_pADL = 0; // Construction records all ASIO compiant drivers. They can now be selected by name static IASIO *g_piasioDRV = 0; // Through this interface, we will access driver functions static uint g_Status; // Cumulative status //static uint g_ErrNum; // Cumulative error set static ERR g_Err; // Error handler // Data structures for CreateBuffers static ASIOBufferInfo g_asioBINF[ MAX_CHNS+2 ]; // Used for input/output with the ASIO driver static long g_lNumChns = MAX_CHNS; //static long g_lBufSZ = NUM_SAMPLES; // Debugging static char szDebug[ 256 ]; void ShowError ( ASIOError asioErr ); // Callback function prototypes void bufSW ( long lBufferIdx ,ASIOBool bProcessNow ); ASIOTime *bufSWtInfo ( ASIOTime *asioTM ,long lBufferIdx ,ASIOBool bProcessNow ); void SRchange ( ASIOSampleRate asioSR ); long asioMsg ( long lMsgSelector ,long lMsgValue ,void *pvMsg ,double *option ); static ASIOCallbacks g_asioCallBacks = { &bufSW ,&SRchange ,&asioMsg ,&bufSWtInfo }; // Basically our runtime interface with the ASIO driver // --------------------------------------------------------------------------------------------------- void ASIO_link ( char *pszDriverName ); void ASIO_config ( void ); void ASIO_buffers ( void ); void ASIO_start ( void ); void ASIO_stop ( void ); void ASIO_err ( ASIOError asioErr ); // ======================================== THE LAYLA CONSTRUCT... encapsulates high level manipulation // of the Layla 24 sound box. void LAYLA_init() { int ch; // ------- g_lBufSZ = NUM_SAMPLES; _wvchIN.ChCnt = 0; _wvchIN.Changed = 0; _wvchIN.Missed = 0; _wvchOUT.ChCnt = 0; _wvchOUT.Changed= 0; _wvchOUT.Missed = 0; for( ch = LAYLA_CHNS; ch--; ) { _wvchIN.wvch[ ch ].pCH = NULL; _wvchIN.wvch[ ch ].Chan = 0; _wvchIN.wvch[ ch ].pWV = NULL; _wvchIN.wvch[ ch ].LenWV = 0L; _wvchIN.wvch[ ch ].NumSeg = 0; _wvchIN.wvch[ ch ].CurSeg = 0; _wvchIN.wvch[ ch ].Changed = 0; _wvchIN.wvch[ ch ].Accessed = 0; _wvchIN.wvch[ ch ].NotPure = 0; _wvchOUT.wvch[ ch ].pCH = NULL; _wvchOUT.wvch[ ch ].Chan = 0; _wvchOUT.wvch[ ch ].pWV = NULL; _wvchOUT.wvch[ ch ].LenWV = 0L; _wvchOUT.wvch[ ch ].NumSeg = 0; _wvchOUT.wvch[ ch ].CurSeg = 0; _wvchOUT.wvch[ ch ].Changed = 0; _wvchOUT.wvch[ ch ].Accessed= 0; _wvchOUT.wvch[ ch ].NotPure = 0; } g_pADL = new AsioDriverList; } void LAYLA_init2( long SoundBufSZ ) { LAYLA_init(); for( g_lBufSZ = 0x4000; g_lBufSZ; g_lBufSZ >>= 1 ) // LAYLA sound buffer size must be no greater than 16384 and a power of 2 if( g_lBufSZ & SoundBufSZ ) break; if( !g_lBufSZ ) g_lBufSZ = NUM_SAMPLES; } int LAYLA_go() { // char *pszName = "ASIO Echo WDM\0 "; // Echo ASIO compliant driver // char *pszName = "Echo 24\0 "; // Echo ASIO compliant driver 6/30/2006 Sternberg to run on W98 machine char *pszName = "ASIO Echo24\0 "; // Echo ASIO compliant driver 6/30/2006 Sternberg to run on W98 machine // ------------------------------------------------------------------------------ g_ErrNum = 0; ASIO_link( pszName ); // Instantiate the runtime driver... ultimately using Windows registry /* if( !g_ErrNum ) ASIO_config(); // Configure the instantiated driver if( !g_ErrNum ) ASIO_buffers(); // Define and create the buffers (ASIO driver will call bufSW for the first time to init sound buffers) if( !g_ErrNum ) ASIO_start(); // Away we go amigos. Start playing/recording "sounds" */ ASIO_config(); // Configure the instantiated driver ASIO_buffers(); // Define and create the buffers (ASIO driver will call bufSW for the first time to init sound buffers) ASIO_start(); // Away we go amigos. Start playing/recording "sounds" return !g_ErrNum; } int LAYLA_stop() { ASIO_stop(); if( g_pADL ) { delete g_pADL; g_pADL = 0; } return !g_ErrNum; } int LAYLA_map( CHANNEL *pCH ) // Return table index into which channel was mapped { WAVECHN_TBL *pwvch; int ch; // ----------------------- if( !pCH ) return -1; g_Err.Warn( "Mapping channel" ); switch( pCH-> m_Dir ) { case CH_IN: pwvch = &_wvchIN; break; case CH_OUT: pwvch = &_wvchOUT; break; default: sprintf( szDebug ,"Bad channel %d dir %d" ,pCH-> m_Ch ,pCH-> m_Dir ); } // pwvch = pCH-> m_Dir == CH_IN ? &_wvchIN : &_wvchOUT; // Pick either input or output table pwvch-> Changed = 1; // Mark the table as changed so bufSW will know to process it ch = pwvch-> ChCnt++ % LAYLA_CHNS; // Kick table index, wrapping if outside bounds pwvch-> wvch[ ch ].pCH = pCH; // Record pointer to channel object pwvch-> wvch[ ch ].Chan = pCH-> m_Ch; // Record LAYLA channel # (1 to 8) pwvch-> wvch[ ch ].pWV = pCH-> m_plWave; pwvch-> wvch[ ch ].LenWV = pCH-> m_WVsize; pwvch-> wvch[ ch ].NumSeg = pCH-> m_WVsize / g_lBufSZ; // Compute # of sound buffer segments in waveform pwvch-> wvch[ ch ].CurSeg = pwvch-> wvch[ ch ].NumSeg - 1; // Current waveform segment to be processed pwvch-> wvch[ ch ].Changed = 1; // 2; // This waveform has changed sprintf( szDebug ,"ID %d ,Chan %d ,LenWV %ld ,#Segs %d" ,ch ,pwvch-> wvch[ch].Chan ,pwvch-> wvch[ch].LenWV ,pwvch-> wvch[ch].NumSeg ); g_Err.Warn( szDebug ); return ch; } int LAYLA_update( int TblIdx ,CHANNEL *pCH ) // Only applies to input channels { WAVECHN_TBL *pwvch; // ----------------------- if( !pCH ) return -1; if( TblIdx < 0 ) return -1; g_Err.Warn( "Updating channel" ); switch( pCH-> m_Dir ) { case CH_IN: pwvch = &_wvchIN; break; case CH_OUT: pwvch = &_wvchOUT; break; default: sprintf( szDebug ,"Bad channel %d dir %d" ,pCH-> m_Ch ,pCH-> m_Dir ); } pwvch-> Changed = 1; pwvch-> wvch[ TblIdx ].pCH = pCH; pwvch-> wvch[ TblIdx ].Chan = pCH-> m_Ch; // Record LAYLA channel # (1 to 8) pwvch-> wvch[ TblIdx ].pWV = pCH-> m_plWave; pwvch-> wvch[ TblIdx ].LenWV = pCH-> m_WVsize; pwvch-> wvch[ TblIdx ].NumSeg = pCH-> m_WVsize / g_lBufSZ; // Compute # of sound buffer segments in waveform pwvch-> wvch[ TblIdx ].CurSeg = pwvch-> wvch[TblIdx].NumSeg-1;// Current waveform segment to be processed pwvch-> wvch[ TblIdx ].Changed = 1; // = 2; // This waveform has changed sprintf( szDebug ,"UPDATE: ID %d ,Chan %d ,LenWV %ld ,#Segs %d" ,TblIdx ,pwvch-> wvch[TblIdx].Chan ,pwvch-> wvch[TblIdx].LenWV ,pwvch-> wvch[TblIdx].NumSeg ); g_Err.Warn( szDebug ); return TblIdx; } int LAYLA_ramp() { return 0; } // ======================================== THE ASIO CONSTRUCT... encapsulates high level manipulation // of the ASIO compliant driver. void ASIO_link( char *pszDriverName ) { if( !g_pADL-> asioLink( pszDriverName ) ) if( !g_pADL-> asioOpen( (LPVOID *) &g_piasioDRV ) ) { if( !g_piasioDRV ) { g_ErrNum |= ASIO_OPENED; g_Err.Warn( "Driver not instantiated!!" ); } else if( g_piasioDRV-> init( GetTopWindow(NULL) ) == ASIOTrue ) return; else { g_ErrNum |= ASIO_ACCESSED; g_Err.Warn( "Runtime access failed" ); } } else { g_ErrNum |= ASIO_OPENED; g_Err.Warn( "Driver open failed" ); } else { g_ErrNum |= ASIO_LINKED; g_Err.Warn( "Logical link failed" ); } } void ASIO_config() { ASIOINFO ainfo; ASIOsr asrSR; char szSelectionHint[ 128 ]; // ------------------------------- // Set the clock source used to drive D/A, A/D g_piasioDRV-> setClockSource( 0 ); // Set the clock that we will be using // Check sound buffer size... it must match our intent g_piasioDRV-> getBufferSize ( &ainfo.bfd.lMinSZ // Get the allowed buffer size... ECHO's ASIO ,&ainfo.bfd.lMaxSZ // compliant driver sets all these values to the same number. ,&ainfo.bfd.lPrefSZ // This number corresponds to the current selection on ,&ainfo.bfd.lGrans // ECHO's ASIO panel. ); if( ainfo.bfd.lPrefSZ != g_lBufSZ ) { // Current sound buffer size mismatch sprintf( szSelectionHint ,"Please select %d for a sound buffer size" ,g_lBufSZ ); g_Err.Warn( "!!Sound buffer mismatch!!" ); g_Err.Warn( szSelectionHint ); g_ErrNum |= ASIO_SIZEFAULT; g_piasioDRV-> controlPanel(); // Give user chance to fix it } // Check the sample rate... user must go the the ECHO control panel and make sure sample rate is "unlocked" asrSR.dSR = (double) SAMPLE_RATE; g_piasioDRV-> setSampleRate( asrSR.asr ); g_piasioDRV-> getSampleRate( &ainfo.opd.asioSR ); if( asrSR.asr != ainfo.opd.asioSR ) { g_Err.Warn( "Cannot set sample rate... use ECHO control panel, unlock sample rate using 'Settings'" ); g_ErrNum |= ASIO_SAMPFAULT; } } // --------------------------------------------------------------------------- void ASIO_buffers() /* PURPOSE: Create a definition of all ASIO sound buffers for active input and output channels. The _wvchIN, and _wvchOUT tables will be interpreted and the definitions loaded into g_asioBINF. The ASIO driver will then be told to create the buffers. NOTE: -> createBuffers will call bufSW twice to initialize house keeping and initial buffer contents. */ { ASIOError asioErr; int ChIdx; int NumIN; // -------------------- NumIN = _wvchIN.ChCnt; for( ChIdx = 0; ChIdx < NumIN; ChIdx++ ) { // Define the output D/A channels g_asioBINF[ ChIdx ].isInput = ASIOFalse; g_asioBINF[ ChIdx ].channelNum = _wvchIN.wvch[ ChIdx ].Chan - 1; } for( ChIdx = 0; ChIdx < _wvchOUT.ChCnt; ChIdx++ ) { // Define the input A/D channels g_asioBINF[ ChIdx + NumIN ].isInput = ASIOTrue; g_asioBINF[ ChIdx + NumIN ].channelNum = _wvchOUT.wvch[ ChIdx ].Chan - 1; } g_lNumChns = _wvchIN.ChCnt + _wvchOUT.ChCnt; if( asioErr = g_piasioDRV-> createBuffers( &g_asioBINF[0] ,g_lNumChns ,g_lBufSZ ,&g_asioCallBacks ) ) { g_ErrNum |= ASIO_BUFFAULT; ASIO_err( asioErr ); } } // --------------------------------------------------------------------------- void ASIO_start() /* PURPOSE: Starts ASIO driver */ { ASIOError asioErr; // ------------------ g_Err.Warn( "ASIO_start:" ); if( asioErr = g_piasioDRV-> start() ) { ASIO_err( asioErr ); g_ErrNum |= ASIO_RUNFAULT; } } // --------------------------------------------------------------------------- void ASIO_stop() /* PURPOSE: Closes instantiated ASIO driver */ { ASIOError asioErr; // int nCH; // -------------------- g_Err.Warn( "ASIO_stop:" ); if( g_piasioDRV ) { if( asioErr = g_piasioDRV-> stop() ) // Stop the driver... callbacks to bufSW ASIO_err( asioErr ); else { g_Err.Warn( "ASIO_stop: Driver stopped" ); g_ErrNum &= ~ASIO_OPENED; } g_piasioDRV-> disposeBuffers(); // Tell driver OK to release sound buf memory g_Err.Warn( "ASIO_stop: Buffers released" ); } if( g_pADL ) { g_Err.Warn( "ASIO_stop: closing Driver list..." ); g_pADL-> asioClose(); // This calls g_piasioDRV-> Release() g_Err.Warn( "ASIO_stop: Driver list closed" ); delete g_pADL; g_pADL = 0; } if( g_piasioDRV ) { delete g_piasioDRV; g_piasioDRV = 0; } } //--------------------------------------------------------------------------- void ASIO_err( ASIOError asioErr ) /* PURPOSE: Decode an ASIO error. */ { char szErrMsg[ 1024 ]; // ------------------------- sprintf( szErrMsg ,"!! UNknown ASIO error !!" ); switch( asioErr ) { case ASE_OK: sprintf( szErrMsg ,"call succeeded, no Error" ); break; case ASE_SUCCESS: sprintf( szErrMsg ,"success return ASIOFuture calls?!" ); break; case ASE_NotPresent: sprintf( szErrMsg ,"hardware input or output is not present/available" ); break; case ASE_HWMalfunction: sprintf( szErrMsg ,"hardware is malfunctioning" ); break; case ASE_InvalidParameter: sprintf( szErrMsg ,"input parameter invalid" ); break; case ASE_InvalidMode: sprintf( szErrMsg ,"hardware is in a bad mode or used in a bad mode" ); break; case ASE_SPNotAdvancing: sprintf( szErrMsg ,"hardware not running when sample position inquired" ); break; case ASE_NoClock: sprintf( szErrMsg ,"sample clock/rate cannot be determined or not present" ); break; case ASE_NoMemory: sprintf( szErrMsg ,"not enough memory for completing the request" ); break; } g_Err.Warn( szErrMsg ); } // ======================================== The ASIO driver call back routines // // --------------------------------------------------------------------------- void bufSW( long lBufI ,ASIOBool bNow ) /* PURPOSE: Called by the ASIO sound driver thread at its discression. INPUT: lBufI Sound buffer index telling us which half of the sound buffer is available for new waveform storage (D/A) or new waveform measurement (A/D) NOTE: The ASIO sound driver thread and ultimately the LabView CIN (embeded in ASIO.vi) coordinate their access to channel sound buffers via 2 WAVECHN_TBL structures. It is in this routine, that timing bits, are maintained. NOTE: This routine AND the managing tables have no ability to handle waveforms (either D/A or A/D) which do not have the following size relationship to the sound buffer size: WAVE/SBUF = 1 WAVE/SBUF = N N = 2,3,... SBUF/WAVE = N N = 2,3,... D/A channels only */ { // Our main workhorse. int nCH; int nWV; WAVEtoCHN *pwvch; int nChanged; long *pseg ,*pwform; int nNewWave = 0; // ----------------------------- // g_Err.Warn( "bufSW: Entry" ); for( nCH = _wvchIN.ChCnt; nCH--; _wvchIN.Missed |= (1<<(_wvchIN.wvch[nCH].Chan-1)) ); // If we are late servicing a D/A buffer, set a bit // FIRST process the D/A channels if( _wvchIN.Changed ) { // If at least 1 D/A channel has changed... is ready to be loaded // g_Err.Warn( " output waveform changes" ); _wvchIN.Changed = 0; // Zero this out in expectation of implementing a change _wvchIN.Missed = 0; for( nCH = 0 ,pwvch = _wvchIN.wvch; nCH < _wvchIN.ChCnt; pwvch++ ,nCH++ ) { // For each active D/A channel (channel was "ON" when the sound driver was started) // sprintf( szDebug ," Scanning %d for change %d" ,nCH ,pwvch-> Changed ); // g_Err.Warn( szDebug ); if( pwvch-> Changed && pwvch-> pWV ) { // See if a new waveform is available for the given D/A channel // sprintf( szDebug ," Seg size %d" ,pwvch-> NumSeg ); // g_Err.Warn( szDebug ); pseg = (long *) g_asioBINF[ nCH ].buffers[ lBufI ]; // Point to that channels 1/2 sound buffer nNewWave = 1; // Flag that a new waveform has been installed switch( pwvch-> NumSeg ) { // Now we must related waveform size to sound buffer size case 0: nWV = BUF_SIZE / pwvch-> LenWV; // Waveform is SMALLER than a sound buffer while( nWV-- ) { // Then repeat the waveform until sound buffer is full memcpy( pseg ,pwvch-> pWV ,pwvch-> LenWV * BYTES ); pseg += pwvch-> LenWV; } // g_Err.Warn( " Partial segment output waveform copied to buffer" ); pwvch-> Changed--; // Decrement the changed counter (when zero we will no longer waste time copying a "static" waveform) break; case 1: memcpy( pseg ,pwvch-> pWV ,BUF_SIZE ); // Waveform is the same size as a sound buffer // g_Err.Warn( " Single segment output waveform copied to buffer" ); pwvch-> Changed--; // Decrement change counter break; case 2: pwvch-> Changed--; // Waveform is the size of two sound buffers // g_Err.Warn( " Double segment output waveform copied to buffer" ); default: pwvch-> CurSeg = ++pwvch-> CurSeg % pwvch-> NumSeg; // Waveform is some integer multiple of a sound buffer pwform = pwvch-> pWV + pwvch-> CurSeg * g_lBufSZ; memcpy( pseg ,pwform ,BUF_SIZE ); // Load the current sound buffer with the current waveform segment // g_Err.Warn( " Multi segment output waveform copied to buffer" ); } _wvchIN.Changed |= pwvch-> Changed; } else // The CIN is currently accessing the sound buffer _wvchIN.Missed |= (1 << (pwvch-> Chan-1)); // BUT the sound driver thread called us... mark that we are late } } // now process the A/D channels pwvch = _wvchOUT.wvch; // Select the A/D management table _wvchOUT.Changed = nChanged = 0; // Zero the global flag indicating at least 1 A/D channel has a new measurement available _wvchOUT.Missed = 0; // Assume that we haven't missed any measurements yet for( nCH = 0; nCH < _wvchOUT.ChCnt; pwvch++ ,nCH++ ) { // For each active A/D channel pseg = (long *) g_asioBINF[ nCH + _wvchIN.ChCnt ].buffers[ lBufI ]; // Select the appropriate 1/2 sound buffer for that channel if( pwvch-> NumSeg < 1 ) // Channel activated at start up has been temporarily deactivated continue; if( nNewWave ) // If new transmission on D/A, A/D won't be pure until this segment (used for fast turn around transfer function I.D.) pwvch-> NotPure = pwvch-> NumSeg + 1; // Compute the 1rst A/D segment which will NOT contain waveform transisition information if( pwvch-> Accessed ) { // Wave form is currently being accessed, measurement not possible if( nNewWave ) // we're going to skip the waveform pwvch-> NotPure = pwvch-> NumSeg; // so the first pure segment calculation is altered continue; } // sprintf( szDebug ,"LOADING waveform segment %d" ,pwvch-> CurSeg ); // g_Err.Warn( szDebug ); if( pwvch-> pWV ) { // Is there a waveform measurement buffer space allocated if( pwvch-> Changed ) // The CIN should have cleared this when it processed the latest measurement _wvchOUT.Missed |= (1 << (pwvch-> Chan-1)); // It didn't so mark this channel as being missed switch( pwvch-> NumSeg ) { // Now we must account for size differences between the sound buffer and measurement space case 1: memcpy( pwvch-> pWV ,pseg ,pwvch-> LenWV * BYTES ); // 1 waveform measurement = 1 sound buffer pwvch-> Changed = 1; // Mark that a new measurement is available break; default: pwvch-> CurSeg = ++pwvch-> CurSeg % pwvch-> NumSeg; // 1 waveform measurement = NumSeg sound buffers pwform = pwvch-> pWV + (pwvch-> CurSeg * g_lBufSZ); // Compute current storage position pwvch-> Changed = !pwvch-> CurSeg; // Only set measurement available flag if ALL segments have been collected memcpy( pwform ,pseg ,BUF_SIZE ); // And copy the sound buffer into the appropriate segment } } nChanged |= pwvch-> Changed; // Mark that a measurement has been taken if( !nNewWave && pwvch-> NotPure ) // Decrement the pure count if appropriate pwvch-> NotPure--; } _wvchOUT.Changed = nChanged; // g_Err.Warn( "bufSW: Exit" ); } ASIOTime *bufSWtInfo( ASIOTime *asioTM ,long lBufI ,ASIOBool bNow ) { // This should not be called as we denied it in asioMsg. Also v 1.0 // ASIO driver is not supposed to be capable of asking for it. return NULL; } void SRchange( ASIOSampleRate asioSR ) { // Very unlikely as we will be only ones using the sound system // Status( "SRchange" ); } long asioMsg( long lMsgSelector ,long lMsgVal ,void *pvMsg ,double *option ) { #define SUPPORTED 1L #define UNSUPPORTED 0L #define ACCEPTED 1L #define DENIED 0L #define ACKNOWLEDGED 1L #define ENGINEVERSION 1L char szMsg[ 256 ]; // -------------------------- sprintf( szMsg ,"asioMsg( %ld ,%ld ... )" ,lMsgSelector ,lMsgVal); g_Err.Warn( szMsg ); switch( lMsgSelector ) { case kAsioSelectorSupported: switch( lMsgVal ) { case kAsioEngineVersion: return ENGINEVERSION; case kAsioResetRequest: case kAsioResyncRequest: case kAsioLatenciesChanged: break; /* Irrelevant for our version 1.0 driver case kAsioSupportsTimeInfo: case kAsioSupportsTimeCode: case kAsioSupportsInputMonitor: */ }; return DENIED; case kAsioResetRequest: return DENIED; case kAsioResyncRequest: return ACCEPTED; case kAsioLatenciesChanged: return ACKNOWLEDGED; case kAsioEngineVersion: return ENGINEVERSION; case kAsioSupportsTimeInfo: return UNSUPPORTED; case kAsioSupportsTimeCode: return UNSUPPORTED; }; return UNSUPPORTED; }