课程内容
Ø Playing sound effects
Ø Composition target’s rendering event
Cowbell是一个模仿简单声乐仪器的应用程序。有了它,我们可以在屏幕上做任意节奏的点击,应用程序会为每次用户点击发出一个类似铃铛的声音。我们甚至可以将界面切换到Music + Videos hub上,播放其中的一首歌或者是启动一个播放列表,然后再返回应用程序,使得应用程序和音乐播放同时进行。Cowbell的特色在于,它的唯一目的就是实现播放的音效。
为什么在众多的乐器当中唯独选择了铃铛?这还要归功于2000年时由Will Ferrell 和 Christopher Walken主演的肥皂剧Saturday Night Live,很多人从中学会了用铃铛来娱乐的方法。在剧中,Christopher Walken不断地要求更多的“铃铛声”,而Will Ferrell却演奏着“Blue Öyster Cult”摇滚乐队的“(Don’t Fear) The Reaper”。如果手机的媒体库中有了这首歌,再配上这个应用程序,我们就可以重演这个著名的肥皂剧。
Playing Sound Effects
在Windows Phone平台上,Silverlight只有唯一的方法可以播放音频和视频,那就是使用MediaElement。但是,这对于实现音效来说,又有点“杀鸡用牛刀”的感觉。而且,一旦使用MediaElement播放音效,那么其他正在播放的媒体文件就会停止(例如,通过Music + Videos hub播放的音乐)。一般来说,我们可以使用MediaElement播放背景音乐,,而且只能使用它来播放嵌入的视频(参考第33章“Subservient Cat”),但是,在播放较短或者中等长度的音频时,我们一般不用它。相反,我们会使用XNA中的接口来实现音效。正如前文第二章“Flashlight”所述,基于Silverlight的Windows Phone应用程序可以使用XNA中的一些API。
与音效相关的XNA类就是SoundEffect,它位于Microsoft.Xna.Framework.Audio命名空间中。在使用时,我们需要在工程中添加对Microsoft.Xna.Framework的引用。在本章的内容中,我们将会从一个音频文件中加载音效,并且进行播放。SoundEffect类还提供了其他的特性,我们会在下一章中进行介绍。
使用MediaElement播放音效会导致我们的应用程序无法通过marketplace认证!
由于使用MediaElement播放音效会导致背景音乐的暂停,带来较差的用户体验。因此,在应用程序发布于marketplace之前,微软会对它进行审查。如果我们的确使用了MediaElement来播放音效,那么应用程序就无法通过审查。
如果应用程序需要音效,而且自己不会制作音效,那么我们就可以使用以下的资源:
➔ Freesound 项目(freesound.org)
➔ Partners in Rhyme (partnersinrhyme.com)
➔ Soungle (soungle.com)
➔ Sounddogs (sounddogs.com)
➔ SoundLab, 微软的游戏音效集 (create.msdn.com/ en-US/education/catalog/utility/soundlab)
The User Interface
Cowbell具有一个主页面,一个乐器页面和一个关于页面。后两者比较普通,所以这里就不做介绍。这是一个具有应用程序栏和一个铃铛图案的Grid控件的简单页面,它利用MouseLeftButtonDown事件处理程序来处理用户的点击。为了实现铃铛图案边缘的白化效果,我们将Grid控件的背景设置为黑色。因此,如图30.1所示,除了半透明的应用程序栏以外,该页面在两种主题模式下看上去都差不多。
图30.1 在暗色和亮色主题下,除了应用程序栏,主页面看上去几乎一致
The Code-Behind
➔ 在构造函数中,本应用程序使用的“.wav” 音频流文件通过静态的Application.GetResourceStream方法获得。该方法在第24章“Baby Name Eliminator”中首次使用。cowbell.wav文件已经包含在工程文件中,它的Build Action属性值为Content,使得我们可以使用简单的URI来表示。然后,该音频流被送到静态的SoundEffect.FromStream方法,构造一个合适的SoundEffect实例,并返回。
SoundEffect.FromStream方法只适用于PCM编码的音频文件!换句话说,我们使用的音频文件只能是.wav格式的。
➔ 为了实现音效,我们需要使用CompositionTarget.Rendering事件。其中的原因会在下面进行阐述。
使用XNA播放音效时,我们必须不断地调用XNA framework dispatcher中的Update方法!
与XNA中的其他功能一样,音效功能的实现依赖于Microsoft.Xna.Framework命名空间中静态方法FrameworkDispatcher.Update的频繁调用(如一秒钟几次)。这种做法在XNA应用程序中很自然,因为它们处于一个游戏循环中(XNA甚至提供一个Game的基类,它自动来完成这件事情,而不需要开发者来考虑)。但是,在这种基于事件的Silverlight应用程序中,我们就不能像XNA那种游戏循环的思路,而必须用常规的机制来处理。
为了正常调用FrameworkDispatcher.Update方法,和前一章类似,我们可以使用DispatcherTimer。我们甚至可以使用普通的System.Threading.Timer(在第2章中有介绍),因为FrameworkDispatcher.Update可以在任何线程中调用。
但是,我还是偏向于使用事件的方式,在单个帧渲染之前触发。该事件就是Rendering,它位于静态类CompositionTarget中。该事件对于Silverlight中难以实现的自定义动画非常有用,如Part II“Transforms & Animations”中的基于物体的移动。在Cowbell中,该事件很适合FrameworkDispatcher.Update的调用,它的调用频率与XNA应用几乎一致。注意,第一次对FrameworkDispatcher.Update的调用出现在页面的构造函数中,因为第一次渲染会占用比较长的时间。
在一段时间之内,如果我们在没有调用FrameworkDispatcher.Update的情况下,调用了Play方法,程序会抛出一个异常操作的信息,具体内容如下:
“FrameworkDispatcher.Update has not been called. Regular FrameworkDispatcher. Update calls are necessary for fire and forget sound effects and framework events to function correctly. See http://go.microsoft.com/fwlink/?LinkId=193853 for details.”
➔ OnNavigatedTo和OnNavigatedFrom方法的存在是为了确保程序不会自动锁屏。如果应用程序在演奏过程中有一段间隙,那么如果这时出现应用程序锁屏的话,会使得用户很懊恼。另外,为了确保程序的运行,要求用户间隔一段时间点击屏幕的方式并不好,因为那样会导致程序发出用户没有预想到的铃铛声。
➔ 在Grid_MouseLeftButtonDown事件中调用SoundEffect.Play方法,就可以实现音效。假如上一次的音效还没有播放结束,程序再一次对SoundEffect.Play方法进行调用,那么,本次调用产生的音效将会与上一次的音效重叠。
The Audio Transport Controls
如果在手机的media player播放音乐的同时,运行本应用程序,那么音效同样可以播放。在用户按下硬件的音量调整按钮后,任何应用程序的界面顶端会弹出一个93像素高度的媒体控制界面,通过它,我们可以进行暂停、回退、前进或者是更改曲目等操作。在运行Cowbell应用程序时,这个功能仍然可以使用。在2011年底发布的下一个版本的Windows Phone OS中(Mango),第三方应用程序也可以通过后台来播放音乐,就像系统内置的media player一样。