Skip to content

Commit

Permalink
PlatformPlayer + Manager: Make MIDI Synthesizer and Receiver static
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AShiningRay committed Dec 7, 2024
1 parent 62b0d7a commit 0f2f8c9
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 63 deletions.
61 changes: 47 additions & 14 deletions src/javax/microedition/media/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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()
Expand All @@ -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());}
}
}
}
71 changes: 22 additions & 49 deletions src/org/recompile/mobile/PlatformPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlayerListener>();
Expand Down Expand Up @@ -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());
Expand All @@ -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;
Expand Down Expand Up @@ -508,9 +487,7 @@ public void stop()
public void close()
{
midi.close();
synthesizer = null;
midiSequence = null;
receiver = null;
}

public void setLoopCount(int count)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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");}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand All @@ -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
Expand All @@ -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");}

Expand Down Expand Up @@ -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()); }
}
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit 0f2f8c9

Please sign in to comment.