声音功能教程-----转

声音功能

 

下载文件:test.wav(storm注:经测试已坏,请自己找可用的wav文件代替)

 博士,上一讲我们学习了N840Java开发环境的一些知识,这一讲要讲新的东西了吧?
 恩,这一讲就来讲N840被加强的声音功能吧。
 声音播放功能以前的手机就实现了,还有什么新功能么?
 MIDP2.0新增加了WAV播放和TONE。此外,声音的播放方法也不同了。首先,咱们先来了解一下WAV文件的播放。

 

 

1. WAV文件的播放

 所谓的WAV,指Windows标准的声音文件格式。Windows启动时的声音就是以WAV格式保存的。
 那电脑中的WAV文件也可以在手机上面播放么?
 恩。但是,N840可播放的WAV文件要限定在sampling频率8KHz、8比特、monoPCM(单耳非压缩)。Windows可以播放的声音,放到手机上不一定能播放出来。
 恩。真麻烦啊。那手机上能播放的声音数据应该怎么来准备呢?
 市面上卖的音乐编辑软件就可以,网上也有许多on-line软件可以转换文件的形式的。另外,使用Windows自带的录音机([程序]→[附件]→[娱乐]→[录音机])也可以保存为WAV的形式。不过在保存时一定要按照刚才所说的进行频率等的设定。
 原来如此啊。这样,就可以将自己喜欢的音乐做成WAV形式了。
 还有一点,注意文件的尺寸不能超过128KB,太大的话就无效了。
下面我们就来制作播放WAV的Java程序吧。
先启动KtoolBar选择制作“WAVTest”程序。类名也同样设为WAVTest。

 

 

 接下来介绍一下播放WAV文件的简单用例。下面的代码有“Play”和“Exit” 两个command,使用“Play”command来播放音乐。

 

WAVTest.java
    
1 import java.io.*; 
2 import javax.microedition.lcdui.*; 
3 import javax.microedition.media.*; 
4 import javax.microedition.midlet.*; 
5   
6 public class WAVTest extends MIDlet implements CommandListener { 
7    private final String WAV_FILE = "test.wav"; // 播放的WAV文件名 
8    private Display display; 
9    private Form mainForm; 
10    private Command playCommand; 
11   
12    public WAVTest() { 
13       mainForm = new Form("WAVTest"); 
14       playCommand = new Command("Play", Command.SCREEN, 0); 
15       mainForm.addCommand(playCommand); 
16       mainForm.addCommand(new Command("Exit", Command.EXIT, 0)); 
17       mainForm.setCommandListener(this); 
18       display = Display.getDisplay(this); 
19    } 
20   
21    public void startApp() { 
22       display.setCurrent(mainForm); 
23    } 
24   
25    public void pauseApp() {}  
26   
27    public void destroyApp(boolean unconditional) {} 
28   
29    /** 
30    * command按键被按下时的处理 
31    */ 
32    public void commandAction(Command c, Displayable d) { 
33       if (c.getCommandType() == Command.EXIT) { 
34          notifyDestroyed(); 
35       } else if (c == playCommand) { 
36          playSound(); 
37       } 
38    } 
39   
40    /** 
41    * 声音的播放 
42    */ 
43    private void playSound() { 
44       try { 
45          // 从WAV文件取得InputStream  
46          InputStream in = getClass().getResourceAsStream(WAV_FILE); 
47   
48          // 生成Player对象 
49          Player p = Manager.createPlayer(in, "audio/x-wav"); 
50   
51          // 声音的播放 
52          p.start(); 
53       } 
54       catch (Exception e) { // 发生异常时的处理 
55          // 生成Alert对象 
56          Alert alert = new Alert("Exception", e.toString(), null, AlertType.ERROR); 
57   
58          // 显示异常的内容 
59          display.setCurrent(alert, mainForm); 
60       } 
61    } 
62 } 
63   
    

 

 首先,来实际运行一下上面的程序,然后再对代码的内容进行说明。
 好的。代码是保存在程序中的src文件夹中的,那么,WAV数据应该保存在哪里呢?
 声音数据保存在resource文件夹中。在程序文件夹中,有以res命名的文件夹,保存在里面就可以了。这次播放的WAV用例的名称为“test.wav”,因此就以“test.wav”的名称保存。
 是,我明白了。准备好文件之后,点击“Build”-“Run”是吧?
 恩,没错。记得很清楚啊,如果忘记了就复习一下前讲的内容吧。

 

 

 接下来,我们就来分析代码。
