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.