Browse Source

Merge pull request #22 from sdlpal/native_midi

New native MIDI framework: Windows, Android, iOS, macOS, Linux (preliminary)
Yihua LOU 6 years ago
parent
commit
af76e5dff9

+ 3 - 0
.gitmodules

@@ -4,3 +4,6 @@
 [submodule "3rd/SDL"]
 	path = 3rd/SDL
 	url = https://github.com/sdlpal/SDL.git
+[submodule "3rd/mingw-std-threads"]
+	path = 3rd/mingw-std-threads
+	url = https://github.com/sdlpal/mingw-std-threads.git

+ 1 - 0
3rd/mingw-std-threads

@@ -0,0 +1 @@
+Subproject commit aabf8e384b9d5e1d4f9774865c942f274b7f68d7

File diff suppressed because it is too large
+ 14 - 15
README.md


+ 13 - 11
android/jni/src/Android.mk

@@ -1,23 +1,25 @@
-SDLPAL_LOCAL_PATH := $(call my-dir)
-SDL_PATH := $(SDLPAL_LOCAL_PATH)/../../../3rd/SDL
+NDK_LOCAL_PATH := $(call my-dir)
+SDLPAL_PATH := $(NDK_LOCAL_PATH)/../../..
+SDL_PATH := $(SDLPAL_PATH)/3rd/SDL
 
 include $(SDL_PATH)/Android.mk
 
 include $(CLEAR_VARS)
-LOCAL_PATH := $(SDLPAL_LOCAL_PATH)
+LOCAL_PATH := $(NDK_LOCAL_PATH)
 LOCAL_MODULE := main
 
-OGG_PATH := ../../../liboggvorbis
+OGG_PATH := $(SDLPAL_PATH)/liboggvorbis
 
-LOCAL_C_INCLUDES := $(LOCAL_PATH) $(LOCAL_PATH)/../../.. $(LOCAL_PATH)/$(SDL_PATH)/include $(LOCAL_PATH)/$(OGG_PATH)/include $(LOCAL_PATH)/$(OGG_PATH)/src
+LOCAL_C_INCLUDES := $(LOCAL_PATH) $(SDLPAL_PATH) $(SDL_PATH)/include $(OGG_PATH)/include $(OGG_PATH)/src
 
 LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
