This guide discusses how to use the Corona audio library to play sound effects and streaming audio.
The Corona audio system gives you access to advanced OpenAL features. It features 32 distinct channels on which you can play sound effects or streaming audio files. The system offers a unique volume setting for each channel plus a master volume level for all channels.
The audio system is a “best effort” system. Requested sounds will play as quickly as possible, but there is no guarantee that they’ll start or end at exact times. If you are streaming a song and there’s a buffer underrun due to CPU stress, the audio system will continue playing as soon as it can, but the song may finish playing later than anticipated.
Please read further to learn about audio API functions and methods, acceptable file formats, and performance tips.
The audio system offers two functions for loading: audio.loadSound() and audio.loadStream(). There is no mandated distinction between the file you provide to either function, but the proper method should be used according to these general guidelines:
audio.loadSound()
— loads an entire sound into memory. It should be used for shorter audio files that may be used repeatedly throughout the app.
audio.loadStream()
— reads in small chunks of an audio file over the course of its duration. It should be used for longer audio tracks like background music. Note that streamed files may have a slightly higher latency cost and CPU cost than files loaded with audio.loadSound()
.
All of the audio APIs can be applied to files loaded via either method. However, in some cases, the differences are not entirely transparent.
The Corona audio system supports different formats depending on the environment/platform:
Platform | .wav | .mp3 | .ogg | .aac | .caf | .aif |
---|---|---|---|---|---|---|
iOS | ✓ | ✓ | ✓ | ✓ | ✓ | |
Android | ✓ | ✓ | ✓ | |||
macOS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Windows Desktop | ✓ | ✓ | ✓ | ✓ |
Note that Windows Desktop only supports .aac if the system has the DirectX Media Foundation installed.
Cross-platform .wav
files must be 16-bit uncompressed.
For highly-compressed formats such as .mp3
and .aac
(.mp4
), the latter is a better option. .aac
is the official successor to .mp3
by the MPEG Group. .mp3
had various patent and royalty issues that expired in 2017, but when .aac
was ratified, it was agreed that there would be no royalties required for distribution.
Ogg Vorbis (.ogg
) is royalty-free and
Be aware that certain formats — particularly highly-compressed, lossy formats like .mp3
, .aac
, and .ogg
— can pad/remove samples at the end of an audio sample and potentially break a .wav
and make sure that your starting and ending points are clean.
Depending on the format — .mp3
in particular — the audio.getDuration() call may return inaccurate information, particularly for files loaded via audio.loadStream()
.
As stated in the overview, there are 32 audio channels available. Each sound effect or streaming audio track must play on a distinct channel. If you don’t explicitly set the channel on which you want to play audio, Corona will attempt to locate a free channel on which to play the audio file.
In some cases, it’s useful to reserve certain channels for different purposes. For example, you may want to set different volume levels for music, speech, and sound effects. In this case, you can reserve a few channels toward the lower end of the range and effectively prevent x
via audio.reserveChannels( x )
Corona also provides various functions to check the status of a channel. These include audio.isChannelActive(), audio.isChannelPlaying(), and audio.isChannelPaused().
The Corona audio system has both a “master” volume level and a volume level for each channel. Both the master volume and individual channel volumes can be controlled by passing a decimal representation of 0%-100% to the audio.setVolume() API. Note that the master volume is not necessarily the same as the device’s internal volume, but all volume levels are scaled proportionally to that internal volume.
If you wish to set the master volume for all channels, simply pass a volume level without a channel specification as follows:
audio.setVolume( 0.5 ) --set the master volume to 50%
Or, to set the volume on a specific channel, pass an optional table with a channel
key as follows:
audio.setVolume( 0.5, { channel=1 } ) --set the volume level of channel 1 to 50%
Finally, the following functions are provided to set the minimum and maximum volume:
audio.setMinVolume( volume )
— clamps the minimum volume to a value. Any volumes that fall below the value will be played at the minimum volume level.
audio.setMaxVolume( volume )
— clamps the maximum volume to a value. Any volumes that exceed the value will be played at the maximum volume level.
The master volume proportionally controls the volume of all other channels. If you set the master volume to 40% and later set the volume of channel 2 to 1.0
, channel 2 will play at 100% of the master volume (40%). Similarly, if you set the master volume to 50% and then set the volume of channel 2 to 0.2
, channel 2 will play at 20% of the master volume (50%) for an effective volume of 10%.
When you set the volume, that level is persistent until you set it again. If you change the volume of channel 4, that channel will remain at that volume level even after sound finishes playing on that channel.
If you’re playing audio on
When you load an audio file using either audio.loadSound() or audio.loadStream(), Corona returns a handle for that audio file. This handle can then be used to reference and play the audio file, assuming the handle remains in memory.
The following example shows how to load a basic sound file into a handle soundEffect
and play it immediately.
local soundEffect = audio.loadSound( "chime.wav" ) audio.play( soundEffect )
Note that audio handles are only used when you need to play, dispose, seek/rewind, or check the duration of an audio file. Once an audio file is playing, you cannot pause, stop, or control the volume of it by referencing its handle. Instead, you must reference the channel on which it’s playing, since the same file (handle) may be playing on multiple channels.
As stated earlier, the audio.loadSound()
function loads an entire sound into memory. It should be used for shorter audio files that may be used repeatedly throughout the app. While you can load each sound into a local handle, it’s often better to organize your sounds inside a Lua table using named keys for the handles:
local soundTable = { chimeSound = audio.loadSound( "chime.wav" ), bellSound = audio.loadSound( "bell.wav" ), buzzSound = audio.loadSound( "buzz.aac" ), clickSound = audio.loadSound( "click.aac" ) }
Then, with this structure in place, playing a sound effect is simple:
audio.play( soundTable["chimeSound"] )
Audio files loaded via the audio.loadSound()
function should almost always be preloaded at the launch of your app or the start of a new level/scene. Loading sounds during a time-critical period may cause the app to skip while the system loads the audio file into memory.
Since the audio.loadStream()
function reads in small chunks of an audio file over the course of its duration, audio loaded with this method does not need to be loaded in advance. Simply load and play the file as follows:
local backgroundMusic = audio.loadStream( "backgroundMusic.wav" ) audio.play( backgroundMusic )
The audio system offers a completion event for audio.play()
which can be used to perform an action related to the sound. For example, if you have a ticking time bomb object, you may use the onComplete
callback to trigger an animated bomb explosion and a separate explosion sound.
To listen for the completion event of a sound, just specify the listener function in the onComplete
value of the options table:
local function bombExplode( event ) --bomb has exploded! end local tickSound = audio.loadSound( "tick.wav" ) audio.play( tickSound, { onComplete=bombExplode } )
In turn, when the listener function is called, the following event
parameters are available:
event.name
— string with the value "soundCompletion"
.event.channel
— the channel number associated with the completed sound.event.handle
— the audio handle associated with the completed sound.event.completed
— boolean value which will be true
if the audio file completed by itself or false
if it was prematurely stopped.In addition to the volume control methods described above, Corona provides the following audio control functions:
Function | Description |
---|---|
audio.pause() | Pauses playback on one playing channel or all playing channels. |
audio.resume() | Resumes playback on one paused channel or all paused channels. |
audio.rewind() | Rewinds audio to the beginning position on one active channel/handle or all active channels. |
audio.seek() | Seeks to a time position on an active channel or specific audio handle. |
audio.stop() | Stops playback on one channel or all channels and clears the channel(s) so they can be played on again. |
audio.stopWithDelay() | Stops playback on one channel or all channels after a specified amount of time. |
audio.fade() | Fades one channel or all channels to a specified volume over the specified amount of time. |
audio.fadeOut() | Fades one channel or all channels to the minimum volume over the specified amount of time. |
Disposing audio files is essential if you’re finished using them. This frees the memory allocated to the audio file. To dispose of an audio file, call the audio.dispose() function and pass the handle of the audio file as its sole parameter.
audio.dispose( audioHandle )
If you’ve organized your audio handles into a table as illustrated in Playing Audio, a simple pairs
loop can be used to dispose of each audio file:
audio.stop() for s,v in pairs( soundTable ) do audio.dispose( soundTable[s] ) soundTable[s] = nil end
The audio should not be active (playing/paused) on any channel when you try to dispose it. Consider calling audio.stop()
before audio.dispose()
to ensure that the channel is freed. Also, do not attempt to use the handle after the audio file has been disposed and the memory freed.
In the project config.lua
file, you may specify the audioPlayFrequency
parameter. This optimization hint prompts OpenAL to mix/play at a specific frequency.
application = { content = { width = 320, height = 480, scale = "letterbox", audioPlayFrequency = 22050 }, }
For best results, set this value no higher than you actually need. If you require high quality, set the value to 44100
. Otherwise, 22050
should suffice. Note that this value is only a “hint” and the underlying audio system is free to ignore it, although iOS seems to consistently respect it. In addition, for best performance, encode all audio files at the same frequency as this setting. For example, if you set this to 22050
, your audio files should be encoded at 22050 Hz.
Supported values are 11025
, 22050
, and 44100
. Other values are not tested.
Mono sounds take half the amount of memory as stereo sounds and, on mobile devices, the difference is often negligible unless the user is listening through headphones.
For fastest loading/decoding time, use linear .wav
files use this format but you may need to confirm this depending on the source of the audio files.