From 890956772a880be75840ba3fa0624efae8193cdb Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 00:28:04 -0300 Subject: [PATCH 1/6] media: PlatformPlayer: Fix wav and midi incorrect loop count It appears that most java games (Gameloft ones, like Asphalt 4, Asphalt 6, Ferrari GT 3, Assassin's Creed Revelations and many others for example) set the loop count on both midi and wav streams as 1. This, however, translates to Java 8 looping those samples one time more than needed, so every sfx and bgm playback repeated two times instead of just one. By reducing the requested loop count by one and checking cases where J2ME apps might request loop count 0, pretty much all the aforementioned games have their audio playback fixed. --- src/org/recompile/mobile/PlatformPlayer.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/org/recompile/mobile/PlatformPlayer.java b/src/org/recompile/mobile/PlatformPlayer.java index 960abb56..c229521d 100644 --- a/src/org/recompile/mobile/PlatformPlayer.java +++ b/src/org/recompile/mobile/PlatformPlayer.java @@ -206,8 +206,6 @@ private class midiPlayer extends audioplayer { private Sequencer midi; - private int loops = 0; - private long tick = 0L; public midiPlayer(InputStream stream) @@ -250,8 +248,8 @@ public void deallocate() public void setLoopCount(int count) { - loops = count; - midi.setLoopCount(count); + if(count < 1) {count = 1;} /* Treat cases where an app might set loops as 0 */ + midi.setLoopCount(count-1); } public long setMediaTime(long now) { @@ -283,8 +281,6 @@ private class wavPlayer extends audioplayer private int[] wavHeaderData = new int[4]; /* Player control variables */ - private int loops = 0; - private long time = 0L; public wavPlayer(InputStream stream) @@ -349,8 +345,8 @@ public void stop() public void setLoopCount(int count) { - loops = count; - wavClip.loop(count); + if(count < 1) {count = 1;} /* Treat cases where an app might set loops as 0 */ + wavClip.loop(count-1); } public long setMediaTime(long now) From 49c2ca77c7aa1383de15a546d116e15ecea90cc2 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 00:17:48 -0300 Subject: [PATCH 2/6] media: Manager: Limit the amount of MIDI "channels" for J2ME apps DOOM II as well as Orcs and Elves II appear to constantly allocate new MIDI samples whenever a different sample from the previously played one is loaded, and never deallocate any of their samples, resulting in memory leaks. While not the best solution to this, we can work around those leaks by imposing a hard limit on how many MIDI "channel slots" a J2ME app can populate. Currently set at 36 slots, it seems to be big enough for anything i tested so far, including Ferrari GT 3 which loads up many different MIDI BGMs alongside wav files (unrestricted, can allocate as many as needed) during a race. --- src/javax/microedition/media/Manager.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/javax/microedition/media/Manager.java b/src/javax/microedition/media/Manager.java index 2cec45ea..07db55ca 100644 --- a/src/javax/microedition/media/Manager.java +++ b/src/javax/microedition/media/Manager.java @@ -25,12 +25,24 @@ public final class Manager { public static final String TONE_DEVICE_LOCATOR = "device://tone"; - + public static Player midiChannel[] = new Player[36]; + public static byte midiChannelIndex = 0; public static Player createPlayer(InputStream stream, String type) throws IOException, MediaException { //System.out.println("Create Player Stream "+type); - return new PlatformPlayer(stream, type); + if(type.equalsIgnoreCase("audio/mid") || type.equalsIgnoreCase("audio/midi") || type.equalsIgnoreCase("sp-midi") || type.equalsIgnoreCase("audio/spmidi")) + { + if(midiChannelIndex >= midiChannel.length) { midiChannelIndex = 0; } + if(midiChannel[midiChannelIndex] != null) { midiChannel[midiChannelIndex].deallocate(); } + midiChannel[midiChannelIndex] = new PlatformPlayer(stream, type); + midiChannelIndex++; + return midiChannel[midiChannelIndex-1]; + } + else + { + return new PlatformPlayer(stream, type); + } } public static Player createPlayer(String locator) throws MediaException From 7e5b2272da73b2ffe3c089950e66a4ca8c2c45eb Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 10:09:13 -0300 Subject: [PATCH 3/6] WavImaAdpcmDecoder: Change some variables from int to byte type Swapping them to byte variables helps indicate that they should not receive larger values. --- src/org/recompile/mobile/WavImaAdpcmDecoder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/recompile/mobile/WavImaAdpcmDecoder.java b/src/org/recompile/mobile/WavImaAdpcmDecoder.java index 426b9855..0639f759 100644 --- a/src/org/recompile/mobile/WavImaAdpcmDecoder.java +++ b/src/org/recompile/mobile/WavImaAdpcmDecoder.java @@ -36,16 +36,16 @@ public class WavImaAdpcmDecoder * are being decoded. So far only seems to happen in Java 8 and on my more limited devices." * - @AShiningRay */ - private static final int LEFTCHANNEL = 0; - private static final int RIGHTCHANNEL = 1; + private static final byte LEFTCHANNEL = 0; + private static final byte RIGHTCHANNEL = 1; - private static final int HEADERSIZE = 44; - private static final int PCMPREAMBLESIZE = 16; + private static final byte HEADERSIZE = 44; + private static final byte PCMPREAMBLESIZE = 16; private static final int[] prevSample = {0, 0}; private static final int[] prevStep = {0, 0}; - private static final int[] ima_step_index_table = + private static final byte[] ima_step_index_table = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 From 2509a43618fae804c60bcf49b8ae55ed7bc56abd Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 11:21:37 -0300 Subject: [PATCH 4/6] WavImaAdpcmDecoder: Offload ADPCM decoding to a separate thread The main thread on FreeJ2ME is already pretty busy with graphics and everything else, we don't need ADPCM decoding clogging things up even further. This should help make decoding faster and reduce stuttering. --- .../recompile/mobile/WavImaAdpcmDecoder.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/org/recompile/mobile/WavImaAdpcmDecoder.java b/src/org/recompile/mobile/WavImaAdpcmDecoder.java index 0639f759..a036f2d8 100644 --- a/src/org/recompile/mobile/WavImaAdpcmDecoder.java +++ b/src/org/recompile/mobile/WavImaAdpcmDecoder.java @@ -23,9 +23,14 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + public class WavImaAdpcmDecoder { - /* Information about this audio format: https://wiki.multimedia.cx/index.php/IMA_ADPCM */ /* @@ -414,18 +419,28 @@ private void buildHeader(final byte[] buffer, final short numChannels, final int } /* Decode the received IMA WAV ADPCM stream into a signed PCM16LE byte array, then return it to PlatformPlayer. */ - public ByteArrayInputStream decodeImaAdpcm(InputStream stream, int[] wavHeaderData) throws IOException + public ByteArrayInputStream decodeImaAdpcm(InputStream stream, int[] wavHeaderData) throws IOException, InterruptedException, ExecutionException { - /* Remove the header from the stream, we shouldn't "decode" it as if it was a sample */ - readHeader(stream); + /* Offload IMA ADPCM decoding to a separate thread */ + Callable threadedDecoder = () -> + { + /* Remove the header from the stream, we shouldn't "decode" it as if it was a sample */ + readHeader(stream); + + final byte[] input = new byte[stream.available()]; + readInputStreamData(stream, input, 0, stream.available()); - final byte[] input = new byte[stream.available()]; - readInputStreamData(stream, input, 0, stream.available()); + return decodeADPCM(input, input.length, wavHeaderData[2], wavHeaderData[3]); + }; + + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(threadedDecoder); + byte[] output = future.get(); /* Get the asynchronously decoded stream */ - byte[] output = decodeADPCM(input, input.length, wavHeaderData[2], wavHeaderData[3]); buildHeader(output, (short) wavHeaderData[2], wavHeaderData[1]); /* Builds a new header for the decoded stream. */ + executor.shutdown(); + return new ByteArrayInputStream(output); } - } From 1aa4a1d97f7263d3975120e4358228c341915be1 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 23:18:57 -0300 Subject: [PATCH 5/6] Make the max amount of MIDI players a configurable setting Instead of relying on a fixed value for the max, a user setting for that allows more control and fine tuning for specific apps. --- src/javax/microedition/media/Manager.java | 19 +++++---- src/libretro/freej2me_libretro.c | 37 +++++++++++++----- src/libretro/freej2me_libretro.h | 47 ++++++++++++++++++++++- src/org/recompile/freej2me/Config.java | 33 ++++++++++++++-- src/org/recompile/freej2me/FreeJ2ME.java | 3 ++ src/org/recompile/freej2me/Libretro.java | 20 ++++++++++ 6 files changed, 136 insertions(+), 23 deletions(-) diff --git a/src/javax/microedition/media/Manager.java b/src/javax/microedition/media/Manager.java index 07db55ca..3f856816 100644 --- a/src/javax/microedition/media/Manager.java +++ b/src/javax/microedition/media/Manager.java @@ -23,21 +23,20 @@ public final class Manager { - public static final String TONE_DEVICE_LOCATOR = "device://tone"; - public static Player midiChannel[] = new Player[36]; - public static byte midiChannelIndex = 0; + public static Player midiPlayers[] = new Player[32]; /* Default max amount of players in FreeJ2ME's config */ + public static byte midiPlayersIndex = 0; public static Player createPlayer(InputStream stream, String type) throws IOException, MediaException { //System.out.println("Create Player Stream "+type); if(type.equalsIgnoreCase("audio/mid") || type.equalsIgnoreCase("audio/midi") || type.equalsIgnoreCase("sp-midi") || type.equalsIgnoreCase("audio/spmidi")) { - if(midiChannelIndex >= midiChannel.length) { midiChannelIndex = 0; } - if(midiChannel[midiChannelIndex] != null) { midiChannel[midiChannelIndex].deallocate(); } - midiChannel[midiChannelIndex] = new PlatformPlayer(stream, type); - midiChannelIndex++; - return midiChannel[midiChannelIndex-1]; + if(midiPlayersIndex >= midiPlayers.length) { midiPlayersIndex = 0; } + if(midiPlayers[midiPlayersIndex] != null) { midiPlayers[midiPlayersIndex].deallocate(); } + midiPlayers[midiPlayersIndex] = new PlatformPlayer(stream, type); + midiPlayersIndex++; + return midiPlayers[midiPlayersIndex-1]; } else { @@ -69,4 +68,8 @@ public static void playTone(int note, int duration, int volume) System.out.println("Play Tone"); } + public static void updatePlayerNum(byte num) + { + midiPlayers = new Player[num]; + } } diff --git a/src/libretro/freej2me_libretro.c b/src/libretro/freej2me_libretro.c index b9a91f31..05184b4c 100755 --- a/src/libretro/freej2me_libretro.c +++ b/src/libretro/freej2me_libretro.c @@ -144,6 +144,7 @@ int rotateScreen; /* acts as a boolean */ int phoneType; /* 0=standard, 1=nokia, 2=siemens, 3=motorola */ int gameFPS; /* Auto(0), 60, 30, 15 */ int soundEnabled; /* also acts as a boolean */ +int maxMidiPlayers; /* Maximum amount of MIDI Players allowed on FreeJ2ME at any given time */ /* Variables used to manage the pointer speed when controlled from an analog stick */ int pointerXSpeed = 8; int pointerYSpeed = 8; @@ -331,6 +332,21 @@ static void check_variables(bool first_time_startup) else if (!strcmp(var.value, "on")) { soundEnabled = 1; } } + var.key = "freej2me_maxmidiplayers"; + if (Environ(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "32")) { maxMidiPlayers = 32; } + else if (!strcmp(var.value, "1")) { maxMidiPlayers = 1; } + else if (!strcmp(var.value, "2")) { maxMidiPlayers = 2; } + else if (!strcmp(var.value, "4")) { maxMidiPlayers = 4; } + else if (!strcmp(var.value, "8")) { maxMidiPlayers = 8; } + else if (!strcmp(var.value, "16")) { maxMidiPlayers = 16; } + else if (!strcmp(var.value, "32")) { maxMidiPlayers = 32; } + else if (!strcmp(var.value, "48")) { maxMidiPlayers = 48; } + else if (!strcmp(var.value, "64")) { maxMidiPlayers = 64; } + else if (!strcmp(var.value, "96")) { maxMidiPlayers = 96; } + } + var.key = "freej2me_pointertype"; if (Environ(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) @@ -410,7 +426,7 @@ static void check_variables(bool first_time_startup) /* Prepare a string to pass those core options to the Java app */ - snprintf(options_update, PIPE_MAX_LEN, "FJ2ME_LR_OPTS:|%lux%lu|%d|%d|%d|%d", screenRes[0], screenRes[1], rotateScreen, phoneType, gameFPS, soundEnabled); + snprintf(options_update, PIPE_MAX_LEN, "FJ2ME_LR_OPTS:|%lux%lu|%d|%d|%d|%d|%d", screenRes[0], screenRes[1], rotateScreen, phoneType, gameFPS, soundEnabled, maxMidiPlayers); optstrlen = strlen(options_update); /* 0xD = 13, which is the special case where the java app will receive the updated configs */ @@ -470,23 +486,24 @@ void retro_init(void) */ check_variables(true); - char resArg[2][4], rotateArg[2], phoneArg[2], fpsArg[3], soundArg[2]; + char resArg[2][4], rotateArg[2], phoneArg[2], fpsArg[3], soundArg[2], maxMidiArg[3]; sprintf(resArg[0], "%lu", screenRes[0]); /* Libretro config Width */ sprintf(resArg[1], "%lu", screenRes[1]); /* Libretro config Height */ sprintf(rotateArg, "%d", rotateScreen); sprintf(phoneArg, "%d", phoneType); sprintf(fpsArg, "%d", gameFPS); sprintf(soundArg, "%d", soundEnabled); + sprintf(maxMidiArg, "%d", maxMidiPlayers); /* start java process */ char *javapath; Environ(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &javapath); char *outPath = malloc(sizeof(char) * PATH_MAX_LENGTH); fill_pathname_join(outPath, javapath, "freej2me-lr.jar", PATH_MAX_LENGTH); - char *params[] = { "java", "-jar", outPath, resArg[0], resArg[1], rotateArg, phoneArg, fpsArg, soundArg, NULL }; + char *params[] = { "java", "-jar", outPath, resArg[0], resArg[1], rotateArg, phoneArg, fpsArg, soundArg, maxMidiArg, NULL }; - log_fn(RETRO_LOG_INFO, "Passing params: %s | %s | %s | %s | %s | %s \n", *(params+3), - *(params+4), *(params+5), *(params+6), *(params+7), *(params+8)); + log_fn(RETRO_LOG_INFO, "Passing params: %s | %s | %s | %s | %s | %s | %s \n", *(params+3), + *(params+4), *(params+5), *(params+6), *(params+7), *(params+8), *(params+9)); log_fn(RETRO_LOG_INFO, "Preparing to open FreeJ2ME's Java app (make sure freej2me-lr.jar is inside system/).\n"); #ifdef __linux__ @@ -974,8 +991,8 @@ pid_t javaOpen(char *cmd, char **params) log_fn(RETRO_LOG_INFO, "Setting up java app's process and pipes...\n"); log_fn(RETRO_LOG_INFO, "Opening: %s %s %s ...\n", *(params+0), *(params+1), *(params+2)); - log_fn(RETRO_LOG_INFO, "Params: %s | %s | %s | %s | %s | %s \n", *(params+3), - *(params+4), *(params+5), *(params+6), *(params+7), *(params+8)); + log_fn(RETRO_LOG_INFO, "Params: %s | %s | %s | %s | %s | %s | %s \n", *(params+3), + *(params+4), *(params+5), *(params+6), *(params+7), *(params+8), *(params+9)); int fd_stdin = 0; int fd_stdout = 1; @@ -1098,15 +1115,15 @@ void javaOpen(char *cmd, char **params) sprintf(cmdWin, "javaw -jar %s", cmd); log_fn(RETRO_LOG_INFO, "Opening: %s \n", cmd); - for (int i = 3; i <= 8; i++) /* There are 8 cmd arguments for now */ + for (int i = 3; i <= 9; i++) /* There are 8 cmd arguments for now */ { log_fn(RETRO_LOG_INFO, "Processing arg %d: %s \n", i, *(params+i)); sprintf(cmdWin, "%s %s", cmdWin, *(params+i)); } log_fn(RETRO_LOG_INFO, "Creating proc: %s \n", cmdWin); - log_fn(RETRO_LOG_INFO, "Params: %s | %s | %s | %s | %s | %s \n", *(params+3), - *(params+4), *(params+5), *(params+6), *(params+7), *(params+8)); + log_fn(RETRO_LOG_INFO, "Params: %s | %s | %s | %s | %s | %s | %s\n", *(params+3), + *(params+4), *(params+5), *(params+6), *(params+7), *(params+8), *(params+9)); GetStartupInfo(&startInfo); startInfo.dwFlags = STARTF_USESTDHANDLES; diff --git a/src/libretro/freej2me_libretro.h b/src/libretro/freej2me_libretro.h index 3dd33d97..b691b341 100644 --- a/src/libretro/freej2me_libretro.h +++ b/src/libretro/freej2me_libretro.h @@ -150,8 +150,8 @@ struct retro_core_option_v2_definition core_options[] = "freej2me_sound", "Virtual Phone Settings > Virtual Phone Sound", "Virtual Phone Sound", - "Enables or disables the virtual phone's ability to load and play audio samples/tones. Some games require support for codecs not yet implemented, or have issues that can be worked around by disabling audio in FreeJ2ME (ID Software games such as DOOM II RPG having memory leaks with MIDI samples being one example). If a game doesn't run or has issues during longer sessions, try disabling this option.", - "Enables or disables the virtual phone's ability to load and play audio samples/tones. Some games require support for codecs not yet implemented, or have issues that can be worked around by disabling audio in FreeJ2ME (ID Software games such as DOOM II RPG having memory leaks with MIDI samples being one example). If a game doesn't run or has issues during longer sessions, try disabling this option.", + "Enables or disables the virtual phone's ability to load and play audio samples/tones. Some games require support for codecs not yet implemented, or have issues that can be worked around by disabling audio in FreeJ2ME. If a game doesn't run or has issues during longer sessions, try disabling this option.", + "Enables or disables the virtual phone's ability to load and play audio samples/tones. Some games require support for codecs not yet implemented, or have issues that can be worked around by disabling audio in FreeJ2ME. If a game doesn't run or has issues during longer sessions, try disabling this option.", "vphone_settings", { { "on", "On" }, @@ -160,6 +160,27 @@ struct retro_core_option_v2_definition core_options[] = }, "on" }, + { + "freej2me_maxmidiplayers", + "Virtual Phone Settings > Max MIDI Players", + "Max MIDI Players", + "Sets the maximum amount of MIDI files that a given J2ME app can load up and play at any given time. Lower values may reduce memory usage and improve the VM's performance (especially on ID Software games like DOOM II RPG where setting this to 1 vastly reduces memory usage compared to the default), but some games might need bigger values.", + "Sets the maximum amount of MIDI files that a given J2ME app can load up and play at any given time. Lower values may reduce memory usage and improve the VM's performance (especially on ID Software games like DOOM II RPG where setting this to 1 vastly reduces memory usage compared to the default), but some games might need bigger values.", + "vphone_settings", + { + { "1", "1" }, + { "2", "2" }, + { "4", "4" }, + { "8", "8" }, + { "16", "16" }, + { "32", "32" }, + { "48", "48" }, + { "64", "64" }, + { "96", "96" }, + { NULL, NULL }, + }, + "32" + }, { "freej2me_pointertype", "Advanced Settings > Pointer Type", @@ -357,6 +378,24 @@ struct retro_core_option_definition core_options_v1 [] = }, "on" }, + { + "freej2me_maxmidiplayers", + "Max MIDI Players", + "Sets the maximum amount of MIDI files that a given J2ME app can load up and play at any given time. Lower values may reduce memory usage and improve the VM's performance (especially on ID Software games like DOOM II RPG where setting this to 1 vastly reduces memory usage compared to the default), but some games might need bigger values.", + { + { "1", "1" }, + { "2", "2" }, + { "4", "4" }, + { "8", "8" }, + { "16", "16" }, + { "32", "32" }, + { "48", "48" }, + { "64", "64" }, + { "96", "96" }, + { NULL, NULL }, + }, + "32" + }, { "freej2me_pointertype", "Pointer Type", @@ -475,6 +514,10 @@ static const struct retro_variable vars[] = "freej2me_sound", "Virtual Phone Sound; on|off" }, + { /* Max MIDI Players */ + "freej2me_maxmidiplayers", + "Max MIDI Players: 32|1|2|4|8|16|48|64|96" + }, { /* Pointer Type */ "freej2me_pointertype", "Pointer Type; Mouse|Touch|None", diff --git a/src/org/recompile/freej2me/Config.java b/src/org/recompile/freej2me/Config.java index b994923f..92fe3cda 100644 --- a/src/org/recompile/freej2me/Config.java +++ b/src/org/recompile/freej2me/Config.java @@ -31,6 +31,7 @@ import java.nio.file.Path; import javax.microedition.lcdui.Graphics; +import javax.microedition.media.Manager; import org.recompile.mobile.Mobile; import org.recompile.mobile.PlatformImage; @@ -65,13 +66,14 @@ public Config() gc = lcd.getGraphics(); menu = new ArrayList(); - menu.add(new String[]{"Resume Game", "Display Size", "Sound", "Limit FPS", "Phone", "Rotate", "Exit"}); // 0 - Main Menu + menu.add(new String[]{"Resume Game", "Display Size", "Sound", "Limit FPS", "Phone", "Rotate", "Max MIDI Players", "Exit"}); // 0 - Main Menu menu.add(new String[]{"96x65","96x96","104x80","128x128","132x176","128x160","176x208","176x220", "208x208", "240x320", "320x240", "240x400", "352x416", "360x640", "640x360" ,"480x800", "800x480"}); // 1 - Size menu.add(new String[]{"Quit", "Main Menu"}); // 2 - Restart Notice menu.add(new String[]{"On", "Off"}); // 3 - sound menu.add(new String[]{"Standard", "Nokia", "Siemens","Motorola"}); // 4 - Phone menu.add(new String[]{"On", "Off"}); // 5 - rotate menu.add(new String[]{"Auto", "60 - Fast", "30 - Slow", "15 - Turtle"}); // 6 - FPS + menu.add(new String[]{"1", "2", "4", "8", "16", "32", "48", "64", "96"}); // 7 - Max amount of MIDI Players onChange = new Runnable() @@ -150,6 +152,7 @@ public void init() if(!settings.containsKey("phone")) { settings.put("phone", "Standard"); } if(!settings.containsKey("rotate")) { settings.put("rotate", "off"); } if(!settings.containsKey("fps")) { settings.put("fps", "0"); } + if(!settings.containsKey("maxmidiplayers")) { settings.put("maxmidiplayers", "32"); } int w = Integer.parseInt(settings.get("width")); int h = Integer.parseInt(settings.get("height")); @@ -160,6 +163,7 @@ public void init() lcd = new PlatformImage(width, height); gc = lcd.getGraphics(); } + } catch (Exception e) { @@ -311,7 +315,7 @@ public void render() for(int i=start; (i<(start+max))&(i1 && i<7) + if(menuid==0 && i>1 && i<8) { switch(i) { @@ -319,6 +323,7 @@ public void render() case 3: label = label+": "+settings.get("fps"); break; case 4: label = label+": "+settings.get("phone"); break; case 5: label = label+": "+settings.get("rotate"); break; + case 6: label = label+": "+settings.get("maxmidiplayers"); break; } } if(i==itemid) @@ -349,7 +354,8 @@ private void doMenuAction() case 3: menuid=6; itemid=0; break; // fps case 4: menuid=4; itemid=0; break; // phone case 5: menuid=5; itemid=0; break; // rotate - case 6: System.exit(0); break; + case 6: menuid=7; itemid=0; break; // max MIDI Players + case 7: System.exit(0); break; } break; @@ -397,6 +403,19 @@ private void doMenuAction() menuid=0; itemid=0; break; + case 7: // Max Midi Players + if(itemid==0) { updateMIDIPlayers("1"); } + if(itemid==1) { updateMIDIPlayers("2"); } + if(itemid==2) { updateMIDIPlayers("4"); } + if(itemid==3) { updateMIDIPlayers("8"); } + if(itemid==4) { updateMIDIPlayers("16"); } + if(itemid==5) { updateMIDIPlayers("32"); } + if(itemid==6) { updateMIDIPlayers("48"); } + if(itemid==7) { updateMIDIPlayers("64"); } + if(itemid==8) { updateMIDIPlayers("96"); } + menuid=2; itemid=0; + break; + } render(); @@ -446,4 +465,12 @@ private void updateFPS(String value) saveConfig(); onChange.run(); } + + private void updateMIDIPlayers(String value) + { + System.out.println("Config: maxmidiplayers "+value); + settings.put("maxmidiplayers", value); + saveConfig(); + onChange.run(); + } } diff --git a/src/org/recompile/freej2me/FreeJ2ME.java b/src/org/recompile/freej2me/FreeJ2ME.java index c9f7b13f..8ecad00d 100644 --- a/src/org/recompile/freej2me/FreeJ2ME.java +++ b/src/org/recompile/freej2me/FreeJ2ME.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.FilenameFilter; import javax.imageio.ImageIO; +import javax.microedition.media.Manager; public class FreeJ2ME { @@ -349,6 +350,8 @@ private void settingsChanged() resize(); main.setSize(lcdWidth*scaleFactor+xborder , lcdHeight*scaleFactor+yborder); } + + Manager.updatePlayerNum((byte) Integer.parseInt(config.settings.get("maxmidiplayers"))); } private int getMobileKey(int keycode) diff --git a/src/org/recompile/freej2me/Libretro.java b/src/org/recompile/freej2me/Libretro.java index 0810c5c8..4127b044 100644 --- a/src/org/recompile/freej2me/Libretro.java +++ b/src/org/recompile/freej2me/Libretro.java @@ -33,6 +33,8 @@ import java.io.File; import java.net.URL; +import javax.microedition.media.Manager; + public class Libretro { private int lcdWidth; @@ -50,6 +52,7 @@ public class Libretro private boolean rotateDisplay = false; private boolean soundEnabled = true; private int limitFPS = 0; + private int maxmidiplayers = 32; private boolean[] pressedKeys = new boolean[128]; @@ -98,6 +101,9 @@ public Libretro(String args[]) if(Integer.parseInt(args[5]) == 0) { soundEnabled = false; } + maxmidiplayers = Integer.parseInt(args[6]); + Manager.updatePlayerNum((byte) maxmidiplayers); + /* Once it finishes parsing all arguments, it's time to set up freej2me-lr */ surface = new BufferedImage(lcdWidth, lcdHeight, BufferedImage.TYPE_INT_ARGB); // libretro display @@ -275,6 +281,8 @@ public void run() config.settings.put("fps", ""+limitFPS); + config.settings.put("maxmidiplayers", ""+maxmidiplayers); + config.saveConfig(); settingsChanged(); @@ -330,6 +338,16 @@ public void run() if(Integer.parseInt(cfgtokens[6])==1) { config.settings.put("sound", "on"); } if(Integer.parseInt(cfgtokens[6])==0) { config.settings.put("sound", "off"); } + if(Integer.parseInt(cfgtokens[7])==0) { config.settings.put("maxmidiplayers", "1");} + if(Integer.parseInt(cfgtokens[7])==1) { config.settings.put("maxmidiplayers", "2");} + if(Integer.parseInt(cfgtokens[7])==2) { config.settings.put("maxmidiplayers", "4");} + if(Integer.parseInt(cfgtokens[7])==3) { config.settings.put("maxmidiplayers", "8");} + if(Integer.parseInt(cfgtokens[7])==4) { config.settings.put("maxmidiplayers", "16");} + if(Integer.parseInt(cfgtokens[7])==5) { config.settings.put("maxmidiplayers", "32");} + if(Integer.parseInt(cfgtokens[7])==6) { config.settings.put("maxmidiplayers", "48");} + if(Integer.parseInt(cfgtokens[7])==7) { config.settings.put("maxmidiplayers", "64");} + if(Integer.parseInt(cfgtokens[7])==8) { config.settings.put("maxmidiplayers", "96");} + config.saveConfig(); settingsChanged(); break; @@ -423,6 +441,8 @@ private void settingsChanged() surface = new BufferedImage(lcdWidth, lcdHeight, BufferedImage.TYPE_INT_ARGB); // libretro display gc = (Graphics2D)surface.getGraphics(); } + + Manager.updatePlayerNum((byte) Integer.parseInt(config.settings.get("maxmidiplayers"))); } private void keyDown(int key) From 0dfcb9a6ac9fa8bfde36ab5b155270d80e314242 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Sat, 21 Oct 2023 23:58:52 -0300 Subject: [PATCH 6/6] Manager: Avoid deallocating streams that are currently running Previously, the logic behind deallocating streams only took into account if the player index had reached the end of the array, and started deallocating from its first position onwards. This was a problem because it is possible that some of those positions would still be running when FreeJ2ME went to deallocate them. Now it will also take into account whether the current position is running or not, and will only forcefully deallocate a position that's running if the entire array is allocated, and all the players are running, to make space for a new one. --- src/javax/microedition/media/Manager.java | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/javax/microedition/media/Manager.java b/src/javax/microedition/media/Manager.java index 3f856816..240e1263 100644 --- a/src/javax/microedition/media/Manager.java +++ b/src/javax/microedition/media/Manager.java @@ -33,10 +33,24 @@ public static Player createPlayer(InputStream stream, String type) throws IOExce if(type.equalsIgnoreCase("audio/mid") || type.equalsIgnoreCase("audio/midi") || type.equalsIgnoreCase("sp-midi") || type.equalsIgnoreCase("audio/spmidi")) { if(midiPlayersIndex >= midiPlayers.length) { midiPlayersIndex = 0; } - if(midiPlayers[midiPlayersIndex] != null) { midiPlayers[midiPlayersIndex].deallocate(); } + for(; midiPlayersIndex < midiPlayers.length; midiPlayersIndex++) + { + if(midiPlayers[midiPlayersIndex] == null) { break; } /* A null position means we can use it right away */ + /* Otherwise, we only deallocate a position if it is not playing (running). */ + else if(midiPlayers[midiPlayersIndex] != null && midiPlayers[midiPlayersIndex].getState() == Player.PREFETCHED) + { + midiPlayers[midiPlayersIndex].deallocate(); + break; + } + /* If we ever reach this one, it's because all the other slots are used, and are playing */ + else if(midiPlayersIndex == midiPlayers.length-1) + { + midiPlayers[midiPlayersIndex].deallocate(); + break; + } + } midiPlayers[midiPlayersIndex] = new PlatformPlayer(stream, type); - midiPlayersIndex++; - return midiPlayers[midiPlayersIndex-1]; + return midiPlayers[midiPlayersIndex++]; } else {