最近折腾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/fbsource 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: Monacoosssssssssssssssssssssssso/`  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,如果没有就走catchvc.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转为数组,注意不能直接toArrayif (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,如果没有就走catchvc.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转为数组,注意不能直接toArrayif (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

Java播放midi文件及加载sf2音色库示例相关推荐

  1. java中class文件如何加载的_jvm如何加载class文件

    编译期: javac是JDK自带的编译器, 可以将java文件编译为class字节码文件, javap是JDK自带的反编译器,将.class字节码反编译为.java文件,javap -help是jav ...

  2. 一文了解 Java 中 so 文件的加载原理

    前言 无论是 Android 开发者还是 Java 工程师应该都有使用过 JNI 开发,但对于 JVM 如何加载 so.Android 系统如何加载 so,可能鲜有时间了解. 本文通过代码.流程解释, ...

  3. 导入Java文件还是class文件_java程序运行的时候,是把所有的class文件都加载到内存吗?还是用的什么加载什么?...

    这一块还没有深入了解,不敢误人子弟. 我知道的,虚拟机在运行的时候,会预先加载一个常用的class,比如java.lang包下面的. 至于你在程序中自己引用的class文件/jar包之类的,是有一个加 ...

  4. java web配置dll文件_JavaWeb项目中dll文件动态加载方法解析(详细步骤)

    相信很多做Java的朋友都有过用Java调用JNI实现调用C或C++方法的经历,那么Java Web中又如何实现DLL/SO文件的动态加载方法呢.今天就给大家带来一篇JAVA Web项目中DLL/SO ...

  5. Flash:关键帧中调用自建的音乐类,进行播放、关闭、循环音乐+按键播放或者关闭外部音乐+循环播放背景音乐+播放按钮音乐+停止+加载外部音乐文件+AS2播放背景音乐

    目录 一.关键帧里面:调用自建的音乐类, 循环播放外部音乐.进行播放.关闭.循环音乐 1.代码:自建音乐类 2.关键帧中创建.播放.暂停背景音乐+按钮音乐 3.文件布置 二.关键帧中:按键播放或者关闭 ...

  6. wav文件 服务器失败,.wav存储在服务器上的声音文件在加载web应用程序时无法播放...

    这是我试图实现的场景:与Web应用程序存储在同一服务器上的声音,在客户端遇到条件时播放.当我在IDE中运行它并将webconfig更改为指向数据库所在的服务器时,它完美地工作.但是,当我部署并通过浏览 ...

  7. java加载js_[Java教程]javascript如何动态加载js文件

    [Java教程]javascript如何动态加载js文件 0 2016-01-01 00:00:52 javascript如何动态加载js文件: 有时候我们需要根据需要动态加载js文件,本章节就简单介 ...

  8. 深入理解java虚拟机之类文件结构以及加载

    我们都知道,java是一种平台无关的语言.java代码通过java编译器(如javac等),将.java文件编译成字节码,也就是.class文件.字节码是运行在jvm虚拟机之上的.而不同的平台则 有不 ...

  9. 《R数据可视化手册》一1.4 从Excel文件中加载数据

    本节书摘来自异步社区<R数据可视化手册>一书中的第1章,第1.4节,作者 [美]Winston Chang,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.4 ...

最新文章

  1. java中的深浅拷贝
  2. 无声息格式化磁盘的API,VB版
  3. C++:C++语言入门级基础知识考察点回顾之基本数据类型、流程控制
  4. DA14580开发血迹12--完整Profile解析(以心率服务为例)
  5. Android 让图片等比例缩放的三种方法
  6. Utils.toDip()的用法
  7. 互联网人必读 | 大数据思维的十大核心原理
  8. [osg]osgDB的加载机制,使用3DS插件做参考(转,整理现有osgDB资料)
  9. init是一个自定义方法名
  10. POI中设置Excel单元格格式样式(居中,字体,边框等)
  11. 【windows10】CMD命令行隐藏窗口运行
  12. [Unity]使用状态机模式创建平台控制游戏(以Unity酱为例)
  13. 洛谷p3376 网络流最大流模板题
  14. 后端理解ajax和axios
  15. 网络安全-点击劫持(ClickJacking)的原理、攻击及防御
  16. 数据库里存放的是什么?
  17. 贝叶斯统计bayes statistics
  18. python 对图片进行颜色转换
  19. 前端实现图片快速反转替换_canvas实现图片镜像翻转的2种方式
  20. 竖流式沉淀池集水槽设计计算_竖流式沉淀池计算说明

热门文章

  1. FFT求频谱图和功率谱密度图
  2. 别让外包关系在谈判后止步(转)
  3. 通讯:博物馆里过大年——英国科学博物馆举办科学“春晚”
  4. SEO优化—知己知彼,百战百胜
  5. 计算机维护与管理实践报告,计算机维护实习报告.doc
  6. 保研经验分享:浙江大学
  7. 高等代数_第3章线性方程组的解集的结构
  8. 微软云计算与动态数据中心概览
  9. 【180929】败走华容道游戏源码
  10. Android开发——实现一个拨号器(一)