How to loop game music without gaps
To loop game music without a gap, use a WAV or Ogg Vorbis file instead of MP3, make sure the file is exactly a whole number of bars long, and loop it with your engine's loop setting. MP3 adds silence at the start and end, which is what you hear as a gap.
Last updated:
Example tracks
These are real catalog tracks. The credit line under each row is the one Uncommon Ear generates.
-
HoliznaCC0 Slow. Suggested for loop.
0:59 -
josefpres Mid-tempo chiptune. Suggested for loop, retro game.
0:45 -
isaiah658 Mid-tempo ambient. Suggested for loop.
0:24 -
nene Fast metal. Suggested for loop.
0:39 -
Kevin MacLeod Slow. Suggested for loop.
0:42
Steps
Start from a loop, not a song
Choose a track that is made to repeat. Uncommon Ear flags loops in the catalog, and you can filter for them. Songs with a distinct intro and ending rarely loop well.
Avoid MP3 for the loop file
An MP3 encoder adds a delay at the start and pads the end, and decoders add a delay of their own. The LAME tag stores the padding, but a player has to read it to remove it. WAV has no such padding, and Ogg Vorbis stores positions in samples. If you have an MP3, convert the original WAV, not the MP3.
Cut the file on a bar line
A loop is seamless when its length is a whole number of bars. If the track lists a tempo, the length in seconds is bars times beats per bar times 60, divided by the tempo. Sixteen bars of 4/4 at 120 bpm is 32 seconds. This command keeps the first 32 seconds, so check the start also lands on the first beat.
Cut the file on a bar lineffmpeg -i music.wav -t 32 loop.wavLoop in web audio
Use an AudioBufferSourceNode, which suits sounds that need accurate timing and fit in memory. Decode the file once, set loop to true, and set loopStart and loopEnd in seconds.
Loop in web audioconst context = new AudioContext(); async function playLoop(url) { const response = await fetch(url); const buffer = await context.decodeAudioData(await response.arrayBuffer()); const source = context.createBufferSource(); source.buffer = buffer; source.loop = true; source.loopStart = 0; source.loopEnd = buffer.duration; source.connect(context.destination); source.start(); return source; } document.querySelector("#play").addEventListener("click", () => playLoop("loop.wav"));Loop in Godot
For Ogg Vorbis, turn on Loop in the Import dock, or set loop in a script. Loop Offset is in seconds, and 0 loops the whole file. For WAV, set Edit > Loop Mode to Forward in the Import dock. Loop Begin and Loop End are in samples, and a Loop End of -1 uses the end of the file.
Loop in Godot@onready var music_player: AudioStreamPlayer = $MusicPlayer func _ready() -> void: var music := load("res://audio/music/loop.ogg") as AudioStreamOggVorbis music.loop = true music.loop_offset = 0.0 music_player.stream = music music_player.play()Loop in Unity
Set Loop on the AudioSource, and import the clip as PCM or Vorbis, not MP3. If you still hear a gap in a build, schedule the repeats yourself. PlayScheduled starts a clip at an exact time on the AudioSettings.dspTime timeline. This script alternates two sources and schedules each repeat about a second ahead. Use a clip longer than two seconds.
Loop in Unityusing UnityEngine; public class ScheduledLoop : MonoBehaviour { [SerializeField] private AudioSource[] sources = new AudioSource[2]; [SerializeField] private AudioClip clip; private double nextStartTime; private int nextSource; private void Start() { nextStartTime = AudioSettings.dspTime + 0.2; } private void Update() { if (AudioSettings.dspTime < nextStartTime - 1.0) return; AudioSource source = sources[nextSource]; source.clip = clip; source.PlayScheduled(nextStartTime); nextStartTime += (double)clip.samples / clip.frequency; nextSource = 1 - nextSource; } }Listen at the join
Play the loop for a few minutes with headphones and listen at the join. If you hear a click, check that the file starts and ends at the same point in the waveform, and cut again.
Common mistakes
- Looping an MP3. The padding at each end becomes a pause or a click at every repeat.
- Cutting the loop at a round number of seconds instead of on a bar line.
- Mixing up units. Godot's Ogg Vorbis Loop Offset is in seconds; WAV Loop Begin and Loop End are in samples.
- Ending a loop on a reverb or delay tail that rings past the cut. The tail is chopped at the join unless it is mixed into the start.
- Converting to MP3 after you cut the loop. Cut and loop from the lossless file.
- Skipping the license check. CC0 needs no credit; CC BY needs its credit line in your game credits.