-    $(wildcard $(LOCAL_PATH)/../../../*.cpp) $(wildcard $(LOCAL_PATH)/../../../*.c) \
-    $(wildcard $(LOCAL_PATH)/../../../adplug/*.c) $(wildcard $(LOCAL_PATH)/../../../adplug/*.cpp) \
-    $(wildcard $(LOCAL_PATH)/../../../liboggvorbis/src/*.c) \
-    $(wildcard $(LOCAL_PATH)/../../../libmad/*.c) \
-    $(wildcard $(LOCAL_PATH)/../../../native_midi/*.c) \
-    $(wildcard $(LOCAL_PATH)/*.c) \
+    $(wildcard $(SDLPAL_PATH)/*.cpp) $(wildcard $(SDLPAL_PATH)/*.c) \
+    $(wildcard $(SDLPAL_PATH)/adplug/*.c) $(wildcard $(SDLPAL_PATH)/adplug/*.cpp) \
+    $(wildcard $(SDLPAL_PATH)/liboggvorbis/src/*.c) \
+    $(wildcard $(SDLPAL_PATH)/libmad/*.c) \
+    $(wildcard $(SDLPAL_PATH)/native_midi/*.c) \
+    $(wildcard $(LOCAL_PATH)/*.cpp) \
+    $(wildcard $(LOCAL_PATH)/*.c)
 
 LOCAL_CFLAGS += -std=gnu99 -DPAL_HAS_PLATFORM_SPECIFIC_UTILS
 

+ 0 - 258
android/jni/src/android_jni.c

@@ -1,258 +0,0 @@
-#include <jni.h>
-#include <android/log.h>
-#define TAG "sdlpal-jni"
-
-#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG,__VA_ARGS__)
-#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , TAG,__VA_ARGS__)
-#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , TAG,__VA_ARGS__)
-#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , TAG,__VA_ARGS__)
-#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , TAG,__VA_ARGS__)
-
-#include "palcommon.h"
-#include "global.h"
-#include "palcfg.h"
-
-char externalStoragePath[255];
-char midiInterFile[255];
-
-static int jstring_to_utf8(JNIEnv *env, jstring j_str, char *buffer, int capacity)
-{
-    jsize pos = 0, length = (*env)->GetStringLength(env, j_str);
-    const jchar * const base = (*env)->GetStringCritical(env, j_str, NULL);
-    if (base == NULL)
-    {
-        return 0;
-    }
-    // Convert at char boundary, no incomplete output can be generated
-    for(const jchar *str = base;pos < length && pos < capacity - 1;str++)
-    {
-        if (*str > 4095 && pos < capacity - 3)
-        {
-            buffer[pos++] = 0xe0 | (*str >> 12);
-            buffer[pos++] = 0x80 | ((*str >> 6) & 0x3f);
-            buffer[pos++] = 0x80 | (*str & 0x3f);
-        }
-        else if (*str > 127 && pos < capacity - 2)
-        {
-            buffer[pos++] = 0xc0 | (*str >> 6);
-            buffer[pos++] = 0x80 | (*str & 0x3f);
-        }
-        else if (*str <= 127)
-        {
-            buffer[pos++] = *str;
-        }
-        else
-        {
-            break;
-        }
-    }
-    (*env)->ReleaseStringCritical(env, j_str, base);
-    buffer[pos] = '\0';
-    return pos;
-}
-
-static jstring jstring_from_utf8(JNIEnv *env, const char *str)
-{
-    jstring retval = NULL;
-    jchar *temp = NULL;
-    int wlen = 0, len = strlen(str);
-    // Count length of the UTF-8 string, stop at any error
-    for(int i = 0, state = 0, count = 0;i < len;i++)
-    {
-        if (state == 0)
-        {
-            if (str[i] < 127)
-            {
-                wlen++;
-            }
-            else if (str[i] >= 0xc0 && str[i] < 0xf0)
-            {
-                state = 1;
-                count = (str[i] >> 5) - 5;
-            }
-            else
-            {
-                break;
-            }
-        }
-        else
-        {
-            if (str[i] >= 0x80 && str[i] < 0xc0)
-            {
-                if (count == 0)
-                {
-                    state = 0;
-                    wlen++;
-                }
-                else
-                {
-                    count--;
-                }
-            }
-            else
-            {
-                break;
-            }
-        }
-    }
-    if (wlen == 0)
-    {
-        return (*env)->NewString(env, L"", 0);
-    }
-
-    temp = (jchar *)malloc(wlen * sizeof(jchar));
-    for(int i = 0, j = 0;j < wlen;j++)
-    {
-        if (str[i] > 127)
-        {
-            // Trick here:
-            // 2-byte form: 110x xxxx -> 0xxx xx000000
-            // 3-byte form: 1110 xxxx -> 10xx xx000000
-            temp[j] = (str[i++] & 0x3f) << 6;
-            temp[j] |= str[i++] & 0x3f;
-            if (temp[j] & 0x800)
-            {
-                // 3-byte form, the top-most bit will be dicarded during shift
-                temp[j] <<= 6;
-                temp[j] |= str[i++] & 0x3f;
-            }
-        }
-        else
-        {
-            temp[j] = str[i++];
-        }
-    }
-    retval = (*env)->NewString(env, temp, wlen);
-    free(temp);
-    return retval;
-}
-
-JavaVM *globalVM;
-
-jint JNI_OnLoad(JavaVM* vm, void* reserved)
-{
-    JNIEnv* env;
-    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_2) != JNI_OK) {
-        return -1;
-    }
-    globalVM = vm;
-    return JNI_VERSION_1_2;
-}
-
-JNIEnv *getJNIEnv()
-{
-    JNIEnv* env;
-    if ((*globalVM)->GetEnv(globalVM, (void**)&env, JNI_VERSION_1_2) != JNI_OK) {
-        return NULL;
-    }
-    return env;
-}
-
-/* 
- * Class:     io_github_sdlpal_PalActivity 
- * Method:    setExternalStorage 
- * Signature: (Ljava/lang/String;)V
- */  
-JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setExternalStorage(JNIEnv *env, jclass cls, jstring j_str)  
-{
-    jstring_to_utf8(env, j_str, externalStoragePath, 255 - 8);
-    strncat(externalStoragePath, "/sdlpal/", 8);
-    return;
-}
-
-/* 
- * Class:     io_github_sdlpal_PalActivity 
- * Method:    setMIDIInterFile 
- * Signature: (Ljava/lang/String;)V
- */  
-JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setMIDIInterFile(JNIEnv *env, jclass cls, jstring j_str)  
-{
-    jstring_to_utf8(env, j_str, midiInterFile, 255);
-    LOGV("JNI got midi inter filename:%s", midiInterFile);
-    return;
-}
-
-void JNI_mediaplayer_load(const char *filename)
-{
-    JNIEnv *env = getJNIEnv();
-    jclass clazz = (*env)->FindClass(env, "io/github/sdlpal/PalActivity");
-    jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "JNI_mediaplayer_load", "(Ljava/lang/String;)V");
-    (*env)->CallStaticVoidMethod(env, clazz, mid, jstring_from_utf8(env, filename));
-}
-
-void JNI_mediaplayer_play()
-{
-    JNIEnv *env = getJNIEnv();
-    jclass clazz = (*env)->FindClass(env, "io/github/sdlpal/PalActivity");
-    jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "JNI_mediaplayer_play", "()V");
-    (*env)->CallStaticVoidMethod(env, clazz, mid);
-}
-
-void JNI_mediaplayer_stop()
-{
-    JNIEnv *env = getJNIEnv();
-    jclass clazz = (*env)->FindClass(env, "io/github/sdlpal/PalActivity");
-    jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "JNI_mediaplayer_stop", "()V");
-    (*env)->CallStaticVoidMethod(env, clazz, mid);
-}
-
-int JNI_mediaplayer_isplaying()
-{
-    return 0;
-}
-
-void JNI_mediaplayer_setvolume(int volume)
-{
-}
-
-LPCSTR
-UTIL_BasePath(
-   VOID
-)
-{
-    return externalStoragePath;
-}
-
-LPCSTR
-UTIL_SavePath(
-   VOID
-)
-{
-    return externalStoragePath;
-}
-
-BOOL
-UTIL_GetScreenSize(
-   DWORD *pdwScreenWidth,
-   DWORD *pdwScreenHeight
-)
-{
-    *pdwScreenWidth  = 640;
-    *pdwScreenHeight = 400;
-    return TRUE;
-}
-
-BOOL
-UTIL_IsAbsolutePath(
-	LPCSTR  lpszFileName
-)
-{
-	return FALSE;
-}
-
-INT
-UTIL_Platform_Init(
-   int argc,
-   char* argv[]
-)
-{
-   gConfig.fLaunchSetting = FALSE;
-   return 0;
-}
-
-VOID
-UTIL_Platform_Quit(
-   VOID
-)
-{
-}

