| /*---------------------------------------------------------------------------- |
| * |
| * File: |
| * eas_smf.c |
| * |
| * Contents and purpose: |
| * SMF Type 0 and 1 File Parser |
| * |
| * For SMF timebase analysis, see "MIDI Sequencer Analysis.xls". |
| * |
| * Copyright Sonic Network Inc. 2005 |
| |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *---------------------------------------------------------------------------- |
| * Revision Control: |
| * $Revision: 803 $ |
| * $Date: 2007-08-01 09:57:00 -0700 (Wed, 01 Aug 2007) $ |
| *---------------------------------------------------------------------------- |
| */ |
| |
| #include "eas_data.h" |
| #include "eas_miditypes.h" |
| #include "eas_parser.h" |
| #include "eas_report.h" |
| #include "eas_host.h" |
| #include "eas_midi.h" |
| #include "eas_config.h" |
| #include "eas_vm_protos.h" |
| #include "eas_smfdata.h" |
| #include "eas_smf.h" |
| |
| #ifdef JET_INTERFACE |
| #include "jet_data.h" |
| #endif |
| |
| //3 dls: The timebase for this module is adequate to keep MIDI and |
| //3 digital audio synchronized for only a few minutes. It should be |
| //3 sufficient for most mobile applications. If better accuracy is |
| //3 required, more fractional bits should be added to the timebase. |
| |
| static const EAS_U8 smfHeader[] = { 'M', 'T', 'h', 'd' }; |
| |
| /* local prototypes */ |
| static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData); |
| static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream); |
| static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode); |
| static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode); |
| static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream); |
| static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks); |
| |
| |
| /*---------------------------------------------------------------------------- |
| * |
| * SMF_Parser |
| * |
| * This structure contains the functional interface for the SMF parser |
| *---------------------------------------------------------------------------- |
| */ |
| const S_FILE_PARSER_INTERFACE EAS_SMF_Parser = |
| { |
| SMF_CheckFileType, |
| SMF_Prepare, |
| SMF_Time, |
| SMF_Event, |
| SMF_State, |
| SMF_Close, |
| SMF_Reset, |
| SMF_Pause, |
| SMF_Resume, |
| NULL, |
| SMF_SetData, |
| SMF_GetData, |
| NULL |
| }; |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_CheckFileType() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Check the file type to see if we can parse it |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset) |
| { |
| S_SMF_DATA* pSMFData; |
| EAS_RESULT result; |
| |
| /* seek to starting offset - usually 0 */ |
| *ppHandle = NULL; |
| if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset)) != EAS_SUCCESS) |
| return result; |
| |
| /* search through file for header - slow method */ |
| if (pEASData->searchHeaderFlag) |
| { |
| result = EAS_SearchFile(pEASData, fileHandle, smfHeader, sizeof(smfHeader), &offset); |
| if (result != EAS_SUCCESS) |
| return (result == EAS_EOF) ? EAS_SUCCESS : result; |
| } |
| |
| /* read the first 4 bytes of the file - quick method */ |
| else { |
| EAS_U8 header[4]; |
| EAS_I32 count; |
| if ((result = EAS_HWReadFile(pEASData->hwInstData, fileHandle, header, sizeof(header), &count)) != EAS_SUCCESS) |
| return result; |
| |
| /* check for 'MTrk' - return if no match */ |
| if ((header[0] != 'M') || (header[1] != 'T') || (header[2] != 'h') || (header[3] != 'd')) |
| return EAS_SUCCESS; |
| } |
| |
| /* check for static memory allocation */ |
| if (pEASData->staticMemoryModel) |
| pSMFData = EAS_CMEnumData(EAS_CM_SMF_DATA); |
| else |
| { |
| pSMFData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_SMF_DATA)); |
| EAS_HWMemSet((void *)pSMFData,0, sizeof(S_SMF_DATA)); |
| } |
| if (!pSMFData) |
| return EAS_ERROR_MALLOC_FAILED; |
| |
| /* initialize some critical data */ |
| pSMFData->fileHandle = fileHandle; |
| pSMFData->fileOffset = offset; |
| pSMFData->pSynth = NULL; |
| pSMFData->time = 0; |
| pSMFData->state = EAS_STATE_OPEN; |
| *ppHandle = pSMFData; |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Prepare() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Prepare to parse the file. Allocates instance data (or uses static allocation for |
| * static memory model). |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData) |
| { |
| S_SMF_DATA* pSMFData; |
| EAS_RESULT result; |
| |
| /* check for valid state */ |
| pSMFData = (S_SMF_DATA *) pInstData; |
| if (pSMFData->state != EAS_STATE_OPEN) |
| return EAS_ERROR_NOT_VALID_IN_THIS_STATE; |
| |
| /* instantiate a synthesizer */ |
| if ((result = VMInitMIDI(pEASData, &pSMFData->pSynth)) != EAS_SUCCESS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI returned %d\n", result); */ } |
| return result; |
| } |
| |
| /* parse the file header and setup the individual stream parsers */ |
| if ((result = SMF_ParseHeader(pEASData->hwInstData, pSMFData)) != EAS_SUCCESS) |
| return result; |
| |
| /* ready to play */ |
| pSMFData->state = EAS_STATE_READY; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Time() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Returns the time of the next event in msecs |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * pTime - pointer to variable to hold time of next event (in msecs) |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pEASData) reserved for future use */ |
| EAS_RESULT SMF_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime) |
| { |
| S_SMF_DATA *pSMFData; |
| |
| pSMFData = (S_SMF_DATA*) pInstData; |
| |
| /* sanity check */ |
| #ifdef _CHECKED_BUILD |
| if (pSMFData->state == EAS_STATE_STOPPED) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Can't ask for time on a stopped stream\n"); */ } |
| } |
| |
| if (pSMFData->nextStream == NULL) |
| { |
| { /* dpp: EAS_ReportEx( _EAS_SEVERITY_ERROR, "no is NULL\n"); */ } |
| } |
| #endif |
| |
| #if 0 |
| /* return time in milliseconds */ |
| /* if chase mode, lie about time */ |
| if (pSMFData->flags & SMF_FLAGS_CHASE_MODE) |
| *pTime = 0; |
| |
| else |
| #endif |
| |
| /*lint -e{704} use shift instead of division */ |
| *pTime = pSMFData->time >> 8; |
| |
| *pTime = pSMFData->time >> 8; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Event() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Parse the next event in the file |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode) |
| { |
| S_SMF_DATA* pSMFData; |
| EAS_RESULT result; |
| EAS_I32 i; |
| EAS_U32 ticks; |
| EAS_U32 temp; |
| |
| /* establish pointer to instance data */ |
| pSMFData = (S_SMF_DATA*) pInstData; |
| if (pSMFData->state >= EAS_STATE_OPEN) |
| return EAS_SUCCESS; |
| |
| /* get current ticks */ |
| ticks = pSMFData->nextStream->ticks; |
| |
| /* assume that an error occurred */ |
| pSMFData->state = EAS_STATE_ERROR; |
| |
| #ifdef JET_INTERFACE |
| /* if JET has track muted, set parser mode to mute */ |
| if (pSMFData->nextStream->midiStream.jetData & MIDI_FLAGS_JET_MUTE) |
| parserMode = eParserModeMute; |
| #endif |
| |
| /* parse the next event from all the streams */ |
| if ((result = SMF_ParseEvent(pEASData, pSMFData, pSMFData->nextStream, parserMode)) != EAS_SUCCESS) |
| { |
| /* check for unexpected end-of-file */ |
| if (result != EAS_EOF) |
| return result; |
| |
| /* indicate end of track for this stream */ |
| pSMFData->nextStream->ticks = SMF_END_OF_TRACK; |
| } |
| |
| /* get next delta time, unless already at end of track */ |
| else if (pSMFData->nextStream->ticks != SMF_END_OF_TRACK) |
| { |
| if ((result = SMF_GetDeltaTime(pEASData->hwInstData, pSMFData->nextStream)) != EAS_SUCCESS) |
| { |
| /* check for unexpected end-of-file */ |
| if (result != EAS_EOF) |
| return result; |
| |
| /* indicate end of track for this stream */ |
| pSMFData->nextStream->ticks = SMF_END_OF_TRACK; |
| } |
| |
| /* if zero delta to next event, stay with this stream */ |
| else if (pSMFData->nextStream->ticks == ticks) |
| { |
| pSMFData->state = EAS_STATE_PLAY; |
| return EAS_SUCCESS; |
| } |
| } |
| |
| /* find next event in all streams */ |
| temp = 0x7ffffff; |
| pSMFData->nextStream = NULL; |
| for (i = 0; i < pSMFData->numStreams; i++) |
| { |
| if (pSMFData->streams[i].ticks < temp) |
| { |
| temp = pSMFData->streams[i].ticks; |
| pSMFData->nextStream = &pSMFData->streams[i]; |
| } |
| } |
| |
| /* are there any more events to parse? */ |
| if (pSMFData->nextStream) |
| { |
| pSMFData->state = EAS_STATE_PLAY; |
| |
| /* update the time of the next event */ |
| SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks - ticks); |
| } |
| else |
| { |
| pSMFData->state = EAS_STATE_STOPPING; |
| VMReleaseAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth); |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_State() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Returns the current state of the stream |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * pState - pointer to variable to store state |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pEASData) reserved for future use */ |
| EAS_RESULT SMF_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 *pState) |
| { |
| S_SMF_DATA* pSMFData; |
| |
| /* establish pointer to instance data */ |
| pSMFData = (S_SMF_DATA*) pInstData; |
| |
| /* if stopping, check to see if synth voices are active */ |
| if (pSMFData->state == EAS_STATE_STOPPING) |
| { |
| if (VMActiveVoices(pSMFData->pSynth) == 0) |
| pSMFData->state = EAS_STATE_STOPPED; |
| } |
| |
| if (pSMFData->state == EAS_STATE_PAUSING) |
| { |
| if (VMActiveVoices(pSMFData->pSynth) == 0) |
| pSMFData->state = EAS_STATE_PAUSED; |
| } |
| |
| /* return current state */ |
| *pState = pSMFData->state; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Close() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Close the file and clean up |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData) |
| { |
| S_SMF_DATA* pSMFData; |
| EAS_I32 i; |
| EAS_RESULT result; |
| |
| pSMFData = (S_SMF_DATA*) pInstData; |
| |
| /* close all the streams */ |
| for (i = 0; i < pSMFData->numStreams; i++) |
| { |
| if (pSMFData->streams[i].fileHandle != NULL) |
| { |
| if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->streams[i].fileHandle)) != EAS_SUCCESS) |
| return result; |
| } |
| } |
| if (pSMFData->fileHandle != NULL) |
| if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->fileHandle)) != EAS_SUCCESS) |
| return result; |
| |
| /* free the synth */ |
| if (pSMFData->pSynth != NULL) |
| VMMIDIShutdown(pEASData, pSMFData->pSynth); |
| |
| /* if using dynamic memory, free it */ |
| if (!pEASData->staticMemoryModel) |
| { |
| if (pSMFData->streams) |
| EAS_HWFree(pEASData->hwInstData, pSMFData->streams); |
| |
| /* free the instance data */ |
| EAS_HWFree(pEASData->hwInstData, pSMFData); |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Reset() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reset the sequencer. Used for locating backwards in the file. |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData) |
| { |
| S_SMF_DATA* pSMFData; |
| EAS_I32 i; |
| EAS_RESULT result; |
| EAS_U32 ticks; |
| |
| pSMFData = (S_SMF_DATA*) pInstData; |
| |
| /* reset time to zero */ |
| pSMFData->time = 0; |
| |
| /* reset the synth */ |
| VMReset(pEASData->pVoiceMgr, pSMFData->pSynth, EAS_TRUE); |
| |
| /* find the start of each track */ |
| ticks = 0x7fffffffL; |
| pSMFData->nextStream = NULL; |
| for (i = 0; i < pSMFData->numStreams; i++) |
| { |
| |
| /* reset file position to first byte of data in track */ |
| if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFData->streams[i].fileHandle, pSMFData->streams[i].startFilePos)) != EAS_SUCCESS) |
| return result; |
| |
| /* initalize some data */ |
| pSMFData->streams[i].ticks = 0; |
| |
| /* initalize the MIDI parser data */ |
| EAS_InitMIDIStream(&pSMFData->streams[i].midiStream); |
| |
| /* parse the first delta time in each stream */ |
| if ((result = SMF_GetDeltaTime(pEASData->hwInstData,&pSMFData->streams[i])) != EAS_SUCCESS) |
| return result; |
| if (pSMFData->streams[i].ticks < ticks) |
| { |
| ticks = pSMFData->streams[i].ticks; |
| pSMFData->nextStream = &pSMFData->streams[i]; |
| } |
| } |
| |
| |
| pSMFData->state = EAS_STATE_READY; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Pause() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Pauses the sequencer. Mutes all voices and sets state to pause. |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| EAS_RESULT SMF_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData) |
| { |
| S_SMF_DATA *pSMFData; |
| |
| /* can't pause a stopped stream */ |
| pSMFData = (S_SMF_DATA*) pInstData; |
| if (pSMFData->state == EAS_STATE_STOPPED) |
| return EAS_ERROR_ALREADY_STOPPED; |
| |
| /* mute the synthesizer */ |
| VMMuteAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth); |
| pSMFData->state = EAS_STATE_PAUSING; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_Resume() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Resume playing after a pause, sets state back to playing. |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pEASData) reserved for future use */ |
| EAS_RESULT SMF_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData) |
| { |
| S_SMF_DATA *pSMFData; |
| |
| /* can't resume a stopped stream */ |
| pSMFData = (S_SMF_DATA*) pInstData; |
| if (pSMFData->state == EAS_STATE_STOPPED) |
| return EAS_ERROR_ALREADY_STOPPED; |
| |
| /* nothing to do but resume playback */ |
| pSMFData->state = EAS_STATE_PLAY; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_SetData() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Sets parser parameters |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pEASData) reserved for future use */ |
| EAS_RESULT SMF_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value) |
| { |
| S_SMF_DATA *pSMFData; |
| |
| pSMFData = (S_SMF_DATA*) pInstData; |
| switch (param) |
| { |
| |
| /* set metadata callback */ |
| case PARSER_DATA_METADATA_CB: |
| EAS_HWMemCpy(&pSMFData->metadata, (void*) value, sizeof(S_METADATA_CB)); |
| break; |
| |
| #ifdef JET_INTERFACE |
| /* set jet segment and track ID of all tracks for callback function */ |
| case PARSER_DATA_JET_CB: |
| { |
| EAS_U32 i; |
| EAS_U32 bit = (EAS_U32) value; |
| bit = (bit << JET_EVENT_SEG_SHIFT) & JET_EVENT_SEG_MASK; |
| for (i = 0; i < pSMFData->numStreams; i++) |
| pSMFData->streams[i].midiStream.jetData = |
| (pSMFData->streams[i].midiStream.jetData & |
| ~(JET_EVENT_TRACK_MASK | JET_EVENT_SEG_MASK)) | |
| i << JET_EVENT_TRACK_SHIFT | bit | MIDI_FLAGS_JET_CB; |
| pSMFData->flags |= SMF_FLAGS_JET_STREAM; |
| } |
| break; |
| |
| /* set state of all mute flags at once */ |
| case PARSER_DATA_MUTE_FLAGS: |
| { |
| EAS_INT i; |
| EAS_U32 bit = (EAS_U32) value; |
| for (i = 0; i < pSMFData->numStreams; i++) |
| { |
| if (bit & 1) |
| pSMFData->streams[i].midiStream.jetData |= MIDI_FLAGS_JET_MUTE; |
| else |
| pSMFData->streams[i].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE; |
| bit >>= 1; |
| } |
| } |
| break; |
| |
| /* set track mute */ |
| case PARSER_DATA_SET_MUTE: |
| if (value < pSMFData->numStreams) |
| pSMFData->streams[value].midiStream.jetData |= MIDI_FLAGS_JET_MUTE; |
| else |
| return EAS_ERROR_PARAMETER_RANGE; |
| break; |
| |
| /* clear track mute */ |
| case PARSER_DATA_CLEAR_MUTE: |
| if (value < pSMFData->numStreams) |
| pSMFData->streams[value].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE; |
| else |
| return EAS_ERROR_PARAMETER_RANGE; |
| break; |
| #endif |
| |
| default: |
| return EAS_ERROR_INVALID_PARAMETER; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_GetData() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Retrieves parser parameters |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * handle - pointer to file handle |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -esym(715, pEASData) reserved for future use */ |
| EAS_RESULT SMF_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue) |
| { |
| S_SMF_DATA *pSMFData; |
| |
| pSMFData = (S_SMF_DATA*) pInstData; |
| switch (param) |
| { |
| /* return file type */ |
| case PARSER_DATA_FILE_TYPE: |
| if (pSMFData->numStreams == 1) |
| *pValue = EAS_FILE_SMF0; |
| else |
| *pValue = EAS_FILE_SMF1; |
| break; |
| |
| /* now handled in eas_public.c */ |
| #if 0 |
| case PARSER_DATA_POLYPHONY: |
| if (pSMFData->pSynth) |
| VMGetPolyphony(pEASData->pVoiceMgr, pSMFData->pSynth, pValue); |
| else |
| return EAS_ERROR_NOT_VALID_IN_THIS_STATE; |
| break; |
| |
| case PARSER_DATA_PRIORITY: |
| if (pSMFData->pSynth) |
| VMGetPriority(pEASData->pVoiceMgr, pSMFData->pSynth, pValue); |
| break; |
| |
| /* set transposition */ |
| case PARSER_DATA_TRANSPOSITION: |
| *pValue = pSMFData->transposition; |
| break; |
| #endif |
| |
| case PARSER_DATA_SYNTH_HANDLE: |
| *pValue = (EAS_I32) pSMFData->pSynth; |
| break; |
| |
| default: |
| return EAS_ERROR_INVALID_PARAMETER; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_GetVarLenData() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reads a varible length quantity from an SMF file |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData) |
| { |
| EAS_RESULT result; |
| EAS_U32 data; |
| EAS_U8 c; |
| |
| /* read until bit 7 is zero */ |
| data = 0; |
| do |
| { |
| if ((result = EAS_HWGetByte(hwInstData, fileHandle,&c)) != EAS_SUCCESS) |
| return result; |
| data = (data << 7) | (c & 0x7f); |
| } while (c & 0x80); |
| *pData = data; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_GetDeltaTime() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reads a varible length quantity from an SMF file |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream) |
| { |
| EAS_RESULT result; |
| EAS_U32 ticks; |
| |
| if ((result = SMF_GetVarLenData(hwInstData, pSMFStream->fileHandle, &ticks)) != EAS_SUCCESS) |
| return result; |
| |
| pSMFStream->ticks += ticks; |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_ParseMetaEvent() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reads a varible length quantity from an SMF file |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream) |
| { |
| EAS_RESULT result; |
| EAS_U32 len; |
| EAS_I32 pos; |
| EAS_U32 temp; |
| EAS_U8 c; |
| |
| /* get the meta-event type */ |
| if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS) |
| return result; |
| |
| /* get the length */ |
| if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS) |
| return result; |
| |
| /* get the current file position so we can skip the event */ |
| if ((result = EAS_HWFilePos(pEASData->hwInstData, pSMFStream->fileHandle, &pos)) != EAS_SUCCESS) |
| return result; |
| pos += (EAS_I32) len; |
| |
| /* end of track? */ |
| if (c == SMF_META_END_OF_TRACK) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: end of track\n", c, len); */ } |
| pSMFStream->ticks = SMF_END_OF_TRACK; |
| } |
| |
| /* tempo event? */ |
| else if (c == SMF_META_TEMPO) |
| { |
| /* read the 3-byte timebase value */ |
| temp = 0; |
| while (len--) |
| { |
| if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS) |
| return result; |
| temp = (temp << 8) | c; |
| } |
| |
| pSMFData->tickConv = (EAS_U16) (((temp * 1024) / pSMFData->ppqn + 500) / 1000); |
| pSMFData->flags |= SMF_FLAGS_HAS_TEMPO; |
| } |
| |
| /* check for time signature - see iMelody spec V1.4 section 4.1.2.2.3.6 */ |
| else if (c == SMF_META_TIME_SIGNATURE) |
| { |
| pSMFData->flags |= SMF_FLAGS_HAS_TIME_SIG; |
| } |
| |
| /* if the host has registered a metadata callback return the metadata */ |
| else if (pSMFData->metadata.callback) |
| { |
| EAS_I32 readLen; |
| E_EAS_METADATA_TYPE metaType; |
| |
| metaType = EAS_METADATA_UNKNOWN; |
| |
| /* only process title on the first track */ |
| if (c == SMF_META_SEQTRK_NAME) |
| metaType = EAS_METADATA_TITLE; |
| else if (c == SMF_META_TEXT) |
| metaType = EAS_METADATA_TEXT; |
| else if (c == SMF_META_COPYRIGHT) |
| metaType = EAS_METADATA_COPYRIGHT; |
| else if (c == SMF_META_LYRIC) |
| metaType = EAS_METADATA_LYRIC; |
| |
| if (metaType != EAS_METADATA_UNKNOWN) |
| { |
| readLen = pSMFData->metadata.bufferSize - 1; |
| if ((EAS_I32) len < readLen) |
| readLen = (EAS_I32) len; |
| if ((result = EAS_HWReadFile(pEASData->hwInstData, pSMFStream->fileHandle, pSMFData->metadata.buffer, readLen, &readLen)) != EAS_SUCCESS) |
| return result; |
| pSMFData->metadata.buffer[readLen] = 0; |
| pSMFData->metadata.callback(metaType, pSMFData->metadata.buffer, pSMFData->metadata.pUserData); |
| } |
| } |
| |
| /* position file to next event - in case we ignored all or part of the meta-event */ |
| if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFStream->fileHandle, pos)) != EAS_SUCCESS) |
| return result; |
| |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: type=%02x, len=%d\n", c, len); */ } |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_ParseSysEx() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reads a varible length quantity from an SMF file |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode) |
| { |
| EAS_RESULT result; |
| EAS_U32 len; |
| EAS_U8 c; |
| |
| /* get the length */ |
| if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS) |
| return result; |
| |
| /* start of SysEx message? */ |
| if (f0 == 0xf0) |
| { |
| if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, f0, parserMode)) != EAS_SUCCESS) |
| return result; |
| } |
| |
| /* feed the SysEx to the stream parser */ |
| while (len--) |
| { |
| if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS) |
| return result; |
| if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS) |
| return result; |
| |
| /* check for GM system ON */ |
| if (pSMFStream->midiStream.flags & MIDI_FLAG_GM_ON) |
| pSMFData->flags |= SMF_FLAGS_HAS_GM_ON; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_ParseEvent() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Reads a varible length quantity from an SMF file |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode) |
| { |
| EAS_RESULT result; |
| EAS_U8 c; |
| |
| /* get the event type */ |
| if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS) |
| return result; |
| |
| /* parse meta-event */ |
| if (c == 0xff) |
| { |
| if ((result = SMF_ParseMetaEvent(pEASData, pSMFData, pSMFStream)) != EAS_SUCCESS) |
| return result; |
| } |
| |
| /* parse SysEx */ |
| else if ((c == 0xf0) || (c == 0xf7)) |
| { |
| if ((result = SMF_ParseSysEx(pEASData, pSMFData, pSMFStream, c, parserMode)) != EAS_SUCCESS) |
| return result; |
| } |
| |
| /* parse MIDI message */ |
| else |
| { |
| if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS) |
| return result; |
| |
| /* keep streaming data to the MIDI parser until the message is complete */ |
| while (pSMFStream->midiStream.pending) |
| { |
| if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS) |
| return result; |
| if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS) |
| return result; |
| } |
| |
| } |
| |
| /* chase mode logic */ |
| if (pSMFData->time == 0) |
| { |
| if (pSMFData->flags & SMF_FLAGS_CHASE_MODE) |
| { |
| if (pSMFStream->midiStream.flags & MIDI_FLAG_FIRST_NOTE) |
| pSMFData->flags &= ~SMF_FLAGS_CHASE_MODE; |
| } |
| else if ((pSMFData->flags & SMF_FLAGS_SETUP_BAR) == SMF_FLAGS_SETUP_BAR) |
| pSMFData->flags = (pSMFData->flags & ~SMF_FLAGS_SETUP_BAR) | SMF_FLAGS_CHASE_MODE; |
| } |
| |
| return EAS_SUCCESS; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_ParseHeader() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Parses the header of an SMF file, allocates memory the stream parsers and initializes the |
| * stream parsers. |
| * |
| * Inputs: |
| * pEASData - pointer to overall EAS data structure |
| * pSMFData - pointer to parser instance data |
| * fileHandle - file handle |
| * fileOffset - offset in the file where the header data starts, usually 0 |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /*lint -e{801} we know that 'goto' is deprecated - but it's cleaner in this case */ |
| EAS_RESULT SMF_ParseHeader (EAS_HW_DATA_HANDLE hwInstData, S_SMF_DATA *pSMFData) |
| { |
| EAS_RESULT result; |
| EAS_I32 i; |
| EAS_U16 division; |
| EAS_U32 chunkSize; |
| EAS_U32 chunkStart; |
| EAS_U32 temp; |
| EAS_U32 ticks; |
| |
| /* rewind the file and find the end of the header chunk */ |
| if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_HEADER_SIZE)) != EAS_SUCCESS) |
| goto ReadError; |
| if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* determine the number of tracks */ |
| if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_NUM_TRACKS)) != EAS_SUCCESS) |
| goto ReadError; |
| if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &pSMFData->numStreams, EAS_TRUE)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* limit the number of tracks */ |
| if (pSMFData->numStreams > MAX_SMF_STREAMS) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "SMF file contains %u tracks, playing %d tracks\n", pSMFData->numStreams, MAX_SMF_STREAMS); */ } |
| pSMFData->numStreams = MAX_SMF_STREAMS; |
| } else if (pSMFData->numStreams == 0) |
| { |
| /* avoid 0 sized allocation */ |
| return EAS_ERROR_PARAMETER_RANGE; |
| } |
| |
| /* get the time division */ |
| if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &division, EAS_TRUE)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* setup default timebase for 120 bpm */ |
| pSMFData->ppqn = 192; |
| if (!division || division & 0x8000) |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"No support for SMPTE code timebase\n"); */ } |
| else |
| pSMFData->ppqn = (division & 0x7fff); |
| pSMFData->tickConv = (EAS_U16) (((SMF_DEFAULT_TIMEBASE * 1024) / pSMFData->ppqn + 500) / 1000); |
| |
| /* dynamic memory allocation, allocate memory for streams */ |
| if (pSMFData->streams == NULL) |
| { |
| pSMFData->streams = EAS_HWMalloc(hwInstData,sizeof(S_SMF_STREAM) * pSMFData->numStreams); |
| if (pSMFData->streams == NULL) |
| return EAS_ERROR_MALLOC_FAILED; |
| |
| /* zero the memory to insure complete initialization */ |
| EAS_HWMemSet((void *)(pSMFData->streams), 0, sizeof(S_SMF_STREAM) * pSMFData->numStreams); |
| } |
| |
| /* find the start of each track */ |
| chunkStart = (EAS_U32) pSMFData->fileOffset; |
| ticks = 0x7fffffffL; |
| pSMFData->nextStream = NULL; |
| for (i = 0; i < pSMFData->numStreams; i++) |
| { |
| |
| for (;;) |
| { |
| |
| /* calculate start of next chunk - checking for errors */ |
| temp = chunkStart + SMF_CHUNK_INFO_SIZE + chunkSize; |
| if (temp <= chunkStart) |
| { |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Error in chunk size at offset %d\n", chunkStart); */ } |
| return EAS_ERROR_FILE_FORMAT; |
| } |
| chunkStart = temp; |
| |
| /* seek to the start of the next chunk */ |
| if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, (EAS_I32) chunkStart)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* read the chunk identifier */ |
| if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &temp, EAS_TRUE)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* read the chunk size */ |
| if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS) |
| goto ReadError; |
| |
| /* make sure this is an 'MTrk' chunk */ |
| if (temp == SMF_CHUNK_TYPE_TRACK) |
| break; |
| |
| { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Unexpected chunk type: 0x%08x\n", temp); */ } |
| } |
| |
| /* initalize some data */ |
| pSMFData->streams[i].ticks = 0; |
| pSMFData->streams[i].fileHandle = pSMFData->fileHandle; |
| |
| /* NULL the file handle so we don't try to close it twice */ |
| pSMFData->fileHandle = NULL; |
| |
| /* save this file position as the start of the track */ |
| pSMFData->streams[i].startFilePos = (EAS_I32) chunkStart + SMF_CHUNK_INFO_SIZE; |
| |
| /* initalize the MIDI parser data */ |
| EAS_InitMIDIStream(&pSMFData->streams[i].midiStream); |
| |
| /* parse the first delta time in each stream */ |
| if ((result = SMF_GetDeltaTime(hwInstData, &pSMFData->streams[i])) != EAS_SUCCESS) |
| goto ReadError; |
| |
| if (pSMFData->streams[i].ticks < ticks) |
| { |
| ticks = pSMFData->streams[i].ticks; |
| pSMFData->nextStream = &pSMFData->streams[i]; |
| } |
| |
| /* more tracks to do, create a duplicate file handle */ |
| if (i < (pSMFData->numStreams - 1)) |
| { |
| if ((result = EAS_HWDupHandle(hwInstData, pSMFData->streams[i].fileHandle, &pSMFData->fileHandle)) != EAS_SUCCESS) |
| goto ReadError; |
| } |
| } |
| |
| /* update the time of the next event */ |
| if (pSMFData->nextStream) |
| SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks); |
| |
| return EAS_SUCCESS; |
| |
| /* ugly goto: but simpler than structured */ |
| ReadError: |
| if (result == EAS_EOF) |
| return EAS_ERROR_FILE_FORMAT; |
| return result; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * SMF_UpdateTime() |
| *---------------------------------------------------------------------------- |
| * Purpose: |
| * Update the millisecond time base by converting the ticks into millieconds |
| * |
| * Inputs: |
| * |
| * |
| * Outputs: |
| * |
| * |
| * Side Effects: |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks) |
| { |
| EAS_U32 temp1, temp2; |
| |
| if (pSMFData->flags & SMF_FLAGS_CHASE_MODE) |
| return; |
| |
| temp1 = (ticks >> 10) * pSMFData->tickConv; |
| temp2 = (ticks & 0x3ff) * pSMFData->tickConv; |
| pSMFData->time += (EAS_I32)((temp1 << 8) + (temp2 >> 2)); |
| } |
| |