In this short lesson you will learn how to play sound in your HOpenGL applications. Please keep two things in mind here:
Ok, let's start. First of all, let me explain how the sound stuff works: there is a Windows system routine called PlaySound
, that is used to play simple sound files (such as .wav). I'll make available to you a module, written in Haskell, that treats this routine as a foreing function, and makes it possible to call it in a Haskell program, although it was written in another language. I won't explain how this module works, because this is not a HOpenGL topic, but you can get more information about the Haskell FFI (Foreign Function Interface) here.
Ok, since you download this Sound module you'll be ready to start. We'll use only one single funciton from it, playSound
, which is a binding to the original PlaySound
Windows function. Its signature is:
playSound :: String -> Win32.HMODULE -> [SoundFlag] -> IO Bool
Notice that it returns a boolean, indicating if the sound file was played successfully or not. The String
corresponds to the path to the .wav file. Don't worry about the second argument: it'll always be nullAddr
, because we won't use any advanced sound features, as mentioned before. By the way, you can check out the PlaySound
documentation for more details if you wish.
The third argument of playSound
is the most interesting: it tells how the sound will be played. Its possible values are:
data SoundFlag
= Application
| Alias
| AliasId
| Async
| Filename
| Loop
| Memory
| NoDefault
| NoStop
| NoWait
| Purge
| Resource
| Sync
Some of these values are too advanced for we to cover here, but others are very interesting. Let's check some of them:
Sync
- Synchronous playback of a sound event: playSound
returns after the sound event completes. In other words, your HOpenGL application will stop completely (freeze) during the execution of the sound.
Async
- The sound is played asynchronously and playSound
returns immediately after beginning the sound (your HOpenGL application won't freeze and its computations will occur in parallel with the sound execution).
Loop
- The sound plays repeatedly until playSound
is called again. You must also specify the Async
flag to indicate an asynchronous sound event.
NoStop
- The specified sound event will yield to another sound event that is already playing (the sound won't interrupt another sound that is being executed).
NoWait
- If the driver is busy, return immediately without playing the sound.
Ok, let's create some code now, since you understood some sound theory. Our application will be very simple: if the user presses the 1
, 2
, 3
or 4
keyboard key, the sound 1.wav
, 2.wav
, 3.wav
or 4.wav
will be played respectively. Sound 1 will be synchronous, sound 2 will be asynchronous, sound 3 will be played repeatedly (loop) and sound 4 won't interrupt any other sound that is being currently played.
The first thing to do is to import the Sound
module (besides the GL
and GLUT
modules, of course). Then create an empty window and register the function keyboard
as our keyboard callback function:
import Sound
import GLUT
import GL
main :: IO ()
main = do
GLUT.init Nothing
createWindow "Sound Example" (return ()) [ Single, GLUT.Rgb ]
(Just (WindowPosition 100 100))
(Just (WindowSize 300 250))
keyboardFunc (Just keyboard)
mainLoop
Now there is only one thing left: define what keyboard
should do. As you may have guessed, it's a very easy job: it's just necessary to set the correct flags to the sound events:
keyboard :: KeyboardAction
keyboard '1' _ = playSound "1.wav" nullAddr [Async] >>= \soundStatus -> print soundStatus
keyboard '2' _ = playSound "2.wav" nullAddr [Sync] >>= \soundStatus -> print soundStatus
keyboard '3' _ = playSound "3.wav" nullAddr [Loop,Async] >>= \soundStatus -> print soundStatus
keyboard '4' _ = playSound "4.wav" nullAddr [NoStop] >>= \soundStatus -> print soundStatus
keyboard _ _ = return ()
Notice that the prompt shows if the sound was played successfully or not (soundStatus
). Notice also that we can call playSound
with more that one sound flag. For example, if you wish that sound 4 to also be played asynchronously, you would use:
keyboard '4' _ = playSound "4.wav" nullAddr [Async,NoStop] >>= \soundStatus -> print soundStatus
That's it, now we are done. I'd like to thank Monique Monteiro for helping with sound issues.
Sound
too!