创建MIDI音乐播放器
创建音效应用程序,包括BeatBox Drum播放机,涉及创建Swing GUI,网络连接,连接到输入,输出设备
JavaSoundAPI
放在J2SE类函数库的一组类与接口,分为两部分:MIDI和取样。
MIDI
Music Instrument Digital Interface,不同电子发声装置沟通的标准协议,本身不具备声音,带的是有MIDI播放功能装置的指令,当作乐谱.MIDI数据表示执行的动作,仅仅其指导作用,如播放中央,音量大小和长度。对BeatBox来说,只会使用内建,纯软件的乐器音效,称为synthesizer或software synth。
Sequencer
首先要取得Sequencer对象,此对象将MIDI数据送到产生音乐的装置中,本应用中sequencer指的是播放的装置
import javax.sound.midi.*;
public class MusicTest1{
public void play(){
Sequencer sequencer = MidiSystem.getSequencer(); //对象起将MIDI信息组合成乐曲的作用
System.out.printIn('got a sequencer')
}
public static void main(String[] args){
MusicTest1 mt =new MusicTest1();
mt.play();}
}
但因为有异常情况必须处理,上述代码无法通过编译
异常处理机制
JAVA的异常处理机制是中简洁,轻量化的执行期间例外状况处理方式。依赖于可能产生异常的方法,预先准备处理程序。
方法的声明中存在throws语句代表该方法可能抛出异常.编译器要确定你了解所调用的方法有风险,所以要把有风险的程序代码包含在try\catch块中。catch块摆放异常状况的处理程序。
import javax.sound.midi.*;
public class MusicTest1{
public void play(){
try{
Sequencer sequencer = MidiSystem.getSequencer(); //对象起将MIDI信息组合成乐曲的作用
System.out.printIn('got a sequencer')
}catch(MidiUnavailableException ex){
System.out.printIn("Bummer")
}
}
public static void main(String[] args){
MusicTest1 mt =new MusicTest1();
mt.play();}
}
异常是种Exception类型的对象
Exception类型的对象可以是任何它的子类的实例。因为它是对象,所以catch住的也是对象。
try{
//危险动作
}catch(Exception ex){ //ex是Exception类型的引用变量
//尝试恢复
}
抛出异常的方法和抓住异常的方法
在编写可能会抛出异常的方法,都必须声明有异常
//有风险,会抛出异常的程序
public void takeRisk() throws BadException{//必须声明会抛出异常
if(abandonALLHope){
throw new BadException();//创建异常对象并抛出
}
}
//调用该方法的程序
public void crossFingers(){
try{
anObject.takeRisk();
}catch(BadException ex){
ex.printStackTrace();//列出有用信息
}
}
方法可以抓住其他方法抛出的异常。异常总是丢回调用方法。
会抛出异常的方法必须声明它有可能这么做。
编译器会核对每件事,除了RuntimeExceptions(不检查异常,可以自己抛出与抓住它们,但没必要)。编译器保证:
1.若抛出异常,则一定要使用throw来声明。
2.若调用会抛出异常的方法,则必定使用try\catch块。
3.方法通过throw关键字抛出异常,如:
throw new BadException();//创建异常对象并抛出
try\catch块的流程控制
方法若不能成功地完成try块,就会把异常丢回调用方的方法。即执行try块的某程序抛出异常,则跳过try块剩下程序,直接到catch块执行,再继续try\catch块下方的内容。
finally
finally块用来存放不管有无异常都得执行的程序。
try{
//危险动作
}catch(Exception ex){ //ex是Exception类型的引用变量
//尝试恢复
}finally{
// 无论如何要执行
}
执行过程
方法若不能成功完成try块,则跳过try块剩下程序,去到catch块执行,再执行finally内容,然后继续try\catch\finally块下方的内容。若完成try块内容,则再执行finally内容,然后继续try\catch\finally块下方的内容。
注意:try和catch块有return指令,finally块仍然会执行。流程跳到finally然后再回到return指令。
抛出多个异常
方法可以抛出多个异常,但该方法声明中必须含有全部可能的检查异常(若两个以上的异常有共同的父类时,可以只声明该父类即可)
多个异常时catch块出现的先后顺序有影响
public class Laundry{
public void doLaundry() throws PantsException, LingerieException{
//CODE
}
}
public class Foo{
public void go(){
Laundry laundry = new Laundry();
try{
laundry.doLaundry(); //根据抛出的不同异常对应不同catch块
}catch(PantsException pex){
//code1
}catch(LingerieException lex){
//code2
}
}
}
异常的多态性
异常也是对象,除了可以被抛出也与对象无区别。因此异常也能以多态方式引用,即子类对象引用可以赋值给父类对象引用,好处在于只用声明父类,而不必明确声明每个可能抛出的异常。对于catch块,可以不用对每个可能的异常处理,只要少数的catch块处理所有的异常。
1.以异常的父型声明会抛出的异常
public void doLaundry() throws ClothingException{ //可抛出任何该异常的子类
2.以抛出的异常父型来catch所有子类的异常
try{
laundry.doLaundry();
}catch(ClothingException cex){
//plans
}
3.用super来处理所有异常但不代表应该这么做
try{
laundry.doLaundry();
}catch(Exception cex){ //捕获所有异常,会搞不清出错处
//plans
4.为每个需单独处理的异常(相同处理方法的异常用父类异常捕获,不同的处理方法需单独编写)编写不同catch块
5.多个catch块时要从小排到大(小大的概念指继承树层次,下层自然比上层小)
不要大篮子在上,小篮子在下(同一层次无所谓,必定代表不同异常)会无法通过编译,因为catch块无法类同重载调出最符合的项目。JVM对于catch块的处理流程是从上往下寻找第一个符合范围的异常处理块。
沿着继承层次向下走,异常类会越来越有特定的去向,catch的篮子越来越小,这是多态的常态现象。
6.若不想处理异常,可通过duck来避开。
调用危险方法,要包含在try\catch块中,但也可以把其duck掉,来让调用你的方法程序来catch该异常。该方法要求会抛出异常(不用你亲自抛出)。
7.ducking只是在踢皮球
使用ducker时,当方法抛出异常,方法会从栈上取出,异常被抛给栈上的方法,即调用方,若调用方为ducker,则此ducker也从栈上取出,异常再被抛给栈上方的方法,如此一路下去。
ducking只是在踢皮球,但早晚要处理这件事,若main()也duck掉异常,则见下例
public class Washer{
Laundry laundry = new Laundry();
public void foo() throws ClothingException{
laundry.doLaundry();
}
public static void main(String[] args) throws ClothingException{
Washer a = new Washer();
a.foo();
}
}
上述程序可通过编译,但会运行出错
1)抛出ClothingException
main()调用foo(),foo()调用doLaundry(),doLaundry抛出ClothingException
2)foo()已经duck掉异常
doLaundry()从stack上被取走,异常抛给foo()
3)main()也duck掉异常
foo()也被取走,只剩下JVM
4)JVM驾崩
两种满足编译器的有风险方法的调用方式
1.try\catch块
2.声明(duck掉)
把方法声明为会抛出异常
void foo throws ClothingException{//声明会抛出异常,但无try\catch块
laundry.doLaundry(); //所以会duck掉,异常给调用方
}
代表调用foo()的程序必须要处理或同样声明异常
public static void main(String[] args) throws ClothingException{//同样声明
Washer a = new Washer();
a.foo(); //要不就用try\catch块处理异常
}
}
回到音乐播放程序
利用Midi.getSequencer()创建出sequencer对象,方法要求检查异常,则包含在try\catch块中。
异常处理规则
1.catch与finally不能没有try
2.try与catch之间不能有程序
try{
}
int a=1; //error
}catch(Exception ex)
3.try一定有有catch或finally
try{
x.doStuff();
}finally{
}
4.只带有finally的try必须要声明异常。
void go() throws FooException{
try{
x.doStuff();
}finally{
}
}
总结:
1.方法可在运行期间碰到问题抛出异常,而异常是Exception类型对象。
2.可能会抛出异常的方法必须声明为throws Exception
3.如果不打算处理异常,还可以将异常给ducking来通过编译。
实际发出声音
回顾:MIDI数据保存该演奏那些音乐的指令,所以要将其数据传递到某个MIDI装置,再将数据转化为声音。
类比:
Sequencer:CD播放机
sequence:单曲CD
Track:单曲CD的歌曲信息,包括乐曲的音符等信息(Midi Event)
Midi Event:如同乐谱上的音符,也可表示更换乐器的指令
public void play(){
try{
Sequencer player = MidiSystem.getSequencer();
player.open(); //取得Sequencer并打开
Sequence seq = new Sequence(Sequence.PPQ,4); //创建新的Sequence
Track track = seq.createTrack(); //从Sequence中创建新的Track
ShortMessage a = new ShortMessage(); //创建Message
a.setMessage(144,1,44,100); //置入指令:发出44音
MidiEvent noteOn = new MidiEvent(a,1);//用Message创建MidiEvent,第一拍启动a
track.add(noteOn); //将MidiEvent加到Track中
...... //Track带有全部的MidiEvent对象
track.add(noteOff); //填入MidiEvent
player.setSequence(seq); //将Sequence送到Sequencer上
player.starter();
......
制作MidiEvent(乐曲信息)
MidiEvent类似于乐谱,指定何时开始播放某个音符(NOTE ON事件)以及何时停止(NOTE OFF 事件)。MIDI指令放在Message对象中,MidiEvent由Message加上发音时机组成,发音时机比如第四拍执行指令。MidiEvent指定何时做,Message描述做什么。
Track带有全部的MidiEvent对象,Sequence会根据事件时间组织它们,然后Sequencer根据此顺序播放,同一时间可以执行多个操作,如多个乐器的和声。
Message
Message代表做什么,即要sequencer实际执行的指令。要创建MIDI的Message,要用ShortMessage的实例调用setMessage(),传入该信息的四个参数。
信息的格式
a.setMessage(144,1,44,100)
144作为第一个参数代表信息类型,后面三个参数根据信息类型决定其意义,其中1代表频道,不同频道对应不同乐器,44代表音高,100代表音道,取零值几乎听不到。
144代表打开,即NOTE ON,128代表关闭,即NOTE OFF。
展望
12章:创建GUI,能根据MIDI的音乐节奏画出随机的字符,学习事件处理
13章:独立的BeatBox
14章:存储和还原播放进度
书海拾荒
1.任何继承过RuntimeExceptions的类都不会受编译器关于是否声明它会抛出RuntimeExceptions的检查,同样地,不会管调用方是否认识到运行期间的异常,所以诸如NullPointerException和DivideByZero都不要处理
2.编译器不处理RuntimeExceptions,不需要包含在try\catch块,是因为此类异常多是逻辑错误所致,在开发时可以避免,try\catch块是来处理真正的异常。
3.Exception类可以catch所有异常,包括运行期间(unchecked)的异常。