Introduction

Did you ever need to impersonate the role of the sound-effects man in the shadow-theatre show at your young child's nursery school? It happened to me... and - trust me - I had some trouble while figuring out how many different sounds and noises I was expected to produce in the exact moment they were needed to accompany the show actions. So, I started to think about a way to create a mixing sounds keyboard, capable of playing different sound files (.WAV, .MP3, and so on) associated to each key, making me able to reproduce them by simply pressing a key when the corresponding noise was needed by an actor/animator.

Even if I'm sure lots of programs exist to accomplish this goal, I decided to create my own in VB.NET. The simple utility presented here, named "Sampled Sounds Player", creates an association matrix between files containing sampled sounds and keyboard keys, and lets you play those files (also looping and overlapping them) by programmatically controlling the Windows Media Player.

How the utility works

The utility is very simple: it's a single-form VB.NET Windows Application basically, containing a DataGrid that shows the filename/key association you chose. This association is saved in a configuration XML file resulting from the WriteXML() method invocation on the DataSet underlying the grid. This XML file is editable also from the user interface of the grid itself. We'll call this kind of files "sounds maps", because they actually simply contain the mapping between sound files and keyboard keys.

As stated previously, this utility plays sound files by using Windows Media Player (version 10, for example). To programmatically control Windows Media Player, I added a project reference to the WMPLib.DLL component (that is a non-managed code library, so Visual Studio created for me the needed interoperability assembly). The WMPLib library is easy to use; it exposes the WindowsMediaPlayerClass class that represents an instance of a player, on which you can invoke the same actions of the interactive player: you can programmatically load a file to be played, start/stop/pause the playing, set the "loop" property to repeat the sound, and so on.

My goal was to embed the logic of controlling sounds inside the grid shown on the user interface. Then, I created the RunningSound class, that represents a sound ready to be played: this class wraps the WindowsMediaPlayerClass, and is designed to be used directly as a datatype of the DataTable underlying the UI grid. So, for example, it exposes a custom ToString() method, used to show the current status of the sound being played as a field in the grid table. In fact, while using the "Sampled Sounds Player", it's important to keep track of the sounds currently playing, as in the following picture, where you see the grid showing this situation:

the sounds associated to keys "P" and "U" are being played, the sound "U" played continuously (note the infinite loop symbol near the play symbol);

the sounds associated to keys "J" and "L" are currently paused (being the "L" sound set for repeated play);

all other sounds are ready to be played, just by pressing the corresponding key.

Sounds statuses are visually updated on the grid by intercepting the PlayStateChange event of each instantiated player. This stuff is, again, carried out by the RunningSound wrapper class, capable of accessing the DataRow currently hosting the actual instance of the player that changed its state.

To play a sound when the user hits the corresponding key, I simply handled the form's KeyDown event, after setting the form's KeyPreview property to True (notice that if the DataGrid has the focus, the form is unable to fire that event handler). In the KeyDown event handler, a simple lookup is performed on the table containing the sounds/keys mapping, and - if a row is found - the corresponing player is controlled. I decided to use the mapped keys in combination with the Shift and Control keys to perform different actions:

Key combination

Action

Simple KEY

PLAY / PAUSE the sound

CTRL + KEY

STOP the sound

SHIFT + KEY

Toggle LOOP ON/OFF

The "Load", "Edit", and "Save" controls on the form allow you to:

load a new sounds map on the grid;

start the edit mode, that permits you to modify the grid content;

save the currently shown grid as a sounds map file.

Some notes:

when a new sounds map is loaded, the old one is discarded, and on each grid row, a new player with the corresponding sound is created;

when edit mode starts, all sounds being played are automatically stopped and the corresponding players are unloaded;

when edit mode ends, the grid reverts to the read-only/playing mode;

when edit mode ends, all sounds and players are reloaded, complying with the modified grid;

while editing, you can - as in a standard DataGrid - add/delete/modify any grid row and any field but the Status column, that is obviously always read-only;

the "Save" button is enabled only in edit mode;

when you save the grid, it automatically reverts to the read-only/playing mode;

you can discard the changes you made on the grid by simply unchecking the "Edit" checkbox and loading again the sound map file previously saved;

you can fill the Filename field with the name of any file that Windows Media Player can play (.WAV, .MP3 and so on), but keep in mind that the file you specify must be located in the same folder where you saved the sound map file referencing it.

Points of interest

The main points in this utility are: the use of WMP and the manipulation of DataTables and DataSets. Being honest, controlling Windows Media Player is very easy: most of the magic is done by the component itself. The wrapper class I created and used directly as a DataColumn datatype made the rest. Notice the way I managed the serialization issues about this "special" DataSet: due to the nature of the Status column (that hosts a custom RunningSound datatype, that is redundant/dependant from the other data on the same row, and that is read-only), it is not serialized at all (in fact: it is suppressed when the grid is saved, and it is re-created when re-loading the sounds map later).

Future enhancements

Currently, there is no check about duplicated entries in the Key column (a simple UNIQUE constraint could be enough to accomplish this).

Currently, all the sounds are played at the same volume level. Potentially, each instance of the Windows Media Player can be controlled on its playing volume, so it's not difficult to implement this enhancement. For this utility, the only challenge is about making each sound's volume controllable by a very simple keyboard input (an idea could be the use of up/down keys to modify the volume of the sound selected on the grid, also bypassing in some way the KeyPreview issue on the DataGrid).

It could be very useful to add a feature capable to fill a new sounds map automatically from the contents of a given sound files folder (the System.IO.Directory.GetFiles() method makes it trivial to implement this enhancement).

Acknowledgments

Many thanks to my friend Davide Melis for some initial "knowledge feeds" on the Windows Media Player technology.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.