The short version: a generated track almost never loops on its own. To fix that, generate a steady instrumental and work from a lossless copy. Cut a whole number of bars on a downbeat, then crossfade the audio that follows the loop end into the loop start. Check the seam numerically and by ear, ship it as OGG or WAV (not MP3), and give the engine an intro plus a loop point. The rest of this post walks through each step with copy-paste commands.

The question that keeps coming up

On r/SunoAI, a thread titled "Loop music ?" (April 2026) collected the usual answers. One reply sums up the problem well:

"I've been trying to do this with some of my instrumentals, and it has been a struggle. The closest success I've had is adding in the style prompt to have 'outro matches intro for consistent loop' - but even then, the tonal quality and fade in/out can still be jarring." (u/KinkyHuggingJerk, r/SunoAI)

Game developers ask the same thing about music they wrote themselves. A February 2026 r/gamedev thread asks "How do I make music loop seamlessly when it has a slight tail of reverb", and the highest-voted reply is the classic answer: "Use two clips. One that starts the track and one that loops."

Prompting alone won't get you there. You need a small, repeatable editing step, and that's what this post covers.

Why generated tracks don't loop

Four separate things go wrong, and each has its own fix:

  1. Song form. Generators write intros, builds, and endings. If you loop the whole file, the ending crashes into the intro.
  2. The cut is not on the grid. If the loop isn't an exact number of bars long, the beat stumbles every time it wraps.
  3. Discontinuity and tails. If you cut in the middle of a waveform, you get a click. If you cut off a reverb or cymbal tail, the sound collapses audibly at the seam.
  4. The file format adds silence. MP3 encoders and decoders add delay and padding. The LAME technical FAQ describes a 528-sample decoder delay in standard decoders, plus an encoder delay on top. As Wikipedia's gapless playback article puts it, MP3 itself "defines no way to record the amount of delay or padding." Some encoders store it in a LAME tag, but only players that read that tag can remove it. Ogg Vorbis and Opus define the start and end exactly, so the padding is trimmed when the file is decoded.

Step 1: Generate something that wants to loop

Ask for material that stays in one place musically:

  • Instrumental only. Vocals have phrases with beginnings and endings, so they rarely survive looping.
  • A fixed tempo stated as a number ("100 BPM"), plus "steady groove, no tempo changes."
  • No dramatic ending. Leave out words like "finale," "build to a climax," or "fade out." If the tool has an exclusion field, put those there.
  • Longer than you need. A 2–3 minute render gives you several candidate sections to choose from.

I use EasyMusic for this. In its Advanced Mode you choose Instrumental, write the Style brief, list unwanted elements under Exclude styles, and set a suggested duration between 10 and 360 seconds. The EasyMusic Style brief guide covers how to structure that text. Any generator with an instrumental mode will do, though, and so will a bounce from your own DAW project.

One licensing note before you ship anything: check the terms of the tool you used. Many generators, including ours, limit free-tier output to personal use and include commercial licensing only on paid plans. A game you sell is commercial use.

Step 2: Work from a lossless copy

Download WAV if your tool offers it. If you only have MP3, decode it once (ffmpeg -i track.mp3 track.wav) and do every edit on the WAV. Because you're cutting a region from inside the track, the padding at the very start of the MP3 doesn't affect your loop points. It only causes trouble if you ship an MP3 and expect the engine to loop it gaplessly.

If FFmpeg is new to you, HackerNoon's audio handling basics post covers trimming and conversion from the command line.

Step 3: Find the bar grid

In 4/4 time, one bar lasts 240 ÷ BPM seconds:

BPM

1 bar

16 bars

16 bars at 48 kHz

90

2.667 s

42.667 s

2,048,000 samples

100

2.4 s

38.4 s

1,843,200 samples

120

2.0 s

32.0 s

1,536,000 samples

140

1.714 s

27.429 s

1,316,571 samples (rounded)

Work in samples, not seconds, because rounding errors in seconds add up into audible drift. Check the file's sample rate with ffprobe -v error -show_entries stream=sample_rate -of csv=p=0 track.wav.

If you don't know the tempo or where the first downbeat falls, a beat tracker gets you close. This uses librosa's beat_track:

import librosa, numpy as np y, sr = librosa.load("track.wav", sr=None, mono=True) tempo, beats = librosa.beat.beat_track(y=y, sr=sr, units="time") print("tempo estimate:", float(np.atleast_1d(tempo)[0])) print("first beats (s):", np.round(beats[:8], 3)) 

Use the output as a starting point, not as the answer. The beat times are snapped to analysis frames, so they can be off by a few tens of milliseconds. Tracker tempos can also come out at half or double the real value. Confirm the downbeat by ear, and pick a loop start after the intro, where the arrangement has settled.

AI-generated audio isn't locked to a click track, so the tempo can drift slightly. For long loops, measure the loop length directly: find the downbeat 16 bars later and use the actual sample distance between the two downbeats, not the BPM formula.

Step 4: Cut and crossfade with FFmpeg

The approach: take exactly N bars starting at sample S. Then take the next X samples, the audio that naturally follows the loop end, including any reverb tail. Fade that tail out while fading the head of the loop in, using an equal-power curve. When the loop wraps, playback runs from the original audio just before the loop end into the original audio just after it, so the seam is continuous. The script also writes an intro-plus-loop file for engines that support a loop offset. It uses the atrim, afade, and amix filters.

