A group blog from members of the VB team
This is Part 4 of the "VB Universal Windows App" series:
So far we've built a solid app, sharing as much code and XAML as possible by placing it in our PCL. For today's post we'll continue the process, adding game-quality sound effects to our app. SharpDX is the best way to do this.
Please bear with me. This is a long technical post, because playing game-like audio is a technically involved low-level task. The video lasts 3 minutes, and you should watch it first before returning to read the rest of this post, in which I’ll explain the why as well as the how.
I wish audio weren't this difficult! I'd actually started using MediaElement, a simple easy-to-use XAML control for playing audio, and it worked great on Windows. But then I discovered that Windows Phone only allows a maximum of two MediaElements, i.e. no more than two sounds playing at a time. Not good for a game. That's why I was forced to turn to low-level audio and SharpDX. SharpDX is a tremendous project. If anyone wants to volunteer their time to help improve it, please start at the SharpDX website.
I wrote a small wrapper for SharpDX and put it into a class called WavePlayer.
Pro tip: The SharpDX audio library is just a thin wrapper around XAudio2, one of the two low-level audio APIs in Windows - the other is called WASAPI. XAudio2 is complicated to use effectively. You load the audio sample from disk into an XAudio2.AudioBuffer. You can load it just once, and then play repeatedly from the buffer. To play a sound, you construct an XAudio2.SourceVoice. There can be many SourceVoice objects playing audio at a time. Once a SourceVoice has finished playing, it's good to re-use it for future requests, rather than wastefully creating a fresh SourceVoice every time. All this work should all be done on background threads, since otherwise it causes UI "hiccups". WavePlayer.vb does all this.
Step 1: App1_Common > Manage Nuget Packages > Online > nuget.org > SharpDX.Toolkit.Audio > Install
Step 2: App1_Common > Add > Class "WavePlayer.vb". Paste in the implementation of class WavePlayer, from the download link above.
Step 3: App1_Common > App.vb > Add a two variables in the TRANSIENT STATE section, and initialize the WavePlayer object
Shared BeepFile As StorageFile Shared wavePlayer As New WavePlayer
Step 4: Initialize the BeepFile variable. This has to be done asynchronously, because it involves reading from disk and therefore might take some time. We wouldn't want to delay showing the first screen while waiting for BeepFile.
Note: I've done disk work on a background thread, via Task.Run. This is because disk access can sometimes cause "hiccups" of 1-2ms. This kind of hiccup would be un-noticeable in a regular data-entry app. But because I'm writing a game, I don't want any hiccups.
Public Shared Sub OnLaunched(e As LaunchActivatedEventArgs) StartInitializationAsync() .... End Sub
Shared Async Sub StartInitializationAsync() Try Await Task.Run(Async Function() Dim folder = AwaitPackage.Current.InstalledLocation.GetFolderAsync("App1_Common\Assets") BeepFile = Await folder.GetFileAsync("beep.wav") End Function) Catch ex As Exception SendErrorReport(ex) End Try End Sub
Step 5: Dispose of the wavePlayer in the OnSuspending method:
Public Shared Async Function OnSuspendingAsync() As Task .... wavePlayer.Dispose() : wavePlayer = Nothing ... End Sub
Step 6: Restore the wavePlayer in the OnResuming method:
Public Shared Sub OnResuming() If wavePlayer Is Nothing Then wavePlayer = New WavePlayer End Sub
Step 7: Inside the two App.xaml.vb files, from both Windows and Phone projects, call into the common App.vb implementation of OnResuming:
Sub OnResuming(sender As Object, e As Object) Handles Me.Resuming App1_Common.App.OnResuming() End Sub
Step 8: Add a call to play audio, wherever you want it! Remember that BeepFile gets initialized asynchronously, so we have to cope with the possibility that it's still Nothing.
If BeepFile IsNot Nothing Then wavePlayer.StartPlay(BeepFile)
We've now created a solid VB Universal app - although under the hood, a universal app is really two separate apps. We've successfully been able to do all of our coding inside App1_Common, because we've been calling into APIs that are common to Windows and Windows Phone.
Stay tuned for tomorrow's blog post, where we cover how to call into APIs that aren't common to Windows and Windows Phone. Thankfully there aren't many of them!
Thanks the great code.
I made an WP8.1WinRT app containing WavePlayer and sent it to the Windows Phone store.
However, it was refused for the following reasons.
Supported API test
•Error Found: The supported APIs test detected the following errors:◦This API is not supported for this application type - Api=LoadPackagedLibrary. Module=kernel32.dll. File=SharpDX.dll.
How did you do?
@biac, It is a known issue that I just opened github.com/.../420
A fix version will come in the coming days
Mr. Mutel, thank you.
It is very good news!
Mr. Mutel, thank you, again.
I included v2.6.3 in my application.
My application passed of the store!