Browse Source

New MIDI framework: base & WIN32 & UWP

LouYihua 7 years ago
parent
commit
bcdb2ae6fc
11 changed files with 663 additions and 539 deletions
  1. 0 1
      input.c
  2. 53 91
      midi.c
  3. 1 9
      midi.h
  4. 164 26
      native_midi/native_midi.h
  5. 7 0
      native_midi/native_midi_common.h
  6. 2 6
      util.c
  7. 0 318
      win32/native_midi.c
  8. 348 0
      win32/native_midi.cpp
  9. 1 1
      win32/sdlpal.vcxproj
  10. 1 1
      win32/sdlpal.vcxproj.filters
  11. 86 86
      winrt/native_midi.cpp

+ 0 - 1
input.c

@@ -1018,7 +1018,6 @@ PAL_ProcessEvent(
 
 --*/
 {
-   MIDI_CheckLoop();
    while (PAL_PollEvent(NULL));
 }
 

+ 53 - 91
midi.c

@@ -22,10 +22,8 @@
 
 #include "main.h"
 
-static int  iMidCurrent = -1;
-static BOOL fMidLoop = FALSE;
-
-static NativeMidiSong *g_pMid = NULL;
+static int  g_iMidiCurrent = -1;
+static NativeMidiSong *g_pMidi = NULL;
 
 void
 MIDI_Play(
@@ -34,92 +32,56 @@ MIDI_Play(
 )
 {
 #if PAL_HAS_NATIVEMIDI
-   if (g_pMid != NULL && iNumRIX == iMidCurrent && native_midi_active())
-   {
-      return;
-   }
-
-   AUDIO_PlayCDTrack(-1);
-   native_midi_freesong(g_pMid);
-   g_pMid = NULL;
-   iMidCurrent = -1;
-
-   if (!AUDIO_MusicEnabled() || iNumRIX <= 0)
-   {
-      return;
-   }
-
-   if (gConfig.fIsWIN95)
-   {
-      char filename[1024];
-      sprintf(filename, "%s/Musics/%.3d.mid", PAL_PREFIX, iNumRIX);
-
-      g_pMid = native_midi_loadsong(filename);
-      if (g_pMid != NULL)
-      {
-         native_midi_start(g_pMid);
-
-         iMidCurrent = iNumRIX;
-         fMidLoop = fLoop;
-      }
-   }
-
-   if (!g_pMid)
-   {
-      unsigned char   *buf;
-      int              size;
-      SDL_RWops       *rw;
-      FILE            *fp = UTIL_OpenFile("midi.mkf");
-
-      if (fp == NULL)
-      {
-         return;
-      }
-
-      if (iNumRIX > PAL_MKFGetChunkCount(fp))
-      {
-         fclose(fp);
-         return;
-      }
-
-      size = PAL_MKFGetChunkSize(iNumRIX, fp);
-      if (size <= 0)
-      {
-         fclose(fp);
-         return;
-      }
-
-      buf = (unsigned char *)UTIL_malloc(size);
-
-      PAL_MKFReadChunk((LPBYTE)buf, size, iNumRIX, fp);
-      fclose(fp);
-
-      rw = SDL_RWFromConstMem((const void *)buf, size);
-
-      g_pMid = native_midi_loadsong_RW(rw);
-      if (g_pMid != NULL)
-      {
-         native_midi_start(g_pMid);
-
-         iMidCurrent = iNumRIX;
-         fMidLoop = fLoop;
-      }
-
-      SDL_RWclose(rw);
-      free(buf);
-   }
-#endif
-}
-
-void
-MIDI_CheckLoop(
-   void
-)
-{
-#if PAL_HAS_NATIVEMIDI
-   if (fMidLoop && g_pMid != NULL && !native_midi_active())
-   {
-      MIDI_Play(iMidCurrent, TRUE);
-   }
+	if (native_midi_active(g_pMidi) && iNumRIX == g_iMidiCurrent)
+	{
+		return;
+	}
+
+	AUDIO_PlayCDTrack(-1);
+	native_midi_freesong(g_pMidi);
+	g_pMidi = NULL;
+	g_iMidiCurrent = -1;
+
+	if (!AUDIO_MusicEnabled() || iNumRIX <= 0)
+	{
+		return;
+	}
+
+	if (gConfig.fIsWIN95)
+	{
+		g_pMidi = native_midi_loadsong(va("%s/musics/%.3d.mid", PAL_PREFIX, iNumRIX));
+	}
+
+	if (!g_pMidi)
+	{
+		FILE    *fp  = NULL;
+		uint8_t *buf = NULL;
+		int      size;
+
+		if (fp = UTIL_OpenFile("midi.mkf"))
+		{
+			
+			if ((size = PAL_MKFGetChunkSize(iNumRIX, fp)) > 0 &&
+				(buf = (uint8_t*)UTIL_malloc(size)))
+			{
+				PAL_MKFReadChunk(buf, size, iNumRIX, fp);
+			}
+			fclose(fp);
+		}
+
+		if (buf)
+		{
+			SDL_RWops *rw = SDL_RWFromConstMem(buf, size);
+			g_pMidi = native_midi_loadsong_RW(rw);
+			SDL_RWclose(rw);
+			free(buf);
+		}
+	}
+
+	if (g_pMidi)
+	{
+		native_midi_start(g_pMidi, fLoop);
+		g_iMidiCurrent = iNumRIX;
+	}
 #endif
 }

+ 1 - 9
midi.h

@@ -26,8 +26,6 @@
 #include "common.h"
 #include "native_midi/native_midi.h"
 
-PAL_C_LINKAGE_BEGIN
-
 /*++
   Purpose:
 
@@ -44,17 +42,11 @@ PAL_C_LINKAGE_BEGIN
     None.
 
 --*/
+PAL_C_LINKAGE
 void
 MIDI_Play(
 	int       iNumRIX,
 	BOOL      fLoop
 );
 
-void
-MIDI_CheckLoop(
-	void
-);
-
-PAL_C_LINKAGE_END
-
 #endif

+ 164 - 26
native_midi/native_midi.h

@@ -1,24 +1,27 @@
-/*
-    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
-*/
+/* -*- mode: c; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- */
+//
+// Copyright (c) 2009-2011, Wei Mingzhi <whistler_wmz@users.sf.net>.
+// Copyright (c) 2011-2017, SDLPAL development team.
+// All rights reserved.
+//
+// This file is part of SDLPAL.
+//
+// SDLPAL is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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, see <http://www.gnu.org/licenses/>.
+//
+// native_midi.h: Header of native MIDI player for SDLPal.
+//       @Author: Lou Yihua, 2017
+//
 
 #ifndef _NATIVE_MIDI_H_
 #define _NATIVE_MIDI_H_
@@ -32,15 +35,150 @@ extern "C"
 {
 #endif
 
+/*++
+  Purpose:
+
+    Check whether there is an available MIDI player.
+
+  Parameters:
+
+    None.
+
+  Return value:
+
+    Returns 1 for success and 0 for failure.
+
+--*/
 int native_midi_detect();
+
+/*++
+  Purpose:
+
+    Load the MIDI song from given file.
+
+  Parameters:
+
+    [IN]  midifile - the MIDI file name.
+
+  Return value:
+
+    A NativeMidiSong object on success, NULL on failure.
+
+--*/
 NativeMidiSong *native_midi_loadsong(const char *midifile);
+
+/*++
+  Purpose:
+
+    Load the MIDI song from SDL_RWops stream.
+
+  Parameters:
+
+    [IN]  rw - pointer to the SDL_RWops stream.
+
+  Return value:
+
+    A NativeMidiSong object on success, NULL on failure.
+
+--*/
 NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw);
+
+/*++
+  Purpose:
+
+    Free a NativeMidiSong object returned from native_midi_loadsong_*.
+
+  Parameters:
+
+    [IN]  song - the NativeMidiSong object.
+
+  Return value:
+
+    None.
+
+--*/
 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);
-const char *native_midi_error(void);
+
+/*++
+  Purpose:
+
+    Start playing the MIDI.
+
+  Parameters:
+
+    [IN]  song    - the NativeMidiSong object.
+	[IN]  looping - whether the play should be automatically looped.
+
+  Return value:
+
+    None.
+
+--*/
+void native_midi_start(NativeMidiSong *song, int looping);
+
+/*++
+  Purpose:
+
+    Stop playing the MIDI.
+
+  Parameters:
+
+    [IN]  song - the NativeMidiSong object.
+
+  Return value:
+
+    None.
+
+--*/
+void native_midi_stop(NativeMidiSong *song);
+
+/*++
+  Purpose:
+
+    Check whether the MIDI is now playing.
+
+  Parameters:
+
+    [IN]  song - the NativeMidiSong object.
+
+  Return value:
+
+    Returns 1 if MIDI is now playing, and 0 otherwise.
+
+--*/
+int native_midi_active(NativeMidiSong *song);
+
+/*++
+  Purpose:
+
+    Set the volume for the MIDI playing.
+
+  Parameters:
+
+    [IN]  song - the NativeMidiSong object.
+
+  Return value:
+
+    None.
+
+--*/
+void native_midi_setvolume(NativeMidiSong *song, int volume);
+
+/*++
+  Purpose:
+
+    Get the last error if any.
+
+  Parameters:
+
+    [IN]  song - the NativeMidiSong object.
+
+  Return value:
+
+    None.
+
+--*/
+const char *native_midi_error(NativeMidiSong *song);
 
 #ifdef __cplusplus
 }

