This is Part 4 of the “VB Universal Windows App” series:
- Part 1: Setting up the universal Windows app in the Dev Centers, and in VS Solution Explorer
- Part 2: Sharing XAML, Assets and Code
- Part 3: Local and Roaming settings, and In-App purchases
- > Part 4: Sound effects with SharpDX
- Part 5: How to call platform-specific APIs from common code
- Download full source code
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)
Conclusion
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!
—
Lucian
0 comments
Be the first to start the discussion.