《101 Windows Phone 7 Apps》读书笔记-Trombone
课程内容
Ø Sound Manipulation
Ø Sound Looping
Ø SoundEffectInstance
相对于前一章的Cowbell 应用程序来说,本章的Trombone是一个更加专业的乐器应用。我们可以通过控制滑片的上下移动来发出对应的音阶(应用程序中滑片的位置并非从F调开始,这一点与实际的trombone滑片位置有所不同)。本应用程序支持两种不同的滑片模式。如果我们触摸左边屏幕的话,可以自由地移动滑片。如果我们触摸右边屏幕的话,它会对齐到已经标注好的音阶。这款软件除了操作更加简便以外,还可以用来作为定音管。
Trombone可以在两个八度音程的范围内发音,如果我们想要将一个音声提高八度,把另一个手指放在屏幕的任何地方就可以实现。本应用程序最有趣的一点在于,与真实的长号一样,我们必须对着手机吹气,才能使其发出声音。
应用程序的后两个特点需要的功能会在后面的章节中进行介绍(多点触摸以及麦克风的使用),因此,与这部分功能相关的代码这里不做介绍。相反,本章内容关注于单个音效的音高和音长操作,使其能够满足本应用程序的需求。
The User Interface
Trombone具有一个主页面、一个介绍页面和一个设置页面。设置页面的代码本章不作介绍,那是因为除了页面标题以外,它与第34章“Bubble Blower”应用程序的设置页面几乎一致。设置页面使得用户可以在音量过大或者过小时,对麦克风进行调整。介绍页面的代码这里也不作介绍,因为它没有特殊的地方。
主页面的初始化状态如图31.1所示,包含了可移动的滑片、标注的音阶和指向另外两个页面的链接按钮。
图31.1 主页面模仿了长号的实际外观
注意:
➔ 图31.1中标注的音阶线通过该页面背后的cs代码实现。
➔ 应用程序栏会遮挡应用程序的用户界面,所以就用两个长方形的按钮来代替。我们使用熟悉的不透明模板来确保它们在不同主题下的显示效果达到一致。
图31.2 长号的滑片通过静态图片上添加一个可移动的图片来实现
The Code-Behind
注意:
➔ 本应用程序长号发音时采用的音频文件只有一个,那就是F调时的音频文件。其他声调的声音是通过动态改变F调的声音频率来实现的。
➔ 前一章内容中,我们直接使用了SoundEffect对象,本应用程序调用SoundEffect中的CreateInstance方法来获取SoundEffectInstance对象。与SoundEffect 相比,SoundEffectInstance提供了更多的特性,因为它与单个声音实例进行绑定,所以即便是声音已经开始播放,仍旧可以对它进行操作。一方面,Trombone应用程序需要SoundEffectInstance来完成周期性的任务;另一方面,SoundEffectInstance可以动态改变已播放的声音的音阶。
➔ SoundEffectInstance提供了一个IsLooped属性(默认设置为false),它使得用户可以无限期地循环播放一段音频文件,直到调用Stop方法为止。按照音频源文件的不同,它可以由两种方法来实现:
1.对于一个普通的音频文件来说,这种循环是应用在整段音频范围的。所以,在前一段播放结束时,会无缝地开始再一次播放。
2.对于一个有循环区域的音频文件来说,第一次播放时,程序会从头开始播放,但接下来的循环中,只有循环区域会被播放。一旦程序调用默认的Stop方法,声音就停止播放。但是,如果我们重写该Stop方法,并传入false参数时,它会停止当前的播放,然后跳出该循环,并播放该段音频的剩余部分。
图31.3展示了这两种行为。后一种行为对于本应用程序来说正合适,因为它使用了一段真实的长号F调音频,并且从声音的开始到结束进行了平滑的过渡。因此,工程中包含的“F.wav”文件定义了一个循环区域。虽然该音频文件的长度还不到三分之一秒,但使用循环区域的话,只要用户能够维持他对手机吹气的动作,应用程序就可以播放任意长的时间。
图31.3 SoundEffectInstance.IsLooped的属性值设置为true
注意我们定义的循环区域的长度!
如果我们不想立即停止声音的播放,而是在调用Stop(false)方法以后,慢慢地停止下来,那么,我们定义的循环区域(以及声音文件的剩余部分)必须尽可能得短。否则,结束当前的播放循环会占用较长的时间。
Wavosaur (www.wavosaur.com) 是一个免费而且非常强大的音频编辑器,通过它,我们可以在一个.wav文件内部创建一个循环区域。选中一个声音文件的部分区域,点击“Tools”菜单中的“Loop”选项,然后点击“Create”来创建循环区域。在正常的环境下,展开的.wav文件仍旧可以直接播放,但是使用SoundEffectInstance实例,并且将其IsLooped属性设置为true的情况下,就会根据设置的循环区域来播放了。
SoundEffect VS. SoundEffectInstance
SoundEffect可以播放声音文件,而SoundEffectInstance可以利用其Pause、 Resume 和 Stop方法对某一个制定的声音文件进行暂停、继续和停止操作。每次调用SoundEffect的Play方法后,就开始播放声音的一个新实例,我们无法对其进行停止操作(它有可能会对之前播放的声音产生影响);而调用SoundEffectInstance的Play方法时,如果该声音的实例当前正在播放,那么它不会做任何动作。因为SoundEffectInstance与一个制定的声音实例进行了绑定,所以它同样也具有State属性,用于指示该声音目前的状态是处于播放、暂停或者停止。
除了IsLooped属性以外,SoundEffectInstance还具有三个控制声音效果的属性。我们可以在任何时候对其进行设置,甚至在播放过程中也可以:
➔ Volume (默认值为1):范围为0~1,其中0表示静音,1表示最大音量。
➔ Pitch (默认值为0):范围为-1~1,其中-1表示低八度音阶,1表示高八度音阶,0表示按照其原来的声调播放。
➔ Pan(默认值为0):范围为-1~1,其中-1表示只用左扬声器发音,1表示只用右扬声器发音,0表示左右平衡发音。
SoundEffect还可以通过接受volume、pitch 和 pan 这三个参数的Play方法重载来进行控制。但是,这些值会经常导致声音播放时间的延长(在前一章,SoundEffect中的Play方法不带任何参数,它的volume属性为1,pitch 和 pan的属性值为0)。
SoundEffectInstance也具有两个重载的Apply3D方法,使得我们可以将三维的位置应用到声音的播放过程中去。该特性对于Xbox和PC游戏来说是非常有趣的。对于手机来说,三维的定位(甚至是自定义的pan属性值)没有多大用武之地。
注意:
➔ 在CompositionTarget.Rendering事件处理中,不断地将麦克风获得的当前音量值与一个门限值(在设置页面中可以调整)进行比较。如果其值足够大,而且声音没有播放,那么程序就调用Play方法(并没有必要对State属性进行严查,那是因为,与SoundEffect.Play方法不同,SoundEffectInstance.Play方法在声音正在播放的情况下,并不会叠加播放)。如果声音正在播放,而麦克风的音量值不够大,那么程序就会调用Stop(false)方法,跳出播放循环,直到声音结束。
➔ 在用户的一个手指与手机屏幕接触的情况下,另一个手指也触摸到了屏幕,这会触发Touch_FrameReported事件(详细参考Part VII中的“Touch and Multi-Touch”章节),程序对声音的音调进行调整。startingPitch变量会跟踪基调F处于哪个音程(0代表原来的音阶,1代表高八度音阶),手指与屏幕底部之间的距离决定了音阶下调的度。我们可以在代码开始的音阶数组中发现,下调F音阶25%可以获得D音阶(在原音阶的基础上下调0.25或者是原音阶的0.75),同样,下调F音阶50%可以获得B音阶(在原音阶的基础上下调0.5或者是原音阶的0.5)。
在手机主音量静音的情况下,我可以听到声音吗?我是否可以播放比主音量更大的声音?
答案是否定的,因为用户允许选择播放的最大音量需要经过授权。一个范围为0~1的音效volume参数值是相对于主音量来说的。注意,SoundEffect具有静态的MasterVolume属性,它可以同步调整所有声音的音量(无论是通过SoundEffect播放或者是SoundEffectInstance播放),但是这种音量不会超过用户选择的音量值。