+ 7 - 0
native_midi/native_midi_common.h

@@ -54,6 +54,10 @@ typedef struct MIDIEvent
 	struct MIDIEvent *next;
 } MIDIEvent;
 
+#ifdef __cplusplus
+extern "C"
+{
+#endif
 
 /* Load a midifile to memory, converting it to a list of MIDIEvents.
    This function returns a linked lists of MIDIEvents, 0 if an error occured.
@@ -63,5 +67,8 @@ MIDIEvent *CreateMIDIEventList(SDL_RWops *rw, Uint16 *division);
 /* Release a MIDIEvent list after usage. */
 void FreeMIDIEventList(MIDIEvent *head);
 
+#ifdef __cplusplus
+}
+#endif
 
 #endif /* _NATIVE_MIDI_COMMON_H_ */

+ 2 - 6
util.c

@@ -118,11 +118,11 @@ va(
 
 --*/
 {
-   static char string[256];
+   static char string[1024];
    va_list     argptr;
 
    va_start(argptr, format);
-   vsnprintf(string, 256, format, argptr);
+   vsnprintf(string, sizeof(string), format, argptr);
    va_end(argptr);
 
    return string;
@@ -296,10 +296,6 @@ UTIL_Delay(
       SDL_Delay(1);
       while (PAL_PollEvent(NULL));
    }
-
-#if PAL_HAS_NATIVEMIDI
-   MIDI_CheckLoop();
-#endif
 }
 
 void

+ 0 - 318
win32/native_midi.c

@@ -1,318 +0,0 @@
-/*
-    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
-*/
-
-#include "SDL.h"
-
-/* everything below is currently one very big bad hack ;) Proff */
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <windowsx.h>
-#include <mmsystem.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include "native_midi/native_midi.h"
-#include "native_midi/native_midi_common.h"
-
-struct _NativeMidiSong {
-  int MusicLoaded;
-  int MusicPlaying;
-  MIDIHDR MidiStreamHdr;
-  MIDIEVENT *NewEvents;
-	Uint16 ppqn;
-  int Size;
-  int NewPos;
-};
-
-static UINT MidiDevice=MIDI_MAPPER;
-static HMIDISTRM hMidiStream;
-static NativeMidiSong *currentsong;
-
-static int BlockOut(NativeMidiSong *song)
-{
-  MMRESULT err;
-  int BlockSize;
-
-  if ((song->MusicLoaded) && (song->NewEvents))
-  {
-    // proff 12/8/98: Added for savety
-    midiOutUnprepareHeader((HMIDIOUT)hMidiStream,&song->MidiStreamHdr,sizeof(MIDIHDR));
-    if (song->NewPos>=song->Size)
-      return 0;
-    BlockSize=(song->Size-song->NewPos);
-    if (BlockSize<=0)
-      return 0;
-    if (BlockSize>36000)
-      BlockSize=36000;
-    song->MidiStreamHdr.lpData=(void *)((unsigned char *)song->NewEvents+song->NewPos);
-    song->NewPos+=BlockSize;
-    song->MidiStreamHdr.dwBufferLength=BlockSize;
-    song->MidiStreamHdr.dwBytesRecorded=BlockSize;
-    song->MidiStreamHdr.dwFlags=0;
-    err=midiOutPrepareHeader((HMIDIOUT)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, MIDIEvent *evntlist)
-{
-  int eventcount;
-  MIDIEvent *event;
-  MIDIEVENT *newevent;
-
-  eventcount=0;
-  event=evntlist;
-  while (event)
-  {
-    eventcount++;
-    event=event->next;
-  }
-  song->NewEvents=malloc(eventcount*3*sizeof(DWORD));
-  if (!song->NewEvents)
-    return;
-  memset(song->NewEvents,0,(eventcount*3*sizeof(DWORD)));
-
-  eventcount=0;
-  event=evntlist;
-  newevent=song->NewEvents;
-  while (event)
-  {
-		int status = (event->status&0xF0)>>4;
-		switch (status)
-		{
-		case MIDI_STATUS_NOTE_OFF:
-		case MIDI_STATUS_NOTE_ON:
-		case MIDI_STATUS_AFTERTOUCH:
-		case MIDI_STATUS_CONTROLLER:
-		case MIDI_STATUS_PROG_CHANGE:
-		case MIDI_STATUS_PRESSURE:
-		case MIDI_STATUS_PITCH_WHEEL:
-      newevent->dwDeltaTime=event->time;
-		  newevent->dwEvent=(event->status|0x80)|(event->data[0]<<8)|(event->data[1]<<16)|(MEVT_SHORTMSG<<24);
-      newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
-      eventcount++;
-			break;
-
-		case MIDI_STATUS_SYSEX:
-			if (event->status == 0xFF && event->data[0] == 0x51) /* Tempo change */
-			{
-				int tempo = (event->extraData[0] << 16) |
-					          (event->extraData[1] << 8) |
-					           event->extraData[2];
-        newevent->dwDeltaTime=event->time;
-				newevent->dwEvent=(MEVT_TEMPO<<24) | tempo;
-        newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
-        eventcount++;
-			}
-			break;
-    }
-
-    event=event->next;
-  }
-
-  song->Size=eventcount*3*sizeof(DWORD);
-
-  {
-    int time;
-    int temptime;
-
-    song->NewPos=0;
-    time=0;
-    newevent=song->NewEvents;
-    while (song->NewPos<song->Size)
-    {
-      temptime=newevent->dwDeltaTime;
-      newevent->dwDeltaTime-=time;
-      time=temptime;
-      if ((song->NewPos+12)>=song->Size)
-        newevent->dwEvent |= MEVT_F_CALLBACK;
-      newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
-      song->NewPos+=12;
-    }
-  }
-  song->NewPos=0;
-  song->MusicLoaded=1;
-}
-
-void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, unsigned long dwInstance,
-                        unsigned long dwParam1, unsigned long dwParam2 )
-{
-    switch( uMsg )
-    {
-    case MOM_DONE:
-      if ((currentsong->MusicLoaded) && (dwParam1 == (unsigned long)&currentsong->MidiStreamHdr))
-        BlockOut(currentsong);
-      break;
-    case MOM_POSITIONCB:
-      if ((currentsong->MusicLoaded) && (dwParam1 == (unsigned long)&currentsong->MidiStreamHdr))
-        currentsong->MusicPlaying=0;
-      break;
-    default:
-      break;
-    }
-}
-
-int native_midi_detect()
-{
-  MMRESULT merr;
-  HMIDISTRM MidiStream;
-
-  merr=midiStreamOpen(&MidiStream,&MidiDevice,(DWORD)1,(unsigned long)MidiProc,(unsigned long)0,CALLBACK_FUNCTION);
-  if (merr!=MMSYSERR_NOERROR)
-    return 0;
-  midiStreamClose(MidiStream);
-  return 1;
-}
-
-NativeMidiSong *native_midi_loadsong(const char *midifile)
-{
-	NativeMidiSong *newsong;
-	MIDIEvent		*evntlist = NULL;
-	SDL_RWops	*rw;
-
-	newsong=malloc(sizeof(NativeMidiSong));
-	if (!newsong)
-		return NULL;
-	memset(newsong,0,sizeof(NativeMidiSong));
-
-	/* Attempt to load the midi file */
-	rw = SDL_RWFromFile(midifile, "rb");
-	if (rw) {
-		evntlist = CreateMIDIEventList(rw, &newsong->ppqn);
-		SDL_RWclose(rw);
-		if (!evntlist)
-		{
-			free(newsong);
-			return NULL;
-		}
-	}
-
-	MIDItoStream(newsong, evntlist);
-
-	FreeMIDIEventList(evntlist);
-
-	return newsong;
-}
-
-NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
-{
-	NativeMidiSong *newsong;
-	MIDIEvent		*evntlist = NULL;
-
-	newsong=malloc(sizeof(NativeMidiSong));
-	if (!newsong)
-		return NULL;
-	memset(newsong,0,sizeof(NativeMidiSong));
-
-	/* Attempt to load the midi file */
-	evntlist = CreateMIDIEventList(rw, &newsong->ppqn);
-	if (!evntlist)
-	{
-		free(newsong);
-		return NULL;
-	}
-
-	MIDItoStream(newsong, evntlist);
-
-	FreeMIDIEventList(evntlist);
-
-	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);
-    currentsong=song;
-    currentsong->NewPos=0;
-    currentsong->MusicPlaying=1;
-    mptd.cbStruct=sizeof(MIDIPROPTIMEDIV);
-    mptd.dwTimeDiv=currentsong->ppqn;
-    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)
-{
-  int calcVolume;
-  if (volume > 128)
-    volume = 128;
-  if (volume < 0)
-    volume = 0;
-  calcVolume = (65535 * volume / 128);
-
-  midiOutSetVolume((HMIDIOUT)hMidiStream, MAKELONG(calcVolume , calcVolume));
-}
-
-const char *native_midi_error(void)
-{
-  return "";
-}
-

+ 348 - 0
win32/native_midi.cpp

@@ -0,0 +1,348 @@
+/*
+    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
+*/
+
+#include "SDL.h"
+
+/* everything below is currently one very big bad hack ;) Proff */
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmsystem.h>
+#include <stdlib.h>
+#include <memory>
+#include <future>
+#include <thread>
+#include <condition_variable>
+#include <mutex>
+#include <atomic>
+#include <vector>
+
+#include "native_midi/native_midi.h"
+#include "native_midi/native_midi_common.h"
+
+enum class MidiSystemMessage {
+	Exclusive = 0,
+	TimeCode = 1,
+	SongPositionPointer = 2,
+	SongSelect = 3,
+	TuneRequest = 6,
+	EndOfExclusive = 7,
+	TimingClock = 8,
+	Start = 10,
+	Continue = 11,
+	Stop = 12,
+	ActiveSensing = 14,
+	SystemReset = 15
+};
+
+struct MidiMessage
+{
+	virtual MMRESULT Send(HMIDIOUT hmo) = 0;
+};
+
+struct MidiShortMessage
+	: public MidiMessage
+{
+	uint32_t data;
+
+	MidiShortMessage(uint32_t data) : data(data) {}
+
+	virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutShortMsg(hmo, data); }
+};
+
+struct MidiLongMessage
+	: public MidiMessage
+{
+	MIDIHDR hdr;
+
+	MidiLongMessage(uint8_t* data, int length)
+	{
+		memset(&hdr, 0, sizeof(MIDIHDR));
+		hdr.lpData = (LPSTR)malloc(length);
+		hdr.dwBufferLength = hdr.dwBytesRecorded = length;
+		memcpy(hdr.lpData, data, length);
+	}
+
+	virtual MMRESULT Send(HMIDIOUT hmo)
+	{
+		MMRESULT retval;
+		if (MMSYSERR_NOERROR == (retval = midiOutPrepareHeader(hmo, &hdr, sizeof(MIDIHDR))))
+		{
+			retval = midiOutLongMsg(hmo, &hdr, sizeof(MIDIHDR));
+			midiOutUnprepareHeader(hmo, &hdr, sizeof(MIDIHDR));
+		}
+		return retval;
+	}
+};
+
+struct MidiResetMessage
+	: public MidiMessage
+{
+	virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutReset(hmo); }
+};
+
+struct MidiCustomMessage
+	: public MidiMessage
+{
+	uint32_t message;
+	uint32_t data1;
+	uint32_t data2;
+
+	MidiCustomMessage(uint8_t status, uint8_t data1, uint8_t data2)
+		: message(status), data1(data1), data2(data2)
+	{}
+
+	virtual MMRESULT SendEvent(HMIDIOUT hmo) { return midiOutMessage(hmo, message, data1, data2); }
+};
+
+struct MidiEvent
+{
+	std::unique_ptr<MidiMessage> message;
+	uint32_t                     deltaTime;		// time in ticks
+	uint32_t                     tempo;			// microseconds per quarter note
+
+	std::chrono::system_clock::duration DeltaTimeAsTick(uint16_t ppq)
+	{
+		return std::chrono::system_clock::duration((int64_t)deltaTime * tempo * 10 / ppq);
+	}
+
+	MMRESULT Send(HMIDIOUT hmo) { return message->Send(hmo); }
+};
+
+struct _NativeMidiSong {
+	std::vector<MidiEvent>  Events;
+	std::thread             Thread;
+	std::mutex              Mutex;
+	std::condition_variable Stop;
+	HMIDIOUT                Synthesizer;
+	int                     Size;
+	int                     Position;
+	Uint16                  ppq;		// parts (ticks) per quarter note
+	volatile bool           Playing;
+	bool                    Loaded;
+	bool                    Looping;
+
+	_NativeMidiSong()
+		: Synthesizer(nullptr), Size(0), Position(0)
+		, ppq(0), Playing(false), Loaded(false), Looping(false)
+	{ }
+};
+
+static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
+{
+	int eventcount = 0, prevtime = 0, tempo = 500000;
+
+	for (MIDIEvent* event = eventlist; event; event = event->next)
+	{
+		if (event->status != 0xFF)
+			eventcount++;
+	}
+
+	song->Events.resize(song->Size = eventcount);
+	song->Position = 0;
+	song->Loaded = true;
+
+	eventcount = 0;
+	for (MIDIEvent* event = eventlist; event; event = event->next)
+	{
+		MidiMessage* message = nullptr;
+		int status = (event->status & 0xF0) >> 4;
+		switch (status)
+		{
+		case MIDI_STATUS_NOTE_OFF:
+		case MIDI_STATUS_NOTE_ON:
+		case MIDI_STATUS_AFTERTOUCH:
+		case MIDI_STATUS_CONTROLLER:
+		case MIDI_STATUS_PROG_CHANGE:
+		case MIDI_STATUS_PRESSURE:
+		case MIDI_STATUS_PITCH_WHEEL:
+			message = new MidiShortMessage(event->status | (event->data[0] << 8) | (event->data[1] << 16));
+			break;
+
+		case MIDI_STATUS_SYSEX:
+			switch ((MidiSystemMessage)(event->status & 0xF))
+			{
+			case MidiSystemMessage::Exclusive:
+				message = new MidiLongMessage(event->extraData, event->extraLen);
+				break;
+
+			case MidiSystemMessage::TimeCode:
+			case MidiSystemMessage::SongSelect:
+				message = new MidiShortMessage(event->status | (event->extraData[0] << 8));
+				break;
+
+			case MidiSystemMessage::SongPositionPointer:
+				message = new MidiShortMessage(event->status | (event->extraData[0] << 8) | (event->extraData[1] << 16));
+				break;
+
+			case MidiSystemMessage::TuneRequest:
+			case MidiSystemMessage::TimingClock:
+			case MidiSystemMessage::Start:
+			case MidiSystemMessage::Continue:
+			case MidiSystemMessage::Stop:
+			case MidiSystemMessage::ActiveSensing:
+				message = new MidiShortMessage(event->status);
+				break;
+
+			case MidiSystemMessage::SystemReset:
+				// This message is only used as meta-event in MIDI files
+				if (event->data[0] == 0x51)
+					tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2];
+				break;
+			}
+			break;
+		}
+
+		if (message)
+		{
+			auto evt = &song->Events[eventcount++];
+			evt->message.reset(message);
+			evt->deltaTime = event->time - prevtime;
+			evt->tempo = tempo;
+			prevtime = event->time;
+		}
+	}
+}
+
+int native_midi_detect()
+{
+	HMIDIOUT out;
+
+	if (MMSYSERR_NOERROR == midiOutOpen(&out, MIDI_MAPPER, 0, 0, CALLBACK_NULL))
+	{
+		midiOutClose(out);
+		return 1;
+	}
+
+	return 0;
+}
+
+NativeMidiSong *native_midi_loadsong(const char *midifile)
+{
+	/* Attempt to load the midi file */
+	auto rw = SDL_RWFromFile(midifile, "rb");
+	return native_midi_loadsong_RW(rw);
+}
+
+NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
+{
+	std::unique_ptr<NativeMidiSong> newsong(new NativeMidiSong);
+
+	if (newsong)
+	{
+		auto eventlist = CreateMIDIEventList(rw, &newsong->ppq);
+
+		if (eventlist)
+		{
+			MIDItoStream(newsong.get(), eventlist);
+			FreeMIDIEventList(eventlist);
+			
+			if (midiOutOpen(&newsong->Synthesizer, MIDI_MAPPER, NULL, 0, CALLBACK_NULL) == MMSYSERR_NOERROR)
+				return newsong.release();
+		}
+	}
+
+	return nullptr;
+}
+
+void native_midi_freesong(NativeMidiSong *song)
+{
+	if (song)
+	{
+		native_midi_stop(song);
+		if (song->Synthesizer)
+			midiOutClose(song->Synthesizer);
+		delete song;
+	}
+}
+
+void native_midi_start(NativeMidiSong *song, int looping)
+{
+	if (!song) return;
+
+	native_midi_stop(song);
+
+	song->Playing = true;
+	song->Looping = looping ? true : false;
+	song->Thread = std::move(std::thread([](NativeMidiSong *song)->void {
+		auto time = std::chrono::system_clock::now();
+		while (song->Playing)
+		{
+			do
+			{
+				song->Events[song->Position++].Send(song->Synthesizer);
+			} while (song->Position < song->Size && song->Events[song->Position].deltaTime == 0);
+			if (song->Position < song->Size)
+			{
+				time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
+				while (song->Playing)
+				{
+					if (song->Stop.wait_until(std::unique_lock<std::mutex>(song->Mutex), time) == std::cv_status::timeout)
+						break;
+				}
+			}
+			else if (song->Playing = song->Looping)
+			{
+				song->Position = 0;
+				midiOutReset(song->Synthesizer);
+			}
+		}
+	}, song));
+}
+
+void native_midi_stop(NativeMidiSong *song)
+{
+	if (song)
+	{
+		song->Playing = false;
+		song->Stop.notify_all();
+		if (song->Thread.joinable())
+			song->Thread.join();
+		song->Thread = std::move(std::thread());
+		if (song->Synthesizer)
+			midiOutReset(song->Synthesizer);
+	}
+}
+
+int native_midi_active(NativeMidiSong *song)
+{
+	return (song && song->Playing) ? 1 : 0;
+}
+
+void native_midi_setvolume(NativeMidiSong *song, int volume)
+{
+	if (song && song->Synthesizer)
+	{
+		uint16_t calcVolume;
+		if (volume > 127)
+			volume = 127;
+		if (volume < 0)
+			volume = 0;
+		calcVolume = volume << 9;
+		midiOutSetVolume(song->Synthesizer, MAKELONG(calcVolume, calcVolume));
+	}
+}
+
+const char *native_midi_error(NativeMidiSong *song)
+{
+  return "";
+}

