Next.js app for:
- generating music loops and SFX with ElevenLabs,
- processing local WAVs through the same trim/crossfade loop pipeline,
- A/B testing loop smoothness in-browser.
- Node.js 20+
ELEVENLABS_API_KEYin.env.localffmpegavailable onPATH(required for music mode MP3 -> PCM decode)
npm run devOpen http://localhost:3000.
POST /api/samples/generatemode: "music": calls ElevenLabs music API (mp3_44100_128), decodes MP3 to PCM, trims/processes, returns WAV.mode: "sfx": calls ElevenLabs sound-generation API and returns MP3.
POST /api/samples/process- accepts multipart WAV upload and runs local trim/crossfade loop processing.
All loop math is centralized in lib/audio/pcm.ts:
computeBarFrameCount: target bar-aligned frame count.computeFadeFrameCount: crossfade window size in frames.trimToFrameCount: deterministic frame trimming.prepareLoopPcm: single orchestration function used by both API routes.
prepareLoopPcm behavior:
- Compute
targetFramesfrom(bars, bpm, sampleRate). - If crossfade enabled, trim to
targetFrames + fadeFrames(pre-crossfade window). - Fold tail onto head with equal-power gains.
- Return exactly
targetFrameswhen sufficient input exists.
This prevents the old drift where each loop iteration was shorter by fadeFrames.
For 4/4 loops, expected duration is:
duration_seconds = bars * 4 * (60 / bpm)
Example:
- 24 bars @ 120 BPM = 48.000 s
- 24 bars @ 90 BPM = 64.000 s
If you import a 48s loop and your DAW shows 64s at 120 BPM, the clip is being time-stretched/warped on import (or interpreted with a different source BPM).
Quick verification:
ffprobe -v error -show_entries stream=sample_rate,channels,duration \
-of default=noprint_wrappers=1:nokey=0 /path/to/file.wavGeneration responses also include timing headers:
X-Loop-Target-BPMX-Loop-Target-BarsX-Loop-Target-Duration-SecondsX-Loop-BPM-Source
npm run lint
npx tsc --noEmit
npx next build --webpack