这个代码比较简单,尼克你能看明白吧。
 是的。在WAVTest()这个constructor中,添加了“Play”和“Exit”命令。启动命令按键时的处理是,启动“Play”命令时,执行第43行的playSound()方法。
 没错。playSound()方法里,包含了播放WAV文件的程序的main部分。其中,下面三行读取和播放WAV文件。

 

InputStream in = getClass().getResourceAsStream(WAV_FILE);
Player p = Manager.createPlayer(in, "audio/x-wav");
p.start();

 

 首先,第一行取得了用于从WAV文件读取数据的InputStream对象。在getResourceAsStream方法的参量里,指定了文件名。
 文件名WAV_FILE,在程序的第七行如下所示定义。
private final String WAV_FILE = "test.wav";
 对。变更文件名时,可以在test.wav的位置输入喜欢的名称。
接下来,第二行使用Manager类的createPlayer方法,生成Player对象。在参量里,指定InputStream对象和显示内容类型的字符串。
 什么是内容类型呢?
 简单的说,就是文件内包含的数据的种类。这次讲的是WAV形式的声音文件,因此内容类型就是“audio/x-wav”。
 可以播放WAV格式以外的文件么?
 恩,N840可以播放下面格式的声音。

 

  文件类型文件的扩展名内容类型
 SMF, SP-MIDI.midaudio/midi
audio/x-midi
audio/sp-midi
 8bit, 8KHz, mono PCM.wavaudio/x-wav
 Tone sequence.jtsaudio/x-tone-seq

 

 我们现在讲的是WAV形式的例子,如果是MIDI形式或者TONE序列文件,只需要改一下显示内容类型的字符串,就可以同样生成Player对象了。
 这样啊,不同文件形式也可以使用同样的记录方法,真方便啊。
 生成Player对象后,使用Player类的start方法就可以播放声音了。
 try{ }里面包含了从取得InputStream到播放的代码,这是为什么啊?
 恩,因为声音播放可能会出现问题。例如,声音文件没有被放在resource里面,或者文件形式是手机不能播放的,或者尺寸太大等,可能会发生各种各样的问题。当这些问题发生时,就可以运行try{ }后面的catch(Exception e) { }中的命令来解决。
 Catch后面的{ }包含的命令是下面这两个吧。

 

Alert alert = new Alert("Exception", e.toString(), null, AlertType.ERROR);
display.setCurrent(alert, mainForm);

 

 恩,利用上面的命令,就可以在手机的画面上面显示问题发生的原因了。开发时设置这样的命令真是很方便啊。
 是啊是啊。这样WAVTest.java的全部内容我都明白了。

 

2. TONE的播放

 MIDP2.0新增了播放TONE的功能。下面我们就来试一下这个功能吧。
 与WAV的播放不一样么?
 与以前学的,事先准备声音数据才能播放的文件不同,TONE的播放是指定的声音只能在指定的时间播放。因此,可以在程序中生成melody或者一些细微的控制。
 原来是这样啊。
 下面我们就来看使用TONE的例子。ToneTest类的代码和刚才讲的差不多,只是播放声音的playSound方法的内容有些差异。

 

ToneTest.java
    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.media.*; 
