== Lesson 10: Sound image::learn/10-sound.png[The finished lesson, 480] A game with no sound feels broken in a way that is hard to put a finger on. The shots go out and nothing happens. The rocks break and nothing happens. Put a half second noise on each of those and the same game suddenly feels like it is made of something. This lesson gives your game a shot, an explosion, and music underneath. On the way it introduces your third callback: the one the engine uses to tell you that a sound has finished playing. === One Noise The art kit you copied in lesson nine has two sounds in it, `shoot.wav` and `boom.wav`, and the explosion is the easier of the two to hear. Make a new file beside your `art` folder and type this in. [source,lua] ---- dofile("Singe/Framework.singe") local boom = soundLoad(DIR .. "art/boom.wav") function onInputPressed(what) if what == SWITCH_BUTTON1 then soundPlay(boom) end end function onOverlayUpdate() overlayClear() overlayPrint(2, 2, "Press space.") return OVERLAY_UPDATED end function onShutdown() soundUnload(boom) end ---- Run it and tap the space bar three or four times quickly. The bangs pile up on top of each other instead of cutting each other off. Holding the space bar down gives you exactly one bang, not a stream of them. That is the same behaviour you met in lesson three: a held key is one press, reported once, and the engine throws away the repeats your keyboard sends. It is why steering needs `onInputReleased` and a variable rather than counting presses. === What Just Happened The shape is the same as lesson nine's: load once at the top, use the handle, give it back at the end. [source,lua] ---- local boom = soundLoad(DIR .. "art/boom.wav") ---- `soundLoad` reads an audio file and keeps it in memory, and hands back a handle -- the same idea as a sprite handle, a number that means "that clip" to the engine. `DIR` is there for the same reason it was there in lesson nine: the sound lives in your game's folder, and the game has to be able to find it from wherever Singe was started. Everything lesson nine said about loading applies here word for word. Load at the top of the file, once. A `soundLoad` inside `onOverlayUpdate` loads the clip sixty times a second and never gives one back, and the game dies of it some minutes later. [source,lua] ---- soundPlay(boom) ---- Play it. That is the whole of playing a sound effect, and the engine takes care of the rest: the clip starts, it finishes, and it never gets in the way of your program. But `soundPlay` hands something back too, and it is *not* the same number you gave it. Change the line and look: [source,lua] ---- local channel = soundPlay(boom) debugPrint("clip " .. boom .. " on channel " .. channel) ---- Tap the space bar quickly half a dozen times and watch the two numbers. The clip number never changes. The channel number climbs -- 0, then 1, then 2 -- and once the earlier bangs have finished it drops back and starts reusing the low numbers again. This is the single thing about sound that catches everybody, so it is worth saying slowly. *Two different kinds of number are in play.* The *clip handle* from `soundLoad` is the recording. There is one of it. It does not change, it is not playing or not playing, it is the sound sitting in memory waiting to be used. The *channel* from `soundPlay` is one performance of that recording. Singe has sixteen of them, numbered `0` to `15`, and every sound that is currently audible is using one. That is why the bangs overlap: the second press did not interrupt the first, it started a second performance on the next free channel. Everything that acts on a *sound you can hear* takes a channel: `soundStop`, `soundIsPlaying`, `soundPause`, `soundResume`. Everything that acts on the *recording* takes the handle: `soundPlay` and `soundUnload`. Hand one where the other belongs and you will not always get an error, because `3` is a valid channel number and also a perfectly good clip handle -- you will get silence, or the wrong sound stopping, which is far harder to find. Sixteen is a lot, but it is not endless. When every channel is busy, nothing plays and `soundPlay` hands back `-1` instead of a channel number. A game that fires a great many sounds at once should expect that, and you will write one in a minute. === Volume There are three volumes, and they multiply together. The *master* effects volume covers everything. `soundGetVolume` reads it and `soundSetVolume` sets it, on a scale of `0` to `63`. [source,lua] ---- soundSetVolume(40) ---- Resist. That number is the player's, not yours: it starts at whatever they asked for when they launched the game, and a game that overwrites it is a game that is too loud in somebody's quiet room. Change the master volume only when the player asks you to, from an options screen. `soundGetVolume` is there so that an options screen can show what it currently is instead of guessing. What you should reach for is the *channel* volume, which sits under the master. Give `soundPlay` a third argument and this one performance is quieter: [source,lua] ---- soundPlay(shootClip, 0, 35) ---- The `0` in the middle is how many times to repeat, which you have to give because the volume comes after it. `0` means play it once. `-1` means loop until something stops it, which is how you would do an engine drone or wind. The manual's entry for `soundPlay` lists all three arguments. The third volume is distance, for sounds placed in a 3D world. That is for part four, when the game moves into three dimensions. Music has a volume of its own, and it is on a different scale. More on that in a moment. === Stopping, and Not Stopping `soundStop` takes a channel and cuts it off where it is: [source,lua] ---- soundStop(engineChannel) engineChannel = -1 ---- Note the second line. As soon as a channel stops, the engine is free to hand that same number to the next `soundPlay`, so a channel number you are still holding may now belong to a completely different sound. Throw it away the moment you stop it. `-1` is the useful thing to put there, because it is the same value `soundPlay` gives you when it could not play at all, so one test covers both: `if channel >= 0 then`. `soundStop` is for a sound that is wrong now -- the engine noise when the engine dies, the alarm when the alarm is answered. It is the wrong tool for a short effect. A half second explosion chopped off after a tenth of a second sounds like a mistake, because it is one. So most of the time, do nothing at all. Let it finish. A sound effect that you start and never think about again ends by itself and frees its channel by itself, which is exactly what you want. This holds even at the end. `soundUnload` does not cut off a clip that is still playing: the engine keeps the audio alive until the last channel using it has stopped. You can unload in `onShutdown` without worrying that you have silenced something. `soundIsPlaying(channel)` answers whether a channel is making noise right now. Its main use is not curiosity but restraint: an alarm that should sound once, and not restart every time the game notices the danger is still there. [source,lua] ---- function raiseAlarm() if alarmChannel < 0 or not soundIsPlaying(alarmChannel) then alarmChannel = soundPlay(alarmClip) end end ---- Read the condition aloud: play the alarm if there is no alarm channel, or if the one you have has stopped. Otherwise leave it alone. === The Sound That Stutters Here is the mistake. Everybody writes it, usually within a week of discovering sound. Suppose you wanted the ship to keep firing while the button is held down, rather than once per press. You would do it the way you steer: a `firing` variable set to `true` in `onInputPressed` and back to `false` in `onInputReleased`, and the shot created in `onOverlayUpdate` while it is true. That is a perfectly sensible thing to want, and the noise looks like it belongs right there with the shot: [source,lua] ---- function onOverlayUpdate() if firing then shots[#shots + 1] = { x = shipX, y = shipY } soundPlay(shootClip) end ... ---- What comes out is not a shot. It is a buzzing rattle, or a hard flat tone, or a sound like a small machine breaking. `onOverlayUpdate` runs about sixty times a second. Hold the button for one second and you have asked for sixty performances of a clip that lasts about a fifth of a second, each one starting on top of the last. Sixteen of them are playing at once within a quarter of a second, the other forty-four come back `-1` and are silently dropped, and what you hear is sixty copies of the same attack piled on each other. The fix is not a cleverer sound. It is playing the sound where the *event* happens rather than where the *state* is true: [source,lua] ---- function onInputPressed(what) if what == SWITCH_BUTTON1 then soundPlay(shootClip, 0, 35) end end ---- `onInputPressed` happens once, when the button goes down. One press, one shot, one noise. If you really do want held fire, the answer is the same one in a different shape: make the firing itself an event. Count frames, exactly as the walk cycle counted them in lesson nine, and create a shot only every eighth frame. The noise goes with the shot, so it plays seven or eight times a second instead of sixty, and that is a machine gun rather than a rattle. That is the rule, and it is worth carrying out of this lesson: *sounds belong with events, not with conditions*. A press, a hit, a death, a pickup -- each of those happens once and deserves one sound. "The button is down" and "the ship is moving" are not events; they are true for hundreds of frames in a row, and anything you play from them plays hundreds of times. When a sound really does belong to a state that lasts -- an engine, wind, a siren -- start it once with `-1` for the loop count, keep the channel, and stop it when the state ends. === The Third Callback Sometimes you do need to know when a sound has finished. Write this function and the engine will call it: [source,lua] ---- function onSoundCompleted(channel) end ---- That is your third callback, after `onOverlayUpdate` in lesson one and the two input callbacks in lesson three, and it works the same way as all of them: you write it, you never call it, the engine calls you. The `channel` it hands you is the channel that has just gone quiet -- the number `soundPlay` gave you when you started it. It fires when a clip runs out, when the last repeat of a loop finishes, *and* when you stop a channel yourself with `soundStop`. That last one surprises people who use the callback to chain one sound into the next: stopping a channel on purpose still reports it as completed, and if you are not careful the next sound in the chain starts anyway. The game is about to use it for something worth doing. When the last life goes, the ship's explosion plays, and the game holds still -- no GAME OVER, no music fading -- until that explosion has actually finished. Then everything stops at once. It takes a handful of lines and it is the difference between an ending and a cut. === Music Is Not a Sound Effect You could load a three minute tune with `soundLoad` and play it with `soundPlay`. Do not. Music gets its own family of calls, for reasons that matter: * Music has its own volume, separate from the effects, so a player can turn the music down and still hear the shots. * There is no sixteen channel limit and no channel number. One handle is one piece of music, and you stop it, pause it, and resume it by that handle. * `musicStop` can fade a piece out over a second or two instead of cutting it. The calls mirror the ones you already know: [source,lua] ---- local theme = musicLoad("Singe/menuIntro.flac") musicSetVolume(50) musicPlay(theme, -1) ---- `musicLoad` gives a handle. `musicPlay` starts it, with the same loop count as `soundPlay`: `-1` for forever. `musicStop(theme)` ends it, and `musicStop(theme, 1500)` takes a second and a half to fade it away. `musicUnload` gives it back. Now the trap. *Music volume runs from 0 to 128, not 0 to 63.* Sound effects use one scale and music uses another, twice as long, for historical reasons that are no comfort at all when your music comes out at half the volume you meant. When you are working with music, double the number you had in mind for an effect. The piece being loaded there is the engine's own menu music, out of the `Singe` folder the engine unpacked into your work folder on your first run. It is ten seconds long and it was written to fade out at the end, so looping it sounds exactly like a ten second loop that keeps fading out. That is fine for learning the calls and no good at all for a finished game, which ships a longer piece of its own. Any of the usual formats will do: the reference's Music section lists them, and Ogg Vorbis or MP3 are the sensible choices, because a few minutes of music as a WAV is an enormous file. === The Game Gets a Soundtrack Open the game from lesson nine. Everything here is an addition to it; nothing comes out. Four things to load, at the top, with the sprites: [source,lua] ---- local shootClip = soundLoad(DIR .. "art/shoot.wav") local boomClip = soundLoad(DIR .. "art/boom.wav") local clickClip = soundLoad("Singe/click.wav") local theme = musicLoad("Singe/menuIntro.flac") ---- The first two have `DIR` on them and the last two do not, and that is on purpose. `DIR` means *your* game's folder. A name that begins with `Singe/` means the engine's own folder instead, wherever that is -- which is why `dofile("Singe/Framework.singe")` has worked since lesson seven without a `DIR` either. Your own files need `DIR`; the engine's never do. Two new variables, beside the others: [source,lua] ---- local dying = false local deathChannel = -1 ---- `dying` is the moment between the last life going and the game being over: the explosion is still sounding, and nothing on screen is moving. The shot gets its noise in `onInputPressed`, right where the shot itself is created, and the restart gets a click so that pressing `1` feels like it did something: [source,lua] ---- function onInputPressed(what) if over then if what == SWITCH_START1 then soundPlay(clickClip) startGame() end return end if what == SWITCH_LEFT then goLeft = true elseif what == SWITCH_RIGHT then goRight = true elseif what == SWITCH_BUTTON1 then shots[#shots + 1] = { x = shipX + shipWidth / 2 - shotWidth / 2, y = shipY } soundPlay(shootClip, 0, 35) end end ---- A rock that is shot bangs, and so does a rock that hits you. In `updateShots`, beside the score: [source,lua] ---- newRock(rock) score = score + HIT_SCORE gone = true soundPlay(boomClip, 0, 45) ---- And in `updateRocks`, where the life is lost, the interesting part: [source,lua] ---- if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then local channel = soundPlay(boomClip) newRock(rock) lives = lives - 1 if lives <= 0 then dying = true deathChannel = channel if channel < 0 then endGame() end end end ---- When the last life goes, the explosion plays and its channel is remembered. The game is now `dying`, which stops everything moving but does not yet say GAME OVER. The `if channel < 0` is the sixteen channel limit, handled. If every channel happened to be busy at that exact moment, there is no explosion and no channel to wait for, and without those two lines the game would sit in `dying` forever, waiting for a sound that never played. Waiting for something that cannot arrive is one of the easiest ways to hang a program, and the guard is always cheaper than the bug. Now the new callback, and the little function it shares with that guard: [source,lua] ---- function endGame() dying = false deathChannel = -1 over = true musicStop(theme, 1500) end function onSoundCompleted(channel) if channel == deathChannel then endGame() end end ---- The engine calls `onSoundCompleted` for *every* channel that stops, so the first thing it does is check whether this one is the channel it cares about. Everything else -- every shot, every rock -- passes through and is ignored. `onOverlayUpdate` needs to hold still while the explosion runs, which is one word: [source,lua] ---- if not over and not dying then updateShip() updateShots() updateRocks() end ---- `startGame` clears the two new variables and starts the music over: [source,lua] ---- dying = false deathChannel = -1 musicSetVolume(50) musicPlay(theme, -1) ---- And `onShutdown` gives back what was loaded: [source,lua] ---- function onShutdown() spriteUnload(shipSprite) spriteUnload(rockSprite) spriteUnload(shotSprite) spriteUnload(starSprite) soundUnload(shootClip) soundUnload(boomClip) soundUnload(clickClip) musicUnload(theme) end ---- Run it. Fire at the rocks, let one hit you three times, and listen to the end: the last bang plays out in full, and only when it is gone does the music start to fade and the words come up. The finished script is in the `learn` folder as `10-sound.singe` if you want to compare. === Try It . *Make the shot quieter still.* Change the `35` in the shot's `soundPlay` to `10`, then to `63`. Find the number where it sits under the explosions instead of on top of them. . *Turn the music down without touching the effects.* Change `musicSetVolume(50)` to `musicSetVolume(15)`. Confirm that the bangs are exactly as loud as they were, which is the whole reason music is its own system. . *Cut the ending off.* In `endGame`, change `musicStop(theme, 1500)` to `musicStop(theme)`. Listen to both endings twice. The difference is a number in one argument and it is not a small difference. . *Give the ship an engine.* Start `soundPlay(clickClip, -1, 12)` in `startGame`, keep the channel it gives you in a variable of its own, and stop it in `endGame`. The click lasts a fiftieth of a second, so looping it is a buzz, which is as close to an engine as this kit gets. Then take the `soundStop` out again, play three games in a row, and listen to what you have built. . *Count the channels.* Print the channel number every time a rock explodes. Then raise `ROCK_COUNT` until several explode at once, and keep raising it until you see a `-1` go by. === Break It on Purpose You have two volume scales in your head now, and sooner or later the wrong one comes out of your fingers. Add this line on its own, just below the four `soundLoad` and `musicLoad` lines: [source,lua] ---- soundSetVolume(100) ---- The game refuses to start: ---- 20:soundSetVolume: Invalid sound volume value: 100 ---- The line number you get is the line you actually typed it on, so yours may not be 20. Then the function that objected, and the value it objected to. The message does not tell you what the range is -- that is what the reference's entry for `soundSetVolume` is for -- but it does tell you the number it would not take, and once you know that effects stop at 63 the fix is one digit. `100` is a suspicious number to find in a volume, and it is worth knowing why you typed it. Almost certainly you thought of volume as a percentage, because every volume slider you have ever used goes to 100. Singe's does not. Effects are `0` to `63` and music is `0` to `128`, and neither is a percentage of anything. This is the general shape of an engine error, as opposed to the Lua errors in lesson one: a line number, the name of the engine function you called, and a complaint about the values you handed it. When you see one, the reference's entry for that function is the next thing to read. === What You Learned * `soundLoad` reads a clip and returns a handle. Load once, at the top of the file. * `soundPlay` starts a clip and returns a *channel*, which is a different kind of number from the handle. * Singe mixes sixteen channels. When they are all busy, `soundPlay` returns `-1` and nothing plays. * Channel numbers are reused. Forget one as soon as you have stopped it. * Short effects should be left alone to finish; `soundStop` is for sounds that have become wrong. * `soundUnload` does not cut off a clip that is still playing. * Play a sound where the event happens, not where a condition is true, or it plays sixty times a second. * `onSoundCompleted(channel)` is a callback the engine calls each time a channel goes quiet, for any reason. * Music is a separate system with its own handles, its own volume, and no channel limit. * Effects volume is `0` to `63`. Music volume is `0` to `128`. They are not percentages. === Next Time Your rocks are hit when their rectangles overlap, which is why a shot that passes through a corner of the sky still counts. In lesson eleven you get the engine's own collision tests, learn what a hitbox is, and find out why the rectangle around a round rock is the wrong shape.