raze/polymer/eduke32/source/midi.c

2063 lines
50 KiB
C
Raw Normal View History

//-------------------------------------------------------------------------
/*
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 <stdlib.h>
#include <string.h>
#include "standard.h"
#include "music.h"
#include "_midi.h"
#include "midi.h"
#include "mpu401.h"
#include "compat.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
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, int32_t, int32_t);
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, int32_t, int32_t)
)
{
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();
}