VB Universal Windows App Part 4: using SharpDX for sound effects

VBTeam

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)

 

 

 

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

Leave a comment

Feedback usabilla icon