Music and Sound

From OrbEdit Wiki

Jump to: navigation, search

Contents

Sound Introduction

Image:BassClef.png

Orb uses the FMOD sound library for low-level sound processing. FMOD is fast, lightweight, and supports just about any sound format you can think of. Orb provides a special Sound class that lets your scripts play sound and music. A variety of effects are possible through the DSP class. All Sound processing within Orb is done in software, this allows for more accurate, hardware-independent playback as well as a wider range of DSP support.
As elsewhere in this wiki, a distinction has been attempted with capital letters. The term sound (lowercase) is meant to refer to a noise coming out of your speakers whereas Sound refers to an Orb Sound object. Usually.

Orb supports 100 channels at once. A channel is a single instance of a sound being played; though a single Sound object can play on multiple channels at once. In addition, you can access the sound engine itself via the orb.SoundManager variable to control your sounds at a higher level. The SoundManager allows you to play, pause, and stop all Sounds at once, as well as add effects on a global level.

Playing sounds in Orb is really easy!

-- Play the sound file Sounds/BananaPhone.mp3
Sound:new("Sounds/BananaPhone.mp3"):play();

Just create a Sound object and call its play() method! But of course there's slightly more to it than that :D


The Sound Class

Orb's Sound class gives you the ability to load a sound file once and play it as many times as you want. You can pause or stop it once it's playing, retrieve tag information embedded in it, adjust its volume, or add special effects to it. The constructor for the Sound class looks like this:

  • Sound:new(filename, streaming, id)
filename - The name of the sound file to load
streaming - A boolean indicating whether or not to stream the file from disk
id - An Event ID to fire when loading has completed


Once you have created a Sound object, you can call any of its methods:

  • Sound:play(): Plays the Sound on a new channel. If the Sound is already playing, calling play() again will play it again on a new channel, unless it is a streaming Sound, in which case it will restart from the beginning. This method will block until the sound file is finished loading.
  • Sound:pause(paused): Pauses the Sound on all channels it's currently playing on. Calling pause() again will restart the Sound on those same channels. Optionally, you can pass in a boolean value paused to explicitly set the paused state, rather than relying on the toggle behavior.
  • Sound:stop(): Stops the Sound on all channels it's currently playing on. Subsequently calling play() will start the Sound on a single channel.
  • Sound:setVolume(volume): Sets the volume of the Sound on all its channels to the value specified by volume. Value must be between 0.0 (silent) and 1.0 (full volume).
  • Sound:addDSP(dsp): Attaches the DSP dsp to the Sound. Each instance of a DSP can only be attached to one Sound at a time.
  • Sound:release(): Releases the Sound and all memory it's using. Be sure to release Sounds you no longer need.

As usual, check the API for the full set of Sound methods.

Streaming vs Non-Streaming

Sounds come in two varieties, streaming and non-streaming, which you control via the streaming flag in the constructor. They work like this:

Streaming
Streaming sounds are read from disk in small chunks and never completely loaded into memory. Streams can begin playing almost immediately and are very memory-efficient. You should always use streams for large sound files such as background music. A streaming sound can only be played one at a time, that is, if you call play() on a streaming sound that's already playing, it will restart the sound from the beginning, rather than playing itself again on a new channel.
Non-Streaming
Non-streaming sounds must be completely loaded into memory before they can be played. As such, they can be played very quickly on many different channels at once. They are best used for sound effects, which need to play at a moment's notice. Non-streaming is the default behavior if you do not specify the streaming flag.

Loading is done in the background for both types, meaning the constructor will always return immediately, but play() will block until the loading is done. You can safely call play() for a streaming sound at any time, since they have much less data to load before being ready. However, you should take care not to call play() on a non-streaming sound until you know it has finished loading, which brings us to our next topic.


Sound Callbacks

Now is a good time to read up on Event IDs and coroutines if you haven't already.

Several Sound methods allow you to pass in an Event ID as the final parameter. One such method is the Sound constructor, as seen above. When the constructor finishes loading the specified file, it will fire the Event ID passed to it, signaling you that the Sound is ready to be used. Consider the following example:

-- Get an Event ID to control our coroutine
local id = orb.gueid();
 
-- Create a coroutine with the above ID that we can pause until our Sound loads
orb_run(function()
          -- Create a new non-streaming Sound of a very big file
          local sound = Sound:new("Sounds/VeryBigFile.wav", false, id);
 
          -- Pause until done loading
          pause();
 
          -- Now that the Sound is loaded, play it
          sound:play();
 
          -- Return -1 to indicate that the coroutine is done
          return -1;
        end, id);

This is somewhat complicated, but will guarantee that your game does not halt while the sound is played.


The play() method also can take an Event ID to fire when the sound finishes playing:

-- Create a streaming Sound
local sound = Sound:new("Sounds/BananaPhone.mp3", true);
 
-- Get an Event ID to create a callback with
local id = orb.gueid();
 
-- Create a callback with the above ID
orb_set(function()
          print("Sound is done playing!");
          return -1;
        end, id);
 
-- Play the Sound, passing in the callback ID
sound:play(id);

