Sometimes You Gotta Make a Little Noise

Sometimes you just gotta make some noise. Sound is great if you are writing a game, want to add some pizzazz to a Windows or Web application, are writing your own media player, or need to include audio cues just because it makes sense to do so. (And occasionally it is just too quiet, but that's another kind of noise.)

A few years ago, I worked on a telecom application that permitted remotely managing ring tones, speed dials, and what is commonly called hoteling, in general. It made sense for our application to permit users to preview audio cues, for example, when assigning ring tones. If memory serves, we used the ActiveX media player because it was expedient. Since then, Microsoft has released two versions of .NET, and you may elect not to import an ActiveX control or use COM Interop to add sound to your applications.

In this article, I demonstrate how to create a wrapper for the Windows Multimedia Library. This wrapper makes it very easy to add sound to your applications. Along the way, you will learn about bit fields, take a wee peek at what the InteropServices namespace offers, and discover how our old friend, the Declare statement, is supported to import APIs in .NET.

Creating Bit Field Enumerations

To support playing .wav and .avi files, you can use the winmm.dll for Win32 and mmsystem.dll for Win 9x systems. We'll focus on support for playing sound in 32-bit Windows, using the Windows MultiMedia Library winmm.dll.

Common to many older style library calls is the notion of flags and bit fields. A bit field is where many pieces of data are Or'd and And'd together to compress a lot of information into a small space. Historically, bit fields were used because of smaller amounts of memory and fewer and smaller CPU registers. Over the years, as a code-base grows and evolves, some of these anachronistic techniques fall by the wayside while others remain.

For our purposes, we will elect to indicate whether a sound file is played synchronously or asynchronously, what the file's name and path are, and which resource to play the media on. (For a complete reference to the winmm.dll, go to the Microsoft.com Web site.)

Listing 1: The PlaySoundFlags are defined here.

<Flags()> _
Public Enum PlaySoundFlags
   SND_SYNC  = 0
   SND_ASYNC = 1
   SND_FILENAME = &H20000
   SND_RESOURCE = &H40004
End Enum

The FlagsAttribute is defined in the System namespace. Adorning the PlaySoundFlags enumeration with this attribute will permit us to treat PlaySoundFlags variables as bit fields. As a bit field, we are permitted to assign bitwise combinations of PlaySoundFlags values in the enumeration. Without the flag, we technically are only supposed to be able to assign one value from the enumeration at a time (see Listings 2 and 3, respectively), but Visual Basic .NET doesn't squawk about it.

Listing 2: PlaySoundFlags values with the FlagsAttribute.

0 - SND_SYN
1 - SND_ASYNC
&H20000 (or 131072) - SND_FILENAME
&H20001 (or 131073) - SND_FILENAME
&H40004 (or 262148) - SND_RESOURCE
&H60004 (or 393220) - SND_FILENAME, SND_RESOURCE
&H60001 (or 393221) - SND_ASYNC, SND_FILENAME, SND_RESOURCE

Listing 3: PlaySoundFlags values without the FlagsAttribute.

0 - SND_SYN
1 - SND_ASYNC
&H20000 (or 131072) - SND_FILENAME
&H40004 (or 262148) - SND_RESOURCE

It is worth noting that Visual Basic did not seem to care about the cardinality of the value whether the FlagsAttribute was used or not. The effect of the FlagsAttribute is visible when we invoke the ToString method on instances of the FlagsAttribute. If we combine more than one PlaySoundFlags values with the FlagsAttribute used, the ToString method displays each named enumeration; in the same scenario without the flags attribute, the cardinal value of the flag is returned from ToString() (see Listing 4).

Listing 4: Illustrates a subtle difference depending on the presence or absence of the FlagsAttribute.

<Flags()> _
Public Enum PlaySoundFlags
SND_SYNC = 0
        SND_ASYNC = 1
        SND_FILENAME = &H20000
        SND_RESOURCE = &H40004
End Enum

Dim flags As Sounds.PlaySoundFlags = Sounds.PlaySoundFlags.SND_ASYNC _
  Or Sounds.PlaySoundFlags.SND_FILENAME

Console.WriteLine(flags.ToString())
Console.ReadLine()

Without the FlagsAttribute, Listing 4 outputs 131072 to the console; with the FlagsAttribute, Listing 4 outputs SND_ASYNC, SND_FILENAME to the console.

Sometimes You Gotta Make a Little Noise

Importing the Windows MultiMedia Library

The ability to import an external API library is supported by the DllImportAttribute. In Visual Basic, importing an external library also is manifested with the synonymous Declare modifier. That is, Visual Basic .NET supports the Declare statement. To import the winmm.dll PlaySound method, we can reference the library—which does an implicit LoadLibrary and FreeLibrary in the background—using the following Declare statement:

Private Declare Function PlaySound Lib "winmm.dll" ( _
  ByVal fileName As String, ByVal hmod As IntPtr, _
  ByVal flags As PlaySoundFlags) As Integer

You will have to perform modest translations between managed types and API types on a case by case basis, but the preceding Declare statement is correct for PlaySound.

Wrapping the PlaySound API Method

If we want to make it easier to use the PlaySound API method, we can define our own wrapper that eliminates elements that we won't be changing in a given context. For our example, the module handle will always be zero, the sound will always be played asynchronously, and the filename is the only dynamic parameter. Consequently, we can simplify the API call with the following wrapper method:

Public Shared Sub Play(ByVal fileName As String)
  Try
    PlaySound(fileName, IntPtr.Zero, PlaySoundFlags.SND_FILENAME Or _
      PlaySoundFlags.SND_ASYNC)
  Catch
    Debug.WriteLine("Can't play sound file")
  End Try
End Sub

The IntPtr class and the IntPtr.Zero constant come from the InteropServices namespace. Sometimes, we need a pointer that may not be a native type in a particular .NET language, such as VB.NET. PlaySound supports a null module pointer—as opposed to, for example, the actual address of a module pointer—which is expressed using IntPtr.Zero.

Playing Sounds Asynchronously

The winmm.dll PlaySound method supports synchronous and asynchronous playback of media. If we send PlaySoundFlags.SND_SYNC, our code will have to wait for the API call to complete before we re-attain control. Passing PlaySoundFlags.SND_ASYNC means that the call returns immediately and the media is played in a background thread.

Testing the Wrapper Code

For your convenience, the entire example listing is provided here complete with a test method, beep (see Listing 5). Assuming your Windows directory is Windows and not WINNT and the chord.wav file exists, you should hear the chord.wav file if you call the Sounds.Beep static method. To compare the difference between asynchronous and synchronous behavior, pick a longer .wav file and switch between passing SND_ASYNC and SND_SYNC to see a difference in how the code responds.

Listing 5: The Complete Sounds class.

Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Runtime.InteropServices

Public Class Sounds
  <Flags()> _
  Public Enum PlaySoundFlags
    SND_SYNC  = 0
    SND_ASYNC = 1
    SND_FILENAME = &H20000
    SND_RESOURCE = &H40004
  End Enum

  Private Declare Function PlaySound Lib "winmm.dll" (ByVal _
    fileName As String, _
    ByVal hmod As IntPtr, ByVal flags As PlaySoundFlags) As Integer

  Public Shared Sub Play(ByVal fileName As String)
    Try
      PlaySound(fileName, IntPtr.Zero, PlaySoundFlags.SND_FILENAME Or _
        PlaySoundFlags.SND_ASYNC)
    Catch
      Debug.WriteLine("Can't play sound file")
    End Try
  End Sub

  Public Shared Sub Beep()
    Try
      Const soundFile As String = "c:\winnt\media\chord.wav"
      If (File.Exists(soundFile)) Then Play(soundFile)

    Catch ex As Exception
      Debug.WriteLine(ex.Message)
      Throw
    End Try
  End Sub

End Class

Summary

A large amount of information can be hidden in a small amount of code. In this article, you learned a little bit about the .NET attributes DllImportAttribute and FlagsAttribute that support the Declare modifier and bitwise Or-behavior for enumerated types. We talked some about InteropServices, and you now have a very simple media player that may add some additional depth to your applications. Enjoy.

Biography

Paul Kimmel is the VB Today columnist, has written several books on .NET programming, and is a software architect. You may contact him at pkimmel@softconcepts.com if you need assistance.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Relying on outside companies to manage your network and server environments for your business and applications to meet the needs and demands of your users can be stressful. This is especially true as many Managed Hosting organizations fail to meet their service level agreements. Read this Forrester total economic impact report and learn what makes INetU different and how they exceed their customers' managed hosting expectations.

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

Most Popular Programming Stories

More for Developers

RSS Feeds