+ 240 - 0
android/jni/src/android_jni.cpp

@@ -0,0 +1,240 @@
+#include <jni.h>
+#include <android/log.h>
+#define TAG "sdlpal-jni"
+
+#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG,__VA_ARGS__)
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , TAG,__VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , TAG,__VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , TAG,__VA_ARGS__)
+
+#define EXTERN_C_LINKAGE extern "C"
+
+#include "palcommon.h"
+#include "global.h"
+#include "palcfg.h"
+#include "util.h"
+
+char externalStoragePath[1024];
+char midiInterFile[1024];
+
+static int jstring_to_utf8(JNIEnv* env, jstring j_str, char *buffer, int capacity)
+{
+    jsize length = env->GetStringUTFLength(j_str);
+    const char * const base = env->GetStringUTFChars(j_str, NULL);
+    if (base == NULL)
+    {
+        return 0;
+    }
+    if (capacity <= length)
+        length = capacity - 1;
+    strncpy(buffer, base, length);
+    env->ReleaseStringUTFChars(j_str, base);
+    buffer[length] = '\0';
+    return length;
+}
+
+static JavaVM* gJVM;
+
+EXTERN_C_LINKAGE jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    JNIEnv* env;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_2) != JNI_OK) {
+        return -1;
+    }
+    gJVM = vm;
+    return JNI_VERSION_1_2;
+}
+
+static JNIEnv* getJNIEnv()
+{
+    JNIEnv* env;
+    if (gJVM->GetEnv((void**)&env, JNI_VERSION_1_2) != JNI_OK) {
+        return NULL;
+    }
+    return env;
+}
+
+/* 
+ * Class:     io_github_sdlpal_PalActivity 
+ * Method:    setExternalStorage 
+ * Signature: (Ljava/lang/String;)V
+ */  
+EXTERN_C_LINKAGE
+JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setExternalStorage(JNIEnv *env, jclass cls, jstring j_str)  
+{
+    jstring_to_utf8(env, j_str, externalStoragePath, sizeof(externalStoragePath) - 8);
+    strcat(externalStoragePath, "/sdlpal/");
+    LOGV("JNI got externalStoragePath:%s", externalStoragePath);
+}
+
+/* 
+ * Class:     io_github_sdlpal_PalActivity 
+ * Method:    setMIDIInterFile 
+ * Signature: (Ljava/lang/String;)V
+ */  
+EXTERN_C_LINKAGE
+JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setMIDIInterFile(JNIEnv *env, jclass cls, jstring j_str)  
+{
+    jstring_to_utf8(env, j_str, midiInterFile, sizeof(midiInterFile));
+    LOGV("JNI got midi inter filename:%s", midiInterFile);
+}
+
+EXTERN_C_LINKAGE
+void* JNI_mediaplayer_load(const char *filename)
+{
+    JNIEnv* env = getJNIEnv();
+    jclass clazz = env->FindClass("io/github/sdlpal/PalActivity");
+    jmethodID mid = env->GetStaticMethodID(clazz, "JNI_mediaplayer_load", "(Ljava/lang/String;)Landroid/media/MediaPlayer;");
+    jstring str = env->NewStringUTF(filename);
+    jobject player = env->CallStaticObjectMethod(clazz, mid, str);
+    env->DeleteLocalRef(str);
+    return env->NewGlobalRef(player);
+}
+
+EXTERN_C_LINKAGE
+void JNI_mediaplayer_free(void *player)
+{
+    getJNIEnv()->DeleteGlobalRef((jobject)player);
+}
+
+EXTERN_C_LINKAGE
+void JNI_mediaplayer_play(void *player, int looping)
+{
+    JNIEnv* env = getJNIEnv();
+    jclass clazz = env->FindClass("android/media/MediaPlayer");
+    env->CallVoidMethod((jobject)player, env->GetMethodID(clazz, "setLooping", "(Z)V"), looping ? JNI_TRUE : JNI_FALSE);
+    env->CallVoidMethod((jobject)player, env->GetMethodID(clazz, "start", "()V"));
+}
+
+EXTERN_C_LINKAGE
+void JNI_mediaplayer_stop(void *player)
+{
+    JNIEnv* env = getJNIEnv();
+    jclass clazz = env->FindClass("android/media/MediaPlayer");
+    env->CallVoidMethod((jobject)player, env->GetMethodID(clazz, "stop", "()V"));
+}
+
+EXTERN_C_LINKAGE
+int JNI_mediaplayer_isplaying(void *player)
+{
+    JNIEnv* env = getJNIEnv();
+    jclass clazz = env->FindClass("android/media/MediaPlayer");
+    return env->CallBooleanMethod((jobject)player, env->GetMethodID(clazz, "isPlaying", "()Z"));
+}
+
+EXTERN_C_LINKAGE
+void JNI_mediaplayer_setvolume(void *player, int volume)
+{
+    float vol = (float)volume / 127.0f;
+    JNIEnv* env = getJNIEnv();
+    jclass clazz = env->FindClass("android/media/MediaPlayer");
+    env->CallVoidMethod((jobject)player, env->GetMethodID(clazz, "setVolume", "(FF)V"), vol, vol);
+}
+
+EXTERN_C_LINKAGE
+LPCSTR
+UTIL_BasePath(
+   VOID
+)
+{
+    return externalStoragePath;
+}
+
+EXTERN_C_LINKAGE
+LPCSTR
+UTIL_SavePath(
+   VOID
+)
+{
+    return externalStoragePath;
+}
+
+EXTERN_C_LINKAGE
+BOOL
+UTIL_GetScreenSize(
+   DWORD *pdwScreenWidth,
+   DWORD *pdwScreenHeight
+)
+{
+    *pdwScreenWidth  = 640;
+    *pdwScreenHeight = 400;
+    return TRUE;
+}
+
+EXTERN_C_LINKAGE
+BOOL
+UTIL_IsAbsolutePath(
+	LPCSTR  lpszFileName
+)
+{
+	return lpszFileName[0] == '/';
+}
+
+EXTERN_C_LINKAGE
+INT
+UTIL_Platform_Init(
+   int argc,
+   char* argv[]
+)
+{
+   gConfig.fLaunchSetting = FALSE;
+   return 0;
+}
+
+EXTERN_C_LINKAGE
+VOID
+UTIL_Platform_Quit(
+   VOID
+)
+{
+}
+
+#ifdef ENABLE_NEWLOG
+
+static int maxLogLevel = LOG_WARNING;
+
+PAL_C_LINKAGE VOID
+UTIL_SetLogLevel(
+	int             level
+)
+{
+	if (level >= LOG_EMERG && level < LOG_LAST_PRIORITY)
+	{
+		maxLogLevel = level;
+	}
+}
+
+EXTERN_C_LINKAGE
+VOID
+UTIL_WriteLog(
+   int             Priority,
+   const char     *Fmt,
+   ...
+)
+{
+	if (Priority < LOG_EMERG || Priority > maxLogLevel)
+	{
+		return;
+	}
+    
+    switch(Priority)
+    {
+    case LOG_EMERG:   Priority = ANDROID_LOG_FATAL; break;
+    case LOG_ALERT:   Priority = ANDROID_LOG_FATAL; break;
+    case LOG_CRIT:    Priority = ANDROID_LOG_ERROR; break;
+    case LOG_ERR:     Priority = ANDROID_LOG_ERROR; break;
+    case LOG_WARNING: Priority = ANDROID_LOG_WARN; break;
+    case LOG_NOTICE:  Priority = ANDROID_LOG_INFO; break;
+    case LOG_INFO:    Priority = ANDROID_LOG_INFO; break;
+    case LOG_DEBUG:   Priority = ANDROID_LOG_DEBUG; break;
+    default:          Priority = ANDROID_LOG_VERBOSE; break;
+    }
+
+    va_list ap;
+    va_start(ap, Fmt);
+    __android_log_vprint(ANDROID_LOG_VERBOSE, TAG, Fmt, ap);
+    va_end(ap);
+}
+
+#endif