3 import javax.microedition.midlet.*; 
4   
5 public class ToneTest extends MIDlet implements CommandListener { 
6    private Display display; 
7    private Form mainForm; 
8    private Command playCommand; 
9   
10    public ToneTest() { 
11       mainForm = new Form("ToneTest"); 
12        playCommand = new Command("Play", Command.SCREEN, 0); 
13       mainForm.addCommand(playCommand); 
14       mainForm.addCommand(new Command("Exit", Command.EXIT, 0)); 
15       mainForm.setCommandListener(this); 
16       display = Display.getDisplay(this); 
17    } 
18   
19    public void startApp() { 
20       display.setCurrent(mainForm); 
21    } 
22   
23    public void pauseApp() {} 
24   
25    public void destroyApp(boolean unconditional) {} 
26   
27    /** 
28    * command按键被按下时的处理 
29    */ 
30    public void commandAction(Command c, Displayable d) { 
31       if (c.getCommandType() == Command.EXIT) { 
32          notifyDestroyed(); 
33       } else if (c == playCommand) { 
34          playSound(); 
35       } 
36    } 
37   
38    /** 
39    * 声音的播放 
40    */ 
41    private void playSound() { 
42       try { 
43          Manager.playTone(60, 500, 100); 
44       } 
45       catch (Exception e) { // 发生异常时的处理 
46          // Alert对象的生成  
47          Alert alert = new Alert("Exception", e.toString(), null, AlertType.ERROR); 
48   
49          // 显示异常的内容 
50          display.setCurrent(alert, mainForm); 
51       } 
52    } 
53 } 
    

 

 制作新程序、编译、运行这些都没问题了吧?
 是的,都没有问题啦。

 

 

 与之前播放WAV文件的程序代码没什么区别啊。第43行播放声音没错吧。Manager.playTone(60, 500, 100);
 恩,没错没错。Manager类的playTone方法可以播放声音。
 PlayTone里面用参量指定了三个数字(60, 500, 100),是什么意思啊?
 恩。这个问题有点复杂,咱们在后面再讲吧。其中第一个参量是用来规定声音的note number的,也就是指定声音的值在0~127范围内。
 0是低音127是高音么?
 对,没错。第二个参量指定声音播放的时间为毫秒单位,第三个参量指定声音的大小值在0~100的范围内。
 这样啊。那么刚才代码中指定的note number表示,高低为60的声音在0.5秒中间以100的大小被播放出来是吧。
 是的。
 如果要像钢琴那样,奏出do·ri·mi·fa·so·la·xi·do这样的音阶的话应该怎么做呢?
 这个啊,那我们就先撇开Java的话题,来讲一下音程吧。
首先,所谓的音程是与作为标准的音相比,声音高低相差的程度。
playTone 方法里面的note number,一般用60表示“do”的音。音乐用语称为“Middle C”。
因此,刚才设参量为60,实际上是在手机上播放“do”的声音。
 这么说61,是表示do后面的音ri么?
 不是不是,值增加1的话只增加了半个音。也就是说61表示do增加半个音到“do#”,音乐用语用“C#”表示。
下面是键盘的音阶分别对应的数字。圆圈内的数字表示对应的note number。

 

 

 恩。do·ri·mi·fa·so·la·xi·do对应的数字就是60, 62, 64, 65, 67, 69, 71, 72吧?
 没错。像这样,两音间的间隔为2, 2, 1, 2, 2, 2, 1排列起来被称为measure scale。钢琴发音是以C音调的measure scale为基础的。
 因此,将playSound方法的try模块的内容做如下替换,就可以播放do·ri·mi·fa·so·la·xi·do了吧。

 

Manager.playTone(60, 500, 100);
Manager.playTone(62, 500, 100);
Manager.playTone(64, 500, 100);
Manager.playTone(65, 500, 100);
Manager.playTone(67, 500, 100);
Manager.playTone(69, 500, 100);
Manager.playTone(71, 500, 100);
Manager.playTone(72, 500, 100);

 

 恩,上面的代码写的很好啊。试试吧。
 咦,复数的音怎么同时响了?然后出现了下面的错误。
「javax.microedition.media.MediaException: can't play tone」
 这是由于playTone没有等一个声音播放完,就开始进行下面的处理了。因此,如果要播放下一个音,就必须等上一个音播放完毕。因为手机一次不能播放那么多的声音,所以就出现错误了。
 原来是这个原因啊。那么怎样才能等前一个音播放完毕呢?
 这个并不难啊。使用Thread类的sleep方法就可以了。
在playTone后加入下面的一行就可以了。

 

Thread.currentThread().sleep(600);

 

 sleep 方法的参量为600,是指等待600毫秒么?
 没错啊。
下面,尼克你就可以使用排列和for语句,实现你想要的短代码了。

 

int note[] = {60, 62, 64, 65, 67, 69, 71, 72};
for(int i = 0; i < note.length; i++) {
   Manager.playTone(note[i], 500, 100);
   Thread.currentThread().sleep(600);
}

 

 是啊。将上面的代码放到playSound方法里面,就可以奏出do·ri·mi·fa·so·la·xi·do的音了。这样,就可以在手机上面演奏自己喜欢听的音阶了。

 

3. 利用按键操作演奏声音

 对于Java程序来讲,从1到8的按键分别对应“1→do、2→re、3→mi、4→fa、5→so、6→la、7→xi、8→do”,下面就试着做一下通过按键操作演奏的程序吧。
给这个程序取名为TonePlayer,代码如下所示。由TonePlayer和TonePlayerCanvas两个类构成。

 

