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) {