#!/usr/bin/env bash # usage: ./cut.sh input.wav START_SAMPLE LOOP_SAMPLES XFADE_SAMPLES set -euo pipefail IN="$1"; S="$2"; L="$3"; X="$4" E=$((S + L)); T=$((E + X)) ffmpeg -hide_banner -loglevel error -y -i "$IN" -filter_complex " [0:a]asplit=3[a][b][c]; [a]atrim=end_sample=$S,asetpts=PTS-STARTPTS[intro]; [b]atrim=start_sample=$S:end_sample=$E,asetpts=PTS-STARTPTS, afade=t=in:ns=$X:curve=qsin[body]; [c]atrim=start_sample=$E:end_sample=$T,asetpts=PTS-STARTPTS, afade=t=out:ns=$X:curve=qsin[tail]; [body][tail]amix=inputs=2:duration=first:normalize=0[loop]; [loop]asplit=2[l1][l2]; [intro][l1]concat=n=2:v=0:a=1[introloop]" \ -map "[l2]" -c:a pcm_s16le loop.wav \ -map "[introloop]" -c:a pcm_s16le intro_plus_loop.wav 

Example: a 100 BPM track at 48 kHz whose fifth bar starts at 11.6 s, looped for 16 bars with a 50 ms crossfade:

./cut.sh track.wav 556800 1843200 2400 

For the crossfade length, start around 10–30 ms (480–1,440 samples at 48 kHz) for dry, drum-heavy material. For pads or anything with a long reverb, go up to roughly the length of the tail, 0.5–2 s. normalize=0 matters: without it, amix scales both inputs down and you'd hear a dip in level at the seam.

Step 5: Check the seam before you trust it

First, the numbers. This short script compares the jump at the wrap-around with the normal sample-to-sample movement in the file:

import soundfile as sf, numpy as np, sys y, sr = sf.read(sys.argv[1]); y = y.mean(axis=1) if y.ndim > 1 else y jump = abs(y[0] - y[-1]) # step at the wrap-around typical = np.percentile(np.abs(np.diff(y)), 99) # big-but-normal sample steps print(f"{len(y)} samples = {len(y)/sr:.4f} s at {sr} Hz") print(f"seam step {jump:.4f} vs 99th-percentile step {typical:.4f}", "-> OK" if jump <= 2 * typical else "-> likely click, move the cut or lengthen the crossfade") 

The sample count should match LOOP_SAMPLES exactly. In a test on a synthetic 100 BPM file, a plain cut at the same bar line failed the check, and the crossfaded version passed.

Then listen. Render four repeats and play them back on headphones, paying attention to each join:

ffmpeg -stream_loop 3 -i loop.wav -c copy loop_x4.wav 

A good loop sounds like one continuous piece. If you can tell exactly where each repeat starts, go to the troubleshooting list below.

Step 6: Export and set the loop points

Export to Ogg Vorbis: ffmpeg -i intro_plus_loop.wav -c:a libvorbis -q:a 6 intro_plus_loop.ogg. In the same test, the OGG decoded back to the exact sample count. Ship WAV if file size doesn't matter. Don't ship MP3 for anything that loops.

Godot 4. Select the OGG and open the Import tab. Enable Loop and set Loop Offset to the intro length in seconds (556800 / 48000 = 11.6 in the example), then reimport. The Godot audio import docs describe Loop Offset as the point "where audio will start to loop after playback reaches the end." For WAV files, Godot takes Loop Begin and Loop End in samples instead.

Unity. Use two AudioSources: one for an intro clip (export it as its own file with atrim=end_sample=$S) and one with loop enabled for loop.ogg. Start them with AudioSource.PlayScheduled, which the docs call "the preferred way to stitch AudioClips in music players":

double t0 = AudioSettings.dspTime + 0.2; introSource.PlayScheduled(t0); double introLen = (double)introSource.clip.samples / introSource.clip.frequency; loopSource.PlayScheduled(t0 + introLen); // loopSource.loop = true 

Web Audio API. Decode the intro-plus-loop file into an AudioBuffer, then set loopStart and loopEnd in seconds:

const ctx = new AudioContext(); // start after a user gesture const data = await (await fetch("intro_plus_loop.ogg")).arrayBuffer(); const src = ctx.createBufferSource(); src.buffer = await ctx.decodeAudioData(data); src.loop = true; src.loopStart = 556800 / 48000; // 11.6 s src.loopEnd = (556800 + 1843200) / 48000; // 50.0 s src.connect(ctx.destination); src.start(); 

Use seconds rather than buffer indices here, because the browser may resample the decoded audio to the context's sample rate. Format support varies by browser, so test your target browsers. If any of them can't play OGG, ship WAV. For Web Audio basics, see HackerNoon's Getting Started with Web Audio API.

Troubleshooting

  • Click at the seam. Move S (and S + L) a few hundred samples to a nearby zero crossing on the downbeat, or lengthen the crossfade.
  • Rhythmic hiccup every repeat. The loop isn't a whole number of bars. Measure downbeat to downbeat instead of trusting the BPM.
  • Flam or smear on drums at the join. The crossfade is too long for transient material. Shorten it to 10–20 ms.
  • The reverb drops out at the seam. The crossfade is shorter than the tail. Make X longer.
  • A gap in the browser or engine. You're probably shipping MP3. Switch to OGG or WAV.
  • Level jump between intro and loop. Both files came from the same render, so don't normalize them separately.

Limits worth knowing

This method can't fix a section that doesn't repeat musically. If the chords at bar 1 and bar 17 differ, the crossfade will smear two different harmonies together, so pick another section. Short loops also get tiring: 30–60 seconds of material, or two or three variations you rotate, holds up better over a long play session. And the edit can only be as clean as the source. If the generated audio has artifacts, regenerate or fix the section before you cut it.

Wrapping up

Seamless looping is an editing problem, not a prompting problem. Generate steady material, cut whole bars in samples, fold the tail into the head with an equal-power crossfade, check the seam, ship OGG or WAV, and let the engine handle intro and loop points. Save the script, and the next track takes minutes. If you have a looping trick for adaptive or layered game music, I'd like to hear it in the comments.