+ 1 - 1
win32/sdlpal.vcxproj

@@ -371,7 +371,7 @@
     <ClCompile Include="..\libmad\stream.c" />
     <ClCompile Include="..\libmad\synth.c" />
     <ClCompile Include="..\libmad\timer.c" />
-    <ClCompile Include="native_midi.c" />
+    <ClCompile Include="native_midi.cpp" />
     <ClCompile Include="win32.cpp" />
   </ItemGroup>
   <ItemGroup>

+ 1 - 1
win32/sdlpal.vcxproj.filters

@@ -323,7 +323,7 @@
     <ClCompile Include="..\tests\test_swprintf.cpp">
       <Filter>Test Files</Filter>
     </ClCompile>
-    <ClCompile Include="native_midi.c">
+    <ClCompile Include="native_midi.cpp">
       <Filter>native_midi</Filter>
     </ClCompile>
   </ItemGroup>

+ 86 - 86
winrt/native_midi.cpp

@@ -20,7 +20,7 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 // native_midi.cpp: Native Windows Runtime MIDI player for SDLPal.
-//         @author: Lou Yihua, 2017
+//         @Author: Lou Yihua, 2017
 //
 
 #include "pch.h"
@@ -34,43 +34,45 @@
 #include <mutex>
 #include <atomic>
 
