1// ****************************************************************************
2//
3//		CMidiInQ.cpp
4//
5//		Implementation file for the CMidiInQ class.
6//		Use a simple fixed size queue for managing MIDI data.
7//		Fill & drain pointers are maintained automatically whenever
8//		an Add or Get function succeeds.
9//
10//		Set editor tabs to 3 for your viewing pleasure.
11//
12// ----------------------------------------------------------------------------
13//
14// This file is part of Echo Digital Audio's generic driver library.
15// Copyright Echo Digital Audio Corporation (c) 1998 - 2005
16// All rights reserved
17// www.echoaudio.com
18//
19// This library is free software; you can redistribute it and/or
20// modify it under the terms of the GNU Lesser General Public
21// License as published by the Free Software Foundation; either
22// version 2.1 of the License, or (at your option) any later version.
23//
24// This library is distributed in the hope that it will be useful,
25// but WITHOUT ANY WARRANTY; without even the implied warranty of
26// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
27// Lesser General Public License for more details.
28//
29// You should have received a copy of the GNU Lesser General Public
30// License along with this library; if not, write to the Free Software
31// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32//
33// ****************************************************************************
34
35#include	"CEchoGals.h"
36
37
38/****************************************************************************
39
40	Construction and destruction, clean up and init
41
42 ****************************************************************************/
43
44//============================================================================
45//
46// Construction & destructor
47//
48//============================================================================
49
50CMidiInQ::CMidiInQ()
51{
52
53	m_pBuffer = NULL;
54	m_ullLastActivityTime = 0;
55
56	m_wMtcState = MIDI_IN_STATE_NORMAL;
57
58	m_pEG = NULL;
59	m_pMtcSync = NULL;
60
61}	// CMidiInQ::CMidiInQ()
62
63
64CMidiInQ::~CMidiInQ()
65{
66
67	Cleanup();
68
69}	// CMidiInQ::~CMidiInQ()
70
71
72//============================================================================
73//
74// Init
75//
76//============================================================================
77
78ECHOSTATUS CMidiInQ::Init( CEchoGals *pEG )
79{
80	ECHO_DEBUGPRINTF(("CMidiInQ::Init\n"));
81
82	m_pEG = pEG;
83
84	m_dwFill = 0;
85	m_dwBufferSizeMask =  MIDI_IN_Q_SIZE - 1;	// MIDI_IN_Q_SIZE must be a power of 2
86
87	return ECHOSTATUS_OK;
88
89}	// Init
90
91
92//============================================================================
93//
94// Cleanup
95//
96//============================================================================
97
98void CMidiInQ::Cleanup()
99{
100	//
101	// Free the MIDI input buffer
102	//
103	if (m_pBuffer)
104	{
105		OsFreeNonPaged( m_pBuffer );
106		m_pBuffer = NULL;
107	}
108
109	//
110	// Free the MTC sync object if it exists
111	//
112	if (m_pMtcSync)
113	{
114		delete m_pMtcSync;
115		m_pMtcSync = NULL;
116	}
117}
118
119
120
121
122/****************************************************************************
123
124	Queue management - add and remove MIDI data
125
126 ****************************************************************************/
127
128//============================================================================
129//
130// GetMidi - get oldest MIDI input byte from the Q
131//
132//============================================================================
133
134ECHOSTATUS CMidiInQ::GetMidi
135(
136	ECHOGALS_MIDI_IN_CONTEXT	*pContext,
137	DWORD								&dwMidiByte,
138	LONGLONG							&llTimestamp
139)
140{
141	DWORD dwDrain;
142
143	if (NULL == m_pBuffer)
144		return ECHOSTATUS_CHANNEL_NOT_OPEN;
145
146	dwDrain = pContext->dwDrain;
147	if ( m_dwFill == dwDrain)
148		return ECHOSTATUS_NO_DATA;
149
150	dwMidiByte = m_pBuffer[ dwDrain ].dwMidi;
151	llTimestamp = m_pBuffer[ dwDrain ].llTimestamp;
152
153	//ECHO_DEBUGPRINTF( ("CMidiInQ::GetMidi 0x%lx\n", dwMidiByte) );
154
155	dwDrain++;
156	dwDrain &= m_dwBufferSizeMask;
157	pContext->dwDrain = dwDrain;
158
159	return ECHOSTATUS_OK;
160
161}	// ECHOSTATUS CMidiInQ::GetMidi
162
163
164//============================================================================
165//
166// AddMidi - add new MIDI input byte to the Q
167//
168//============================================================================
169
170ECHOSTATUS CMidiInQ::AddMidi
171(
172		DWORD		dwMidiByte,
173		LONGLONG	llTimestamp
174)
175{
176	DWORD dwFill;
177
178	//ECHO_DEBUGPRINTF( ("CMidiInQ::AddMidi 0x%lx\n", dwMidiByte) );
179
180	dwFill = m_dwFill;
181	m_pBuffer[ dwFill ].dwMidi = dwMidiByte;
182	m_pBuffer[ dwFill ].llTimestamp = llTimestamp;
183
184	dwFill++;
185	dwFill &= m_dwBufferSizeMask;
186	m_dwFill = dwFill;
187
188	return ECHOSTATUS_OK;
189
190}	// ECHOSTATUS CMidiInQ::AddMidi
191
192
193//============================================================================
194//
195// Reset
196//
197//============================================================================
198
199void CMidiInQ::Reset(ECHOGALS_MIDI_IN_CONTEXT *pContext)
200{
201	pContext->dwDrain = m_dwFill;
202
203}	// void CMidiInQ::Reset()
204
205
206
207
208/****************************************************************************
209
210	Arm and disarm
211
212 ****************************************************************************/
213
214
215//============================================================================
216//
217// Arm - resets the Q and enables the Q to start receiving MIDI input data
218//
219//============================================================================
220
221ECHOSTATUS CMidiInQ::Arm(ECHOGALS_MIDI_IN_CONTEXT *pContext)
222{
223	ECHOSTATUS Status;
224
225	pContext->fOpen = FALSE;
226
227	//
228	// Return if there is no Echogals pointer
229	//
230	if (NULL == m_pEG)
231	{
232		return ECHOSTATUS_CANT_OPEN;
233	}
234
235	//-------------------------------------------------------------------------
236	//
237	// Set up the MIDI input buffer
238	//
239	//-------------------------------------------------------------------------
240
241	if (NULL == m_pBuffer)
242	{
243		//
244		// Reset the buffer pointers
245		//
246		m_dwFill = 0;
247
248		//
249		// Allocate
250		//
251		Status = OsAllocateNonPaged( MIDI_IN_Q_SIZE * sizeof(MIDI_DATA),
252												(PPVOID) &m_pBuffer);
253		if (Status != ECHOSTATUS_OK)
254		{
255			ECHO_DEBUGPRINTF(("CMidiInQ::Arm - Could not allocate MIDI input buffer\n"));
256			return Status;
257		}
258	}
259
260	Reset(pContext);
261	m_dwNumClients++;
262
263	//-------------------------------------------------------------------------
264	//
265	// Enable MIDI input
266	//
267	//-------------------------------------------------------------------------
268
269	m_pEG->GetDspCommObject()->SetMidiOn( TRUE );
270
271	pContext->fOpen = TRUE;
272	return ECHOSTATUS_OK;
273
274} // Arm
275
276
277//============================================================================
278//
279// Disarm - surprisingly, does the opposite of Arm
280//
281//============================================================================
282
283ECHOSTATUS CMidiInQ::Disarm(ECHOGALS_MIDI_IN_CONTEXT *pContext)
284{
285	//
286	// Did this client open the MIDI input?
287	//
288	if (FALSE == pContext->fOpen)
289	{
290		ECHO_DEBUGPRINTF(("CMidiInQ::Disarm - trying to disarm with client that isn't open\n"));
291		return ECHOSTATUS_CHANNEL_NOT_OPEN;
292	}
293
294	pContext->fOpen = FALSE;
295
296	//
297	// Last client?
298	//
299	if (0 == m_dwNumClients)
300		return ECHOSTATUS_OK;
301
302	m_dwNumClients--;
303
304	if (0 == m_dwNumClients)
305	{
306		//
307		// Tell the DSP to disable MIDI input
308		//
309		if (m_pEG)
310			m_pEG->GetDspCommObject()->SetMidiOn( FALSE );
311
312		//
313		// Free the MIDI input buffer
314		//
315		if (m_pBuffer)
316		{
317			OsFreeNonPaged( m_pBuffer );
318			m_pBuffer = NULL;
319		}
320	}
321
322	return ECHOSTATUS_OK;
323
324}	// Disarm
325
326
327//============================================================================
328//
329// ArmMtcSync - turns on MIDI time code sync
330//
331//============================================================================
332
333ECHOSTATUS CMidiInQ::ArmMtcSync()
334{
335	if (NULL == m_pEG)
336		return ECHOSTATUS_DSP_DEAD;
337
338	if (NULL != m_pMtcSync)
339		return ECHOSTATUS_OK;
340
341	//
342	// Create the MTC sync object
343	//
344	m_pMtcSync = new CMtcSync( m_pEG );
345	if (NULL == m_pMtcSync)
346		return ECHOSTATUS_NO_MEM;
347
348	//
349	// Tell the DSP to enable MIDI input
350	//
351	m_pEG->GetDspCommObject()->SetMidiOn( TRUE );
352
353	return ECHOSTATUS_OK;
354
355}	// ArmMtcSync
356
357
358//============================================================================
359//
360// DisarmMtcSync - turn off MIDI time code sync
361//
362//============================================================================
363
364ECHOSTATUS CMidiInQ::DisarmMtcSync()
365{
366	if (NULL == m_pMtcSync)
367		return ECHOSTATUS_OK;
368
369	if (NULL == m_pEG)
370		return ECHOSTATUS_DSP_DEAD;
371
372	//
373	// Tell the DSP to disable MIDI input
374	//
375	m_pEG->GetDspCommObject()->SetMidiOn( FALSE );
376
377	//
378	// Free m_pMtcSync
379	//
380	CMtcSync *pTemp;	// Use temp variable to avoid killing the object
381							// while the ISR is using it
382
383	pTemp = m_pMtcSync;
384	m_pMtcSync = NULL;
385	delete pTemp;
386
387	return ECHOSTATUS_OK;
388
389}	// DisarmMtcSync
390
391
392
393
394/****************************************************************************
395
396	Detect MIDI input activity - see if the driver has recently received
397	any MIDI input
398
399 ****************************************************************************/
400
401BOOL CMidiInQ::IsActive()
402{
403	ULONGLONG	ullCurTime,ullDelta;
404
405	//
406	// See if anything has happened recently
407	//
408	m_pEG->m_pOsSupport->OsGetSystemTime( &ullCurTime );
409	ullDelta = ullCurTime - m_ullLastActivityTime;
410
411	if (ullDelta > MIDI_ACTIVITY_TIMEOUT_USEC)
412		return FALSE;
413
414	return TRUE;
415
416}	// IsActive
417
418
419
420
421/****************************************************************************
422
423	MIDI time code
424
425 ****************************************************************************/
426
427//===========================================================================
428//
429// Get and set the base MTC sample rate
430//
431//===========================================================================
432
433
434ECHOSTATUS CMidiInQ::GetMtcBaseRate(DWORD *pdwBaseRate)
435{
436	ECHOSTATUS Status;
437
438	if (m_pMtcSync)
439	{
440		*pdwBaseRate = m_pMtcSync->m_dwBaseSampleRate;
441		Status = ECHOSTATUS_OK;
442	}
443	else
444	{
445		*pdwBaseRate = 0;
446		Status = ECHOSTATUS_NO_DATA;
447	}
448
449	return Status;
450
451}	// GetMtcBaseRate
452
453
454ECHOSTATUS CMidiInQ::SetMtcBaseRate(DWORD dwBaseRate)
455{
456	ECHOSTATUS Status;
457
458	if (m_pMtcSync)
459	{
460		m_pMtcSync->m_dwBaseSampleRate = dwBaseRate;
461		Status = ECHOSTATUS_OK;
462	}
463	else
464	{
465		Status = ECHOSTATUS_NO_DATA;
466	}
467
468	return Status;
469
470}	// SetMtcBaseRate
471
472
473//===========================================================================
474//
475// Run the state machine for MIDI input data
476//
477// You need this state machine to parse the incoming
478// MIDI data stream.  Every time the DSP sees a 0xF1 byte come in,
479// it adds the DSP sample position to the MIDI data stream.
480// The DSP sample position is represented as a 32 bit unsigned
481// value, with the high 16 bits first, followed by the low 16 bits.
482//
483// The following logic parses the incoming MIDI data.
484//
485//===========================================================================
486
487DWORD CMidiInQ::MtcProcessData( DWORD dwMidiData )
488{
489	DWORD dwRval;
490
491	dwRval = 0;
492
493	switch ( m_wMtcState )
494	{
495
496		case MIDI_IN_STATE_NORMAL :
497
498			if ( dwMidiData == 0xF1L )
499			{
500				m_wMtcState = MIDI_IN_STATE_TS_HIGH;
501			}
502			break;
503
504
505		case MIDI_IN_STATE_TS_HIGH :
506
507			if (m_pMtcSync)
508				m_pMtcSync->StoreTimestampHigh( dwMidiData );
509
510			m_wMtcState = MIDI_IN_STATE_TS_LOW;
511			dwRval = MIDI_IN_SKIP_DATA;
512			break;
513
514
515		case MIDI_IN_STATE_TS_LOW :
516
517			if (m_pMtcSync)
518				m_pMtcSync->StoreTimestampLow( dwMidiData );
519
520			m_wMtcState = MIDI_IN_STATE_F1_DATA;
521			dwRval = MIDI_IN_SKIP_DATA;
522			break;
523
524
525		case MIDI_IN_STATE_F1_DATA :
526
527			if (m_pMtcSync)
528				m_pMtcSync->StoreMtcData( dwMidiData );
529
530			m_wMtcState = MIDI_IN_STATE_NORMAL;
531			break;
532
533	}
534
535	return dwRval;
536
537}	// DWORD CMidiInQ::MtcProcessData
538
539
540//===========================================================================
541//
542// ServiceMtcSync
543//
544//===========================================================================
545
546void CMidiInQ::ServiceMtcSync()
547{
548	if (m_pMtcSync)
549		m_pMtcSync->Sync();
550
551}	// ServiceMtcSync
552
553
554
555
556/****************************************************************************
557
558	Interrupt service
559
560 ****************************************************************************/
561
562ECHOSTATUS CMidiInQ::ServiceIrq()
563{
564	DWORD				dwMidiCount;
565	WORD				wIndex;
566	ECHOSTATUS 		Status;
567	CDspCommObject	*pDCO;
568	LONGLONG			llTimestamp;
569
570	//
571	// Store the time for the activity detector
572	//
573	m_pEG->m_pOsSupport->OsGetSystemTime( &m_ullLastActivityTime );
574
575	//
576	// Get the MIDI count
577	//
578	pDCO = m_pEG->GetDspCommObject();
579	pDCO->ReadMidi( 0, dwMidiCount );	  // The count is at index 0
580
581#ifdef ECHO_DEBUG
582	//ECHO_DEBUGPRINTF( ("\tMIDI interrupt (%ld MIDI bytes)\n", dwMidiCount) );
583	if ( dwMidiCount == 0 )
584	{
585		ECHO_DEBUGBREAK();
586	}
587#endif
588
589	//
590	// Get the timestamp
591	//
592	llTimestamp = m_pEG->m_pOsSupport->GetMidiInTimestamp();
593
594	//
595	// Get the MIDI data from the comm page
596	//
597	wIndex = 1;		// First MIDI byte is at index 1
598	while ( dwMidiCount-- > 0 )
599	{
600		DWORD	dwMidiByte;
601
602		//
603		// Get the MIDI byte
604		//
605		Status = pDCO->ReadMidi( wIndex++, dwMidiByte );
606		if ( ECHOSTATUS_OK != Status )
607		{
608			ECHO_DEBUGPRINTF(("Failed on ReadMidi\n"));
609			ECHO_DEBUGBREAK();	// should never happen...
610			break;
611		}
612
613		//
614		// Parse the incoming MIDI stream.  The incoming MIDI data consists
615		// of MIDI bytes and timestamps for the MIDI time code 0xF1 bytes.
616		// MtcProcessData is a little state machine that parses the stream.
617		//
618		// If you get MIDI_IN_SKIP_DATA back, then this is a timestamp byte,
619		// not a MIDI byte, so don't store it in the MIDI input buffer.
620		//
621		if ( MIDI_IN_SKIP_DATA == MtcProcessData( dwMidiByte ) )
622			continue;
623
624		//
625		// Only store the MIDI data if there is at least one
626		// client
627		//
628		if (0 != m_dwNumClients)
629		{
630			//
631			// Stash the MIDI data and check for overflow
632			//
633			if ( ECHOSTATUS_BUFFER_OVERFLOW == AddMidi( dwMidiByte, llTimestamp ) )
634			{
635				break;
636			}
637
638		}
639	}		// while there is more MIDI data to read
640
641	return ECHOSTATUS_OK;
642
643} // ServiceIrq
644
645
646// *** CMidiInQ.cpp ***
647