From f04d92abfa82bf2538fe78fdbf22c00e140a9b11 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 19 Aug 2001 21:52:48 +0000 Subject: [PATCH] Florian Schulze - Sun Aug 19 14:55:37 PDT 2001 * Added native MIDI music support on Windows --- CHANGES | 2 + music.c | 104 +++++-- native_midi/native_midi.h | 72 +++++ native_midi/native_midi_win32.c | 461 ++++++++++++++++++++++++++++++++ 4 files changed, 624 insertions(+), 15 deletions(-) create mode 100644 native_midi/native_midi.h create mode 100644 native_midi/native_midi_win32.c diff --git a/CHANGES b/CHANGES index 1f1eee56..dbc94d8e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,7 @@ 1.2.1: +Florian Schulze - Sun Aug 19 14:55:37 PDT 2001 + * Added native MIDI music support on Windows Sam Lantinga - Sun Aug 19 02:20:55 PDT 2001 * Added Project Builder projects for building MacOS X framework Darrell Walisser - Sun Aug 19 00:47:22 PDT 2001 diff --git a/music.c b/music.c index 513eb36e..6d520e58 100644 --- a/music.c +++ b/music.c @@ -56,6 +56,9 @@ #endif #ifdef MID_MUSIC #include "timidity.h" +#ifdef USE_NATIVE_MIDI +#include "native_midi.h" +#endif #endif #ifdef OGG_MUSIC #include "music_ogg.h" @@ -97,6 +100,9 @@ struct _Mix_Music { #endif #ifdef MID_MUSIC MidiSong *midi; +#ifdef USE_NATIVE_MIDI + NativeMidiSong *nativemidi; +#endif #endif #ifdef OGG_MUSIC OGG_music *ogg; @@ -113,6 +119,9 @@ struct _Mix_Music { }; #ifdef MID_MUSIC static int timidity_ok; +#ifdef USE_NATIVE_MIDI +static int native_midi_ok; +#endif #endif /* Used to calculate fading steps */ @@ -224,7 +233,10 @@ void music_mixer(void *udata, Uint8 *stream, int len) #endif #ifdef MID_MUSIC case MUS_MID: - Timidity_PlaySome(stream, len/samplesize); + if ( timidity_ok ) { + int samples = len / samplesize; + Timidity_PlaySome(stream, samples); + } break; #endif #ifdef OGG_MUSIC @@ -312,11 +324,20 @@ int open_music(SDL_AudioSpec *mixer) } #endif #ifdef MID_MUSIC - samplesize = mixer->size/mixer->samples; - if ( Timidity_Init(mixer->freq, - mixer->format, mixer->channels, mixer->samples) == 0 ) { - timidity_ok = 1; + samplesize = mixer->size/mixer->samples; +#ifdef USE_NATIVE_MIDI + if ( native_midi_init() ) { + native_midi_ok = 1; } else { + native_midi_ok = 0; + } + timidity_ok = !native_midi_ok; +#else + timidity_ok = 1; +#endif + if ( timidity_ok && + (Timidity_Init(mixer->freq, mixer->format, + mixer->channels, mixer->samples) != 0) ) { timidity_ok = 0; } #endif @@ -395,14 +416,22 @@ Mix_Music *Mix_LoadMUS(const char *file) /* MIDI files have the magic four bytes "MThd" */ if ( strcmp(magic, "MThd") == 0 ) { music->type = MUS_MID; +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + music->data.nativemidi = native_midi_loadsong((char *)file); + if ( music->data.nativemidi == NULL ) { + Mix_SetError("%s", native_midi_error()); + music->error = 1; + } + } else +#endif if ( timidity_ok ) { music->data.midi = Timidity_LoadSong((char *)file); if ( music->data.midi == NULL ) { Mix_SetError("%s", Timidity_Error()); music->error = 1; } - } - else { + } else { Mix_SetError("%s", Timidity_Error()); music->error = 1; } @@ -484,7 +513,14 @@ void Mix_FreeMusic(Mix_Music *music) #endif #ifdef MID_MUSIC case MUS_MID: - Timidity_FreeSong(music->data.midi); +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + native_midi_freesong(music->data.nativemidi); + } else +#endif + if ( timidity_ok ) { + Timidity_FreeSong(music->data.midi); + } break; #endif #ifdef OGG_MUSIC @@ -532,8 +568,16 @@ static int lowlevel_play(Mix_Music *music) #endif #ifdef MID_MUSIC case MUS_MID: - Timidity_SetVolume(music_volume); - Timidity_Start(music->data.midi); +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + native_midi_setvolume(music_volume); + native_midi_start(music->data.nativemidi); + } else +#endif + if ( timidity_ok ) { + Timidity_SetVolume(music_volume); + Timidity_Start(music->data.midi); + } break; #endif #ifdef OGG_MUSIC @@ -629,7 +673,14 @@ int Mix_VolumeMusic(int volume) #endif #ifdef MID_MUSIC case MUS_MID: - Timidity_SetVolume(music_volume); +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + native_midi_setvolume(music_volume); + } else +#endif + if ( timidity_ok ) { + Timidity_SetVolume(music_volume); + } break; #endif #ifdef OGG_MUSIC @@ -670,7 +721,14 @@ static void lowlevel_halt(void) #endif #ifdef MID_MUSIC case MUS_MID: - Timidity_Stop(); +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + native_midi_stop(); + } else +#endif + if ( timidity_ok ) { + Timidity_Stop(); + } break; #endif #ifdef OGG_MUSIC @@ -761,6 +819,15 @@ void Mix_RewindMusic(void) case MUS_MP3: SMPEG_rewind(music_playing->data.mp3); break; +#endif +#ifdef MID_MUSIC + case MUS_MID: +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + native_midi_stop(); + } +#endif + break; #endif default: /* TODO: Implement this for other music backends */ @@ -802,8 +869,15 @@ int Mix_PlayingMusic(void) #endif #ifdef MID_MUSIC case MUS_MID: - if ( ! Timidity_Active() ) { - return(0); +#ifdef USE_NATIVE_MIDI + if ( native_midi_ok ) { + if ( ! native_midi_active() ) + return(0); + } else +#endif + if ( timidity_ok ) { + if ( ! Timidity_Active() ) + return(0); } break; #endif @@ -816,7 +890,7 @@ int Mix_PlayingMusic(void) #endif #ifdef MP3_MUSIC case MUS_MP3: - if(SMPEG_status(music_playing->data.mp3)!=SMPEG_PLAYING) + if ( SMPEG_status(music_playing->data.mp3) != SMPEG_PLAYING ) return(0); break; #endif diff --git a/native_midi/native_midi.h b/native_midi/native_midi.h new file mode 100644 index 00000000..b8334642 --- /dev/null +++ b/native_midi/native_midi.h @@ -0,0 +1,72 @@ +/* + native_midi: Hardware Midi support for the SDL_mixer library + Copyright (C) 2000 Florian 'Proff' Schulze + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Florian 'Proff' Schulze + florian.proff.schulze@gmx.net +*/ + +#ifndef _NATIVE_MIDI_H_ +#define _NATIVE_MIDI_H_ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#define MIDI_TRACKS 32 + +typedef unsigned char byte; +typedef struct MIDI /* a midi file */ +{ + int divisions; /* number of ticks per quarter note */ + struct { + unsigned char *data; /* MIDI message stream */ + int len; /* length of the track data */ + } track[MIDI_TRACKS]; + int loaded; +} MIDI; + +struct _NativeMidiSong { + MIDI mididata; + int MusicLoaded; + int MusicPlaying; + MIDIEVENT *MidiEvents[MIDI_TRACKS]; + MIDIHDR MidiStreamHdr; + MIDIEVENT *NewEvents; + int NewSize; + int NewPos; + int BytesRecorded[MIDI_TRACKS]; + int BufferSize[MIDI_TRACKS]; + int CurrentTrack; + int CurrentPos; +}; +#endif /* WINDOWS */ + +typedef struct _NativeMidiSong NativeMidiSong; + +int native_midi_init(); +NativeMidiSong *native_midi_loadsong(char *midifile); +void native_midi_freesong(NativeMidiSong *song); +void native_midi_start(NativeMidiSong *song); +void native_midi_stop(); +int native_midi_active(); +void native_midi_setvolume(int volume); +char *native_midi_error(); + +#endif /* _NATIVE_MIDI_H_ */ diff --git a/native_midi/native_midi_win32.c b/native_midi/native_midi_win32.c new file mode 100644 index 00000000..5b70e0aa --- /dev/null +++ b/native_midi/native_midi_win32.c @@ -0,0 +1,461 @@ +/* + native_midi: Hardware Midi support for the SDL_mixer library + Copyright (C) 2000,2001 Florian 'Proff' Schulze + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Florian 'Proff' Schulze + florian.proff.schulze@gmx.net +*/ + +/* everything below is currently one very big bad hack ;) Proff */ + +#include +#include +#include +#include "native_midi.h" + +#define XCHG_SHORT(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF)) +# define XCHG_LONG(x) ((((x)&0xFF)<<24) | \ + (((x)&0xFF00)<<8) | \ + (((x)&0xFF0000)>>8) | \ + (((x)>>24)&0xFF)) + +#define LE_SHORT(x) x +#define LE_LONG(x) x +#define BE_SHORT(x) XCHG_SHORT(x) +#define BE_LONG(x) XCHG_LONG(x) + +static UINT MidiDevice=MIDI_MAPPER; +static HMIDISTRM hMidiStream; +static NativeMidiSong *currentsong; + +static int getvl(NativeMidiSong *song) +{ + int l=0; + byte c; + for (;;) + { + c=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + l += (c & 0x7f); + if (!(c & 0x80)) + return l; + l<<=7; + } +} + +static void AddEvent(NativeMidiSong *song, DWORD at, DWORD type, byte event, byte a, byte b) +{ + MIDIEVENT *CurEvent; + + if ((song->BytesRecorded[song->CurrentTrack]+(int)sizeof(MIDIEVENT))>=song->BufferSize[song->CurrentTrack]) + { + song->BufferSize[song->CurrentTrack]+=100*sizeof(MIDIEVENT); + song->MidiEvents[song->CurrentTrack]=realloc(song->MidiEvents[song->CurrentTrack],song->BufferSize[song->CurrentTrack]); + } + CurEvent=(MIDIEVENT *)((byte *)song->MidiEvents[song->CurrentTrack]+song->BytesRecorded[song->CurrentTrack]); + memset(CurEvent,0,sizeof(MIDIEVENT)); + CurEvent->dwDeltaTime=at; + CurEvent->dwEvent=event+(a<<8)+(b<<16)+(type<<24); + song->BytesRecorded[song->CurrentTrack]+=3*sizeof(DWORD); +} + +static void MidiTracktoStream(NativeMidiSong *song) +{ + DWORD atime,len; + byte event,type,a,b,c; + byte laststatus,lastchan; + + song->CurrentPos=0; + laststatus=0; + lastchan=0; + atime=0; + for (;;) + { + if (song->CurrentPos>=song->mididata.track[song->CurrentTrack].len) + return; + atime+=getvl(song); + event=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + if(event==0xF0 || event == 0xF7) // SysEx event + { + len=getvl(song); + song->CurrentPos+=len; + } + else if(event==0xFF) // Meta event + { + type=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + len=getvl(song); + switch(type) + { + case 0x2f: + return; + case 0x51: // Tempo + a=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + c=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + AddEvent(song, atime, MEVT_TEMPO, c, b, a); + break; + default: + song->CurrentPos+=len; + break; + } + } + else + { + a=event; + if (a & 0x80) // status byte + { + lastchan=a & 0x0F; + laststatus=(a>>4) & 0x07; + a=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + a &= 0x7F; + } + switch(laststatus) + { + case 0: // Note off + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b &= 0x7F; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, b); + break; + + case 1: // Note on + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b &= 0x7F; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, b); + break; + + case 2: // Key Pressure + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b &= 0x7F; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, b); + break; + + case 3: // Control change + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b &= 0x7F; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, b); + break; + + case 4: // Program change + a &= 0x7f; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, 0); + break; + + case 5: // Channel pressure + a &= 0x7f; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, 0); + break; + + case 6: // Pitch wheel + b=song->mididata.track[song->CurrentTrack].data[song->CurrentPos]; + song->CurrentPos++; + b &= 0x7F; + AddEvent(song, atime, MEVT_SHORTMSG, (byte)((laststatus<<4)+lastchan+0x80), a, b); + break; + + default: + break; + } + } + } +} + +static int BlockOut(NativeMidiSong *song) +{ + MMRESULT err; + int BlockSize; + + if ((song->MusicLoaded) && (song->NewEvents)) + { + // proff 12/8/98: Added for savety + midiOutUnprepareHeader(hMidiStream,&song->MidiStreamHdr,sizeof(MIDIHDR)); + if (song->NewPos>=song->NewSize) + return 0; + BlockSize=(song->NewSize-song->NewPos); + if (BlockSize<=0) + return 0; + if (BlockSize>36000) + BlockSize=36000; + song->MidiStreamHdr.lpData=(void *)((byte *)song->NewEvents+song->NewPos); + song->NewPos+=BlockSize; + song->MidiStreamHdr.dwBufferLength=BlockSize; + song->MidiStreamHdr.dwBytesRecorded=BlockSize; + song->MidiStreamHdr.dwFlags=0; +// lprintf(LO_DEBUG,"Data: %p, Size: %i\n",MidiStreamHdr.lpData,BlockSize); + err=midiOutPrepareHeader(hMidiStream,&song->MidiStreamHdr,sizeof(MIDIHDR)); + if (err!=MMSYSERR_NOERROR) + return 0; + err=midiStreamOut(hMidiStream,&song->MidiStreamHdr,sizeof(MIDIHDR)); + return 0; + } + return 1; +} + +static void MIDItoStream(NativeMidiSong *song) +{ + int BufferPos[MIDI_TRACKS]; + MIDIEVENT *CurEvent; + MIDIEVENT *NewEvent; + int lTime; + int Dummy; + int Track; + + //if (!hMidiStream) + //return; + song->NewSize=0; + for (song->CurrentTrack=0;song->CurrentTrackCurrentTrack++) + { + song->MidiEvents[song->CurrentTrack]=NULL; + song->BytesRecorded[song->CurrentTrack]=0; + song->BufferSize[song->CurrentTrack]=0; + MidiTracktoStream(song); + song->NewSize+=song->BytesRecorded[song->CurrentTrack]; + BufferPos[song->CurrentTrack]=0; + } + song->NewEvents=realloc(song->NewEvents,song->NewSize); + if (song->NewEvents) + { + song->NewPos=0; + while (1) + { + lTime=INT_MAX; + Track=-1; + for (song->CurrentTrack=MIDI_TRACKS-1;song->CurrentTrack>=0;song->CurrentTrack--) + { + if ((song->BytesRecorded[song->CurrentTrack]>0) && (BufferPos[song->CurrentTrack]BytesRecorded[song->CurrentTrack])) + CurEvent=(MIDIEVENT *)((byte *)song->MidiEvents[song->CurrentTrack]+BufferPos[song->CurrentTrack]); + else + continue; + if ((int)CurEvent->dwDeltaTime<=lTime) + { + lTime=CurEvent->dwDeltaTime; + Track=song->CurrentTrack; + } + } + if (Track==-1) + break; + else + { + CurEvent=(MIDIEVENT *)((byte *)song->MidiEvents[Track]+BufferPos[Track]); + BufferPos[Track]+=12; + NewEvent=(MIDIEVENT *)((byte *)song->NewEvents+song->NewPos); + memcpy(NewEvent,CurEvent,12); + song->NewPos+=12; + } + } + song->NewPos=0; + lTime=0; + while (song->NewPosNewSize) + { + NewEvent=(MIDIEVENT *)((byte *)song->NewEvents+song->NewPos); + Dummy=NewEvent->dwDeltaTime; + NewEvent->dwDeltaTime-=lTime; + lTime=Dummy; + if ((song->NewPos+12)>=song->NewSize) + NewEvent->dwEvent |= MEVT_F_CALLBACK; + song->NewPos+=12; + } + song->NewPos=0; + song->MusicLoaded=1; + //BlockOut(song); + } + for (song->CurrentTrack=0;song->CurrentTrackCurrentTrack++) + { + if (song->MidiEvents[song->CurrentTrack]) + free(song->MidiEvents[song->CurrentTrack]); + } +} + +void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD dwInstance, + DWORD dwParam1, DWORD dwParam2 ) +{ + switch( uMsg ) + { + case MOM_DONE: + if ((currentsong->MusicLoaded) && ((DWORD)dwParam1 == (DWORD)¤tsong->MidiStreamHdr)) + BlockOut(currentsong); + break; + case MOM_POSITIONCB: + if ((currentsong->MusicLoaded) && ((DWORD)dwParam1 == (DWORD)¤tsong->MidiStreamHdr)) + currentsong->MusicPlaying=0; + break; + default: + break; + } +} + +int native_midi_init() +{ + MMRESULT merr; + HMIDISTRM MidiStream; + + merr=midiStreamOpen(&MidiStream,&MidiDevice,1,(DWORD)&MidiProc,0,CALLBACK_FUNCTION); + if (merr!=MMSYSERR_NOERROR) + MidiStream=0; + midiStreamClose(MidiStream); + if (!MidiStream) + return 0; + else + return 1; +} + +typedef struct +{ + char ID[4]; + int size; + short format; + short tracks; + short deltatime; +} fmidihdr; + +typedef struct +{ + char ID[4]; + int size; +} fmiditrack; + +static void load_mididata(MIDI *mididata, FILE *fp) +{ + fmidihdr midihdr; + fmiditrack trackhdr; + int i; + + if (!mididata) + return; + if (!fp) + return; + fread(&midihdr,1,14,fp); + mididata->divisions=BE_SHORT(midihdr.deltatime); + for (i=0;itrack[i].len=BE_LONG(trackhdr.size); + mididata->track[i].data=malloc(mididata->track[i].len); + if (mididata->track[i].data) + fread(mididata->track[i].data,1,mididata->track[i].len,fp); + } + mididata->loaded=1; +} + +NativeMidiSong *native_midi_loadsong(char *midifile) +{ + FILE *fp; + NativeMidiSong *newsong; + + newsong=malloc(sizeof(NativeMidiSong)); + if (!newsong) + return NULL; + memset(newsong,0,sizeof(NativeMidiSong)); + /* Open the file */ + fp = fopen(midifile, "rb"); + if ( fp != NULL ) + { + newsong->mididata.loaded=0; + load_mididata(&newsong->mididata, fp); + if (!newsong->mididata.loaded) + { + free(newsong); + fclose(fp); + return NULL; + } + fclose(fp); + } + else + { + free(newsong); + return NULL; + } + MIDItoStream(newsong); + + return newsong; +} + +void native_midi_freesong(NativeMidiSong *song) +{ + if (hMidiStream) + { + midiStreamStop(hMidiStream); + midiStreamClose(hMidiStream); + } + if (song) + { + if (song->NewEvents) + free(song->NewEvents); + free(song); + } +} + +void native_midi_start(NativeMidiSong *song) +{ + MMRESULT merr; + MIDIPROPTIMEDIV mptd; + + native_midi_stop(); + if (!hMidiStream) + { + merr=midiStreamOpen(&hMidiStream,&MidiDevice,1,(DWORD)&MidiProc,0,CALLBACK_FUNCTION); + if (merr!=MMSYSERR_NOERROR) + { + hMidiStream=0; + return; + } + //midiStreamStop(hMidiStream); +// MusicLoop=looping; + currentsong=song; + currentsong->NewPos=0; + currentsong->MusicPlaying=1; + mptd.cbStruct=sizeof(MIDIPROPTIMEDIV); + mptd.dwTimeDiv=currentsong->mididata.divisions; + merr=midiStreamProperty(hMidiStream,(LPBYTE)&mptd,MIDIPROP_SET | MIDIPROP_TIMEDIV); + BlockOut(song); + merr=midiStreamRestart(hMidiStream); + } +} + +void native_midi_stop() +{ + if (!hMidiStream) + return; + midiStreamStop(hMidiStream); + midiStreamClose(hMidiStream); + currentsong=NULL; + hMidiStream = 0; +} + +int native_midi_active() +{ + return currentsong->MusicPlaying; +} + +void native_midi_setvolume(int volume) +{ +} + +char *native_midi_error() +{ + return ""; +} +