mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-22 22:48:08 +03:00
Android: MIDI support
This commit is contained in:
+4
-1
@@ -161,8 +161,11 @@ set(CDROM_DEVICE "/dev/cdrom" CACHE STRING "CDROM Device Name")
|
||||
if (EMSCRIPTEN)
|
||||
list(APPEND SRC_MIDI midi.emscripten.c)
|
||||
list(APPEND SUMMARY_MIDI "Emscripten")
|
||||
elseif (ANDROID)
|
||||
list(APPEND SRC_MIDI midi.android.c)
|
||||
list(APPEND SUMMARY_MIDI "Android")
|
||||
else()
|
||||
if (HAVE_SIGACTION AND NOT ANDROID)
|
||||
if (HAVE_SIGACTION)
|
||||
list(APPEND SRC_MIDI midi.rawmidi.c midifile.c)
|
||||
list(APPEND SUMMARY_MIDI "raw")
|
||||
set(MIDI_DEVICE "/dev/midi" CACHE STRING "midi device")
|
||||
|
||||
@@ -40,7 +40,3 @@ cd xsystem35-sdl2/android
|
||||
4. The game starts. Two-finger touch is treated as a right click.
|
||||
|
||||
To uninstall a game, long-tap the title in the game list.
|
||||
|
||||
## TODO
|
||||
- Improve launcher
|
||||
- MIDI BGM support
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.github.kichikuou.xsystem35
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import org.libsdl.app.SDLActivity
|
||||
import java.io.File
|
||||
@@ -35,21 +36,24 @@ class GameActivity : SDLActivity() {
|
||||
const val EXTRA_PLAYLIST_FILE = "PLAYLIST_FILE"
|
||||
}
|
||||
|
||||
private lateinit var player: BGMPlayer
|
||||
private lateinit var cdda: CddaPlayer
|
||||
private val midi = MidiPlayer()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
player = BGMPlayer(intent.getStringExtra(EXTRA_PLAYLIST_FILE))
|
||||
cdda = CddaPlayer(intent.getStringExtra(EXTRA_PLAYLIST_FILE))
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
player.onStop()
|
||||
cdda.onActivityStop()
|
||||
midi.onActivityStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
player.onResume()
|
||||
cdda.onActivityResume()
|
||||
midi.onActivityResume()
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> {
|
||||
@@ -69,12 +73,15 @@ class GameActivity : SDLActivity() {
|
||||
}
|
||||
|
||||
// These functions are called in the SDL thread by JNI.
|
||||
@Suppress("unused") fun cddaStart(track: Int, loop: Int) = player.cddaStart(track, loop)
|
||||
@Suppress("unused") fun cddaStop() = player.cddaStop()
|
||||
@Suppress("unused") fun cddaCurrentPosition(): Int = player.cddaCurrentPosition()
|
||||
@Suppress("unused") fun cddaStart(track: Int, loop: Int) = cdda.start(track, loop)
|
||||
@Suppress("unused") fun cddaStop() = cdda.stop()
|
||||
@Suppress("unused") fun cddaCurrentPosition() = cdda.currentPosition()
|
||||
@Suppress("unused") fun midiStart(buf: ByteArray, loop: Int) = midi.start(buf, loop)
|
||||
@Suppress("unused") fun midiStop() = midi.stop()
|
||||
@Suppress("unused") fun midiCurrentPosition() = midi.currentPosition()
|
||||
}
|
||||
|
||||
private class BGMPlayer(playlistPath: String?) {
|
||||
private class CddaPlayer(playlistPath: String?) {
|
||||
private val playlist = playlistPath?.let {
|
||||
try {
|
||||
File(it).readLines()
|
||||
@@ -87,7 +94,7 @@ private class BGMPlayer(playlistPath: String?) {
|
||||
private val player = MediaPlayer()
|
||||
private var playerPaused = false
|
||||
|
||||
fun cddaStart(track: Int, loop: Int) {
|
||||
fun start(track: Int, loop: Int) {
|
||||
val f = playlist.elementAtOrNull(track)
|
||||
if (f.isNullOrEmpty()) {
|
||||
Log.w("cddaStart", "No playlist entry for track $track")
|
||||
@@ -109,29 +116,79 @@ private class BGMPlayer(playlistPath: String?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun cddaStop() {
|
||||
if (currentTrack > 0 && player.isPlaying)
|
||||
fun stop() {
|
||||
if (currentTrack > 0 && player.isPlaying) {
|
||||
player.stop()
|
||||
currentTrack = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun cddaCurrentPosition(): Int {
|
||||
fun currentPosition(): Int {
|
||||
if (currentTrack == 0)
|
||||
return 0
|
||||
val frames = player.currentPosition * 75 / 1000
|
||||
return currentTrack or (frames shl 8)
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
fun onActivityStop() {
|
||||
if (currentTrack > 0 && player.isPlaying) {
|
||||
player.pause()
|
||||
playerPaused = true
|
||||
}
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
fun onActivityResume() {
|
||||
if (playerPaused) {
|
||||
player.start()
|
||||
playerPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MidiPlayer() {
|
||||
private val player = MediaPlayer()
|
||||
private var playing = false
|
||||
private var playerPaused = false
|
||||
|
||||
fun start(buf: ByteArray, loop: Int) {
|
||||
val url = "data:audio/midi;base64," + Base64.encodeToString(buf, Base64.DEFAULT)
|
||||
try {
|
||||
player.apply {
|
||||
reset()
|
||||
setDataSource(url)
|
||||
isLooping = loop == 0
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
playing = true
|
||||
} catch (e: IOException) {
|
||||
Log.e("midiStart", "Cannot play midi", e)
|
||||
player.reset()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (playing && player.isPlaying) {
|
||||
player.stop()
|
||||
playing = false
|
||||
}
|
||||
}
|
||||
|
||||
fun currentPosition(): Int {
|
||||
return if (playing) player.currentPosition else 0
|
||||
}
|
||||
|
||||
fun onActivityStop() {
|
||||
if (playing && player.isPlaying) {
|
||||
player.pause()
|
||||
playerPaused = true
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityResume() {
|
||||
if (playerPaused) {
|
||||
player.start()
|
||||
playerPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2020 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <SDL.h>
|
||||
#include <jni.h>
|
||||
#include "portab.h"
|
||||
#include "midi.h"
|
||||
|
||||
static int midi_initilize(char *pname, int subdev);
|
||||
static int midi_exit();
|
||||
static int midi_start(int no, int loop, char *data, int datalen);
|
||||
static int midi_stop();
|
||||
static int midi_pause(void);
|
||||
static int midi_unpause(void);
|
||||
static int midi_get_playing_info(midiplaystate *st);
|
||||
static int midi_getflag(int mode, int index);
|
||||
static int midi_setflag(int mode, int index, int val);
|
||||
static int midi_setvol(int vol);
|
||||
static int midi_getvol();
|
||||
static int midi_fadestart(int time, int volume, int stop);
|
||||
static boolean midi_fading();
|
||||
|
||||
#define midi midi_android
|
||||
mididevice_t midi = {
|
||||
midi_initilize,
|
||||
midi_exit,
|
||||
midi_start,
|
||||
midi_stop,
|
||||
midi_pause,
|
||||
midi_unpause,
|
||||
midi_get_playing_info,
|
||||
midi_getflag,
|
||||
midi_setflag,
|
||||
midi_setvol,
|
||||
midi_getvol,
|
||||
midi_fadestart,
|
||||
midi_fading
|
||||
};
|
||||
|
||||
static int midino;
|
||||
|
||||
static int midi_initilize(char *pname, int subdev) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int midi_exit() {
|
||||
midi_stop();
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int midi_start(int no, int loop, char *data, int datalen) {
|
||||
if (midino == no)
|
||||
return OK;
|
||||
|
||||
JNIEnv *env = SDL_AndroidGetJNIEnv();
|
||||
|
||||
jbyteArray array = (*env)->NewByteArray(env, datalen);
|
||||
if (!array)
|
||||
return NG;
|
||||
jbyte *buf = (*env)->GetByteArrayElements(env, array, NULL);
|
||||
memcpy(buf, data, datalen);
|
||||
(*env)->ReleaseByteArrayElements(env, array, buf, 0);
|
||||
|
||||
jobject context = SDL_AndroidGetActivity();
|
||||
jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
|
||||
"midiStart", "([BI)V");
|
||||
(*env)->CallVoidMethod(env, context, mid, array, loop);
|
||||
(*env)->DeleteLocalRef(env, array);
|
||||
(*env)->DeleteLocalRef(env, context);
|
||||
|
||||
midino = no;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int midi_stop() {
|
||||
JNIEnv *env = SDL_AndroidGetJNIEnv();
|
||||
jobject context = SDL_AndroidGetActivity();
|
||||
jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
|
||||
"midiStop", "()V");
|
||||
(*env)->CallVoidMethod(env, context, mid);
|
||||
(*env)->DeleteLocalRef(env, context);
|
||||
|
||||
midino = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int midi_pause(void) {
|
||||
return NG; // FIXME
|
||||
}
|
||||
|
||||
static int midi_unpause(void) {
|
||||
return NG; // FIXME
|
||||
}
|
||||
|
||||
static int midi_get_playing_info(midiplaystate *st) {
|
||||
st->in_play = FALSE;
|
||||
st->play_no = 0;
|
||||
st->loc_ms = 0;
|
||||
if (midino == 0)
|
||||
return OK;
|
||||
|
||||
JNIEnv *env = SDL_AndroidGetJNIEnv();
|
||||
jobject context = SDL_AndroidGetActivity();
|
||||
jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
|
||||
"midiCurrentPosition", "()I");
|
||||
int pos = (*env)->CallIntMethod(env, context, mid);
|
||||
(*env)->DeleteLocalRef(env, context);
|
||||
|
||||
if (pos >= 0) {
|
||||
st->in_play = TRUE;
|
||||
st->play_no = midino;
|
||||
st->loc_ms = pos;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int midi_getflag(int mode, int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_setflag(int mode, int index, int val) {
|
||||
return NG;
|
||||
}
|
||||
|
||||
static int midi_setvol(int vol) {
|
||||
return NG; // FIXME
|
||||
}
|
||||
|
||||
static int midi_getvol() {
|
||||
return 100; // FIXME
|
||||
}
|
||||
|
||||
static int midi_fadestart(int time, int volume, int stop) {
|
||||
return NG; // FIXME
|
||||
}
|
||||
|
||||
static boolean midi_fading() {
|
||||
return FALSE; // FIXME
|
||||
}
|
||||
@@ -37,6 +37,10 @@ static int subdev = -1;
|
||||
extern mididevice_t midi_emscripten;
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
extern mididevice_t midi_android;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MIDI_SDLMIXER
|
||||
extern mididevice_t midi_sdlmixer;
|
||||
#endif
|
||||
@@ -61,6 +65,10 @@ int midi_init(mididevice_t *midi) {
|
||||
ret = midi_emscripten.init(NULL, 0);
|
||||
memcpy(midi, &midi_emscripten, sizeof(mididevice_t));
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
ret = midi_android.init(NULL, 0);
|
||||
memcpy(midi, &midi_android, sizeof(mididevice_t));
|
||||
#endif
|
||||
#ifdef ENABLE_MIDI_SDLMIXER
|
||||
ret = midi_sdlmixer.init(NULL, 0);
|
||||
memcpy(midi, &midi_sdlmixer, sizeof(mididevice_t));
|
||||
|
||||
Reference in New Issue
Block a user