Delphi: playing Chimes.wav as an external file or embedded WAVE resource in Delphi XE5.

  

Recently I had to play some notification sounds in a Windows Delphi application where the application deployment should be as easy as possible: preferable copying the EXE around.
Playing a sound file seems easy, especially if it is a WAV file: just use the PlaySound or the (older) sndPlaySound API functions.
But if you start searching on the internet, you see lots of curious implementations for playing WAV resources through sndPlaySound.
The actual implementation is really really easy though, just make sure you follow the steps right and nothing can go wrong.
The full source code is on my BeSharp.net repository, here is how to to it step by step:
The steps depend on the MMSystem unit, so most of the code translates back to Turbo Pascal for Windows (yes, the 16-bit Pascal days when the MMSystem unit was introduced) with the exception of the SND_SENTRY flag.
The thing that more recent Delphi versions made a lot easier is embedding WAV files as WAVE resources, more on that further on.
All examples use the combination of the flags SND_NODEFAULT, SND_ASYNC or SND_SENTRY:

SND_NODEFAULT makes sure no other sound gets substituted if the file or resource cannot be found.
SND_ASYNC plays the sound asynchronously in a separate thread so your main thread continues to execute while the sound plays.
SND_SENTRY is for Windows Vista and up to notify hearing impaired in a visual way that a sound is being played through a SoundSentry event.

Stop playing sound
Playing sounds starts with stopping anything that is playing from your current app with this line of code:

It uses the nil as a sound parameter in combination with the SND_ASYNC flag to stop playing any sound that started using the PlaySound or sndPlaySound functions in your application.
Playing a WAV file
This is also a one-liner.

The initial demo project is a bit larger, as it involves a VCL Form and a project just for the single purpose on showing how the project will change when embedding a WAVE resource.
Embedding a WAV file as a WAVE resource
“Resources and Images…” in the “Project” menu
In the past this was overly complicated: people were manually assembling RES files, or wrote RC scripts to create RES files and included those in Delphi.
No need for that any more: somewhere around Delphi XE this got much easier:

Open the Project menu, then choose Resources and Images just like shown on the right:

Press the Add button:

Choose a WAV file (in this case C:\Windows\Media\chimes.wav)

Rename the resource to chimes_wav (to make sure there is no . in it):

Ensure the resource type is WAVE (PlaySound requires it, so do not use RCDATA or RC_DATA):

Performing the above steps result in a few very interesting changes in your Delphi project.
A. The cool thing is that Delphi now has embedded the resource as an external link the the .dproj file, as seen by this diff:

B. The .dpr file also got changed loading the *.dres file in-between the program and uses like:

Now you only need to add one more line of code to play this embedded WAVE resource…
Playing a WAVE resource
Two changes you need to do here:

Use the resource name, not the file name.
SND_RESOURCE flag.

Note there is no need any more for uppercase resource names, but there cannot be any periods in them as the valid characters in resource names are limited to A-Z, a-z, 0-9, and underscores ( _ ) and resource names must be unique in the scope of the application (not only the resource file).
So the line becomes this:

leaving the diff like this:

So there is no need to use FindResource, LoadResource, LockResource, nor TResourceStream. You still can, but that makes it overly complicated.
With the PlaySound solution above, you might need to cast the string of the filename or resource name to PChar if you pass the string as variable, but that is about as complicated as it gets.
Playing from memory
I haven’t included an example for playing sounds from memory. The reason is that using SND_MEMORY with SND_ASYNC requires you to make sure the memory buffer lasts longer than the sound will play. And since the sound plays asynchronously in a separate thread, you have to make sure the memory outlives the thread. Which can be hard.
As of Windows 7, Microsoft fixes this by buffering up to a maximum of 2 megabytes of WAV data for you, but still you need to be careful.
Playing cross plaform
When you want to play sounds in a cross platform way, you need to look into the FireMonkey FMX TMediaPlayer. That is beyond the scope of this blog post, but a Delphi XE4 example is here.
–jeroen
keywords:
PlaySound(nil, 0, SND_ASYNC);Filed under: Borland Pascal, Delphi, Delphi 2, Delphi 2005, Delphi 2006, Delphi 2007, Delphi 2009, Delphi 2010, Delphi 3, Delphi 4, Delphi 5, Delphi 6, Delphi 7, Delphi XE, Delphi XE2, Delphi XE3, Delphi XE4, Delphi XE5, Development, Pascal, Software Development, Turbo Pascal

Comments are closed.