1 /*----------------------------------------------------------------------------
2 *
3 * File:
4 * eas_smf.c
5 *
6 * Contents and purpose:
7 * SMF Type 0 and 1 File Parser
8 *
9 * For SMF timebase analysis, see "MIDI Sequencer Analysis.xls".
10 *
11 * Copyright Sonic Network Inc. 2005
12
13 * Licensed under the Apache License, Version 2.0 (the "License");
14 * you may not use this file except in compliance with the License.
15 * You may obtain a copy of the License at
16 *
17 * http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS,
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 * See the License for the specific language governing permissions and
23 * limitations under the License.
24 *
25 *----------------------------------------------------------------------------
26 * Revision Control:
27 * $Revision: 803 $
28 * $Date: 2007-08-01 09:57:00 -0700 (Wed, 01 Aug 2007) $
29 *----------------------------------------------------------------------------
30 */
31
32 #define LOG_TAG "Sonivox"
33 #include "log/log.h"
34
35 #include "eas_data.h"
36 #include "eas_miditypes.h"
37 #include "eas_parser.h"
38 #include "eas_report.h"
39 #include "eas_host.h"
40 #include "eas_midi.h"
41 #include "eas_config.h"
42 #include "eas_vm_protos.h"
43 #include "eas_smfdata.h"
44 #include "eas_smf.h"
45
46 #ifdef JET_INTERFACE
47 #include "jet_data.h"
48 #endif
49
50 //3 dls: The timebase for this module is adequate to keep MIDI and
51 //3 digital audio synchronized for only a few minutes. It should be
52 //3 sufficient for most mobile applications. If better accuracy is
53 //3 required, more fractional bits should be added to the timebase.
54
55 static const EAS_U8 smfHeader[] = { 'M', 'T', 'h', 'd' };
56
57 /* local prototypes */
58 static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData);
59 static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream);
60 static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode);
61 static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode);
62 static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream);
63 static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks);
64
65
66 /*----------------------------------------------------------------------------
67 *
68 * SMF_Parser
69 *
70 * This structure contains the functional interface for the SMF parser
71 *----------------------------------------------------------------------------
72 */
73 const S_FILE_PARSER_INTERFACE EAS_SMF_Parser =
74 {
75 SMF_CheckFileType,
76 SMF_Prepare,
77 SMF_Time,
78 SMF_Event,
79 SMF_State,
80 SMF_Close,
81 SMF_Reset,
82 #ifdef JET_INTERFACE
83 SMF_Pause,
84 SMF_Resume,
85 #else
86 NULL,
87 NULL,
88 #endif
89 NULL,
90 SMF_SetData,
91 SMF_GetData,
92 NULL
93 };
94
95 /*----------------------------------------------------------------------------
96 * SMF_CheckFileType()
97 *----------------------------------------------------------------------------
98 * Purpose:
99 * Check the file type to see if we can parse it
100 *
101 * Inputs:
102 * pEASData - pointer to overall EAS data structure
103 * handle - pointer to file handle
104 *
105 * Outputs:
106 *
107 *
108 * Side Effects:
109 *
110 *----------------------------------------------------------------------------
111 */
SMF_CheckFileType(S_EAS_DATA * pEASData,EAS_FILE_HANDLE fileHandle,EAS_VOID_PTR * ppHandle,EAS_I32 offset)112 EAS_RESULT SMF_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset)
113 {
114 S_SMF_DATA* pSMFData;
115 EAS_RESULT result;
116
117 /* seek to starting offset - usually 0 */
118 *ppHandle = NULL;
119 if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset)) != EAS_SUCCESS)
120 return result;
121
122 #ifdef FILE_HEADER_SEARCH
123 /* search through file for header - slow method */
124 if (pEASData->searchHeaderFlag)
125 {
126 result = EAS_SearchFile(pEASData, fileHandle, smfHeader, sizeof(smfHeader), &offset);
127 if (result != EAS_SUCCESS)
128 return (result == EAS_EOF) ? EAS_SUCCESS : result;
129 }
130
131 /* read the first 4 bytes of the file - quick method */
132 else
133 #endif
134 {
135 EAS_U8 header[4];
136 EAS_I32 count;
137 if ((result = EAS_HWReadFile(pEASData->hwInstData, fileHandle, header, sizeof(header), &count)) != EAS_SUCCESS)
138 return result;
139
140 /* check for 'MThd' - If no match then return SUCCESS with NULL handle
141 * to indicate not an SMF file. */
142 if ((header[0] != 'M') || (header[1] != 'T') || (header[2] != 'h') || (header[3] != 'd'))
143 return EAS_SUCCESS;
144 }
145
146 /* check for static memory allocation */
147 if (pEASData->staticMemoryModel)
148 pSMFData = EAS_CMEnumData(EAS_CM_SMF_DATA);
149 else
150 {
151 pSMFData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_SMF_DATA));
152 EAS_HWMemSet((void *)pSMFData,0, sizeof(S_SMF_DATA));
153 }
154 if (!pSMFData)
155 return EAS_ERROR_MALLOC_FAILED;
156
157 /* initialize some critical data */
158 pSMFData->fileHandle = fileHandle;
159 pSMFData->fileOffset = offset;
160 pSMFData->pSynth = NULL;
161 pSMFData->time = 0;
162 pSMFData->state = EAS_STATE_OPEN;
163 *ppHandle = pSMFData;
164
165 return EAS_SUCCESS;
166 }
167
168 /*----------------------------------------------------------------------------
169 * SMF_Prepare()
170 *----------------------------------------------------------------------------
171 * Purpose:
172 * Prepare to parse the file. Allocates instance data (or uses static allocation for
173 * static memory model).
174 *
175 * Inputs:
176 * pEASData - pointer to overall EAS data structure
177 * handle - pointer to file handle
178 *
179 * Outputs:
180 *
181 *
182 * Side Effects:
183 *
184 *----------------------------------------------------------------------------
185 */
SMF_Prepare(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData)186 EAS_RESULT SMF_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
187 {
188 S_SMF_DATA* pSMFData;
189 EAS_RESULT result;
190
191 /* check for valid state */
192 pSMFData = (S_SMF_DATA *) pInstData;
193 if (pSMFData->state != EAS_STATE_OPEN)
194 return EAS_ERROR_NOT_VALID_IN_THIS_STATE;
195
196 /* instantiate a synthesizer */
197 if ((result = VMInitMIDI(pEASData, &pSMFData->pSynth)) != EAS_SUCCESS)
198 {
199 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI returned %d\n", result); */ }
200 return result;
201 }
202
203 /* parse the file header and setup the individual stream parsers */
204 if ((result = SMF_ParseHeader(pEASData->hwInstData, pSMFData)) != EAS_SUCCESS)
205 return result;
206
207 /* ready to play */
208 pSMFData->state = EAS_STATE_READY;
209 return EAS_SUCCESS;
210 }
211
212 /*----------------------------------------------------------------------------
213 * SMF_Time()
214 *----------------------------------------------------------------------------
215 * Purpose:
216 * Returns the time of the next event in msecs
217 *
218 * Inputs:
219 * pEASData - pointer to overall EAS data structure
220 * handle - pointer to file handle
221 * pTime - pointer to variable to hold time of next event (in msecs)
222 *
223 * Outputs:
224 *
225 *
226 * Side Effects:
227 *
228 *----------------------------------------------------------------------------
229 */
230 /*lint -esym(715, pEASData) reserved for future use */
SMF_Time(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData,EAS_U32 * pTime)231 EAS_RESULT SMF_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime)
232 {
233 S_SMF_DATA *pSMFData;
234
235 pSMFData = (S_SMF_DATA*) pInstData;
236
237 /* sanity check */
238 #ifdef _CHECKED_BUILD
239 if (pSMFData->state == EAS_STATE_STOPPED)
240 {
241 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Can't ask for time on a stopped stream\n"); */ }
242 }
243
244 if (pSMFData->nextStream == NULL)
245 {
246 { /* dpp: EAS_ReportEx( _EAS_SEVERITY_ERROR, "no is NULL\n"); */ }
247 }
248 #endif
249
250 #if 0
251 /* return time in milliseconds */
252 /* if chase mode, lie about time */
253 if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
254 *pTime = 0;
255
256 else
257 #endif
258
259 /*lint -e{704} use shift instead of division */
260 *pTime = pSMFData->time >> 8;
261
262 *pTime = pSMFData->time >> 8;
263 return EAS_SUCCESS;
264 }
265
266 /*----------------------------------------------------------------------------
267 * SMF_Event()
268 *----------------------------------------------------------------------------
269 * Purpose:
270 * Parse the next event in the file
271 *
272 * Inputs:
273 * pEASData - pointer to overall EAS data structure
274 * handle - pointer to file handle
275 *
276 * Outputs:
277 *
278 *
279 * Side Effects:
280 *
281 *----------------------------------------------------------------------------
282 */
SMF_Event(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData,EAS_INT parserMode)283 EAS_RESULT SMF_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode)
284 {
285 S_SMF_DATA* pSMFData;
286 EAS_RESULT result;
287 EAS_I32 i;
288 EAS_U32 ticks;
289 EAS_U32 temp;
290
291 /* establish pointer to instance data */
292 pSMFData = (S_SMF_DATA*) pInstData;
293 if (pSMFData->state >= EAS_STATE_OPEN)
294 return EAS_SUCCESS;
295
296 if (!pSMFData->nextStream) {
297 return EAS_ERROR_FILE_FORMAT;
298 }
299
300
301 /* get current ticks */
302 ticks = pSMFData->nextStream->ticks;
303
304 /* assume that an error occurred */
305 pSMFData->state = EAS_STATE_ERROR;
306
307 #ifdef JET_INTERFACE
308 /* if JET has track muted, set parser mode to mute */
309 if (pSMFData->nextStream->midiStream.jetData & MIDI_FLAGS_JET_MUTE)
310 parserMode = eParserModeMute;
311 #endif
312
313 /* parse the next event from all the streams */
314 if ((result = SMF_ParseEvent(pEASData, pSMFData, pSMFData->nextStream, parserMode)) != EAS_SUCCESS)
315 {
316 /* check for unexpected end-of-file */
317 if (result != EAS_EOF)
318 return result;
319
320 /* indicate end of track for this stream */
321 pSMFData->nextStream->ticks = SMF_END_OF_TRACK;
322 }
323
324 /* get next delta time, unless already at end of track */
325 else if (pSMFData->nextStream->ticks != SMF_END_OF_TRACK)
326 {
327 if ((result = SMF_GetDeltaTime(pEASData->hwInstData, pSMFData->nextStream)) != EAS_SUCCESS)
328 {
329 /* check for unexpected end-of-file */
330 if (result != EAS_EOF)
331 return result;
332
333 /* indicate end of track for this stream */
334 pSMFData->nextStream->ticks = SMF_END_OF_TRACK;
335 }
336
337 /* if zero delta to next event, stay with this stream */
338 else if (pSMFData->nextStream->ticks == ticks)
339 {
340 pSMFData->state = EAS_STATE_PLAY;
341 return EAS_SUCCESS;
342 }
343 }
344
345 /* find next event in all streams */
346 temp = 0x7ffffff;
347 pSMFData->nextStream = NULL;
348 for (i = 0; i < pSMFData->numStreams; i++)
349 {
350 if (pSMFData->streams[i].ticks < temp)
351 {
352 temp = pSMFData->streams[i].ticks;
353 pSMFData->nextStream = &pSMFData->streams[i];
354 }
355 }
356
357 /* are there any more events to parse? */
358 if (pSMFData->nextStream)
359 {
360 pSMFData->state = EAS_STATE_PLAY;
361
362 /* update the time of the next event */
363 SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks - ticks);
364 }
365 else
366 {
367 pSMFData->state = EAS_STATE_STOPPING;
368 VMReleaseAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth);
369 }
370
371 return EAS_SUCCESS;
372 }
373
374 /*----------------------------------------------------------------------------
375 * SMF_State()
376 *----------------------------------------------------------------------------
377 * Purpose:
378 * Returns the current state of the stream
379 *
380 * Inputs:
381 * pEASData - pointer to overall EAS data structure
382 * handle - pointer to file handle
383 * pState - pointer to variable to store state
384 *
385 * Outputs:
386 *
387 *
388 * Side Effects:
389 *
390 *----------------------------------------------------------------------------
391 */
392 /*lint -esym(715, pEASData) reserved for future use */
SMF_State(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData,EAS_I32 * pState)393 EAS_RESULT SMF_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 *pState)
394 {
395 S_SMF_DATA* pSMFData;
396
397 /* establish pointer to instance data */
398 pSMFData = (S_SMF_DATA*) pInstData;
399
400 /* if stopping, check to see if synth voices are active */
401 if (pSMFData->state == EAS_STATE_STOPPING)
402 {
403 if (VMActiveVoices(pSMFData->pSynth) == 0)
404 pSMFData->state = EAS_STATE_STOPPED;
405 }
406
407 if (pSMFData->state == EAS_STATE_PAUSING)
408 {
409 if (VMActiveVoices(pSMFData->pSynth) == 0)
410 pSMFData->state = EAS_STATE_PAUSED;
411 }
412
413 /* return current state */
414 *pState = pSMFData->state;
415 return EAS_SUCCESS;
416 }
417
418 /*----------------------------------------------------------------------------
419 * SMF_Close()
420 *----------------------------------------------------------------------------
421 * Purpose:
422 * Close the file and clean up
423 *
424 * Inputs:
425 * pEASData - pointer to overall EAS data structure
426 * handle - pointer to file handle
427 *
428 * Outputs:
429 *
430 *
431 * Side Effects:
432 *
433 *----------------------------------------------------------------------------
434 */
SMF_Close(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData)435 EAS_RESULT SMF_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
436 {
437 S_SMF_DATA* pSMFData;
438 EAS_I32 i;
439 EAS_RESULT result;
440
441 pSMFData = (S_SMF_DATA*) pInstData;
442
443 /* close all the streams */
444 for (i = 0; i < pSMFData->numStreams; i++)
445 {
446 if (pSMFData->streams[i].fileHandle != NULL)
447 {
448 if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->streams[i].fileHandle)) != EAS_SUCCESS)
449 return result;
450 }
451 }
452 if (pSMFData->fileHandle != NULL)
453 if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->fileHandle)) != EAS_SUCCESS)
454 return result;
455
456 /* free the synth */
457 if (pSMFData->pSynth != NULL)
458 VMMIDIShutdown(pEASData, pSMFData->pSynth);
459
460 /* if using dynamic memory, free it */
461 if (!pEASData->staticMemoryModel)
462 {
463 if (pSMFData->streams)
464 EAS_HWFree(pEASData->hwInstData, pSMFData->streams);
465
466 /* free the instance data */
467 EAS_HWFree(pEASData->hwInstData, pSMFData);
468 }
469
470 return EAS_SUCCESS;
471 }
472
473 /*----------------------------------------------------------------------------
474 * SMF_Reset()
475 *----------------------------------------------------------------------------
476 * Purpose:
477 * Reset the sequencer. Used for locating backwards in the file.
478 *
479 * Inputs:
480 * pEASData - pointer to overall EAS data structure
481 * handle - pointer to file handle
482 *
483 * Outputs:
484 *
485 *
486 * Side Effects:
487 *
488 *----------------------------------------------------------------------------
489 */
SMF_Reset(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData)490 EAS_RESULT SMF_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
491 {
492 S_SMF_DATA* pSMFData;
493 EAS_I32 i;
494 EAS_RESULT result;
495 EAS_U32 ticks;
496
497 pSMFData = (S_SMF_DATA*) pInstData;
498
499 /* reset time to zero */
500 pSMFData->time = 0;
501
502 /* reset the synth */
503 VMReset(pEASData->pVoiceMgr, pSMFData->pSynth, EAS_TRUE);
504
505 /* find the start of each track */
506 ticks = 0x7fffffffL;
507 pSMFData->nextStream = NULL;
508 for (i = 0; i < pSMFData->numStreams; i++)
509 {
510
511 /* reset file position to first byte of data in track */
512 if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFData->streams[i].fileHandle, pSMFData->streams[i].startFilePos)) != EAS_SUCCESS)
513 return result;
514
515 /* initalize some data */
516 pSMFData->streams[i].ticks = 0;
517
518 /* initalize the MIDI parser data */
519 EAS_InitMIDIStream(&pSMFData->streams[i].midiStream);
520
521 /* parse the first delta time in each stream */
522 if ((result = SMF_GetDeltaTime(pEASData->hwInstData,&pSMFData->streams[i])) != EAS_SUCCESS)
523 return result;
524 if (pSMFData->streams[i].ticks < ticks)
525 {
526 ticks = pSMFData->streams[i].ticks;
527 pSMFData->nextStream = &pSMFData->streams[i];
528 }
529 }
530
531
532 pSMFData->state = EAS_STATE_READY;
533 return EAS_SUCCESS;
534 }
535
536 #ifdef JET_INTERFACE
537 /*----------------------------------------------------------------------------
538 * SMF_Pause()
539 *----------------------------------------------------------------------------
540 * Purpose:
541 * Pauses the sequencer. Mutes all voices and sets state to pause.
542 *
543 * Inputs:
544 * pEASData - pointer to overall EAS data structure
545 * handle - pointer to file handle
546 *
547 * Outputs:
548 *
549 *
550 * Side Effects:
551 *
552 *----------------------------------------------------------------------------
553 */
SMF_Pause(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData)554 EAS_RESULT SMF_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
555 {
556 S_SMF_DATA *pSMFData;
557
558 /* can't pause a stopped stream */
559 pSMFData = (S_SMF_DATA*) pInstData;
560 if (pSMFData->state == EAS_STATE_STOPPED)
561 return EAS_ERROR_ALREADY_STOPPED;
562
563 /* mute the synthesizer */
564 VMMuteAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth);
565 pSMFData->state = EAS_STATE_PAUSING;
566 return EAS_SUCCESS;
567 }
568
569 /*----------------------------------------------------------------------------
570 * SMF_Resume()
571 *----------------------------------------------------------------------------
572 * Purpose:
573 * Resume playing after a pause, sets state back to playing.
574 *
575 * Inputs:
576 * pEASData - pointer to overall EAS data structure
577 * handle - pointer to file handle
578 *
579 * Outputs:
580 *
581 *
582 * Side Effects:
583 *
584 *----------------------------------------------------------------------------
585 */
586 /*lint -esym(715, pEASData) reserved for future use */
SMF_Resume(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData)587 EAS_RESULT SMF_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
588 {
589 S_SMF_DATA *pSMFData;
590
591 /* can't resume a stopped stream */
592 pSMFData = (S_SMF_DATA*) pInstData;
593 if (pSMFData->state == EAS_STATE_STOPPED)
594 return EAS_ERROR_ALREADY_STOPPED;
595
596 /* nothing to do but resume playback */
597 pSMFData->state = EAS_STATE_PLAY;
598 return EAS_SUCCESS;
599 }
600 #endif
601
602 /*----------------------------------------------------------------------------
603 * SMF_SetData()
604 *----------------------------------------------------------------------------
605 * Purpose:
606 * Sets parser parameters
607 *
608 * Inputs:
609 * pEASData - pointer to overall EAS data structure
610 * handle - pointer to file handle
611 *
612 * Outputs:
613 *
614 *
615 * Side Effects:
616 *
617 *----------------------------------------------------------------------------
618 */
619 /*lint -esym(715, pEASData) reserved for future use */
SMF_SetData(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData,EAS_I32 param,EAS_I32 value)620 EAS_RESULT SMF_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value)
621 {
622 S_SMF_DATA *pSMFData;
623
624 pSMFData = (S_SMF_DATA*) pInstData;
625 switch (param)
626 {
627
628 /* set metadata callback */
629 case PARSER_DATA_METADATA_CB:
630 EAS_HWMemCpy(&pSMFData->metadata, (void*) value, sizeof(S_METADATA_CB));
631 break;
632
633 #ifdef JET_INTERFACE
634 /* set jet segment and track ID of all tracks for callback function */
635 case PARSER_DATA_JET_CB:
636 {
637 EAS_U32 i;
638 EAS_U32 bit = (EAS_U32) value;
639 bit = (bit << JET_EVENT_SEG_SHIFT) & JET_EVENT_SEG_MASK;
640 for (i = 0; i < pSMFData->numStreams; i++)
641 pSMFData->streams[i].midiStream.jetData =
642 (pSMFData->streams[i].midiStream.jetData &
643 ~(JET_EVENT_TRACK_MASK | JET_EVENT_SEG_MASK)) |
644 i << JET_EVENT_TRACK_SHIFT | bit | MIDI_FLAGS_JET_CB;
645 pSMFData->flags |= SMF_FLAGS_JET_STREAM;
646 }
647 break;
648
649 /* set state of all mute flags at once */
650 case PARSER_DATA_MUTE_FLAGS:
651 {
652 EAS_INT i;
653 EAS_U32 bit = (EAS_U32) value;
654 for (i = 0; i < pSMFData->numStreams; i++)
655 {
656 if (bit & 1)
657 pSMFData->streams[i].midiStream.jetData |= MIDI_FLAGS_JET_MUTE;
658 else
659 pSMFData->streams[i].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE;
660 bit >>= 1;
661 }
662 }
663 break;
664
665 /* set track mute */
666 case PARSER_DATA_SET_MUTE:
667 if (value < pSMFData->numStreams)
668 pSMFData->streams[value].midiStream.jetData |= MIDI_FLAGS_JET_MUTE;
669 else
670 return EAS_ERROR_PARAMETER_RANGE;
671 break;
672
673 /* clear track mute */
674 case PARSER_DATA_CLEAR_MUTE:
675 if (value < pSMFData->numStreams)
676 pSMFData->streams[value].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE;
677 else
678 return EAS_ERROR_PARAMETER_RANGE;
679 break;
680 #endif
681
682 default:
683 return EAS_ERROR_INVALID_PARAMETER;
684 }
685
686 return EAS_SUCCESS;
687 }
688
689 /*----------------------------------------------------------------------------
690 * SMF_GetData()
691 *----------------------------------------------------------------------------
692 * Purpose:
693 * Retrieves parser parameters
694 *
695 * Inputs:
696 * pEASData - pointer to overall EAS data structure
697 * handle - pointer to file handle
698 *
699 * Outputs:
700 *
701 *
702 * Side Effects:
703 *
704 *----------------------------------------------------------------------------
705 */
706 /*lint -esym(715, pEASData) reserved for future use */
SMF_GetData(S_EAS_DATA * pEASData,EAS_VOID_PTR pInstData,EAS_I32 param,EAS_I32 * pValue)707 EAS_RESULT SMF_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue)
708 {
709 S_SMF_DATA *pSMFData;
710
711 pSMFData = (S_SMF_DATA*) pInstData;
712 switch (param)
713 {
714 /* return file type */
715 case PARSER_DATA_FILE_TYPE:
716 if (pSMFData->numStreams == 1)
717 *pValue = EAS_FILE_SMF0;
718 else
719 *pValue = EAS_FILE_SMF1;
720 break;
721
722 /* now handled in eas_public.c */
723 #if 0
724 case PARSER_DATA_POLYPHONY:
725 if (pSMFData->pSynth)
726 VMGetPolyphony(pEASData->pVoiceMgr, pSMFData->pSynth, pValue);
727 else
728 return EAS_ERROR_NOT_VALID_IN_THIS_STATE;
729 break;
730
731 case PARSER_DATA_PRIORITY:
732 if (pSMFData->pSynth)
733 VMGetPriority(pEASData->pVoiceMgr, pSMFData->pSynth, pValue);
734 break;
735
736 /* set transposition */
737 case PARSER_DATA_TRANSPOSITION:
738 *pValue = pSMFData->transposition;
739 break;
740 #endif
741
742 case PARSER_DATA_SYNTH_HANDLE:
743 *pValue = (EAS_I32) pSMFData->pSynth;
744 break;
745
746 default:
747 return EAS_ERROR_INVALID_PARAMETER;
748 }
749
750 return EAS_SUCCESS;
751 }
752
753 /*----------------------------------------------------------------------------
754 * SMF_GetVarLenData()
755 *----------------------------------------------------------------------------
756 * Purpose:
757 * Reads a varible length quantity from an SMF file
758 *
759 * Inputs:
760 *
761 *
762 * Outputs:
763 *
764 *
765 * Side Effects:
766 *
767 *----------------------------------------------------------------------------
768 */
SMF_GetVarLenData(EAS_HW_DATA_HANDLE hwInstData,EAS_FILE_HANDLE fileHandle,EAS_U32 * pData)769 static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData)
770 {
771 EAS_RESULT result;
772 EAS_U32 data;
773 EAS_U8 c;
774
775 /* read until bit 7 is zero */
776 data = 0;
777 do
778 {
779 if ((result = EAS_HWGetByte(hwInstData, fileHandle,&c)) != EAS_SUCCESS)
780 return result;
781 data = (data << 7) | (c & 0x7f);
782 } while (c & 0x80);
783 *pData = data;
784 return EAS_SUCCESS;
785 }
786
787 /*----------------------------------------------------------------------------
788 * SMF_GetDeltaTime()
789 *----------------------------------------------------------------------------
790 * Purpose:
791 * Reads a varible length quantity from an SMF file
792 *
793 * Inputs:
794 *
795 *
796 * Outputs:
797 *
798 *
799 * Side Effects:
800 *
801 *----------------------------------------------------------------------------
802 */
SMF_GetDeltaTime(EAS_HW_DATA_HANDLE hwInstData,S_SMF_STREAM * pSMFStream)803 static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream)
804 {
805 EAS_RESULT result;
806 EAS_U32 ticks;
807
808 if ((result = SMF_GetVarLenData(hwInstData, pSMFStream->fileHandle, &ticks)) != EAS_SUCCESS)
809 return result;
810
811 /* number of ticks must not exceed 32-bits */
812 if (ticks > (UINT32_MAX - pSMFStream->ticks))
813 return EAS_ERROR_FILE_FORMAT;
814
815 pSMFStream->ticks += ticks;
816 return EAS_SUCCESS;
817 }
818
819 /*----------------------------------------------------------------------------
820 * SMF_ParseMetaEvent()
821 *----------------------------------------------------------------------------
822 * Purpose:
823 * Reads a varible length quantity from an SMF file
824 *
825 * Inputs:
826 *
827 *
828 * Outputs:
829 *
830 *
831 * Side Effects:
832 *
833 *----------------------------------------------------------------------------
834 */
SMF_ParseMetaEvent(S_EAS_DATA * pEASData,S_SMF_DATA * pSMFData,S_SMF_STREAM * pSMFStream)835 static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream)
836 {
837 EAS_RESULT result;
838 EAS_U32 len;
839 EAS_I32 pos;
840 EAS_U32 temp;
841 EAS_U8 c;
842
843 /* get the meta-event type */
844 if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
845 return result;
846
847 /* get the length */
848 if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS)
849 return result;
850
851 /* get the current file position so we can skip the event */
852 if ((result = EAS_HWFilePos(pEASData->hwInstData, pSMFStream->fileHandle, &pos)) != EAS_SUCCESS)
853 return result;
854
855 /* prevent a large unsigned length from being treated as a negative length */
856 if ((EAS_I32) len < 0) {
857 /* note that EAS_I32 is a long, which can be 64-bits on some computers */
858 ALOGE("%s() negative len = %ld", __func__, (long) len);
859 android_errorWriteLog(0x534e4554, "68953854");
860 return EAS_ERROR_FILE_FORMAT;
861 }
862 /* prevent numeric overflow caused by a very large len, assume pos > 0 */
863 const EAS_I32 EAS_I32_MAX = 0x7FFFFFFF;
864 if ((EAS_I32) len > (EAS_I32_MAX - pos)) {
865 ALOGE("%s() too large len = %ld", __func__, (long) len);
866 android_errorWriteLog(0x534e4554, "68953854");
867 return EAS_ERROR_FILE_FORMAT;
868 }
869
870 pos += (EAS_I32) len;
871
872 /* end of track? */
873 if (c == SMF_META_END_OF_TRACK)
874 {
875 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: end of track\n", c, len); */ }
876 pSMFStream->ticks = SMF_END_OF_TRACK;
877 }
878
879 /* tempo event? */
880 else if (c == SMF_META_TEMPO)
881 {
882 /* read the 3-byte timebase value */
883 temp = 0;
884 while (len)
885 {
886 len--;
887 if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
888 return result;
889 temp = (temp << 8) | c;
890 }
891 {
892 // pSMFData->tickConv = (EAS_U16) (((temp * 1024) / pSMFData->ppqn + 500) / 1000);
893 uint64_t temp64;
894 if (__builtin_mul_overflow(temp, 1024u, &temp64) ||
895 pSMFData->ppqn == 0 ||
896 (temp64 /= pSMFData->ppqn, false) ||
897 __builtin_add_overflow(temp64, 500, &temp64) ||
898 (temp64 /= 1000, false) ||
899 temp64 > 65535) {
900 pSMFData->tickConv = 65535;
901 } else {
902 pSMFData->tickConv = (EAS_U16) temp64;
903 }
904 }
905 pSMFData->flags |= SMF_FLAGS_HAS_TEMPO;
906 }
907
908 /* check for time signature - see iMelody spec V1.4 section 4.1.2.2.3.6 */
909 else if (c == SMF_META_TIME_SIGNATURE)
910 {
911 pSMFData->flags |= SMF_FLAGS_HAS_TIME_SIG;
912 }
913
914 /* if the host has registered a metadata callback return the metadata */
915 else if (pSMFData->metadata.callback)
916 {
917 EAS_I32 readLen;
918 E_EAS_METADATA_TYPE metaType;
919
920 metaType = EAS_METADATA_UNKNOWN;
921
922 /* only process title on the first track */
923 if (c == SMF_META_SEQTRK_NAME)
924 metaType = EAS_METADATA_TITLE;
925 else if (c == SMF_META_TEXT)
926 metaType = EAS_METADATA_TEXT;
927 else if (c == SMF_META_COPYRIGHT)
928 metaType = EAS_METADATA_COPYRIGHT;
929 else if (c == SMF_META_LYRIC)
930 metaType = EAS_METADATA_LYRIC;
931
932 if (metaType != EAS_METADATA_UNKNOWN)
933 {
934 readLen = pSMFData->metadata.bufferSize - 1;
935 if ((EAS_I32) len < readLen)
936 readLen = (EAS_I32) len;
937 if ((result = EAS_HWReadFile(pEASData->hwInstData, pSMFStream->fileHandle, pSMFData->metadata.buffer, readLen, &readLen)) != EAS_SUCCESS)
938 return result;
939 pSMFData->metadata.buffer[readLen] = 0;
940 pSMFData->metadata.callback(metaType, pSMFData->metadata.buffer, pSMFData->metadata.pUserData);
941 }
942 }
943
944 /* position file to next event - in case we ignored all or part of the meta-event */
945 if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFStream->fileHandle, pos)) != EAS_SUCCESS)
946 return result;
947
948 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: type=%02x, len=%d\n", c, len); */ }
949 return EAS_SUCCESS;
950 }
951
952 /*----------------------------------------------------------------------------
953 * SMF_ParseSysEx()
954 *----------------------------------------------------------------------------
955 * Purpose:
956 * Reads a varible length quantity from an SMF file
957 *
958 * Inputs:
959 *
960 *
961 * Outputs:
962 *
963 *
964 * Side Effects:
965 *
966 *----------------------------------------------------------------------------
967 */
SMF_ParseSysEx(S_EAS_DATA * pEASData,S_SMF_DATA * pSMFData,S_SMF_STREAM * pSMFStream,EAS_U8 f0,EAS_INT parserMode)968 static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode)
969 {
970 EAS_RESULT result;
971 EAS_U32 len;
972 EAS_U8 c;
973
974 /* get the length */
975 if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS)
976 return result;
977
978 /* start of SysEx message? */
979 if (f0 == 0xf0)
980 {
981 if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, f0, parserMode)) != EAS_SUCCESS)
982 return result;
983 }
984
985 /* feed the SysEx to the stream parser */
986 while (len)
987 {
988 len--;
989 if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
990 return result;
991 if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
992 return result;
993
994 /* check for GM system ON */
995 if (pSMFStream->midiStream.flags & MIDI_FLAG_GM_ON)
996 pSMFData->flags |= SMF_FLAGS_HAS_GM_ON;
997 }
998
999 return EAS_SUCCESS;
1000 }
1001
1002 /*----------------------------------------------------------------------------
1003 * SMF_ParseEvent()
1004 *----------------------------------------------------------------------------
1005 * Purpose:
1006 * Reads a varible length quantity from an SMF file
1007 *
1008 * Inputs:
1009 *
1010 *
1011 * Outputs:
1012 *
1013 *
1014 * Side Effects:
1015 *
1016 *----------------------------------------------------------------------------
1017 */
SMF_ParseEvent(S_EAS_DATA * pEASData,S_SMF_DATA * pSMFData,S_SMF_STREAM * pSMFStream,EAS_INT parserMode)1018 static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode)
1019 {
1020 EAS_RESULT result;
1021 EAS_U8 c;
1022
1023 /* get the event type */
1024 if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
1025 return result;
1026
1027 /* parse meta-event */
1028 if (c == 0xff)
1029 {
1030 if ((result = SMF_ParseMetaEvent(pEASData, pSMFData, pSMFStream)) != EAS_SUCCESS)
1031 return result;
1032 }
1033
1034 /* parse SysEx */
1035 else if ((c == 0xf0) || (c == 0xf7))
1036 {
1037 if ((result = SMF_ParseSysEx(pEASData, pSMFData, pSMFStream, c, parserMode)) != EAS_SUCCESS)
1038 return result;
1039 }
1040
1041 /* parse MIDI message */
1042 else
1043 {
1044 if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
1045 return result;
1046
1047 /* keep streaming data to the MIDI parser until the message is complete */
1048 while (pSMFStream->midiStream.pending)
1049 {
1050 if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
1051 return result;
1052 if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
1053 return result;
1054 }
1055
1056 }
1057
1058 /* chase mode logic */
1059 if (pSMFData->time == 0)
1060 {
1061 if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
1062 {
1063 if (pSMFStream->midiStream.flags & MIDI_FLAG_FIRST_NOTE)
1064 pSMFData->flags &= ~SMF_FLAGS_CHASE_MODE;
1065 }
1066 else if ((pSMFData->flags & SMF_FLAGS_SETUP_BAR) == SMF_FLAGS_SETUP_BAR)
1067 pSMFData->flags = (pSMFData->flags & ~SMF_FLAGS_SETUP_BAR) | SMF_FLAGS_CHASE_MODE;
1068 }
1069
1070 return EAS_SUCCESS;
1071 }
1072
1073 /*----------------------------------------------------------------------------
1074 * SMF_ParseHeader()
1075 *----------------------------------------------------------------------------
1076 * Purpose:
1077 * Parses the header of an SMF file, allocates memory the stream parsers and initializes the
1078 * stream parsers.
1079 *
1080 * Inputs:
1081 * pEASData - pointer to overall EAS data structure
1082 * pSMFData - pointer to parser instance data
1083 * fileHandle - file handle
1084 * fileOffset - offset in the file where the header data starts, usually 0
1085 *
1086 *
1087 * Outputs:
1088 *
1089 *
1090 * Side Effects:
1091 *
1092 *----------------------------------------------------------------------------
1093 */
1094 /*lint -e{801} we know that 'goto' is deprecated - but it's cleaner in this case */
SMF_ParseHeader(EAS_HW_DATA_HANDLE hwInstData,S_SMF_DATA * pSMFData)1095 EAS_RESULT SMF_ParseHeader (EAS_HW_DATA_HANDLE hwInstData, S_SMF_DATA *pSMFData)
1096 {
1097 EAS_RESULT result;
1098 EAS_I32 i;
1099 EAS_U16 division;
1100 EAS_U16 numStreams;
1101 EAS_U32 chunkSize;
1102 EAS_U32 chunkStart;
1103 EAS_U32 temp;
1104 EAS_U32 ticks;
1105
1106 /* explicitly set numStreams to 0. It will later be used by SMF_Close to
1107 * determine whether we have valid streams or not. */
1108 pSMFData->numStreams = 0;
1109
1110 /* rewind the file and find the end of the header chunk */
1111 if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_HEADER_SIZE)) != EAS_SUCCESS)
1112 goto ReadError;
1113 if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS)
1114 goto ReadError;
1115
1116 /* determine the number of tracks */
1117 if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_NUM_TRACKS)) != EAS_SUCCESS)
1118 goto ReadError;
1119 if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &numStreams, EAS_TRUE)) != EAS_SUCCESS)
1120 goto ReadError;
1121
1122 /* limit the number of tracks */
1123 if (numStreams > MAX_SMF_STREAMS)
1124 {
1125 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "SMF file contains %u tracks, playing %d tracks\n", numStreams, MAX_SMF_STREAMS); */ }
1126 numStreams = MAX_SMF_STREAMS;
1127 } else if (numStreams == 0)
1128 {
1129 /* avoid 0 sized allocation */
1130 return EAS_ERROR_PARAMETER_RANGE;
1131 }
1132
1133 /* get the time division */
1134 if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &division, EAS_TRUE)) != EAS_SUCCESS)
1135 goto ReadError;
1136
1137 /* setup default timebase for 120 bpm */
1138 pSMFData->ppqn = 192;
1139 if (!division || division & 0x8000)
1140 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"No support for SMPTE code timebase\n"); */ }
1141 else
1142 pSMFData->ppqn = (division & 0x7fff);
1143 pSMFData->tickConv = (EAS_U16) (((SMF_DEFAULT_TIMEBASE * 1024) / pSMFData->ppqn + 500) / 1000);
1144
1145 /* dynamic memory allocation, allocate memory for streams */
1146 if (pSMFData->streams == NULL)
1147 {
1148 pSMFData->streams = EAS_HWMalloc(hwInstData,sizeof(S_SMF_STREAM) * numStreams);
1149 if (pSMFData->streams == NULL)
1150 return EAS_ERROR_MALLOC_FAILED;
1151
1152 /* zero the memory to insure complete initialization */
1153 EAS_HWMemSet((void *)(pSMFData->streams), 0, sizeof(S_SMF_STREAM) * numStreams);
1154 }
1155 pSMFData->numStreams = numStreams;
1156
1157 /* find the start of each track */
1158 chunkStart = (EAS_U32) pSMFData->fileOffset;
1159 ticks = 0x7fffffffL;
1160 pSMFData->nextStream = NULL;
1161 for (i = 0; i < pSMFData->numStreams; i++)
1162 {
1163
1164 for (;;)
1165 {
1166
1167 /* calculate start of next chunk - checking for errors */
1168 temp = chunkStart + SMF_CHUNK_INFO_SIZE + chunkSize;
1169 if (temp <= chunkStart)
1170 {
1171 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Error in chunk size at offset %d\n", chunkStart); */ }
1172 return EAS_ERROR_FILE_FORMAT;
1173 }
1174 chunkStart = temp;
1175
1176 /* seek to the start of the next chunk */
1177 if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, (EAS_I32) chunkStart)) != EAS_SUCCESS)
1178 goto ReadError;
1179
1180 /* read the chunk identifier */
1181 if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &temp, EAS_TRUE)) != EAS_SUCCESS)
1182 goto ReadError;
1183
1184 /* read the chunk size */
1185 if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS)
1186 goto ReadError;
1187
1188 /* make sure this is an 'MTrk' chunk */
1189 if (temp == SMF_CHUNK_TYPE_TRACK)
1190 break;
1191
1192 { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Unexpected chunk type: 0x%08x\n", temp); */ }
1193 }
1194
1195 /* initalize some data */
1196 pSMFData->streams[i].ticks = 0;
1197 pSMFData->streams[i].fileHandle = pSMFData->fileHandle;
1198
1199 /* NULL the file handle so we don't try to close it twice */
1200 pSMFData->fileHandle = NULL;
1201
1202 /* save this file position as the start of the track */
1203 pSMFData->streams[i].startFilePos = (EAS_I32) chunkStart + SMF_CHUNK_INFO_SIZE;
1204
1205 /* initalize the MIDI parser data */
1206 EAS_InitMIDIStream(&pSMFData->streams[i].midiStream);
1207
1208 /* parse the first delta time in each stream */
1209 if ((result = SMF_GetDeltaTime(hwInstData, &pSMFData->streams[i])) != EAS_SUCCESS)
1210 goto ReadError;
1211
1212 if (pSMFData->streams[i].ticks < ticks)
1213 {
1214 ticks = pSMFData->streams[i].ticks;
1215 pSMFData->nextStream = &pSMFData->streams[i];
1216 }
1217
1218 /* more tracks to do, create a duplicate file handle */
1219 if (i < (pSMFData->numStreams - 1))
1220 {
1221 if ((result = EAS_HWDupHandle(hwInstData, pSMFData->streams[i].fileHandle, &pSMFData->fileHandle)) != EAS_SUCCESS)
1222 goto ReadError;
1223 }
1224 }
1225
1226 /* update the time of the next event */
1227 if (pSMFData->nextStream)
1228 SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks);
1229
1230 return EAS_SUCCESS;
1231
1232 /* ugly goto: but simpler than structured */
1233 ReadError:
1234 if (result == EAS_EOF)
1235 return EAS_ERROR_FILE_FORMAT;
1236 return result;
1237 }
1238
1239 /*----------------------------------------------------------------------------
1240 * SMF_UpdateTime()
1241 *----------------------------------------------------------------------------
1242 * Purpose:
1243 * Update the millisecond time base by converting the ticks into millieconds
1244 *
1245 * Inputs:
1246 *
1247 *
1248 * Outputs:
1249 *
1250 *
1251 * Side Effects:
1252 *
1253 *----------------------------------------------------------------------------
1254 */
SMF_UpdateTime(S_SMF_DATA * pSMFData,EAS_U32 ticks)1255 static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks)
1256 {
1257 EAS_U32 temp1, temp2;
1258
1259 if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
1260 return;
1261
1262 temp1 = (ticks >> 10) * pSMFData->tickConv;
1263 temp2 = (ticks & 0x3ff) * pSMFData->tickConv;
1264 pSMFData->time += (EAS_I32)((temp1 << 8) + (temp2 >> 2));
1265 }
1266
1267