From 0f2f8c9e7d0749a8978b3b03c3585fdf043f0675 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Fri, 6 Dec 2024 23:04:03 -0300 Subject: [PATCH] PlatformPlayer + Manager: Make MIDI Synthesizer and Receiver static This allows all sequencers (in short, every MIDI player requested by any jar) to use the same, fixed references to synthesizer and receiver. So instead of opening multiple synthesizers and getting receivers whenever a MIDI is loaded, we cut all of it by just getting the static synth's receiver every time. Not only is it faster, but it also helps make midi volume changes more functional in Java 8, with a small delay of 50ms being applied to every receiver message to further help the sequencer follow through. --- src/javax/microedition/media/Manager.java | 61 +++++++++++++---- src/org/recompile/mobile/PlatformPlayer.java | 71 ++++++-------------- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/javax/microedition/media/Manager.java b/src/javax/microedition/media/Manager.java index c0f579fd..8c242005 100644 --- a/src/javax/microedition/media/Manager.java +++ b/src/javax/microedition/media/Manager.java @@ -43,16 +43,17 @@ public class Manager /* Custom MIDI variables */ public static boolean useCustomMidi = false; - public static boolean hasLoadedCustomMidi = false; + public static boolean hasLoadedSynth = false; + public static boolean hasLoadedToneSynth = false; public static File soundfontDir = new File("freej2me_system" + File.separatorChar + "customMIDI" + File.separatorChar); private static Soundbank customSoundfont; - public static Synthesizer customSynth; + public static Synthesizer mainSynth; private static Synthesizer dedicatedTonePlayer = null; private static MidiChannel dedicatedToneChannel; public static boolean dumpAudioStreams = false; - public static Player createPlayer(InputStream stream, String type) throws IOException, MediaException + public static synchronized Player createPlayer(InputStream stream, String type) throws IOException, MediaException { checkCustomMidi(); @@ -151,7 +152,7 @@ public static void playTone(int note, int duration, int volume) throws MediaExce { dedicatedTonePlayer = MidiSystem.getSynthesizer(); dedicatedTonePlayer.open(); - if(useCustomMidi && hasLoadedCustomMidi) { dedicatedTonePlayer.loadAllInstruments(customSoundfont); } + if(useCustomMidi && !hasLoadedToneSynth) { dedicatedTonePlayer.loadAllInstruments(customSoundfont); hasLoadedToneSynth = true; } dedicatedToneChannel = dedicatedTonePlayer.getChannels()[0]; } @@ -199,9 +200,9 @@ private static final void checkCustomMidi() { /* * Check if the user wants to run a custom MIDI soundfont. Also, there's no harm - * in checking if the directory exists again. + * in checking if the directory exists again. If it has already been loaded, jsut return. */ - if(!useCustomMidi || hasLoadedCustomMidi) { return; } + if(hasLoadedSynth) { return; } /* Get the first sf2 soundfont in the directory */ String[] fontfile = soundfontDir.list(new FilenameFilter() @@ -214,20 +215,52 @@ private static final void checkCustomMidi() * Only really set the player to use a custom midi soundfont if there is * at least one inside the directory. */ - if(fontfile != null && fontfile.length > 0) + if(useCustomMidi && fontfile != null && fontfile.length > 0) { try { // Load the first .sf2 font available, if there's none that's valid, don't set any and use JVM's default customSoundfont = MidiSystem.getSoundbank(new File(soundfontDir, fontfile[0])); - customSynth = MidiSystem.getSynthesizer(); - customSynth.open(); - customSynth.loadAllInstruments(customSoundfont); + mainSynth = MidiSystem.getSynthesizer(); + mainSynth.open(); + mainSynth.loadAllInstruments(customSoundfont); - hasLoadedCustomMidi = true; // We have now loaded the custom midi soundfont, mark as such so we don't waste time entering here again + PlatformPlayer.synthesizer = mainSynth; + PlatformPlayer.receiver = mainSynth.getReceiver(); + + hasLoadedSynth = true; // We have now loaded the custom midi soundfont, mark as such so we don't waste time entering here again } - catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Could not load soundfont: " + e.getMessage());} - } - else { Mobile.log(Mobile.LOG_WARNING, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Custom MIDI enabled but there's no soundfont in" + (soundfontDir.getPath() + File.separatorChar)); } + catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Could not load soundfont into synth: " + e.getMessage());} + } + else if (!useCustomMidi) + { + try + { + mainSynth = MidiSystem.getSynthesizer(); + mainSynth.open(); + + PlatformPlayer.synthesizer = mainSynth; + PlatformPlayer.receiver = mainSynth.getReceiver(); + + hasLoadedSynth = true; + } + catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Could not load default synth: " + e.getMessage());} + } + else + { + Mobile.log(Mobile.LOG_WARNING, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Custom MIDI enabled but there's no soundfont in" + (soundfontDir.getPath() + File.separatorChar)); + + try + { + mainSynth = MidiSystem.getSynthesizer(); + mainSynth.open(); + + PlatformPlayer.synthesizer = mainSynth; + PlatformPlayer.receiver = mainSynth.getReceiver(); + + hasLoadedSynth = true; + } + catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Could not load default synth: " + e.getMessage());} + } } } diff --git a/src/org/recompile/mobile/PlatformPlayer.java b/src/org/recompile/mobile/PlatformPlayer.java index 78e3e803..72926f33 100644 --- a/src/org/recompile/mobile/PlatformPlayer.java +++ b/src/org/recompile/mobile/PlatformPlayer.java @@ -75,6 +75,10 @@ public class PlatformPlayer implements Player private Control[] controls; + // Manager already sets these two + public static Synthesizer synthesizer; + public static Receiver receiver; + public PlatformPlayer(InputStream stream, String type) { listeners = new Vector(); @@ -400,51 +404,26 @@ private class midiPlayer extends audioplayer { private Sequencer midi; private Sequence midiSequence; - private Synthesizer synthesizer; - private Receiver receiver; public midiPlayer() // For when a Locator call (usually for tones) is issued { Mobile.log(Mobile.LOG_WARNING, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Midi Player [locator] untested"); - try - { - midi = MidiSystem.getSequencer(false); - if (Manager.useCustomMidi && Manager.hasLoadedCustomMidi) - { - synthesizer = Manager.customSynth; // Use the custom synthesizer - } - else - { - synthesizer = MidiSystem.getSynthesizer(); // Default synthesizer - } - - synthesizer.open(); - receiver = synthesizer.getReceiver(); - midi.getTransmitter().setReceiver(receiver); - midiSequence = new Sequence(Sequence.PPQ, 24); // Create an empty sequence, which should be overriden with whatever setSequence() receives. - } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Couldn't load midi file:" + e.getMessage()); } + // Create an empty sequence, which should be overriden with whatever setSequence() receives. + try + { + midi = MidiSystem.getSequencer(false); + midiSequence = new Sequence(Sequence.PPQ, 24); + } + catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Couldn't load midi file:" + e.getMessage()); } } public midiPlayer(InputStream stream) { try - { + { midi = MidiSystem.getSequencer(false); - - if (Manager.useCustomMidi && Manager.hasLoadedCustomMidi) - { - synthesizer = Manager.customSynth; // Use the custom synthesizer - } - else - { - synthesizer = MidiSystem.getSynthesizer(); // Default synthesizer - } - - synthesizer.open(); - receiver = synthesizer.getReceiver(); - midiSequence = MidiSystem.getSequence(stream); - } + midiSequence = MidiSystem.getSequence(stream); } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Couldn't load MIDI file: " + e.getMessage()); @@ -456,7 +435,7 @@ public void realize() try { midi = MidiSystem.getSequencer(false); - midi.getTransmitter().setReceiver(receiver); + midi.getTransmitter().setReceiver(PlatformPlayer.receiver); midi.open(); midi.setSequence(midiSequence); state = Player.REALIZED; @@ -508,9 +487,7 @@ public void stop() public void close() { midi.close(); - synthesizer = null; midiSequence = null; - receiver = null; } public void setLoopCount(int count) @@ -548,10 +525,6 @@ public long setMediaTime(long now) public boolean isRunning() { return midi.isRunning(); } - public Receiver getReceiver() { return receiver; } - - public Synthesizer getSynthesizer() { return synthesizer; } - public Sequence getSequence() { return midiSequence; } public void setSequence(InputStream sequence) @@ -918,7 +891,7 @@ public int[] getProgram(int channel) // This is VERY costly, and might not even be correct as it relies on getProgramList and getBankList, which themselves are untested. try { - MidiChannel[] channels = player.getSynthesizer().getChannels(); + MidiChannel[] channels = PlatformPlayer.synthesizer.getChannels(); if(channel < 0 || channel > channels.length) {throw new IllegalArgumentException("midiControl: Tried to call getProgram with invalid channel");} @@ -978,7 +951,7 @@ public String getProgramName(int bank, int prog) // Java doesn't even have a concept of having names for programs, only instruments. So let's return the instrument's name instead. try { - Soundbank soundbank = player.getSynthesizer().getDefaultSoundbank(); + Soundbank soundbank = PlatformPlayer.synthesizer.getDefaultSoundbank(); Instrument[] instruments = soundbank.getInstruments(); for (Instrument instrument : instruments) @@ -1006,7 +979,6 @@ public int longMidiEvent(byte[] data, int offset, int length) { try { - Receiver receiver = player.getReceiver(); if (data[offset] == (byte) 0xF0 && data[offset + length - 1] == (byte) 0xF7) // Check if it is a SysEx message { // Create the SysEx message without the status byte @@ -1015,7 +987,7 @@ public int longMidiEvent(byte[] data, int offset, int length) { // Create the SysexMessage SysexMessage sysexMessage = new SysexMessage(0xF0, sysExData, sysExData.length); - receiver.send(sysexMessage, -1); // Send the message + PlatformPlayer.receiver.send(sysexMessage, player.getMediaTime() + 50_000L); // Send the message } else // If it is not, send data as a series of short messages (probably implemented incorrectly, and being untested only makes things worse) { @@ -1028,7 +1000,7 @@ public int longMidiEvent(byte[] data, int offset, int length) { else if (msgLength == 2) { shortMessage.setMessage(data[i] & 0xFF, data[i + 1] & 0xFF, 0); } // Status byte + one data byte else if (msgLength == 3) { shortMessage.setMessage(data[i] & 0xFF, data[i + 1] & 0xFF, data[i + 2] & 0xFF); } // Full short message - receiver.send(shortMessage, -1); + PlatformPlayer.receiver.send(shortMessage, player.getMediaTime() + 50_000L); } } return length; // Return the number of bytes sent @@ -1046,7 +1018,7 @@ public void setChannelVolume(int channel, int volume) try { - MidiChannel[] channels = player.getSynthesizer().getChannels(); + MidiChannel[] channels = PlatformPlayer.synthesizer.getChannels(); if(channel < 0 || channel > channels.length || volume < 0 || volume > 127) {throw new IllegalArgumentException("midiControl: Tried to call setChannelVolume with invalid args");} @@ -1092,7 +1064,7 @@ public void shortMidiEvent(int type, int data1, int data2) midiMessage.setMessage(type, data1, data2); // Send the MIDI message to the receiver - player.getReceiver().send(midiMessage, -1); // -1 is the timestamp value to send this message immediately. + PlatformPlayer.receiver.send(midiMessage, player.getMediaTime() + 50_000L); // Send message after 50ms } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Failed to send short MIDI event: " + e.getMessage()); } } @@ -1151,7 +1123,8 @@ public int setLevel(int level) try { volumeMessage.setMessage(ShortMessage.CONTROL_CHANGE, channel, 7, midiVolume); - sequencer.getReceiver().send(volumeMessage, -1); + // Apply volume change 50ms after the current playback time, to give sequencer some time to breathe. + PlatformPlayer.receiver.send(volumeMessage, sequencer.getMediaTime() + 50_000L); } catch (Exception e) {