+ 17 - 8
android/jni/src/android_jni.h

@@ -1,13 +1,22 @@
 #ifndef SDLPAL_JNI_H
 #define SDLPAL_JNI_H
 
-extern char externalStoragePath[255];
-extern char midiInterFile[255];
-
-void JNI_mediaplayer_load();
-void JNI_mediaplayer_play();
-void JNI_mediaplayer_stop();
-int JNI_mediaplayer_isplaying();
-void JNI_mediaplayer_setvolume(int);
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern char externalStoragePath[1024];
+extern char midiInterFile[1024];
+
+void* JNI_mediaplayer_load(const char *);
+void JNI_mediaplayer_free(void *);
+void JNI_mediaplayer_play(void *, int);
+void JNI_mediaplayer_stop(void *);
+int JNI_mediaplayer_isplaying(void *);
+void JNI_mediaplayer_setvolume(void *, int);
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif // SDLPAL_JNI_H

+ 31 - 42
android/jni/src/native_midi.c

@@ -32,13 +32,10 @@
 /* Native Midi song */
 struct _NativeMidiSong
 {
-    int _placeholder;
-    int playing;
+    void *player;
+    int   volume;
 };
 
-static NativeMidiSong *currentsong = NULL;
-static int latched_volume = 128;
-
 int native_midi_detect()
 {
     return 1;  /* always available. */
@@ -46,79 +43,71 @@ int native_midi_detect()
 
 NativeMidiSong *native_midi_loadsong(const char *midifile)
 {
-    NativeMidiSong *retval = (NativeMidiSong *)malloc(sizeof(NativeMidiSong));
-    if (retval)
+    NativeMidiSong *song = (NativeMidiSong *)malloc(sizeof(NativeMidiSong));
+    if (song)
     {
-        memset(retval, 0, sizeof(NativeMidiSong));
-        JNI_mediaplayer_load(midifile);
+        song->volume = 127;
+        song->player = JNI_mediaplayer_load(midifile);
     }
-    return retval;
+    return song;
 }
 
 NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
 {
-    NativeMidiSong *retval = (NativeMidiSong *)malloc(sizeof(NativeMidiSong));
-    if (retval)
+    FILE *fp = fopen(midiInterFile, "wb+");
+    if (fp)
     {
-        FILE *fp = fopen(midiInterFile, "wb+");
-        if (fp)
-        {
-            char buf[4096];
-            size_t bytes;
-            while(bytes = SDL_RWread(rw, buf, sizeof(char), sizeof(buf)))
-                fwrite(buf, sizeof(char), bytes, fp);
-            fclose(fp);
-
-            memset(retval, 0, sizeof(NativeMidiSong));
-            JNI_mediaplayer_load(midiInterFile);
-        }
+        char buf[4096];
+        size_t bytes;
+        while(bytes = SDL_RWread(rw, buf, sizeof(char), sizeof(buf)))
+            fwrite(buf, sizeof(char), bytes, fp);
+        fclose(fp);
+
+        return native_midi_loadsong(midiInterFile);
     }
-    return retval;
+    return NULL;
 }
 
 void native_midi_freesong(NativeMidiSong *song)
 {
     if (song != NULL)
     {
-        if (currentsong == song)
-            currentsong = NULL;
+        JNI_mediaplayer_stop(song->player);
+        JNI_mediaplayer_free(song->player);
         free(song);
     }
 }
 
-void native_midi_start(NativeMidiSong *song)
+void native_midi_start(NativeMidiSong *song, int looping)
 {
-    native_midi_stop();
     if (song != NULL)
     {
-        currentsong = song;
-        currentsong->playing = 1;
-        JNI_mediaplayer_play();
+        JNI_mediaplayer_play(song->player, looping);
     }
 }
 
-void native_midi_stop()
+void native_midi_stop(NativeMidiSong *song)
 {
-    if (currentsong) {
-        currentsong->playing = 0;
-        JNI_mediaplayer_stop();
+    if (song)
+    {
+        JNI_mediaplayer_stop(song->player);
     }
 }
 
-int native_midi_active()
+int native_midi_active(NativeMidiSong *song)
 {
-    return currentsong ? currentsong->playing : 0;
+    return song ? JNI_mediaplayer_isplaying(song->player) : 0;
 }
 
-void native_midi_setvolume(int volume)
+void native_midi_setvolume(NativeMidiSong *song, int volume)
 {
-    if (latched_volume != volume)
+    if (song)
     {
-        JNI_mediaplayer_setvolume(latched_volume = volume);
+        JNI_mediaplayer_setvolume(song->player, song->volume = volume);
     }
 }
 
-const char *native_midi_error(void)
+const char *native_midi_error(NativeMidiSong *song)
 {
     return "";  /* !!! FIXME */
 }

+ 11 - 23
android/src/io/github/sdlpal/PalActivity.java

@@ -10,43 +10,29 @@ import java.util.*;
 
 public class PalActivity extends SDLActivity {
     private static final String TAG = "sdlpal-debug";
-    private static final MediaPlayer mediaPlayer = new MediaPlayer();
+    private static MediaPlayer mediaPlayer;
 
-    private static void JNI_mediaplayer_load(String filename){
+    private static MediaPlayer JNI_mediaplayer_load(String filename){
         Log.v(TAG, "loading midi:" + filename);
+        MediaPlayer mediaPlayer = new MediaPlayer();
         mediaPlayer.reset();
-        mediaPlayer.setLooping(true);
         try {
             mediaPlayer.setDataSource(mSingleton.getApplicationContext(), Uri.fromFile(new File(filename)));
             mediaPlayer.prepare();
         } catch(IOException e) {
             Log.e(TAG, filename + " not available for playing, check");
         }
-    }
-
-    private static void JNI_mediaplayer_play() {
-        mediaPlayer.start();
-    }
-
-    private static void JNI_mediaplayer_stop() {
-        mediaPlayer.stop();
-    }
-
-    private static int JNI_mediaplayer_playing() {
-        return mediaPlayer.isPlaying() ? 1 : 0;
-    }
-
-    private static void JNI_mediaplayer_setvolume(int volume) {
-        mediaPlayer.setVolume((float)volume/256, (float)volume/256);
+        PalActivity.mediaPlayer = mediaPlayer;
+        return mediaPlayer;
     }
 
     public static native void setExternalStorage(String str);
     public static native void setMIDIInterFile(String str);
+
     @Override
     public void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);
-        String appDataPath = mSingleton.getApplicationContext().getCacheDir().getPath();
-        String interFilePath = appDataPath+"/intermediates.midi";
+        String interFilePath = mSingleton.getApplicationContext().getCacheDir().getPath() + "/intermediates.mid";
         Log.v(TAG, "java interfile path " + interFilePath);
         setMIDIInterFile(interFilePath);
         String externalStorageState = Environment.getExternalStorageState();
@@ -58,7 +44,7 @@ public class PalActivity extends SDLActivity {
 
     @Override
     protected void onPause() {
-        if (!this.isFinishing()){
+        if (!this.isFinishing() && mediaPlayer != null) {
             mediaPlayer.pause();
         }
         super.onPause();
@@ -66,7 +52,9 @@ public class PalActivity extends SDLActivity {
 
     @Override
     protected void onResume() {
-        mediaPlayer.start();
+        if (mediaPlayer != null) {
+            mediaPlayer.start();
+        }
         super.onResume();
     }
 }

+ 0 - 1
input.c

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

+ 6 - 6
ios/SDLPal/SDLPal/native_midi.m

@@ -93,7 +93,7 @@ void native_midi_freesong(NativeMidiSong *song)
 {
     if (song != NULL)
     {
-        native_midi_stop();
+        native_midi_stop(song);
         if (currentsong == song)
             currentsong = NULL;
         free(song);
@@ -104,9 +104,9 @@ void native_midi_freesong(NativeMidiSong *song)
     }
 }
 
-void native_midi_start(NativeMidiSong *song)
+void native_midi_start(NativeMidiSong *song, int looping)
 {
-    native_midi_stop();
+    native_midi_stop(song);
     if (song != NULL)
     {
         currentsong = song;
@@ -114,7 +114,7 @@ void native_midi_start(NativeMidiSong *song)
         [midiPlayer play:^(){
             if( currentsong ) {
                 midiPlayer.currentPosition = 0;
-                native_midi_start(currentsong);
+                native_midi_start(currentsong,looping);
             }
         }];
     }
@@ -133,11 +133,11 @@ int native_midi_active()
     return currentsong ? currentsong->playing : 0;
 }
 
-void native_midi_setvolume(int volume)
+void native_midi_setvolume(NativeMidiSong *song, int volume)
 {
 }
 
-const char *native_midi_error(void)
+const char *native_midi_error(NativeMidiSong *song)
 {
     return "";  /* !!! FIXME */
 }

+ 7 - 6
macos/native_midi_macosx.c

@@ -41,6 +41,7 @@ struct _NativeMidiSong
     MusicSequence sequence;
     MusicTimeStamp endTime;
     AudioUnit audiounit;
+    int Loopinng;
 };
 
 static NativeMidiSong *currentsong = NULL;
@@ -245,7 +246,7 @@ void native_midi_freesong(NativeMidiSong *song)
     }
 }
 
-void native_midi_start(NativeMidiSong *song)
+void native_midi_start(NativeMidiSong *song, int looping)
 {
     int vol;
 
@@ -265,7 +266,7 @@ void native_midi_start(NativeMidiSong *song)
 
     vol = latched_volume;
     latched_volume++;  /* just make this not match. */
-    native_midi_setvolume(vol);
+    native_midi_setvolume(song,vol);
 
     SDL_LockAudio();
     SDL_PauseAudio(0);
@@ -294,20 +295,20 @@ int native_midi_active()
             (currentTime >= kMusicTimeStamp_EndOfTrack));
 }
 
-void native_midi_setvolume(int volume)
+void native_midi_setvolume(NativeMidiSong *song, int volume)
 {
     if (latched_volume == volume)
         return;
 
     latched_volume = volume;
-    if ((currentsong) && (currentsong->audiounit)) {
+    if ((song) && (song->audiounit)) {
         const float floatvol = ((float) volume) / ((float) 128);
-        AudioUnitSetParameter(currentsong->audiounit, kHALOutputParam_Volume,
+        AudioUnitSetParameter(song->audiounit, kHALOutputParam_Volume,
                               kAudioUnitScope_Global, 0, floatvol, 0);
     }
 }
 
-const char *native_midi_error(void)
+const char *native_midi_error(NativeMidiSong *song)
 {
     return "";  /* !!! FIXME */
 }

+ 57 - 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,60 @@ 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_detect())
+		return;
+
+	if (native_midi_active(g_pMidi) && iNumRIX == g_iMidiCurrent)
+	{
+		return;
+	}
+
+	AUDIO_PlayCDTrack(-1);
+	native_midi_stop(g_pMidi);
+	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")) != NULL)
+		{
+			
+			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_ */

+ 1 - 1
unix/Makefile

@@ -17,7 +17,7 @@ TEST_OBJFILES = $(TEST_CPPFILES:.cpp=.o)
 CCFLAGS = `sdl2-config --cflags` -g -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS $(TEST_CCFLAGS)
 CXXFLAGS = $(CCFLAGS) -std=c++11 `fltk-config --cxxflags`
 CFLAGS = $(CCFLAGS) -std=gnu99 `fltk-config --cflags`
-LDFLAGS = `sdl2-config --libs` `fltk-config --ldflags` -lstdc++ -lm
+LDFLAGS = `sdl2-config --libs` `fltk-config --ldflags` -lstdc++ -lm -pthread
 TEST_CXXFLAGS += -isystem $(GTEST_DIR)/include -I $(GTEST_DIR) -g -Wall -Wextra -pthread
 
 .PHONY : all clean check

+ 161 - 0
unix/native_midi.cpp

@@ -0,0 +1,161 @@
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <future>
+#include <thread>
+#include <condition_variable>
+#include <mutex>
+#include <atomic>
+#include <vector>
+
+#include "native_midi/native_midi.h"
+
+// Warning: this is a unverified implementation and this comment should be removed if verified
+
+struct _NativeMidiSong
+{
+	std::thread   Thread;
+	std::mutex    Mutex;
+
+    char *file;
+    int  pid;
+    volatile bool playing;
+    bool looping;
+};
+
+const char* midi_file = "/tmp/sdlpal.temp.mid";
+char* timidity = nullptr;
+
+extern "C" int native_midi_detect()
+{
+    // FIXME!!!
+    if (timidity)
+    {
+        free(timidity);
+        timidity = nullptr;
+    }
+    //system `timidity -v` will cause CLI blocked by the version info...
+    if (access("/usr/bin/timidity",F_OK) == 0)
+    {
+        timidity = strdup("/usr/bin/timidity");
+        return 1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+extern "C" NativeMidiSong *native_midi_loadsong(const char *midifile)
+{
+    struct stat st;
+    if (0 != stat(midifile, &st)) return NULL;
+
+    auto song = new NativeMidiSong;
+    if (NULL == song) return NULL;
+    if (NULL == (song->file = new char[strlen(midifile) + 1])) return NULL;
+
+    song->pid = -1;
+    song->playing = false;
+    song->looping = false;
+    strcpy(song->file, midifile);
+
+    return song;
+}
+
+extern "C" NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
+{
+    FILE *fp = fopen(midi_file, "wb+");
+    if (fp)
+    {
+        char buf[4096];
+        size_t bytes;
+        while(bytes = SDL_RWread(rw, buf, sizeof(char), sizeof(buf)))
+            fwrite(buf, sizeof(char), bytes, fp);
+        fclose(fp);
+
+        return native_midi_loadsong(midi_file);
+    }
+    return NULL;
+}
+
+extern "C" void native_midi_freesong(NativeMidiSong *song)
+{
+    if (song)
+    {
+	//stop it first to prevent app terminated by destructing joinable thread destruction 
+	if (native_midi_active(song)) 
+		native_midi_stop(song);
+        if (song->file) delete []song->file;
+        delete song;
+    }
+}
+
+extern "C" 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 {
+        while(song->looping)
+        {
+            auto pid = fork();
+            int status;
+
+            if (0 == pid)
+            {
+                char* args[] = { timidity, song->file, NULL };
+                if (-1 == execv(timidity, args)) exit(-1);
+            }
+            else if (-1 == pid)
+            {
+                return;
+            }
+            song->Mutex.lock();
+            song->pid = pid;
+            song->Mutex.unlock();
+            waitpid(pid, &status, 0);
+        }
+        song->playing = false;
+	}, song));
+}
+
+extern "C" void native_midi_stop(NativeMidiSong *song)
+{
+	if (song)
+	{
+		song->looping = false;
+		song->playing = false;
+ 		if (-1 != song->pid)
+            	{
+			song->Mutex.lock();
+			kill(song->pid, SIGTERM);
+			song->Mutex.unlock();
+            	}
+		if (song->Thread.joinable())
+			song->Thread.join();
+		song->Thread = std::move(std::thread());
+	}
+}
+
+extern "C" int native_midi_active(NativeMidiSong *song)
+{
+	return (song && song->playing) ? 1 : 0;
+}
+
+extern "C" void native_midi_setvolume(NativeMidiSong *song, int volume)
+{
+    // TODO
+}
+
+extern "C" const char *native_midi_error(NativeMidiSong *song)
+{
+    return "";
+}

+ 1 - 0
unix/pal_config.h

@@ -56,5 +56,6 @@
 # define PAL_PORTYEAR         NULL
 
 # define PAL_HAS_CONFIG_PAGE  1
+# define PAL_HAS_NATIVEMIDI 1
 
 #endif

+ 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

+ 1 - 1
win32/Makefile.mingw

@@ -15,7 +15,7 @@ OBJFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o) $(RCFILES:.rc=.o)
 TEST_CPPFILES = $(wildcard ../tests/*.cpp)
 TEST_OBJFILES = $(TEST_CPPFILES:.cpp=.o)
 
-override CCFLAGS += -g -msse2 -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS $(TEST_CCFLAGS)
+override CCFLAGS += -g -msse2 -Wall -O2 -fno-strict-aliasing -I../3rd/mingw-std-threads -I../3rd/SDL/include -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS $(TEST_CCFLAGS)
 CXXFLAGS = $(CCFLAGS) -std=c++11
 CFLAGS = $(CCFLAGS) -std=gnu99
 LDFLAGS = -lmingw32 -lSDL2main -lSDL2 -mwindows -lm -lwinmm -lole32 -loleaut32 -limm32 -lcomctl32 -luuid -ldxguid -lversion -static -static-libgcc -static-libstdc++

+ 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 "";
-}
-

+ 368 - 0
win32/native_midi.cpp

@@ -0,0 +1,368 @@
+/* -*- 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.cpp: Native Windows Desktop MIDI player for SDLPal.
+//         @Author: Lou Yihua, 2017
+//
+
+#include "SDL.h"
+
+#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>
+
+#if (defined(__MINGW32__) || defined(__MINGW64__)) && !defined(_GLIBCXX_HAS_GTHREADS)
+#include "mingw.condition_variable.h"
+#include "mingw.mutex.h"
+#include "mingw.thread.h"
+#endif
+
+#include "native_midi/native_midi.h"
+#include "native_midi/native_midi_common.h"
+
+static int native_midi_available = -1;
+
+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::microseconds((int64_t)deltaTime * tempo / 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()
+{
+	if (-1 == native_midi_available)
+	{
+		HMIDIOUT out;
+
+		if (MMSYSERR_NOERROR == midiOutOpen(&out, MIDI_MAPPER, 0, 0, CALLBACK_NULL))
+		{
+			midiOutClose(out);
+			native_midi_available = 1;
+		}
+		else
+			native_midi_available = 0;
+	}
+	return native_midi_available;
+}
+
+NativeMidiSong *native_midi_loadsong(const char *midifile)
+{
+	/* Attempt to load the midi file */
+	auto rw = SDL_RWFromFile(midifile, "rb");
+	if (rw)
+	{
+		auto song = native_midi_loadsong_RW(rw);
+		SDL_RWclose(rw);
+		return song;
+	}
+	return nullptr;
+}
+
+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)
+			{
+				auto mutex = std::unique_lock<std::mutex>(song->Mutex);
+				time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
+				while (song->Playing)
+				{
+					if (song->Stop.wait_until(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>

+ 95 - 88
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;
@@ -220,8 +220,14 @@ int native_midi_detect()
 NativeMidiSong *native_midi_loadsong(const char *midifile)
 {
 	/* Attempt to load the midi file */
-	std::unique_ptr<SDL_RWops> rw(SDL_RWFromFile(midifile, "rb"));
-	return rw ? native_midi_loadsong_RW(rw.get()) : nullptr;
+	auto rw = SDL_RWFromFile(midifile, "rb");
+	if (rw)
+	{
+		auto song = native_midi_loadsong_RW(rw);
+		SDL_RWclose(rw);
+		return song;
+	}
+	return nullptr;
 }
 
 NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
@@ -236,7 +242,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 +255,82 @@ 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)
+			{
+				auto mutex = std::unique_lock<std::mutex>(song->Mutex);
+				time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
+				while (song->Playing)
+				{
+					if (song->Stop.wait_until(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 "";
 }

+ 3 - 1
winrt/pal_config.h

@@ -48,7 +48,9 @@
 
 #define PAL_HAS_CONFIG_PAGE  1
 
-#define PAL_HAS_NATIVEMIDI  1
+#if NTDDI_VERSION >= NTDDI_WIN10
+# define PAL_HAS_NATIVEMIDI  1
+#endif
 
 #define PAL_FILESYSTEM_IGNORE_CASE 1