-extern "C" {
 #include "native_midi/native_midi.h"
 #include "native_midi/native_midi_common.h"
-}
+
+using namespace Windows::Devices::Midi;
 
 struct MidiEvent
 {
-	Windows::Devices::Midi::IMidiMessage^ message;
-	uint32_t deltaTime;		// time in ticks
-	uint32_t tempo;			// microseconds per quarter note
+	IMidiMessage^ message;
+	uint32_t      deltaTime;		// time in ticks
+	uint32_t      tempo;			// microseconds per quarter note
 
 	std::chrono::system_clock::duration DeltaTimeAsTick(uint16_t ppq)
 	{
 		return std::chrono::system_clock::duration((int64_t)deltaTime * tempo * 10 / ppq);
 	}
+
+	void Send(MidiSynthesizer^ synthesizer) { synthesizer->SendMessage(message); }
 };
 
 struct _NativeMidiSong {
-	Windows::Devices::Midi::MidiSynthesizer^ Synthesizer;
-	MidiEvent* Events;
-	int        Size;
-	int        Position;
-	Uint16     ppq;		// parts (ticks) per quarter note
-	volatile bool       Playing;
-	bool       Loaded;
-	std::thread Thread;
-	std::mutex  Mutex;
+	std::vector<MidiEvent>  Events;
+	std::thread             Thread;
+	std::mutex              Mutex;
 	std::condition_variable Stop;
+	MidiSynthesizer^        Synthesizer;
+	int                     Size;
+	int                     Position;
+	Uint16                  ppq;		// parts (ticks) per quarter note
+	volatile bool           Playing;
+	bool                    Loaded;
+	bool                    Looping;
 
 	_NativeMidiSong()
-		: Events(nullptr), Size(0), Position(0)
-		, ppq(0), Playing(false), Loaded(false)
-		, Synthesizer(nullptr)
+		: Synthesizer(nullptr), Size(0), Position(0)
+		, ppq(0), Playing(false), Loaded(false), Looping(false)
 	{ }
 };
 