This will play the sound "Sounds/BananaPhone.mp3" and will print "Sound is done playing!" when it completes. If you play a Sound multiple times at once, each time it completes, your callback will be called.


The SoundManager Class

By default, Orb gives you access to the SoundManager class in the variable orb.SoundManager. The SoundManager represents the sound engine at a high level, and gives you control over all Sounds at once. Through the SoundManager, you can play, pause, and stop all Sounds at once. Adding a DSP to the SoundManager will affect all Sounds at once, including new Sounds that you play afterward.

The SoundManager is a singleton object, that is, there is only one SoundManager that is shared across the entire system. You cannot instantiate another (nor would you want to!)

For example, you might want to implement a pair of functions, PauseSounds() and ResumeSounds() that would pause all Sounds when the player opens a menu and then unpause them when the menu closes, making sure the player doesn't miss any important dialog going on at the time.

function PauseSounds()
  -- Pause all Sounds
  orb.SoundManager:pause(true);
end
 
function ResumeSounds()
  -- Resume all Sounds
  orb.SoundManager:pause(false);
end

The SoundManager's interface is almost identical to the Sound interface, but each of its methods effects all Sounds rather than just one. You can find the full list of SoundManager methods in the API.


The DSP Class

DSP stands for Digital Signal Processor and represents an effect that you can attach to a Sound or to the SoundManager. An effect can be anything from reverb, treble filtering, distortion, to pitch shift. Orb offers 10 different types of DSPs, each with a set of parameters that you can change to alter the end result. The constructor for the DSP class looks like this:

  • DSP:new(name, type)
name - The name of the DSP, should be unique and descriptive
type - The numerical type of the DSP, types are defined in /Core/Scripts/OrbDefinitions.lua or are easily found in the API


Like all Orb objects, a DSP has a number of different methods to control its behavior. You can find the the complete list of DSP methods in the API.

  • DSP:getName(): Returns the name of the DSP. The name can only be set via the constructor.
  • DSP:getParent(): Returns the Sound or SoundManager that the DSP is currently attached to, or nil if not attached to anything. A DSP can only be attached to one Sound or SoundManager at a time.
  • DSP:getParameters(): Returns a table populated with the name and value of each of the DSP's possible parameters. For example, the orb.ORB_DSP_ORB_DSP_LOWPASS DSP has a 2 parameters named "cutoff freq" and "resonance", so the table would look like: { "cutoff freq" = 5000.0, "resonance" = 1.0 }.
  • DSP:setParameter(name, value): Sets the parameter name to the value value. The value of each parameter has a maximum and minimum which can be found in the API.
  • DSP:remove(): Detaches the DSP from its Sound or from the SoundManager, ending the effect. Only once a DSP is unattached, it can be reattached somewhere else.


Attaching a DSP to a Sound is very simple.

-- First create a new Sound
local sound = Sound:new("Sounds/BananaPhone.mp3", true);
 
-- Create a new Echo DSP named "echo dsp"
local dsp = DSP:new("echo dsp", orb.ORB_DSP_ECHO);
 
-- Set the DSP's "delay" parameter to 1000
dsp:setParameter("delay", 1000);
 
-- Add the DSP to the Sound
sound:addDSP(dsp);
 
-- Finally, play the Sound, which will now have a booming echo behind it
sound:play();

Orb offers a built-in Registry to manage your DSPs, called the DSPRegistry. Like all Registries, the DSPRegistry allows you to pre-define a set of DSPs to be instantiated when Orb starts. This way, the load time happens up front, and you can recycle your DSPs without paying for them multiple times.

Registries are discussed in the Registries section, don't worry if you're not familiar with them at this point.

DSP Notes

  • A DSP can only be attached to a single Sound (or SoundManager) at a time. You can unattach a DSP by calling DSP:remove(), Sound:removeDSP(dsp), or orb.SoundManager:removeDSP(dsp).
  • You can attach multiple DSPs of the same type to a Sound to increase their effect.
  • The DSP:printInfo() method is an easy way to get the parameter list of your DSP, as well as the min and max values for each parameter. This can sometimes be quicker than going to the API.
  • When you are done with a DSP, you should always call DSP:release() to free up its resources.


Sound Tips

Here are some tips for managing your Sounds:

  • Stream your background music. Streams can be instantiated and played without a performance hit, don't worry about preloading them.
  • Use .mp3 or .ogg files for music. They compress well on disc and are easy to stream.
  • Preload your level's sound effects. Especially for larger files, you should use Orb's Registry system to instantiate before they are needed. That way, your gameplay won't stutter when you play a sound.
  • Use .wav files for sound effects. Wavs are uncompressed and load fairly quickly.
  • Reuse your Sound objects. Every time you create a new Sound object, you must pay the loading penalty. However, if you load a Sound once and store it in a Registry, you can play it as many times as you want with no extra penalty.
  • Release your Sounds when done. Non-streamed Sounds can eat up a lot of memory, don't load more than you need, and release the ones you are done with.
  • Use sounds sparingly. Playing too many sounds at once will leave you with an ear-splitting, garbled mess. Be crafty about when to play sounds, don't over-do it!
Personal tools