TonePlayer.java

    
1 import javax.microedition.midlet.*; 
2 import javax.microedition.lcdui.*; 
3 import javax.microedition.media.*; 
4 import java.io.*; 
5   
6 public class TonePlayer extends MIDlet implements CommandListener { 
7    private Display display; 
8    private Canvas mainCanvas; 
9   
10    public TonePlayer() { 
11       display = Display.getDisplay(this); 
12       mainCanvas = new TonePlayerCanvas(); 
13       mainCanvas.addCommand(new Command("Exit", Command.EXIT, 0)); 
14       mainCanvas.setCommandListener(this); 
15    } 
16   
17    public void startApp() { 
18       display.setCurrent(mainCanvas); 
19    } 
20   
21    public void pauseApp() {} 
22   
23    public void destroyApp(boolean unconditional) {} 
24   
25    public void commandAction(Command c, Displayable s) { 
26       if (c.getCommandType() == Command.EXIT) { 
27          notifyDestroyed(); 
28       } 
29    } 
30 } 
    

 

TonePlayerCanvas.java

    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.media.*; 
3   
4 public class TonePlayerCanvas extends Canvas { 
5    final int note_diff[] = {0, 2, 4, 5, 7, 9, 11, 12}; 
6    protected void paint(Graphics g) {} 
7   
8       /* 
9       * 键被按下时的处理 
10       */ 
11       protected void keyPressed(int keyCode) { 
12          int key = keyCode - Canvas.KEY_NUM1; // (被按键号码)-1的值 
13             if(key < 0 || key >= note_diff.length) {  
14                return; // 被按键不在1~8范围内不执行任何处理 
15              } 
16             // 决定note number 
17             int note = 60 + note_diff[key]; 
18             try { 
19                Manager.playTone(note, 500, 60); // 演奏Tone500毫秒  
20                Thread.currentThread().sleep(600); // 待机600毫秒 
21             } catch(Exception e) { } 
22          } 
23     } 
    

 

 TonePlayerCanvas是执行键被按下时的处理的啊。
 是的。按键被按下后,执行keyPressed方法。在参量的keyCode里面,输入各按键对应的数值。
 执行一下看看吧,确实1到8的按键可以演奏doremi的音阶呢。好像在演奏乐器呢,呵呵。
 恩,试着弹首曲子玩玩吧。

 

 

4. TONE序列(sequence)

 TONE 的播放不但可以一个音一个音的演奏,也可以演奏连续的音。现在咱们就来说明一下这个TONE序列的播放。
 播放声音的方法有这么多种啊。
 所谓TONE序列,是指将曲子的信息以byte型的排列保存。
将下面的代码,放到ToneTest.java的playSound方法try程序块中,执行一下试试吧。

 

TonePlayerCanvas.java

    
1 /** 
2 * 声音的播放 
3 */ 
4 private void playSound() { 
5    // tone sequence的定义 
6    byte[] toneSequence = { 
7    ToneControl.VERSION, 1, // 指定版本号为1 
8    ToneControl.TEMPO, 8, // 指定tempo为8 
9    // 下面列举出note number与演奏时间的组合 
10       60, 4, 
11       62, 4, 
12       64, 4, 
13       65, 4, 
14       67, 8, 
15       67, 8, 
16       67, 4, 
17       65, 4, 
18       64, 4, 
19       62, 4, 
20       60, 8, 
21       60, 8 
22    }; 
23   
24    try{ 
25       Player p = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR); 
26       p.realize(); 
27       ToneControl c = (ToneControl)p.getControl("ToneControl"); 
28       c.setSequence(toneSequence); 
29       p.start(); 
30    } 
31    catch (Exception e) { // 发生异常时的处理 
32       // Alert对象的生成 
33       Alert alert = new Alert("Exception", e.toString(), null, AlertType.ERROR); 
34   
35       // 显示异常内容 
36       display.setCurrent(alert, mainForm); 
37    } 
38 } 
    

 

 旋律出来了。
 是啊。像编码中的toneSequence那样,可以用byte型值的排列,来定义曲子。可以把开始的ToneControl.VERSION..1和 ToneControl.TEMPO看成是固定的值。设定好tempo的值之后,将note number与演奏的时间排列成组,就可以演奏出喜欢听的旋律了。
 实际用来演奏声音的,是第29行Player对象的start方法吧。
 没错。不过之前一定要作成ToneControl对象,然后使用setSequence方法来设定序列。
 使用这个方法,就可以在程序中添加旋律,也可以根据游戏的情况随意变换啦。
 呵呵,这一讲关于声音的说明就先到这了。
下一讲的内容是,可以提高游戏开发效率的GameAPI。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值