xref: /haiku/src/add-ons/kernel/drivers/audio/echo/generic/CMidiInQ.cpp (revision 626bc4bee107897c38c596c3440cf0a74b4b9c40)
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 
CMidiInQ()50 CMidiInQ::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 
~CMidiInQ()64 CMidiInQ::~CMidiInQ()
65 {
66 
67 	Cleanup();
68 
69 }	// CMidiInQ::~CMidiInQ()
70 
71 
72 //============================================================================
73 //
74 // Init
75 //
76 //============================================================================
77 
Init(CEchoGals * pEG)78 ECHOSTATUS 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 
Cleanup()98 void 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 
GetMidi(ECHOGALS_MIDI_IN_CONTEXT * pContext,DWORD & dwMidiByte,LONGLONG & llTimestamp)134 ECHOSTATUS 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 
AddMidi(DWORD dwMidiByte,LONGLONG llTimestamp)170 ECHOSTATUS 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 
Reset(ECHOGALS_MIDI_IN_CONTEXT * pContext)199 void 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 
Arm(ECHOGALS_MIDI_IN_CONTEXT * pContext)221 ECHOSTATUS 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 
Disarm(ECHOGALS_MIDI_IN_CONTEXT * pContext)283 ECHOSTATUS 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 
ArmMtcSync()333 ECHOSTATUS 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 
DisarmMtcSync()364 ECHOSTATUS 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 
IsActive()401 BOOL 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 
GetMtcBaseRate(DWORD * pdwBaseRate)434 ECHOSTATUS 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 
SetMtcBaseRate(DWORD dwBaseRate)454 ECHOSTATUS 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 
MtcProcessData(DWORD dwMidiData)487 DWORD 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 
ServiceMtcSync()546 void 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 
ServiceIrq()562 ECHOSTATUS 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