/* ------------------------------------------------------------------------------ | CHANNEL | | Author : Terry Leach | Revision: 8/7/01 Initial creation | Revision: 9/23/03 Modified to support GenMon2... timing bits and | access bits. Purpose : Encapsulates ASIO sound channels. Basically provides an interface into the WAVEtoCHANNEL mapping tables of the LAYLA construct. */ #include // need NULL definition #include "CHANNEL.hpp" #include "ERROR.hpp" #include "layla.h" static ERR g_Err; int CHANNEL::g_ON = 0; int CHANNEL::g_Init = 0; int CHANNEL::g_NumCh = 0; WAVE *CHANNEL::g_pWAVE = NULL; uint CHANNEL::g_Status = 0; // --------------------------------------------------------------------------- CHANNEL::CHANNEL() /* | Purpose: This default constuctor has a little more significance than | most in the NULLing project. It's job is to initialize the | global structures common to all channels. This includes the | LAYLA construct. | | This constructs a "Master" channel object which is not tied to | any LAYLA channels, but manages all CHANNEL instantiations. */ { g_Err.Warn( "CHANNEL::CHANNEL() Master channel instantiation" ); // Message to logging file if( !g_Init ) { // If sound system has not yet been iniitalized g_Init = 1; // Initialize global tracking of all CHANNEL instantiations g_ON = 0; g_NumCh = 0; g_pWAVE = (WAVE *) new WAVE; g_Status= 0; LAYLA_init(); // Initialize the LAYLA hardware via the ASIO driver thread } m_Ch = 0; // The master channel doesnt use the member variables m_Dir = CH_NULL; m_pWAVE = NULL; m_plWave = NULL; m_WVsize = 0L; m_TblIdx = -1; } // --------------------------------------------------------------------------- CHANNEL::CHANNEL( long SoundBufSZ ) /* | Purpose: Constructs master channel object as above, | but communicates setting of sound buffer size. This is needed | by GenMon2 to allow user setting of sound buffer size, the NULLING | system has a fixed sound buffer size. */ { g_Err.Warn( "CHANNEL::CHANNEL( SoundBufSZ ) Master channel instantiation" ); if( !g_Init ) { g_Init = 1; g_ON = 0; g_NumCh = 0; g_pWAVE = (WAVE *) new WAVE( SoundBufSZ ); g_Status= 0; LAYLA_init2( SoundBufSZ ); } m_Ch = 0; m_Dir = CH_NULL; m_pWAVE = NULL; m_plWave = NULL; m_WVsize = 0L; m_TblIdx = -1; } // --------------------------------------------------------------------------- CHANNEL::CHANNEL( WAVE *pwave ,int Chan ) /* | Purpose: Constructs a channel object and associates it with a D/A channel. | | INPUT: pwave Pointer to a wave object. The wave object will be the | source of new waveforms to be transmitted on the D/A channel. | Chan The A/D channel requested */ { if( !pwave ) return; // If no wave object, then no ability to generate waves g_Err.Warn( "CHANNEL::CHANNEL( pwave ,Chan ) DA channel..." ); // Tracking for the log file if( channel( pwave-> m_pWAVE.pv ,pwave-> g_Len ,Chan ,CH_IN ) ) { // Call common construction g_Err.Warn( " constructed" ); // success, report to the log file g_NumCh++; // count total channels instantiated m_pWAVE = pwave; // Record pointer to our source of new waveforms m_TblIdx = LAYLA_map( this ); // Now the bufSW call back will know where to get new sound buffer data } } // --------------------------------------------------------------------------- CHANNEL::CHANNEL( void *pv ,ulong WVsize ,int Chan ) /* | Purpose: Constructs a channel object, making a map between memory and | a LAYLA A/D sound buffer channel. | | INPUT: pv Pointer to where measured waveforms will be stored | wvsize Size in bytes of the waveform measurement memory | Chan The A/D channel */ { g_Err.Warn( "CHANNEL::CHANNEL( pv ,WVsize ,Chan ) AD channel..." ); // Log file tracking if( channel( pv ,WVsize ,Chan ,CH_OUT ) ) { // Call common construction g_Err.Warn( " constructed" ); // Success g_NumCh++; // Count total channels allocated m_pWAVE = g_pWAVE; // A/D channels share global WAVE object for conversion to F.P. representation m_TblIdx = LAYLA_map( this ); // Now the bufSW call back will know where to store measured waveform segments } } // --------------------------------------------------------------------------- CHANNEL::~CHANNEL() /* | Purpose: Default shut off of LAYLA. */ { g_Err.Warn( "CHANNEL::~CHANNEL()" ); // Log file tracking if( !m_plWave ) return; // Then this object never sucessfully constructed m_plWave = NULL; // Null pointer to source/sink of waveform memory m_TblIdx = LAYLA_update( m_TblIdx ,this ); // Tell LAYLA to drop this channel from table of active channels if( ! --g_NumCh ) { // Decrement active channel count if( g_Init ) // If the sound system is running LAYLA_stop(); // Stop it if( g_pWAVE ) // If we auto allocated waveform memory delete g_pWAVE; // free it g_Init = 0; // We are now uninitialized. } } // --------------------------------------------------------------------------- int CHANNEL::channel( void *pvWV ,ulong WVsize ,int Chan ,CHDIR ChDir ) /* | Purpose: Common code shared between constructor flavors. | | INPUT: pvWV A pointing to source(D/A) or sink(A/D) waveform memory | WVsize Size of the waveform memory | Chan Layla channel being assigned to this object | ChDir Indicates A/D or D/A channel */ { if( !pvWV || Chan < 1 || Chan > LAYLA_CHNS ) return 0; // Invalid parameters cause call to fail m_Ch = Chan; // Record operating parameters m_Dir = ChDir; m_plWave = (long *) pvWV; m_WVsize = WVsize; m_TblIdx = -1; // The LAYLA object will assign us a table index return 1; // Sucessful construction } // --------------------------------------------------------------------------- void CHANNEL::link( void *pvWV ,ulong WVsize ) /* | Purpose: Changes the waveform memory link on an A/D channel. Changes in | channel direction are NOT allowed. | | INPUT: pvWV New source/sink for waveform memory | WVsize Size in bytes of the new waveform memory */ { if( m_TblIdx < 0 || !pvWV || m_Dir != CH_OUT ) return; m_plWave = (long *) pvWV; // Record the new operating parameters m_WVsize = WVsize; m_TblIdx = LAYLA_update( m_TblIdx ,this ); // Tell the LAYLA object about the change } // --------------------------------------------------------------------------- void CHANNEL::link( WAVE *pwave ) /* | Purpose: Changes the waveform memory link on a D/A channel. */ { g_Err.Warn( "CHANNEL::link( *pwave )" ); if( m_TblIdx < 0 || !pwave || m_Dir != CH_IN ) return; m_plWave = pwave-> m_pWAVE.pl; // Record the new waveform source m_WVsize = pwave-> g_Len; m_TblIdx = LAYLA_update( m_TblIdx ,this ); // Tell the LAYLA object about the change } // --------------------------------------------------------------------------- void CHANNEL::change() /* | Purpose: Here we indicate by direct modification of LAYLA:bufSW management | tables that a channel has changed. Done in the name of efficiency | before we knew how fast the sound system really was. This violation | of LAYLA's encapsulation should really be fixed. | | If a D/A channel has changed, then setting the table indicator to 1 indicates | that a new waveform is available for transmission. If bufSW is called and | this indicator is NOT set, then a late bit is set for that D/A channel. | | If an A/D channel, then the ::change() method is called to indicate that | a measurement has been processed. If bufSW is called by the driver thread | and the .Changed parameter is still set, a late bit is set for that A/D | channel. */ { WAVECHN_TBL *pwvch; // ------------------- pwvch = m_Dir == CH_IN ? &_wvchIN : &_wvchOUT; // Select the appropriate sound driver table pwvch-> Changed = (m_Dir == CH_IN) ? 1 : 0; // Indicates that at least 1 channel needs servicing // pwvch-> wvch[ m_TblIdx ].Changed = 2; // Original nulling system had to load both halves of sound buffer when a change was made pwvch-> wvch[ m_TblIdx ].Changed = (m_Dir == CH_IN) ? 1 : 0; // Marks which channel changed } // --------------------------------------------------------------------------- void CHANNEL::lock() /* | Purpose: Keeps bufSW from writing/reading waveform memory while it is | being operated on by the main application. */ { WAVECHN_TBL *pwvch; // ------------------- pwvch = m_Dir == CH_IN ? &_wvchIN : &_wvchOUT; pwvch-> wvch[ m_TblIdx ].Accessed = 1; } // --------------------------------------------------------------------------- void CHANNEL::unlock() /* | Purpose: Frees access to waveform memory allowing bufSW to read or write it. */ { WAVECHN_TBL *pwvch; // ------------------- pwvch = m_Dir == CH_IN ? &_wvchIN : &_wvchOUT; pwvch-> wvch[ m_TblIdx ].Accessed = 0; } // --------------------------------------------------------------------------- void CHANNEL::convert( WAVEPTR pWV ) /* | Purpose: Converts an entire A/D waveform into floating point | representation. Note an A/D waveform may contain 1 or more | segments. */ { if( !pWV.pv ) return; if( m_pWAVE ) m_pWAVE-> convert( pWV ,_wvchOUT.wvch[m_TblIdx].LenWV ); } // --------------------------------------------------------------------------- void CHANNEL::convert() /* | Purpose: Converts a D/A waveform from floating point (the way it was | in LabView) to left justfied 24 bit representation suitable | for transmission to the LAYLA. */ { if( m_Dir != CH_OUT ) return; WAVEPTR pWAVEform; // Type conversion pWAVEform.pl = _wvchOUT.wvch[m_TblIdx].pWV; // The table uses long pointers, convert needs general pointer if( m_pWAVE ) // If we have an associated wave object m_pWAVE-> convert( pWAVEform ,_wvchOUT.wvch[m_TblIdx].LenWV ); // use it to perform the conversion } // --------------------------------------------------------------------------- void CHANNEL::cvrtSeg( WAVEPTR wpSeg ) /* | Purpose: Converts the indicated A/D waveform segment to floating point | representation. */ { if( m_Dir != CH_OUT ) return; if( m_pWAVE ) m_pWAVE-> convert( wpSeg ,g_lBufSZ ); } // --------------------------------------------------------------------------- void CHANNEL::chan( int NewChan ) /* | Purpose: Changes the channel associated with this object. | | NOTE: This operation cannot be performed if the sound system is | currently running. */ { if( g_ON ) return; // Sound system running, ignore request m_Ch = NewChan; // Make the new channel assignment m_TblIdx = LAYLA_update( m_TblIdx ,this ); // Let the LAYLA know about it. } // --------------------------------------------------------------------------- void CHANNEL::go() /* | Purpose: Starts the sound system. */ { if( !g_ON ) { // If sound system not already running g_ON = LAYLA_go(); // Then tell LAYLA object to start it up g_Status= g_ErrNum; // Record status of start up attempt } } // --------------------------------------------------------------------------- void CHANNEL::ramp() { if( !g_ON ) LAYLA_ramp(); g_ON = 1; } // Not implemented... ramps transmitted waveforms // --------------------------------------------------------------------------- void CHANNEL::stop() /* | Purpose: Stops the sound system */ { if( g_ON ) { // If sound system not on, ignore request g_ON = !LAYLA_stop(); // Tell LAYLA to stop sound driver g_Status = g_ErrNum; // Record status of stop attempt } } // --------------------------------------------------------------------------- int CHANNEL::busy() /* | Purpose: Used to set timing bits for coordination with LabView applications. | | OUTPUT: If D/A channel returns 1 if waveform has yet to be transmitted | otherwise returns 0 indicating that a new waveform is needed. | If A/D channel returns > 0 if waveform segments are still being | collected. A 0 is returned when the entire waveform has been | collected and is ready for measurement. */ { if( m_Dir == CH_IN ) return _wvchIN.wvch[m_TblIdx].Changed; else return _wvchOUT.wvch[m_TblIdx].NotPure; } // --------------------------------------------------------------------------- void CHANNEL::GetNew() /* | Purpose: Not used: */ { if( m_Dir == CH_OUT ) _wvchOUT.wvch[ m_TblIdx ].NotPure = _wvchOUT.wvch[ m_TblIdx ].NumSeg + 1; } // --------------------------------------------------------------------------- int CHANNEL::done() /* | Purpose: The opposite of ::busy() */ { if( m_Dir == CH_IN ) return !_wvchIN.wvch[m_TblIdx].Changed; else return !_wvchOUT.wvch[m_TblIdx].NotPure; } // --------------------------------------------------------------------------- int CHANNEL::seg( int WhichSeg ) /* | Purpose: Indicates if the requested waveform segment has been collected. | | INPUT: WhichSeg Indicates which waveform segment should be checked. | | NOTE: In the original nulling system, at least 1 segment had to | be ignored prior to calculating effects of a D/A change. */ { int nSegCollected = 0; // Assume it hasn't yet been collected // -------------------------- WAVECHN_TBL *pwvch = m_Dir == CH_IN ? &_wvchIN : &_wvchOUT; // Select the appropriate management table WhichSeg %= pwvch-> wvch[ m_TblIdx ].NumSeg; // Make sure WhichSeg is in-bounds nSegCollected = pwvch-> wvch[ m_TblIdx ].CurSeg == WhichSeg; // Test to see if it's been collected return nSegCollected;// && !pwvch-> wvch[ m_TblIdx ].NotPure; // And return the results } // --------------------------------------------------------------------------- int CHANNEL::CurSeg() /* | Purpose: Returns which waveform segment is currently being processed/acquired. */ { WAVEtoCHN *pwv_ch = m_Dir == CH_IN ? &_wvchIN.wvch[m_TblIdx] : &_wvchOUT.wvch[m_TblIdx]; return pwv_ch-> CurSeg; } // --------------------------------------------------------------------------- int CHANNEL::NextSeg() /* | Purpose: Computes the next waveform segment to be collected. */ { WAVEtoCHN *pwv_ch = m_Dir == CH_IN ? &_wvchIN.wvch[m_TblIdx] : &_wvchOUT.wvch[m_TblIdx]; return (pwv_ch-> CurSeg + 1) % pwv_ch-> NumSeg; } // --------------------------------------------------------------------------- int CHANNEL::NextSeg( int SegOffset ) /* | Purpose: Computes the waveform segment index given a count of the | number of segments to be collected after the current | segment. Basically computes an arguement for use with the | ::seg method. */ { if( SegOffset < 0 ) SegOffset = -SegOffset; WAVEtoCHN *pwv_ch = m_Dir == CH_IN ? &_wvchIN.wvch[m_TblIdx] : &_wvchOUT.wvch[m_TblIdx]; return (pwv_ch-> CurSeg + SegOffset) % pwv_ch-> NumSeg; } // --------------------------------------------------------------------------- WAVEPTR CHANNEL::SegAddr( int WhichSeg ) /* | Purpose: Computes waveform memory address of given waveform segment. */ { WAVEPTR wptr; WAVEtoCHN *pwv_ch = m_Dir == CH_IN ? &_wvchIN.wvch[m_TblIdx] : &_wvchOUT.wvch[m_TblIdx]; if( WhichSeg < 0 ) WhichSeg = pwv_ch-> CurSeg; wptr.pl = pwv_ch-> pWV + (WhichSeg * g_lBufSZ); return wptr; } // --------------------------------------------------------------------------- uint CHANNEL::Missed() /* | Purpose: Returns late bits for A/D and D/A channels */ { return ((_wvchOUT.Missed & 0x00FF) << 8) | (_wvchIN.Missed & 0x00FF); } // --------------------------------------------------------------------------- uint CHANNEL::BufStat() /* | Purpose: Collects late bits for use in higher level application. | Late bits indicate if the higher level application failed | to keep up with the sound driver thread. | | Late bits are stored in a 16 bit word. D/A channel status is stored in the | low byte, while A/D channels are stored in the high byte. */ { int ch; uint Stat = 0; // ----------------- if( m_TblIdx == -1 ) { // If this is the master channel object, get status all active channels for( ch = _wvchIN.ChCnt; ch--; ) Stat |= (!_wvchIN.wvch[ch].Changed) << (_wvchIN.wvch[ch].Chan-1); for( ch = _wvchOUT.ChCnt; ch--; ) Stat |= (_wvchOUT.wvch[ch].Changed != 0) << (_wvchOUT.wvch[ch].Chan+7); } else // Otherwise get status for the individual channel Stat|= m_Dir == CH_IN ? (!_wvchIN.wvch[m_TblIdx].Changed) << m_TblIdx : (_wvchOUT.wvch[m_TblIdx].Changed != 0) << m_TblIdx; return Stat; }