Java播放midi文件及加载sf2音色库示例

最近折腾Java的MIDI功能,发现网上的教程大多只讲到怎么用Sequencer,更深入的比较难找,而且大都没的注释,于是自己踩坑无数,来这里发点稍微深入使用Java的MIDI功能的示例(嘛其实也没多么深入,毕竟我玩Java也只是刚入门的水平而已)

运行环境及测试如下,Mac虚拟机,Oracle JDK 8u201。

bogon:temp donmor$ screenfetch
readlink: illegal option -- f
usage: readlink [-n] [file ...]
awk: can't open file /proc/fb
 source line number 1
/usr/local/bin/screenfetch: line 1341: [: =: unary operator expected
                               
                 -/+:.          donmor@bogon
                :++++.          OS: 64bit Mac OS X 10.11.6 15G22010
               /+++/.           Kernel: x86_64 Darwin 15.6.0
       .:-::- .+/:-``.::-       Uptime: 39m
    .:/++++++/::::/++++++/:`    Packages: 9
  .:///:`   Shell: bash 3.2.57
  `     Resolution: 1918x888
 -+++++++++++++++++++++++`      DE: Aqua
 /++++++++++++++++++++++/       WM: Quartz Compositor
 /sssssssssssssssssssssss.      WM Theme: Blue
 :ssssssssssssssssssssssss-     Font: Monaco
  osssssssssssssssssssssssso/`  CPU: Intel Core i5-4210U @ 1.70GHz
  `syyyyyyyyyyyyyyyyyyyyyyyy+`  GPU: 
   `ossssssssssssssssssssss/    RAM: 2402MiB / 4096MiB
     :ooooooooooooooooooo+.    
      `:+oo+/:-..-:/+o+/-      
                               
bogon:temp donmor$ java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
bogon:temp donmor$ java MIDITest 01.mid 


下面贴代码:

bogon:temp donmor$ cat MIDITest.java 
import java.io.File;
import javax.sound.midi.*;

public class MIDITest {

	public static void main(String[] args) {

		try {
			File midif = new File(args[0]);//打开MIDI文件,这里是直接从参数的第一位读取
			Sequence seq = MidiSystem.getSequence(midif);//加载文件到序列(Sequence)中
			Sequencer midip= MidiSystem.getSequencer();//创建一个音序器(Sequencer),即播放器的核心
			midip.open();//启动音序器,
			midip.setSequence(seq);//把序列插入到音序器中
			if(!midip.isRunning())
				midip.start();//开始播放
			long time = midip.getMicrosecondLength() / 1000;//获取MIDI文件长度
			Thread.sleep(time);//让程序等待,直到播放结束
			if(midip.isRunning())
				midip.stop();
			if(midip.isOpen())
				midip.close();//此四句关闭音序器,程序主体结束
		} catch(Exception e) {
			e.printStackTrace();//处理异常
		}
	}
}

如果只是听个响,到这里就可以不用看了。然而说好要深入一点的……

那么上第二版,加入了MidiDevice

bogon:temp donmor$ cat MIDITest.java
import java.io.File;
import java.util.ArrayList;
import javax.sound.midi.*;

public class MIDITest {

	private static MidiDevice midid;

	public static void main(String[] args) {

		try {
			MidiDevice.Info[] vdevs = MidiSystem.getMidiDeviceInfo();//获取所有MIDI设备信息
			ArrayList<MidiDevice.Info> xdevs = new ArrayList<MidiDevice.Info>();//准备筛除一些不能用来播放的设备,用这个ArrayList存放合格的
			for (MidiDevice.Info dev : vdevs) {
				String s = dev.getName();
				try {
					MidiDevice vc = MidiSystem.getMidiDevice(dev);//启动每个设备试验
					vc.getReceiver();//直接访问Receiver,如果没有就走catch
					vc.close();//关闭设备
				} catch (MidiUnavailableException e) {
					s = "$NORECEIVER";//利用名称筛除没有Receiver的设备
				}
				if (s != "Real Time Sequencer" && s != "$NORECEIVER")//筛除没有Receiver的设备和Real Time Sequencer(这个是音序器的Receiver,用来做MIDI录制的,当然不能要它)
					xdevs.add(dev);//过检的插进ArrayList里
			}
			MidiDevice.Info[] arrw = new MidiDevice.Info[xdevs.size()];
			MidiDevice.Info[] devs = xdevs.toArray(arrw);//这两句用来把ArrayList转为数组,注意不能直接toArray
			if (args[0].equals("-devlist")) {//读取命令行参数第一位,如果是-devlist的话
				System.out.println("ID	Name	Description	Vendor	Version");
				int id = 0;
				for (MidiDevice.Info dev : devs) {
					System.out.println(String.valueOf(id) + "	" + dev.getName() + "	" + dev.getDescription() + "	" + dev.getVendor() + "	" + dev.getVersion());
					id += 1;
				}
				System.exit(0);//结束
			}
			String arg1 = "", arg2 = "";//准备读第二、三个参数
			try {
				arg1 = args[1];
				arg2 = args[2];//读第二、三参数,若参数不全则抛异常跳过
			} catch (Exception e) {
				
			}
			File midif = new File(args[0]);//打开MIDI文件,这里是直接从参数的第一位读取
			Sequence seq = MidiSystem.getSequence(midif);//加载文件到序列(Sequence)中
			Sequencer midip= MidiSystem.getSequencer(!arg1.equals("-dev"));//创建一个音序器(Sequencer),即播放器的核心,这里如果传了-dev参数,就不自动连接默认设备
			midip.open();//启动音序器,
			if (arg1.equals("-dev")) {//检查参数
				midid = MidiSystem.getMidiDevice(devs[Integer.parseInt(arg2)]);//获取MIDI设备
				midid.open();//启动MIDI设备
				midip.getTransmitter().setReceiver(midid.getReceiver());//关键的一步,把之前的音序器的Transmitter和MidiDevice的Receiver挂接起来
			}
			midip.setSequence(seq);//把序列插入到音序器中
			if (!midip.isRunning())
				midip.start();//开始播放
			Runtime.getRuntime().addShutdownHook(new Thread() {//加入退出检测线程,Ctrl-C时停止并释放资源
				public void run() {
					try {
						System.out.println("Quit");
						if (midip.isRunning())
							midip.stop();
						if (midip.isOpen())
							midip.close();
						if (midid != null && midid.isOpen())
							midid.close();//关闭音序器和设备,结束程序
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			long time = midip.getMicrosecondLength() / 1000;//获取MIDI文件长度
			Thread.sleep(time);//让程序等待,直到播放结束
			if (midip.isRunning())
				midip.stop();
			if (midip.isOpen())
				midip.close();
			if (midid != null && midid.isOpen())
				midid.close();//此六句关闭音序器和设备,程序主体结束
		} catch(Exception e) {
			e.printStackTrace();//处理异常
		}
	}
}
bogon:temp donmor$ java MIDITest -devlist
ID	Name	Description	Vendor	Version
0	Gervill	Software MIDI Synthesizer	OpenJDK	1.0
1	FluidSynth virtual port (Qsynth1)	FluidSynth virtual port (Qsynth1)	Unknown vendor	Unknown version
bogon:temp donmor$ java MIDITest 01.mid -dev 1
^CQuit

解释一下,此版相比上一版加入MidiDevice的处理,用-devlist参数可以输出全部可以用来输出的设备,再用命令行参数-dev加id选择设备,然后和之前的音序器挂接;此外加入退出处理的代码,防止使用外部设备时Ctrl-C中止程序后设备一直播放最后那一个音,需要按panic的情况(这里我就用了Qsynth)

然而还没有结束!上最终版,支持直接读取sf2音色库文件:

bogon:temp donmor$ cat MIDITest.java 
import java.io.File;
import java.util.ArrayList;
import javax.sound.midi.*;

public class MIDITest {

	private static MidiDevice midid;

	public static void main(String[] args) {

		try {
			MidiDevice.Info[] vdevs = MidiSystem.getMidiDeviceInfo();//获取所有MIDI设备信息
			ArrayList<MidiDevice.Info> xdevs = new ArrayList<MidiDevice.Info>();//准备筛除一些不能用来播放的设备,用这个ArrayList存放合格的
			for (MidiDevice.Info dev : vdevs) {
				String s = dev.getName();
				try {
					MidiDevice vc = MidiSystem.getMidiDevice(dev);//启动每个设备试验
					vc.getReceiver();//直接访问Receiver,如果没有就走catch
					vc.close();//关闭设备
				} catch (MidiUnavailableException e) {
					s = "$NORECEIVER";//利用名称筛除没有Receiver的设备
				}
				if (s != "Real Time Sequencer" && s != "$NORECEIVER")//筛除没有Receiver的设备和Real Time Sequencer(这个是音序器的Receiver,用来做MIDI录制的,当然不能要它)
					xdevs.add(dev);//过检的插进ArrayList里
			}
			MidiDevice.Info[] arrw = new MidiDevice.Info[xdevs.size()];
			MidiDevice.Info[] devs = xdevs.toArray(arrw);//这两句用来把ArrayList转为数组,注意不能直接toArray
			if (args[0].equals("-devlist")) {//读取命令行参数第一位,如果是-devlist的话
				System.out.println("ID	Name	Description	Vendor	Version");
				int i = 0;
				for (MidiDevice.Info dev : devs) {
					System.out.println(String.valueOf(i) + "	" + dev.getName() + "	" + dev.getDescription() + "	" + dev.getVendor() + "	" + dev.getVersion());
					i++;
				}
				System.exit(0);//结束
			} else if (args[0].equals("-help")) {
				System.out.println("Usage:	MIDITest [FILE] [OPTION] ...");
				System.out.println("	MIDITest [OPTION]");
				System.out.println("A test application that plays MIDI files.");
				System.out.println("");
				System.out.println("Options:");
				System.out.println("	-dev ID		Use specific device to play the MIDI file. Use ");
				System.out.println("			-devlist option to enquire about device IDs.");
				System.out.println("	-sf2 		Load sf2 files into the Gervill Software MIDI ");
				System.out.println("			Synthesizer provided by Java by default. ");
				System.out.println("			Please note that following sf2 files overwrites");
				System.out.println("			previous ones.");
				System.out.println("	-help		Display this help and exit.");
				System.out.println("	-devlist	List available MIDI devices and exit.");
				System.exit(0);//显示帮助并退出
			}
			String arg1 = "";
			String[] arg2 = new String[1];
			arg2[0] = "";//准备读更多参数
			ArrayList<String> arg2s = new ArrayList<String>();
			try {
				arg1 = args[1];
				int i = 0;
				boolean granted = false;
				for (String arg : args) {
					if (i > 1)
						arg2s.add(arg);
					i++;
				}
				String[] arrw2 = new String[arg2s.size()];
				arg2 = arg2s.toArray(arrw2);//读参数,若参数不全则抛异常跳过
			} catch (Exception e) {

			}
			File midif = new File(args[0]);//打开MIDI文件,这里是直接从参数的第一位读取
			Sequence seq = MidiSystem.getSequence(midif);//加载文件到序列(Sequence)中
			Sequencer midip= MidiSystem.getSequencer(!(arg1.equals("-dev") || arg1.equals("-sf2")));//创建一个音序器(Sequencer),即播放器的核心,这里如果传了-dev或-sf2参数,就不自动连接默认设备
			midip.open();//启动音序器
			if (arg1.equals("-dev")) {//检查参数
				midid = MidiSystem.getMidiDevice(devs[Integer.parseInt(arg2[0])]);//获取MIDI设备
				midid.open();//启动MIDI设备
				midip.getTransmitter().setReceiver(midid.getReceiver());//关键的一步,把之前的音序器的Transmitter和MidiDevice的Receiver挂接起来
			} else if (arg1.equals("-sf2")) {
				midid = MidiSystem.getMidiDevice(devs[0]);//获取MIDI设备
				midid.open();//启动MIDI设备
				Synthesizer midis = (Synthesizer) midid;//获取合成器
				for (String sf2 : arg2) {//对每个从参数传来的sf2文件:
					try {
						Soundbank sbx = MidiSystem.getSoundbank(new File(sf2));
						midis.loadAllInstruments(sbx);//顺序加入合成器中
					} catch (Exception e) {

					}
				}
				midip.getTransmitter().setReceiver(midid.getReceiver());//关键的一步,把之前的音序器的Transmitter和MidiDevice的Receiver挂接起来
			}
			midip.setSequence(seq);//把序列插入到音序器中
			if (!midip.isRunning()){
				System.out.println("Now Playing...");
				midip.start();//开始播放
			}
			Runtime.getRuntime().addShutdownHook(new Thread() {//加入退出检测线程,Ctrl-C时停止并释放资源
				public void run() {
					try {
						System.out.println("[Quit]");
						if (midip.isRunning())
							midip.stop();
						if (midip.isOpen())
							midip.close();
						if (midid != null && midid.isOpen())
							midid.close();//关闭音序器和设备,结束程序
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			long time = midip.getMicrosecondLength() / 1000;//获取MIDI文件长度
			Thread.sleep(time);//让程序等待,直到播放结束
			if (midip.isRunning())
				midip.stop();
			if (midip.isOpen())
				midip.close();
			if (midid != null && midid.isOpen())
				midid.close();//此六句关闭音序器和设备,程序主体结束
		} catch(Exception e) {
			e.printStackTrace();//处理异常
		}
	}
}
bogon:temp donmor$ java MIDITest 01.mid -sf2 ~/Documents/SGM-180_v1.5.sf2
Now Playing...
^C[Quit]

这一版改了命令参数解析代码,使用-sf2参数加sf2文件名可以导入多个sf2音色库,载入Gervill设备后cast出合成器Synthesizer,然后逐个加入sf2文件(后加载的会覆盖先加载的重复部分),最后挂接;另外加了一点提示信息和帮助(-help)

以上~代码略丑,权当抛砖引玉了
另外有把MIDI部分单独包装成类、用Swing做了UI的版本在这里:
https://github.com/donmor/Java-MIDI-Player

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在Android中使用.sf2音色库播放midi文件,您可以使用Android自带的Midi API和SoundFont文件格式。以下是一个简单的示例代码,可以帮助您了解如何在Android应用程序中实现这个功能: ``` import android.media.midi.MidiDevice; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiManager; import android.media.midi.MidiOutputPort; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import java.io.IOException; public class MainActivity extends AppCompatActivity { private MidiManager midiManager; private MidiDevice device; private MidiOutputPort outputPort; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); midiManager = (MidiManager) getSystemService(MIDI_SERVICE); // 查找可用的MIDI设备 MidiDeviceInfo[] infos = midiManager.getDevices(); if (infos.length > 0) { device = midiManager.openDevice(infos[0], null); if (device != null) { outputPort = device.openOutputPort(0); } } // 播放midi文件 if (outputPort != null) { try { // 加载SoundFont文件 SoundFont soundFont = new SoundFont(getResources().openRawResource(R.raw.soundfont)); soundFont.load(); // 创建一个MidiPlayer MidiPlayer player = new MidiPlayer(outputPort, soundFont); // 播放midi文件 player.play(getResources().openRawResource(R.raw.midi_file)); } catch (IOException e) { e.printStackTrace(); } } } @Override protected void onDestroy() { super.onDestroy(); // 关闭Midi设备和输出端口 if (outputPort != null) { outputPort.close(); } if (device != null) { device.close(); } } } ``` 请注意,上面的代码仅供参考,您需要根据您的具体需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值