-static std::atomic<NativeMidiSong*> CurrentSong = nullptr;
+static MidiSystemResetMessage^ ResetMessage = ref new MidiSystemResetMessage();
 
 enum class MidiSystemMessage {
 	Exclusive = 0,
@@ -89,48 +91,50 @@ enum class MidiSystemMessage {
 static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
 {
 	int eventcount = 0, prevtime = 0, tempo = 500000;
-	MIDIEvent *event = eventlist;
-	while (event)
+
+	for (MIDIEvent* event = eventlist; event; event = event->next)
 	{
-		eventcount++;
-		event = event->next;
+		if (event->status != 0xFF)
+			eventcount++;
 	}
 
-	if (!(song->Events = new MidiEvent[eventcount]))
-		return;
+	song->Events.resize(song->Size = eventcount);
+	song->Position = 0;
+	song->Loaded = true;
 
-	for (event = eventlist, eventcount = 0; event; event = event->next)
+	eventcount = 0;
+	for (MIDIEvent* event = eventlist; event; event = event->next)
 	{
-		Windows::Devices::Midi::IMidiMessage^ message = nullptr;
+		IMidiMessage^ message = nullptr;
 		int status = (event->status & 0xF0) >> 4;
 		switch (status)
 		{
 		case MIDI_STATUS_NOTE_OFF:
-			message = ref new Windows::Devices::Midi::MidiNoteOffMessage(event->status - (status << 4), event->data[0], event->data[1]);
+			message = ref new MidiNoteOffMessage(event->status - (status << 4), event->data[0], event->data[1]);
 			break;
 
 		case MIDI_STATUS_NOTE_ON:
-			message = ref new Windows::Devices::Midi::MidiNoteOnMessage(event->status - (status << 4), event->data[0], event->data[1]);
+			message = ref new MidiNoteOnMessage(event->status - (status << 4), event->data[0], event->data[1]);
 			break;
 
 		case MIDI_STATUS_AFTERTOUCH:
-			message = ref new Windows::Devices::Midi::MidiPolyphonicKeyPressureMessage(event->status - (status << 4), event->data[0], event->data[1]);
+			message = ref new MidiPolyphonicKeyPressureMessage(event->status - (status << 4), event->data[0], event->data[1]);
 			break;
 
 		case MIDI_STATUS_CONTROLLER:
-			message = ref new Windows::Devices::Midi::MidiControlChangeMessage(event->status - (status << 4), event->data[0], event->data[1]);
+			message = ref new MidiControlChangeMessage(event->status - (status << 4), event->data[0], event->data[1]);
 			break;
 
 		case MIDI_STATUS_PROG_CHANGE:
-			message = ref new Windows::Devices::Midi::MidiProgramChangeMessage(event->status - (status << 4), event->data[0]);
+			message = ref new MidiProgramChangeMessage(event->status - (status << 4), event->data[0]);
 			break;
 
 		case MIDI_STATUS_PRESSURE:
-			message = ref new Windows::Devices::Midi::MidiChannelPressureMessage(event->status - (status << 4), event->data[0]);
+			message = ref new MidiChannelPressureMessage(event->status - (status << 4), event->data[0]);
 			break;
 
 		case MIDI_STATUS_PITCH_WHEEL:
-			message = ref new Windows::Devices::Midi::MidiPitchBendChangeMessage(event->status - (status << 4), event->data[0] | (event->data[1] << 7));
+			message = ref new MidiPitchBendChangeMessage(event->status - (status << 4), event->data[0] | (event->data[1] << 7));
 			break;
 
 		case MIDI_STATUS_SYSEX:
@@ -141,50 +145,50 @@ static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
 				auto buffer = NativeBuffer::GetIBuffer(event->extraData, event->extraLen);
 				if (buffer)
 				{
-					message = ref new Windows::Devices::Midi::MidiSystemExclusiveMessage(buffer);
+					message = ref new MidiSystemExclusiveMessage(buffer);
 					delete buffer;
 				}
 			}
 				break;
 
 			case MidiSystemMessage::TimeCode:
-				message = ref new Windows::Devices::Midi::MidiTimeCodeMessage(event->extraData[0] >> 4, event->extraData[0] & 0xF);
+				message = ref new MidiTimeCodeMessage(event->extraData[0] >> 4, event->extraData[0] & 0xF);
 				break;
 
 			case MidiSystemMessage::SongPositionPointer:
-				message = ref new Windows::Devices::Midi::MidiSongPositionPointerMessage(event->extraData[0] | (event->extraData[1] << 7));
+				message = ref new MidiSongPositionPointerMessage(event->extraData[0] | (event->extraData[1] << 7));
 				break;
 
 			case MidiSystemMessage::SongSelect:
-				message = ref new Windows::Devices::Midi::MidiSongSelectMessage(event->extraData[0]);
+				message = ref new MidiSongSelectMessage(event->extraData[0]);
 				break;
 
 			case MidiSystemMessage::TuneRequest:
-				message = ref new Windows::Devices::Midi::MidiTuneRequestMessage();
+				message = ref new MidiTuneRequestMessage();
 				break;
 
 			case MidiSystemMessage::TimingClock:
-				message = ref new Windows::Devices::Midi::MidiTimingClockMessage();
+				message = ref new MidiTimingClockMessage();
 				break;
 
 			case MidiSystemMessage::Start:
-				message = ref new Windows::Devices::Midi::MidiStartMessage();
+				message = ref new MidiStartMessage();
 				break;
 
 			case MidiSystemMessage::Continue:
-				message = ref new Windows::Devices::Midi::MidiContinueMessage();
+				message = ref new MidiContinueMessage();
 				break;
 
 			case MidiSystemMessage::Stop:
-				message = ref new Windows::Devices::Midi::MidiStopMessage();
+				message = ref new MidiStopMessage();
 				break;
 
 			case MidiSystemMessage::ActiveSensing:
-				message = ref new Windows::Devices::Midi::MidiActiveSensingMessage();
+				message = ref new MidiActiveSensingMessage();
 				break;
 
 			case MidiSystemMessage::SystemReset:
-				message = ref new Windows::Devices::Midi::MidiSystemResetMessage();
+				// This message is only used as meta-event in MIDI files
 				if (event->data[0] == 0x51)
 					tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2];
 				break;
@@ -200,15 +204,11 @@ static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
 			prevtime = event->time; eventcount++;
 		}
 	}
-
-	song->Size = eventcount;
-	song->Position = 0;
-	song->Loaded = 1;
 }
 
 int native_midi_detect()
 {
-	auto synthesizer = AWait(Windows::Devices::Midi::MidiSynthesizer::CreateAsync());
+	auto synthesizer = AWait(MidiSynthesizer::CreateAsync());
 	if (synthesizer)
 	{
 		delete synthesizer;
@@ -236,7 +236,9 @@ NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
 		{
 			MIDItoStream(newsong.get(), evntlist);
 			FreeMIDIEventList(evntlist);
-			return newsong.release();
+
+			if (newsong->Synthesizer = AWait(MidiSynthesizer::CreateAsync()))
+				return newsong.release();
 		}
 	}
 
@@ -247,83 +249,81 @@ void native_midi_freesong(NativeMidiSong *song)
 {
 	if (song)
 	{
-		native_midi_stop();
-
-		if (song->Events)
-			delete[] song->Events;
+		native_midi_stop(song);
+		if (song->Synthesizer)
+			delete song->Synthesizer;
 		delete song;
 	}
 }
 
-void native_midi_start(NativeMidiSong *song)
+void native_midi_start(NativeMidiSong *song, int looping)
 {
 	if (!song) return;
 
-	native_midi_stop();
-
-	if (!song->Synthesizer)
-	{
-		song->Synthesizer = AWait(Windows::Devices::Midi::MidiSynthesizer::CreateAsync());
-		if (!song->Synthesizer) return;
-	}
+	native_midi_stop(song);
 
+	song->Playing = true;
+	song->Looping = looping ? true : false;
 	song->Thread = std::move(std::thread([](NativeMidiSong *song)->void {
 		auto time = std::chrono::system_clock::now();
-		while (song->Position < song->Size)
+		while (song->Playing)
 		{
 			do
 			{
-				song->Synthesizer->SendMessage(song->Events[song->Position++].message);
+				song->Events[song->Position++].Send(song->Synthesizer);
 			} while (song->Position < song->Size && song->Events[song->Position].deltaTime == 0);
-			time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
-			if (song->Stop.wait_until(std::unique_lock<std::mutex>(song->Mutex), time) == std::cv_status::no_timeout) break;
+			if (song->Position < song->Size)
+			{
+				time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
+				while (song->Playing)
+				{
+					if (song->Stop.wait_until(std::unique_lock<std::mutex>(song->Mutex), time) == std::cv_status::timeout)
+						break;
+				}
+			}
+			else if (song->Playing = song->Looping)
+			{
+				song->Position = 0;
+				song->Synthesizer->SendMessage(ResetMessage);
+			}
 		}
-		song->Playing = false;
 	}, song));
-
-	song->Playing = true;
-
-	CurrentSong.exchange(song);
 }
 
-void native_midi_stop()
+void native_midi_stop(NativeMidiSong *song)
 {
-	NativeMidiSong* song;
-	if (song = CurrentSong.exchange(nullptr))
+	if (song)
 	{
+		song->Playing = false;
 		song->Stop.notify_all();
 		if (song->Thread.joinable())
 			song->Thread.join();
 		song->Thread = std::move(std::thread());
-		song->Playing = false;
 		if (song->Synthesizer)
 		{
-			delete song->Synthesizer;
-			song->Synthesizer = nullptr;
+			song->Synthesizer->SendMessage(ResetMessage);
 		}
 	}
 }
 
-int native_midi_active()
+int native_midi_active(NativeMidiSong *song)
 {
-	auto song = CurrentSong.load();
 	return (song && song->Playing) ? 1 : 0;
 }
 
-void native_midi_setvolume(int volume)
+void native_midi_setvolume(NativeMidiSong *song, int volume)
 {
-	auto song = CurrentSong.load();
 	if (song && song->Synthesizer)
 	{
-		if (volume > 128)
-			volume = 128;
+		if (volume > 127)
+			volume = 127;
 		else if (volume < 0)
 			volume = 0;
-		song->Synthesizer->Volume = (double)volume / 128.0;
+		song->Synthesizer->Volume = (double)volume / 127.0;
 	}
 }
 
-const char *native_midi_error(void)
+const char *native_midi_error(NativeMidiSong *song)
 {
 	return "";
 }