问题描述
我正在开发一个 MIDI 程序,并且希望用户可以选择使用哪个 MIDI 音序器,如果他们有很多而不是使用 Midisystem.getSequencer()
。
编辑
我的代码看起来像这样。
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();
}
}
}
如果您使用一个指向midi文件的参数运行程序,它会播放它,但如果您有2个参数并且它要求您选择一个音序器,则它是静音的。音序器肯定是在打印语句发生时设置的,并且程序不会像 MIDI 仍在播放一样立即退出,但不会发出噪音。
基于 GUI 的 MRE
此 MRE 将命令行应用程序转换为 GUI,以便更易于使用。
要测试此代码:
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));
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:让用户选择定序器实现是没有意义的,因为如果您的程序使用标准 JDK,则有 99.99999% 的可能性只有 1 个定序器实现可用。
现在解释程序中的错误。
1/ 音序器需要连接到 Midi 输出设备或合成器才能产生声音。
当您使用MidiSystem.getSequencer()
时,它相当于MidiSystem.getSequencer(true)
(参见Javadoc),这意味着返回的音序器会自动连接到默认的合成器。这就是您在此实例中听到声音的原因。
您获得的第二个 Sequencer 实例是通过 MidiSystem.getMidiDevice()
获取的,因此您需要手动进行连接(使用 Transmitter
/Receiver
)。
2/ 您的代码无意中创建了 2 个相同的 Sequencer 实例!
当您调用 MidiSystem.getMidiDeviceInfo()
时,您获得的 MidiDevice.Info
之一是来自使用 MidiSystem.getSequencer()
创建的 Sequencer 实例的信息。因此,当您调用 MidiSystem.getDevice(info)
时,您会使用相同的 Sequencer
实现(从 Java 5 开始实际上是 RealTimeSequencer
)生成第二个实例。
错误修复
通过将 MidiSystem.getSequencer()
中的 MidiSystem.getSequencer(false)
替换为 populateSequencers()
,我能够使您的程序与两个音序器实例一起工作,
并在 sequencer.setSequence()
中的 playSequence()
之后添加下面的连接代码:
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
Transmitter t = sequencer.getTransmitter();
t.setReceiver(synth.getReceiver());
多年来,我在 Java 应用程序中看到过这样一个也是唯一的案例(让用户选择 Sequencer),它是在 TuxGuitar 中,因为 TuxGuitar 包含自己的实时 Sequencer 实现,除了 JDK 的一个(它在 JDK1.4 之前很糟糕,但从 1.5 开始它是可靠的)。