I'm working on a midi program and want the user to have the option to select which midi sequencer is used if they have many instead of using MidiSystem.getSequencer().
EDIT
My code looks like this.
public class Demo {
public static void main(String[] args) {
Sequencer sequencer;
//Gets default sequencer if only one argument given
try {
sequencer = MidiSystem.getSequencer();
} catch (MidiUnavailableException e) {
e.printStackTrace();
return;
}
if (args.length == 0) {
return;
}
File file = new File(args[0]);
boolean select = args.length > 1;
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
List<String> sequencers = new ArrayList<>();
MidiDevice device;
//Populates sequencers lists with potential values and prints
for (int i = 0; i < infos.length; i++) {
try {
device = MidiSystem.getMidiDevice(infos[i]);
if (device instanceof Sequencer) {
System.out.println(sequencers.size() + ": " + device.getDeviceInfo().getName());
sequencers.add(device.getDeviceInfo().getName());
}
} catch (MidiUnavailableException e) {
System.out.println(e.getMessage());
}
}
String name;
//If multiple arguments are given select a new sequencer from the list
if (select) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter the number of the sequencer you wish to use");
name = sequencers.get(scanner.nextInt());
for (int i = 0; i < infos.length; i++) {
try {
device = MidiSystem.getMidiDevice(infos[i]);
if (device.getDeviceInfo().getName().equals(name)) {
//TODO: This line does not create a valid sequencer
sequencer = (Sequencer) device;
System.out.println("Sequencer changed to " + device.getDeviceInfo().getName());
}
} catch (MidiUnavailableException e) {
System.out.println("Cannot locate device " + name);
}
}
}
//Attempt to play midi data from a file into selected sequencer
if (sequencer != null) {
if (!sequencer.isOpen()) {
try {
sequencer.open();
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
}
try {
sequencer.setSequence(MidiSystem.getSequence(file));
} catch (InvalidMidiDataException | IOException e) {
e.printStackTrace();
return;
}
System.out.println("Attempting to play Midi");
sequencer.start();
}
}
}
If you run the program with one argument pointing to a midi file it plays it, but if you have 2 arguments and it asks you to select a sequencer, it is silent. The sequencer is definitely set as the print statement happens and the program doesn't exit immediately as if a midi is still playing, but no noise comes out.
GUI based MRE
This MRE converted the command line app to a GUI to be easier to work with.
To test this code:
- Launch the GUI
- Ensure the text field (at top) points to a valid MIDI file
- Select one of the sequencers in the list below
- Click in the text field to give it focus
- Activate the action listener added to the field (on Windows that means 'hit enter')
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.*;
import javax.sound.midi.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class MidiSequencers {
private JComponent ui = null;
Vector<Sequencer> sequencers = new Vector<>();
public static String URLString = "https://bitmidi.com/uploads/18908.mid";
// Used on my local system, given the hot-link had problems
//"file:/C:/Users/Andrew/Downloads/Queen%20-%20Bohemian%20Rhapsody.mid";
URL url;
JList sequencerList;
MidiSequencers() {
try {
initUI();
} catch (Exception ex) {
ex.printStackTrace();
}
}
public final void initUI() throws Exception {
if (ui != null) {
return;
}
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
populateSequencers();
sequencerList = new JList(sequencers);
ui.add(new JScrollPane(sequencerList));
JTextField textField = new JTextField(URLString, 10);
ui.add(textField,BorderLayout.PAGE_START);
ActionListener playListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Sequencer sequencer = (Sequencer)sequencerList.getSelectedValue();
playSequence(sequencer);
}
};
textField.addActionListener(playListener);
}
private void populateSequencers() throws Exception {
Sequencer sequencer;
//Gets default sequencer if only one argument given
sequencer = MidiSystem.getSequencer();
sequencers.add(sequencer);
url = new URL(URLString);
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
MidiDevice device;
// Populates sequencers list with available sequencers
for (int i = 0; i < infos.length; i++) {
try {
device = MidiSystem.getMidiDevice(infos[i]);
if (device instanceof Sequencer) {
System.out.println(sequencers.size() + ": " + device.getDeviceInfo().getName());
sequencers.add((Sequencer) device);
}
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
}
}
private void playSequence(Sequencer sequencer) {
if (!sequencer.isOpen()) {
try {
sequencer.open();
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
}
try {
sequencer.setSequence(MidiSystem.getSequence(url));
} catch (Exception e) {
e.printStackTrace();
return;
}
System.out.println("Attempting to play Midi");
sequencer.start();
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
MidiSequencers o = new MidiSequencers();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
TLDR: there is no point in letting users choose the sequencer implementation, because if your program uses a standard JDK, there is 99.99999% chances there is only 1 Sequencer implementation available.
Now the explanations of the errors in your program.
1/ A Sequencer needs to be connected to a Midi output device or a synth in order to produce sound.
When you use
MidiSystem.getSequencer(), it is equivalent toMidiSystem.getSequencer(true)(see Javadoc), which means the returned sequencer is automatically connected to the default Synthesizer. This is why you hear sound with this instance.The 2nd Sequencer instance you get is obtained via
MidiSystem.getMidiDevice(), so you need to do the connection manually (usingTransmitter/Receiver).2/ Your code has unintentionally created 2 identical Sequencer instances!
When you call
MidiSystem.getMidiDeviceInfo(), one of theMidiDevice.Infoyou get is the Info from the Sequencer instance created usingMidiSystem.getSequencer(). So when you callMidiSystem.getDevice(info), you generate a second instance using the sameSequencerimplementation (which is actuallyRealTimeSequencersince Java 5).Bug fix
I was able to make your program work with both sequencer instances by replacing
MidiSystem.getSequencer()byMidiSystem.getSequencer(false)inpopulateSequencers(),and add the connection code below, after
sequencer.setSequence()inplaySequence():In many years the one and only case I've seen such a similar feature (let user choose Sequencer) in a Java application, it was in TuxGuitar, because TuxGuitar included its own realtime Sequencer implementation, besides the JDK's one (which was awful until JDK1.4, but since 1.5 it's reliable).