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