From 7df30109963092559d3760c0661a020f9daf1030 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:30:38 -0800 Subject: auto import from //depot/cupcake/@135843 --- arm-hybrid-22k/lib_src/eas_smf.c | 1203 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1203 insertions(+) create mode 100644 arm-hybrid-22k/lib_src/eas_smf.c (limited to 'arm-hybrid-22k/lib_src/eas_smf.c') diff --git a/arm-hybrid-22k/lib_src/eas_smf.c b/arm-hybrid-22k/lib_src/eas_smf.c new file mode 100644 index 0000000..7b56e97 --- /dev/null +++ b/arm-hybrid-22k/lib_src/eas_smf.c @@ -0,0 +1,1203 @@ +/*---------------------------------------------------------------------------- + * + * 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; + } + + /* 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 & 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)); +} + -- cgit v1.2.3