//------------------------------------------------------------------------- /* Copyright (C) 2010 EDuke32 developers and contributors This file is part of EDuke32. EDuke32 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ //------------------------------------------------------------------------- /********************************************************************** module: MIDI.C author: James R. Dose date: May 25, 1994 Midi song file playback routines. (c) Copyright 1994 James R. Dose. All Rights Reserved. **********************************************************************/ #include #include #include "standard.h" #include "music.h" #include "_midi.h" #include "midi.h" #include "mpu401.h" #include "compat.h" #define WIN32_LEAN_AND_MEAN #include extern int32_t MUSIC_SoundDevice; static const int32_t _MIDI_CommandLengths[ NUM_MIDI_CHANNELS ] = { 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0 }; static int32_t (*_MIDI_RerouteFunctions[ NUM_MIDI_CHANNELS ])(int32_t event, int32_t c1, int32_t c2); static track *_MIDI_TrackPtr = NULL; static int32_t _MIDI_TrackMemSize; static int32_t _MIDI_NumTracks; static int32_t _MIDI_SongActive = FALSE; static int32_t _MIDI_SongLoaded = FALSE; static int32_t _MIDI_Loop = FALSE; static int32_t _MIDI_Division; static int32_t _MIDI_Tick = 0; static int32_t _MIDI_Beat = 1; static int32_t _MIDI_Measure = 1; static uint32_t _MIDI_Time; static int32_t _MIDI_BeatsPerMeasure; static int32_t _MIDI_TicksPerBeat; static int32_t _MIDI_TimeBase; static int32_t _MIDI_FPSecondsPerTick; static uint32_t _MIDI_TotalTime; static int32_t _MIDI_TotalTicks; static int32_t _MIDI_TotalBeats; static int32_t _MIDI_TotalMeasures; uint32_t _MIDI_PositionInTicks; uint32_t _MIDI_GlobalPositionInTicks; static int32_t _MIDI_Context; static int32_t _MIDI_ActiveTracks; static int32_t _MIDI_TotalVolume = MIDI_MaxVolume; static int32_t _MIDI_ChannelVolume[ NUM_MIDI_CHANNELS ]; static int32_t _MIDI_UserChannelVolume[ NUM_MIDI_CHANNELS ] = { 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 }; static midifuncs *_MIDI_Funcs = NULL; static int32_t Reset = FALSE; int32_t MIDI_Tempo = 120; char MIDI_PatchMap[ 128 ]; /********************************************************************** Memory locked functions: **********************************************************************/ /*--------------------------------------------------------------------- Function: _MIDI_ReadNumber Reads a variable length number from a MIDI track. ---------------------------------------------------------------------*/ static int32_t _MIDI_ReadNumber ( void *from, size_t size ) { char *FromPtr; int32_t value; if (size > 4) { size = 4; } FromPtr = (char *)from; value = 0; while (size--) { value <<= 8; value += *FromPtr++; } return(value); } /*--------------------------------------------------------------------- Function: _MIDI_ReadDelta Reads a variable length encoded delta delay time from the MIDI data. ---------------------------------------------------------------------*/ static int32_t _MIDI_ReadDelta ( track *ptr ) { int32_t value; char c; GET_NEXT_EVENT(ptr, value); if (value & 0x80) { value &= 0x7f; do { GET_NEXT_EVENT(ptr, c); value = (value << 7) + (c & 0x7f); } while (c & 0x80); } return(value); } /*--------------------------------------------------------------------- Function: _MIDI_ResetTracks Sets the track pointers to the beginning of the song. ---------------------------------------------------------------------*/ static void _MIDI_ResetTracks ( void ) { int32_t i; track *ptr; _MIDI_Tick = 0; _MIDI_Beat = 1; _MIDI_Measure = 1; _MIDI_Time = 0; _MIDI_BeatsPerMeasure = 4; _MIDI_TicksPerBeat = _MIDI_Division; _MIDI_TimeBase = 4; _MIDI_PositionInTicks = 0; //_MIDI_GlobalPositionInTicks = 0; _MIDI_ActiveTracks = 0; _MIDI_Context = 0; ptr = _MIDI_TrackPtr; for (i = 0; i < _MIDI_NumTracks; i++) { ptr->pos = ptr->start; ptr->delay = _MIDI_ReadDelta(ptr); ptr->active = ptr->EMIDI_IncludeTrack; ptr->RunningStatus = 0; ptr->currentcontext = 0; ptr->context[ 0 ].loopstart = ptr->start; ptr->context[ 0 ].loopcount = 0; if (ptr->active) { _MIDI_ActiveTracks++; } ptr++; } } /*--------------------------------------------------------------------- Function: _MIDI_AdvanceTick Increment tick counters. ---------------------------------------------------------------------*/ static void _MIDI_AdvanceTick ( void ) { _MIDI_PositionInTicks++; _MIDI_Time += _MIDI_FPSecondsPerTick; _MIDI_Tick++; while (_MIDI_Tick > _MIDI_TicksPerBeat) { _MIDI_Tick -= _MIDI_TicksPerBeat; _MIDI_Beat++; } while (_MIDI_Beat > _MIDI_BeatsPerMeasure) { _MIDI_Beat -= _MIDI_BeatsPerMeasure; _MIDI_Measure++; } } /*--------------------------------------------------------------------- Function: _MIDI_SysEx Interpret SysEx Event. ---------------------------------------------------------------------*/ static void _MIDI_SysEx ( track *Track ) { int32_t length; length = _MIDI_ReadDelta(Track); Track->pos += length; } /*--------------------------------------------------------------------- Function: _MIDI_MetaEvent Interpret Meta Event. ---------------------------------------------------------------------*/ static void _MIDI_MetaEvent ( track *Track ) { int32_t command; int32_t length; int32_t denominator; int32_t tempo; GET_NEXT_EVENT(Track, command); GET_NEXT_EVENT(Track, length); switch (command) { case MIDI_END_OF_TRACK : Track->active = FALSE; _MIDI_ActiveTracks--; break; case MIDI_TEMPO_CHANGE : tempo = 60000000L / _MIDI_ReadNumber(Track->pos, 3); MIDI_SetTempo(tempo); break; case MIDI_TIME_SIGNATURE : if ((_MIDI_Tick > 0) || (_MIDI_Beat > 1)) { _MIDI_Measure++; } _MIDI_Tick = 0; _MIDI_Beat = 1; _MIDI_BeatsPerMeasure = (int32_t)*Track->pos; denominator = (int32_t)*(Track->pos + 1); _MIDI_TimeBase = 1; while (denominator > 0) { _MIDI_TimeBase += _MIDI_TimeBase; denominator--; } _MIDI_TicksPerBeat = (_MIDI_Division * 4) / _MIDI_TimeBase; break; } Track->pos += length; } /*--------------------------------------------------------------------- Function: _MIDI_InterpretControllerInfo Interprets the MIDI controller info. ---------------------------------------------------------------------*/ static int32_t _MIDI_InterpretControllerInfo ( track *Track, int32_t TimeSet, int32_t channel, int32_t c1, int32_t c2 ) { track *trackptr; int32_t tracknum; int32_t loopcount; switch (c1) { case MIDI_MONO_MODE_ON : Track->pos++; break; case MIDI_VOLUME : if (!Track->EMIDI_VolumeChange) { _MIDI_SetChannelVolume(channel, c2); } break; case EMIDI_INCLUDE_TRACK : case EMIDI_EXCLUDE_TRACK : break; case EMIDI_PROGRAM_CHANGE : if (Track->EMIDI_ProgramChange) { _MIDI_Funcs->ProgramChange(channel, MIDI_PatchMap[ c2 & 0x7f ]); } break; case EMIDI_VOLUME_CHANGE : if (Track->EMIDI_VolumeChange) { _MIDI_SetChannelVolume(channel, c2); } break; case EMIDI_CONTEXT_START : break; case EMIDI_CONTEXT_END : if ((Track->currentcontext == _MIDI_Context) || (_MIDI_Context < 0) || (Track->context[ _MIDI_Context ].pos == NULL)) { break; } Track->currentcontext = _MIDI_Context; Track->context[ 0 ].loopstart = Track->context[ _MIDI_Context ].loopstart; Track->context[ 0 ].loopcount = Track->context[ _MIDI_Context ].loopcount; Track->pos = Track->context[ _MIDI_Context ].pos; Track->RunningStatus = Track->context[ _MIDI_Context ].RunningStatus; if (TimeSet) { break; } _MIDI_Time = Track->context[ _MIDI_Context ].time; _MIDI_FPSecondsPerTick = Track->context[ _MIDI_Context ].FPSecondsPerTick; _MIDI_Tick = Track->context[ _MIDI_Context ].tick; _MIDI_Beat = Track->context[ _MIDI_Context ].beat; _MIDI_Measure = Track->context[ _MIDI_Context ].measure; _MIDI_BeatsPerMeasure = Track->context[ _MIDI_Context ].BeatsPerMeasure; _MIDI_TicksPerBeat = Track->context[ _MIDI_Context ].TicksPerBeat; _MIDI_TimeBase = Track->context[ _MIDI_Context ].TimeBase; TimeSet = TRUE; break; case EMIDI_LOOP_START : case EMIDI_SONG_LOOP_START : if (c2 == 0) { loopcount = EMIDI_INFINITE; } else { loopcount = c2; } if (c1 == EMIDI_SONG_LOOP_START) { trackptr = _MIDI_TrackPtr; tracknum = _MIDI_NumTracks; } else { trackptr = Track; tracknum = 1; } while (tracknum > 0) { trackptr->context[ 0 ].loopcount = loopcount; trackptr->context[ 0 ].pos = trackptr->pos; trackptr->context[ 0 ].loopstart = trackptr->pos; trackptr->context[ 0 ].RunningStatus = trackptr->RunningStatus; trackptr->context[ 0 ].active = trackptr->active; trackptr->context[ 0 ].delay = trackptr->delay; trackptr->context[ 0 ].time = _MIDI_Time; trackptr->context[ 0 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick; trackptr->context[ 0 ].tick = _MIDI_Tick; trackptr->context[ 0 ].beat = _MIDI_Beat; trackptr->context[ 0 ].measure = _MIDI_Measure; trackptr->context[ 0 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure; trackptr->context[ 0 ].TicksPerBeat = _MIDI_TicksPerBeat; trackptr->context[ 0 ].TimeBase = _MIDI_TimeBase; trackptr++; tracknum--; } break; case EMIDI_LOOP_END : case EMIDI_SONG_LOOP_END : if ((c2 != EMIDI_END_LOOP_VALUE) || (Track->context[ 0 ].loopstart == NULL) || (Track->context[ 0 ].loopcount == 0)) { break; } if (c1 == EMIDI_SONG_LOOP_END) { trackptr = _MIDI_TrackPtr; tracknum = _MIDI_NumTracks; _MIDI_ActiveTracks = 0; } else { trackptr = Track; tracknum = 1; _MIDI_ActiveTracks--; } while (tracknum > 0) { if (trackptr->context[ 0 ].loopcount != EMIDI_INFINITE) { trackptr->context[ 0 ].loopcount--; } trackptr->pos = trackptr->context[ 0 ].loopstart; trackptr->RunningStatus = trackptr->context[ 0 ].RunningStatus; trackptr->delay = trackptr->context[ 0 ].delay; trackptr->active = trackptr->context[ 0 ].active; if (trackptr->active) { _MIDI_ActiveTracks++; } if (!TimeSet) { _MIDI_Time = trackptr->context[ 0 ].time; _MIDI_FPSecondsPerTick = trackptr->context[ 0 ].FPSecondsPerTick; _MIDI_Tick = trackptr->context[ 0 ].tick; _MIDI_Beat = trackptr->context[ 0 ].beat; _MIDI_Measure = trackptr->context[ 0 ].measure; _MIDI_BeatsPerMeasure = trackptr->context[ 0 ].BeatsPerMeasure; _MIDI_TicksPerBeat = trackptr->context[ 0 ].TicksPerBeat; _MIDI_TimeBase = trackptr->context[ 0 ].TimeBase; TimeSet = TRUE; } trackptr++; tracknum--; } break; default : if (_MIDI_Funcs->ControlChange) { _MIDI_Funcs->ControlChange(channel, c1, c2); } } return TimeSet; } /*--------------------------------------------------------------------- Function: _MIDI_ServiceRoutine Task that interperates the MIDI data. ---------------------------------------------------------------------*/ static int32_t _MIDI_ServiceRoutine(void) { int32_t event; int32_t channel; int32_t command; track *Track; int32_t tracknum; int32_t status; int32_t c1 = 0; int32_t c2 = 0; int32_t TimeSet = FALSE; if (_MIDI_SongActive) { Track = _MIDI_TrackPtr; tracknum = 0; while (tracknum < _MIDI_NumTracks) { while ((Track->active) && (Track->delay == 0)) { GET_NEXT_EVENT(Track, event); if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL) { switch (event) { case MIDI_SYSEX : case MIDI_SYSEX_CONTINUE : _MIDI_SysEx(Track); break; case MIDI_META_EVENT : _MIDI_MetaEvent(Track); break; } if (Track->active) { Track->delay = _MIDI_ReadDelta(Track); } continue; } if (event & MIDI_RUNNING_STATUS) { Track->RunningStatus = event; } else { event = Track->RunningStatus; Track->pos--; } channel = GET_MIDI_CHANNEL(event); command = GET_MIDI_COMMAND(event); if (_MIDI_CommandLengths[ command ] > 0) { GET_NEXT_EVENT(Track, c1); if (_MIDI_CommandLengths[ command ] > 1) { GET_NEXT_EVENT(Track, c2); } } if (_MIDI_RerouteFunctions[ channel ] != NULL) { status = _MIDI_RerouteFunctions[ channel ](event, c1, c2); if (status == MIDI_DONT_PLAY) { Track->delay = _MIDI_ReadDelta(Track); continue; } } switch (command) { case MIDI_NOTE_OFF : if (_MIDI_Funcs->NoteOff) { _MIDI_Funcs->NoteOff(channel, c1, c2); } break; case MIDI_NOTE_ON : if (_MIDI_Funcs->NoteOn) { _MIDI_Funcs->NoteOn(channel, c1, c2); } break; case MIDI_POLY_AFTER_TCH : if (_MIDI_Funcs->PolyAftertouch) { _MIDI_Funcs->PolyAftertouch(channel, c1, c2); } break; case MIDI_CONTROL_CHANGE : TimeSet = _MIDI_InterpretControllerInfo(Track, TimeSet, channel, c1, c2); break; case MIDI_PROGRAM_CHANGE : if ((_MIDI_Funcs->ProgramChange) && (!Track->EMIDI_ProgramChange)) { _MIDI_Funcs->ProgramChange(channel, MIDI_PatchMap[ c1 & 0x7f ]); } break; case MIDI_AFTER_TOUCH : if (_MIDI_Funcs->ChannelAftertouch) { _MIDI_Funcs->ChannelAftertouch(channel, c1); } break; case MIDI_PITCH_BEND : if (_MIDI_Funcs->PitchBend) { _MIDI_Funcs->PitchBend(channel, c1, c2); } break; default : break; } Track->delay = _MIDI_ReadDelta(Track); } Track->delay--; Track++; tracknum++; if (_MIDI_ActiveTracks == 0) { _MIDI_ResetTracks(); if (_MIDI_Loop) { tracknum = 0; Track = _MIDI_TrackPtr; } else { _MIDI_SongActive = FALSE; break; } } } _MIDI_AdvanceTick(); _MIDI_GlobalPositionInTicks++; } return 0; } /*--------------------------------------------------------------------- Function: _MIDI_SendControlChange Sends a control change to the proper device ---------------------------------------------------------------------*/ static int32_t _MIDI_SendControlChange ( int32_t channel, int32_t c1, int32_t c2 ) { int32_t status; if (_MIDI_RerouteFunctions[ channel ] != NULL) { status = _MIDI_RerouteFunctions[ channel ](0xB0 + channel, c1, c2); if (status == MIDI_DONT_PLAY) { return(MIDI_Ok); } } if (_MIDI_Funcs == NULL) { return(MIDI_Error); } if (_MIDI_Funcs->ControlChange == NULL) { return(MIDI_Error); } _MIDI_Funcs->ControlChange(channel, c1, c2); return(MIDI_Ok); } /*--------------------------------------------------------------------- Function: MIDI_RerouteMidiChannel Sets callback function to reroute MIDI commands from specified function. ---------------------------------------------------------------------*/ void MIDI_RerouteMidiChannel ( int32_t channel, int32_t(*function)(int32_t event, int32_t c1, int32_t c2) ) { if ((channel >= 1) && (channel <= 16)) { _MIDI_RerouteFunctions[ channel - 1 ] = function; } } /*--------------------------------------------------------------------- Function: MIDI_AllNotesOff Sends all notes off commands on all midi channels. ---------------------------------------------------------------------*/ int32_t MIDI_AllNotesOff ( void ) { int32_t channel; for (channel = 0; channel < NUM_MIDI_CHANNELS; channel++) { _MIDI_SendControlChange(channel, 0x40, 0); _MIDI_SendControlChange(channel, MIDI_ALL_NOTES_OFF, 0); _MIDI_SendControlChange(channel, 0x78, 0); } return(MIDI_Ok); } /*--------------------------------------------------------------------- Function: _MIDI_SetChannelVolume Sets the volume of the specified midi channel. ---------------------------------------------------------------------*/ static void _MIDI_SetChannelVolume ( int32_t channel, int32_t volume ) { int32_t status; int32_t remotevolume; _MIDI_ChannelVolume[ channel ] = volume; if (_MIDI_RerouteFunctions[ channel ] != NULL) { remotevolume = volume * _MIDI_TotalVolume; remotevolume *= _MIDI_UserChannelVolume[ channel ]; remotevolume /= MIDI_MaxVolume; remotevolume >>= 8; status = _MIDI_RerouteFunctions[ channel ](0xB0 + channel, MIDI_VOLUME, remotevolume); if (status == MIDI_DONT_PLAY) { return; } } if (_MIDI_Funcs == NULL) { return; } if (_MIDI_Funcs->ControlChange == NULL) { return; } // For user volume volume *= _MIDI_UserChannelVolume[ channel ]; if (_MIDI_Funcs->SetVolume == NULL) { volume *= _MIDI_TotalVolume; volume /= MIDI_MaxVolume; } // For user volume volume >>= 8; _MIDI_Funcs->ControlChange(channel, MIDI_VOLUME, volume); } /*--------------------------------------------------------------------- Function: MIDI_SetUserChannelVolume Sets the volume of the specified midi channel. ---------------------------------------------------------------------*/ void MIDI_SetUserChannelVolume ( int32_t channel, int32_t volume ) { // Convert channel from 1-16 to 0-15 channel--; volume = max(0, volume); volume = min(volume, 256); if ((channel >= 0) && (channel < NUM_MIDI_CHANNELS)) { _MIDI_UserChannelVolume[ channel ] = volume; _MIDI_SetChannelVolume(channel, _MIDI_ChannelVolume[ channel ]); } } /*--------------------------------------------------------------------- Function: MIDI_ResetUserChannelVolume Sets the volume of the specified midi channel. ---------------------------------------------------------------------*/ void MIDI_ResetUserChannelVolume ( void ) { int32_t channel; for (channel = 0; channel < NUM_MIDI_CHANNELS; channel++) { _MIDI_UserChannelVolume[ channel ] = 256; } _MIDI_SendChannelVolumes(); } /*--------------------------------------------------------------------- Function: _MIDI_SendChannelVolumes Sets the volume on all the midi channels. ---------------------------------------------------------------------*/ static void _MIDI_SendChannelVolumes ( void ) { int32_t channel; for (channel = 0; channel < NUM_MIDI_CHANNELS; channel++) { _MIDI_SetChannelVolume(channel, _MIDI_ChannelVolume[ channel ]); } } /*--------------------------------------------------------------------- Function: MIDI_Reset Resets the MIDI device to General Midi defaults. ---------------------------------------------------------------------*/ int32_t MIDI_Reset ( void ) { int32_t channel; MIDI_AllNotesOff(); for (channel = 0; channel < NUM_MIDI_CHANNELS; channel++) { _MIDI_SendControlChange(channel, MIDI_RESET_ALL_CONTROLLERS, 0); _MIDI_SendControlChange(channel, MIDI_RPN_MSB, MIDI_PITCHBEND_MSB); _MIDI_SendControlChange(channel, MIDI_RPN_LSB, MIDI_PITCHBEND_LSB); _MIDI_SendControlChange(channel, MIDI_DATAENTRY_MSB, 2); /* Pitch Bend Sensitivity MSB */ _MIDI_SendControlChange(channel, MIDI_DATAENTRY_LSB, 0); /* Pitch Bend Sensitivity LSB */ _MIDI_ChannelVolume[ channel ] = GENMIDI_DefaultVolume; } _MIDI_SendChannelVolumes(); Reset = TRUE; return(MIDI_Ok); } /*--------------------------------------------------------------------- Function: MIDI_SetVolume Sets the total volume of the music. ---------------------------------------------------------------------*/ int32_t MIDI_SetVolume ( int32_t volume ) { int32_t i; if (_MIDI_Funcs == NULL) { return(MIDI_NullMidiModule); } volume = min(MIDI_MaxVolume, volume); volume = max(0, volume); _MIDI_TotalVolume = volume; if (_MIDI_Funcs->SetVolume) { _MIDI_Funcs->SetVolume(volume); for (i = 0; i < NUM_MIDI_CHANNELS; i++) { if (_MIDI_RerouteFunctions[ i ] != NULL) { _MIDI_SetChannelVolume(i, _MIDI_ChannelVolume[ i ]); } } } else { _MIDI_SendChannelVolumes(); } return(MIDI_Ok); } /*--------------------------------------------------------------------- Function: MIDI_GetVolume Returns the total volume of the music. ---------------------------------------------------------------------*/ int32_t MIDI_GetVolume ( void ) { int32_t volume; if (_MIDI_Funcs == NULL) { return(MIDI_NullMidiModule); } if (_MIDI_Funcs->GetVolume) { volume = _MIDI_Funcs->GetVolume(); } else { volume = _MIDI_TotalVolume; } return(volume); } /*--------------------------------------------------------------------- Function: MIDI_SetContext Sets the song context. ---------------------------------------------------------------------*/ void MIDI_SetContext ( int32_t context ) { if ((context > 0) && (context < EMIDI_NUM_CONTEXTS)) { _MIDI_Context = context; } } /*--------------------------------------------------------------------- Function: MIDI_GetContext Returns the current song context. ---------------------------------------------------------------------*/ int32_t MIDI_GetContext ( void ) { return _MIDI_Context; } /*--------------------------------------------------------------------- Function: MIDI_SetLoopFlag Sets whether the song should loop when finished or not. ---------------------------------------------------------------------*/ void MIDI_SetLoopFlag ( int32_t loopflag ) { _MIDI_Loop = loopflag; } /*--------------------------------------------------------------------- Function: MIDI_ContinueSong Continues playback of a paused song. ---------------------------------------------------------------------*/ void MIDI_ContinueSong ( void ) { if (_MIDI_SongLoaded) { _MIDI_SongActive = TRUE; MPU_Unpause(); } } /*--------------------------------------------------------------------- Function: MIDI_PauseSong Pauses playback of the current song. ---------------------------------------------------------------------*/ void MIDI_PauseSong ( void ) { if (_MIDI_SongLoaded) { _MIDI_SongActive = FALSE; MIDI_AllNotesOff(); MPU_Pause(); } } /*--------------------------------------------------------------------- Function: MIDI_SongPlaying Returns whether a song is playing or not. ---------------------------------------------------------------------*/ int32_t MIDI_SongPlaying ( void ) { return(_MIDI_SongActive); } /*--------------------------------------------------------------------- Function: MIDI_SetMidiFuncs Selects the routines that send the MIDI data to the music device. ---------------------------------------------------------------------*/ void MIDI_SetMidiFuncs ( midifuncs *funcs ) { _MIDI_Funcs = funcs; } /*--------------------------------------------------------------------- Function: MIDI_StopSong Stops playback of the currently playing song. ---------------------------------------------------------------------*/ void MIDI_StopSong ( void ) { if (_MIDI_SongLoaded) { _MIDI_SongActive = FALSE; _MIDI_SongLoaded = FALSE; MIDI_Reset(); _MIDI_ResetTracks(); if (_MIDI_Funcs->ReleasePatches) { _MIDI_Funcs->ReleasePatches(); } Bfree(_MIDI_TrackPtr); _MIDI_TrackPtr = NULL; _MIDI_NumTracks = 0; _MIDI_TrackMemSize = 0; _MIDI_TotalTime = 0; _MIDI_TotalTicks = 0; _MIDI_TotalBeats = 0; _MIDI_TotalMeasures = 0; MPU_Reset(); } } /*--------------------------------------------------------------------- Function: MIDI_PlaySong Begins playback of a MIDI song. ---------------------------------------------------------------------*/ int32_t MIDI_PlaySong ( char *song, int32_t loopflag ) { int32_t numtracks; int32_t format; int32_t headersize; int32_t tracklength; track *CurrentTrack; char *ptr; if (_MIDI_SongLoaded) { MIDI_StopSong(); } MPU_Init(MUSIC_SoundDevice); _MIDI_Loop = loopflag; if (_MIDI_Funcs == NULL) { return(MIDI_NullMidiModule); } if (*(uint32_t *)song != MIDI_HEADER_SIGNATURE) { return(MIDI_InvalidMidiFile); } song += 4; headersize = _MIDI_ReadNumber(song, 4); song += 4; format = _MIDI_ReadNumber(song, 2); _MIDI_NumTracks = _MIDI_ReadNumber(song + 2, 2); _MIDI_Division = _MIDI_ReadNumber(song + 4, 2); if (_MIDI_Division < 0) { // If a SMPTE time division is given, just set to 96 so no errors occur _MIDI_Division = 96; } if (format > MAX_FORMAT) { return(MIDI_UnknownMidiFormat); } ptr = song + headersize; if (_MIDI_NumTracks == 0) { return(MIDI_NoTracks); } _MIDI_TrackMemSize = _MIDI_NumTracks * sizeof(track); _MIDI_TrackPtr = Bmalloc(_MIDI_TrackMemSize); if (_MIDI_TrackPtr == NULL) { return(MIDI_NoMemory); } CurrentTrack = _MIDI_TrackPtr; numtracks = _MIDI_NumTracks; while (numtracks--) { if (*(uint32_t *)ptr != MIDI_TRACK_SIGNATURE) { Bfree(_MIDI_TrackPtr); _MIDI_TrackPtr = NULL; _MIDI_TrackMemSize = 0; return(MIDI_InvalidTrack); } tracklength = _MIDI_ReadNumber(ptr + 4, 4); ptr += 8; CurrentTrack->start = ptr; ptr += tracklength; CurrentTrack++; } if (_MIDI_Funcs->GetVolume != NULL) { _MIDI_TotalVolume = _MIDI_Funcs->GetVolume(); } _MIDI_InitEMIDI(); if (_MIDI_Funcs->LoadPatch) { MIDI_LoadTimbres(); } _MIDI_ResetTracks(); if (!Reset) { MIDI_Reset(); } Reset = FALSE; MIDI_SetDivision(_MIDI_Division); //MIDI_SetTempo( 120 ); _MIDI_SongLoaded = TRUE; _MIDI_SongActive = TRUE; while (_MPU_BuffersWaiting < 4) _MIDI_ServiceRoutine(); MPU_BeginPlayback(); return(MIDI_Ok); } /*--------------------------------------------------------------------- Function: MIDI_SetTempo Sets the song tempo. ---------------------------------------------------------------------*/ void MIDI_SetTempo ( int32_t tempo ) { int32_t tickspersecond; MIDI_Tempo = tempo; tickspersecond = ((tempo) * _MIDI_Division) / 60; _MIDI_FPSecondsPerTick = (1 << TIME_PRECISION) / tickspersecond; MPU_SetTempo(tempo); } void MIDI_SetDivision(int32_t division) { MPU_SetDivision(division); } /*--------------------------------------------------------------------- Function: MIDI_GetTempo Returns the song tempo. ---------------------------------------------------------------------*/ int32_t MIDI_GetTempo ( void ) { return(MIDI_Tempo); } /*--------------------------------------------------------------------- Function: _MIDI_ProcessNextTick Sets the position of the song pointer. ---------------------------------------------------------------------*/ static int32_t _MIDI_ProcessNextTick ( void ) { int32_t event; int32_t channel; int32_t command; track *Track; int32_t tracknum; int32_t status; int32_t c1 = 0; int32_t c2 = 0; int32_t TimeSet = FALSE; Track = _MIDI_TrackPtr; tracknum = 0; while ((tracknum < _MIDI_NumTracks) && (Track != NULL)) { while ((Track->active) && (Track->delay == 0)) { GET_NEXT_EVENT(Track, event); if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL) { switch (event) { case MIDI_SYSEX : case MIDI_SYSEX_CONTINUE : _MIDI_SysEx(Track); break; case MIDI_META_EVENT : _MIDI_MetaEvent(Track); break; } if (Track->active) { Track->delay = _MIDI_ReadDelta(Track); } continue; } if (event & MIDI_RUNNING_STATUS) { Track->RunningStatus = event; } else { event = Track->RunningStatus; Track->pos--; } channel = GET_MIDI_CHANNEL(event); command = GET_MIDI_COMMAND(event); if (_MIDI_CommandLengths[ command ] > 0) { GET_NEXT_EVENT(Track, c1); if (_MIDI_CommandLengths[ command ] > 1) { GET_NEXT_EVENT(Track, c2); } } if (_MIDI_RerouteFunctions[ channel ] != NULL) { status = _MIDI_RerouteFunctions[ channel ](event, c1, c2); if (status == MIDI_DONT_PLAY) { Track->delay = _MIDI_ReadDelta(Track); continue; } } switch (command) { case MIDI_NOTE_OFF : break; case MIDI_NOTE_ON : break; case MIDI_POLY_AFTER_TCH : if (_MIDI_Funcs->PolyAftertouch) { _MIDI_Funcs->PolyAftertouch(channel, c1, c2); } break; case MIDI_CONTROL_CHANGE : TimeSet = _MIDI_InterpretControllerInfo(Track, TimeSet, channel, c1, c2); break; case MIDI_PROGRAM_CHANGE : if ((_MIDI_Funcs->ProgramChange) && (!Track->EMIDI_ProgramChange)) { _MIDI_Funcs->ProgramChange(channel, c1); } break; case MIDI_AFTER_TOUCH : if (_MIDI_Funcs->ChannelAftertouch) { _MIDI_Funcs->ChannelAftertouch(channel, c1); } break; case MIDI_PITCH_BEND : if (_MIDI_Funcs->PitchBend) { _MIDI_Funcs->PitchBend(channel, c1, c2); } break; default : break; } Track->delay = _MIDI_ReadDelta(Track); } Track->delay--; Track++; tracknum++; if (_MIDI_ActiveTracks == 0) { break; } } _MIDI_AdvanceTick(); return(TimeSet); } /*--------------------------------------------------------------------- Function: MIDI_SetSongTick Sets the position of the song pointer. ---------------------------------------------------------------------*/ void MIDI_SetSongTick ( uint32_t PositionInTicks ) { if (!_MIDI_SongLoaded) { return; } MIDI_PauseSong(); if (PositionInTicks < _MIDI_PositionInTicks) { _MIDI_ResetTracks(); MIDI_Reset(); } while (_MIDI_PositionInTicks < PositionInTicks) { if (_MIDI_ProcessNextTick()) { break; } if (_MIDI_ActiveTracks == 0) { _MIDI_ResetTracks(); if (!_MIDI_Loop) { return; } break; } } MIDI_SetVolume(_MIDI_TotalVolume); MIDI_ContinueSong(); } /*--------------------------------------------------------------------- Function: MIDI_SetSongTime Sets the position of the song pointer. ---------------------------------------------------------------------*/ void MIDI_SetSongTime ( uint32_t milliseconds ) { uint32_t mil; uint32_t sec; uint32_t newtime; if (!_MIDI_SongLoaded) { return; } MIDI_PauseSong(); mil = ((milliseconds % 1000) << TIME_PRECISION) / 1000; sec = (milliseconds / 1000) << TIME_PRECISION; newtime = sec + mil; if (newtime < _MIDI_Time) { _MIDI_ResetTracks(); MIDI_Reset(); } while (_MIDI_Time < newtime) { if (_MIDI_ProcessNextTick()) { break; } if (_MIDI_ActiveTracks == 0) { _MIDI_ResetTracks(); if (!_MIDI_Loop) { return; } break; } } MIDI_SetVolume(_MIDI_TotalVolume); MIDI_ContinueSong(); } /*--------------------------------------------------------------------- Function: MIDI_SetSongPosition Sets the position of the song pointer. ---------------------------------------------------------------------*/ void MIDI_SetSongPosition ( int32_t measure, int32_t beat, int32_t tick ) { uint32_t pos; if (!_MIDI_SongLoaded) { return; } MIDI_PauseSong(); pos = RELATIVE_BEAT(measure, beat, tick); if (pos < (uint32_t)RELATIVE_BEAT(_MIDI_Measure, _MIDI_Beat, _MIDI_Tick)) { _MIDI_ResetTracks(); MIDI_Reset(); } while ((uint32_t)RELATIVE_BEAT(_MIDI_Measure, _MIDI_Beat, _MIDI_Tick) < pos) { if (_MIDI_ProcessNextTick()) { break; } if (_MIDI_ActiveTracks == 0) { _MIDI_ResetTracks(); if (!_MIDI_Loop) { return; } break; } } MIDI_SetVolume(_MIDI_TotalVolume); MIDI_ContinueSong(); } /*--------------------------------------------------------------------- Function: MIDI_GetSongPosition Returns the position of the song pointer in Measures, beats, ticks. ---------------------------------------------------------------------*/ void MIDI_GetSongPosition ( songposition *pos ) { uint32_t mil; uint32_t sec; mil = (_MIDI_Time & ((1 << TIME_PRECISION) - 1)) * 1000; sec = _MIDI_Time >> TIME_PRECISION; pos->milliseconds = (mil >> TIME_PRECISION) + (sec * 1000); pos->tickposition = _MIDI_PositionInTicks; pos->measure = _MIDI_Measure; pos->beat = _MIDI_Beat; pos->tick = _MIDI_Tick; } /*--------------------------------------------------------------------- Function: MIDI_GetSongLength Returns the length of the song. ---------------------------------------------------------------------*/ void MIDI_GetSongLength ( songposition *pos ) { uint32_t mil; uint32_t sec; mil = (_MIDI_TotalTime & ((1 << TIME_PRECISION) - 1)) * 1000; sec = _MIDI_TotalTime >> TIME_PRECISION; pos->milliseconds = (mil >> TIME_PRECISION) + (sec * 1000); pos->measure = _MIDI_TotalMeasures; pos->beat = _MIDI_TotalBeats; pos->tick = _MIDI_TotalTicks; pos->tickposition = 0; } /*--------------------------------------------------------------------- Function: MIDI_InitEMIDI Sets up the EMIDI ---------------------------------------------------------------------*/ static void _MIDI_InitEMIDI ( void ) { int32_t event; int32_t command; int32_t channel; int32_t length; int32_t IncludeFound; track *Track; int32_t tracknum; int32_t type; int32_t c1; int32_t c2; type = EMIDI_GeneralMIDI; _MIDI_ResetTracks(); _MIDI_TotalTime = 0; _MIDI_TotalTicks = 0; _MIDI_TotalBeats = 0; _MIDI_TotalMeasures = 0; Track = _MIDI_TrackPtr; tracknum = 0; while ((tracknum < _MIDI_NumTracks) && (Track != NULL)) { _MIDI_Tick = 0; _MIDI_Beat = 1; _MIDI_Measure = 1; _MIDI_Time = 0; _MIDI_BeatsPerMeasure = 4; _MIDI_TicksPerBeat = _MIDI_Division; _MIDI_TimeBase = 4; _MIDI_PositionInTicks = 0; _MIDI_ActiveTracks = 0; _MIDI_Context = -1; Track->RunningStatus = 0; Track->active = TRUE; Track->EMIDI_ProgramChange = FALSE; Track->EMIDI_VolumeChange = FALSE; Track->EMIDI_IncludeTrack = TRUE; memset(Track->context, 0, sizeof(Track->context)); while (Track->delay > 0) { _MIDI_AdvanceTick(); Track->delay--; } IncludeFound = FALSE; while (Track->active) { GET_NEXT_EVENT(Track, event); if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL) { switch (event) { case MIDI_SYSEX : case MIDI_SYSEX_CONTINUE : _MIDI_SysEx(Track); break; case MIDI_META_EVENT : _MIDI_MetaEvent(Track); break; } if (Track->active) { Track->delay = _MIDI_ReadDelta(Track); while (Track->delay > 0) { _MIDI_AdvanceTick(); Track->delay--; } } continue; } if (event & MIDI_RUNNING_STATUS) { Track->RunningStatus = event; } else { event = Track->RunningStatus; Track->pos--; } channel = GET_MIDI_CHANNEL(event); command = GET_MIDI_COMMAND(event); length = _MIDI_CommandLengths[ command ]; if (command == MIDI_CONTROL_CHANGE) { if (*Track->pos == MIDI_MONO_MODE_ON) { length++; } GET_NEXT_EVENT(Track, c1); GET_NEXT_EVENT(Track, c2); length -= 2; switch (c1) { case EMIDI_LOOP_START : case EMIDI_SONG_LOOP_START : if (c2 == 0) { Track->context[ 0 ].loopcount = EMIDI_INFINITE; } else { Track->context[ 0 ].loopcount = c2; } Track->context[ 0 ].pos = Track->pos; Track->context[ 0 ].loopstart = Track->pos; Track->context[ 0 ].RunningStatus = Track->RunningStatus; Track->context[ 0 ].time = _MIDI_Time; Track->context[ 0 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick; Track->context[ 0 ].tick = _MIDI_Tick; Track->context[ 0 ].beat = _MIDI_Beat; Track->context[ 0 ].measure = _MIDI_Measure; Track->context[ 0 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure; Track->context[ 0 ].TicksPerBeat = _MIDI_TicksPerBeat; Track->context[ 0 ].TimeBase = _MIDI_TimeBase; break; case EMIDI_LOOP_END : case EMIDI_SONG_LOOP_END : if (c2 == EMIDI_END_LOOP_VALUE) { Track->context[ 0 ].loopstart = NULL; Track->context[ 0 ].loopcount = 0; } break; case EMIDI_INCLUDE_TRACK : if (EMIDI_AffectsCurrentCard(c2, type)) { //printf( "Include track %d on card %d\n", tracknum, c2 ); IncludeFound = TRUE; Track->EMIDI_IncludeTrack = TRUE; } else if (!IncludeFound) { //printf( "Track excluded %d on card %d\n", tracknum, c2 ); IncludeFound = TRUE; Track->EMIDI_IncludeTrack = FALSE; } break; case EMIDI_EXCLUDE_TRACK : if (EMIDI_AffectsCurrentCard(c2, type)) { //printf( "Exclude track %d on card %d\n", tracknum, c2 ); Track->EMIDI_IncludeTrack = FALSE; } break; case EMIDI_PROGRAM_CHANGE : if (!Track->EMIDI_ProgramChange) //printf( "Program change on track %d\n", tracknum ); Track->EMIDI_ProgramChange = TRUE; break; case EMIDI_VOLUME_CHANGE : if (!Track->EMIDI_VolumeChange) //printf( "Volume change on track %d\n", tracknum ); Track->EMIDI_VolumeChange = TRUE; break; case EMIDI_CONTEXT_START : if ((c2 > 0) && (c2 < EMIDI_NUM_CONTEXTS)) { Track->context[ c2 ].pos = Track->pos; Track->context[ c2 ].loopstart = Track->context[ 0 ].loopstart; Track->context[ c2 ].loopcount = Track->context[ 0 ].loopcount; Track->context[ c2 ].RunningStatus = Track->RunningStatus; Track->context[ c2 ].time = _MIDI_Time; Track->context[ c2 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick; Track->context[ c2 ].tick = _MIDI_Tick; Track->context[ c2 ].beat = _MIDI_Beat; Track->context[ c2 ].measure = _MIDI_Measure; Track->context[ c2 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure; Track->context[ c2 ].TicksPerBeat = _MIDI_TicksPerBeat; Track->context[ c2 ].TimeBase = _MIDI_TimeBase; } break; case EMIDI_CONTEXT_END : break; } } Track->pos += length; Track->delay = _MIDI_ReadDelta(Track); while (Track->delay > 0) { _MIDI_AdvanceTick(); Track->delay--; } } _MIDI_TotalTime = max(_MIDI_TotalTime, _MIDI_Time); if (RELATIVE_BEAT(_MIDI_Measure, _MIDI_Beat, _MIDI_Tick) > RELATIVE_BEAT(_MIDI_TotalMeasures, _MIDI_TotalBeats, _MIDI_TotalTicks)) { _MIDI_TotalTicks = _MIDI_Tick; _MIDI_TotalBeats = _MIDI_Beat; _MIDI_TotalMeasures = _MIDI_Measure; } Track++; tracknum++; } _MIDI_ResetTracks(); } /*--------------------------------------------------------------------- Function: MIDI_LoadTimbres Preloads the timbres on cards that use patch-caching. ---------------------------------------------------------------------*/ void MIDI_LoadTimbres ( void ) { int32_t event; int32_t command; int32_t channel; int32_t length; int32_t Finished; track *Track; int32_t tracknum; Track = _MIDI_TrackPtr; tracknum = 0; while ((tracknum < _MIDI_NumTracks) && (Track != NULL)) { Finished = FALSE; while (!Finished) { GET_NEXT_EVENT(Track, event); if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL) { switch (event) { case MIDI_SYSEX : case MIDI_SYSEX_CONTINUE : length = _MIDI_ReadDelta(Track); Track->pos += length; break; case MIDI_META_EVENT : GET_NEXT_EVENT(Track, command); GET_NEXT_EVENT(Track, length); if (command == MIDI_END_OF_TRACK) { Finished = TRUE; } Track->pos += length; break; } if (!Finished) { _MIDI_ReadDelta(Track); } continue; } if (event & MIDI_RUNNING_STATUS) { Track->RunningStatus = event; } else { event = Track->RunningStatus; Track->pos--; } channel = GET_MIDI_CHANNEL(event); command = GET_MIDI_COMMAND(event); length = _MIDI_CommandLengths[ command ]; if (command == MIDI_CONTROL_CHANGE) { if (*Track->pos == MIDI_MONO_MODE_ON) { length++; } if (*Track->pos == EMIDI_PROGRAM_CHANGE) { _MIDI_Funcs->LoadPatch(*(Track->pos + 1)); } } if (channel == MIDI_RHYTHM_CHANNEL) { if (command == MIDI_NOTE_ON) { _MIDI_Funcs->LoadPatch(128 + *Track->pos); } } else { if (command == MIDI_PROGRAM_CHANGE) { _MIDI_Funcs->LoadPatch(*Track->pos); } } Track->pos += length; _MIDI_ReadDelta(Track); } Track++; tracknum++; } _MIDI_ResetTracks(); } void MIDI_UpdateMusic(void) { if (!_MIDI_SongLoaded || !_MIDI_SongActive) return; while (_MPU_BuffersWaiting < 4) _MIDI_ServiceRoutine(); }