Skip to content

beats keeps the old grid after replacing a track with beat-free audio #7461

Open

Description

@AksharP5

Describe the bug

After replacing music at the same project path with audio that has no steady beat, rerunning hyperframes beats exits successfully and reports count: 0, but leaves the old nonempty beat file on disk. Studio loads that saved grid in preference to detected beats, so reopening the project can still show and snap to beats from the previous track.

Reproduced on upstream main 7bf19ab5f9d1690d596334822f251dece3921e1a. This is about regenerating an existing grid after changing the source, rather than a track that has never had a grid.

Minimal reproduction

Use a source checkout at that revision, Bun 1.4.4, Node 22 or newer, Python 3, and FFmpeg/FFprobe on PATH:

git checkout 7bf19ab5f9d1690d596334822f251dece3921e1a
PUPPETEER_SKIP_DOWNLOAD=1 bun install --frozen-lockfile
bun run build
python3 repro.py "$PWD"

Save this as repro.py. It creates only synthetic audio in a fresh temporary project:

import json, math, pathlib, struct, subprocess, sys, tempfile, wave, os
repo = pathlib.Path(sys.argv[1]).resolve()
project = pathlib.Path(tempfile.mkdtemp(prefix='hf-beats-replaced-'))
(project / 'index.html').write_text('<div data-composition-id="main"><audio id="music" src="song.wav"></audio></div>\n')
rate = 22050
samples = [0.0] * (12 * rate)
seed = 7
for beat in range(24):
    start = round((0.2 + beat * 0.5) * rate)
    for i in range(round(0.05 * rate)):
        if start + i >= len(samples): break
        seed = (seed * 1103515245 + 12345) % 2147483648
        samples[start + i] += 0.6 * (seed / 2**30 - 1) * math.exp(-i / rate / 0.015)
def save(values):
    with wave.open(str(project / 'song.wav'), 'wb') as f:
        f.setparams((1, 2, rate, 0, 'NONE', 'not compressed'))
        f.writeframes(b''.join(struct.pack('<h', round(max(-1, min(1, x)) * 32767)) for x in values))
def run():
    p = subprocess.run(['bun', str(repo / 'packages/cli/src/cli.ts')] + ['beats', str(project), '--json'], text=True, capture_output=True,
        env={**os.environ, 'HYPERFRAMES_NO_TELEMETRY': '1', 'HYPERFRAMES_NO_UPDATE_CHECK': '1'})
    return {'exit': p.returncode, 'stdout': p.stdout, 'stderr': p.stderr}
save(samples)
first = run()
grid = project / 'beats/song.wav.json'
initial = len(json.loads(grid.read_text())['beats']) if grid.exists() else None
save([0.0] * len(samples))
second = run()
print(json.dumps({'project': str(project), 'first': first, 'initialSavedBeats': initial, 'second': second,
    'savedFileAfterReanalysis': grid.exists(), 'savedBeatsAfterReanalysis': len(json.loads(grid.read_text())['beats']) if grid.exists() else None}, indent=2))

Steps to reproduce

The script generates a 12-second 120 BPM click track, runs beats, replaces the same WAV with 12 seconds of silence, then runs beats again and reads the persisted file.

Expected behavior

Successful reanalysis with zero beats leaves no saved grid for that source. An unsuccessful decode preserves the old grid, since it has not established a new result.

Actual behavior

The first run saves 24 beats at 120 BPM. The second run exits 0 and reports { "ok": true, "track": "song.wav", "file": null, "count": 0, "bpm": null }, but savedFileAfterReanalysis is true and savedBeatsAfterReanalysis is still 24.

Environment

Linux arm64, Node v26.13.0, Bun 1.4.4, FFmpeg/FFprobe 7.0.2-static. The command decodes locally; no browser, transcription model, account or private media is needed.

Additional context

The zero-beat return in packages/cli/src/commands/beats.ts happens before the persisted output is updated. packages/studio/src/hooks/useMusicBeatAnalysis.ts gives an existing nonempty saved file precedence. #7173 makes an empty analysis a successful result, but does not cover regeneration after replacing a previously analyzed track. Public issue, PR, review-comment and Discussion searches found no report of this regeneration case.

The CLI/file sequence was executed through the source CLI on the baseline and patch. The built CLI was also exercised with the patch. Studio's saved-grid precedence was traced in source; a live Studio UI was not exercised.

Activity

  1. added
    bugSomething isn't working
    triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.
    difficulty/easyLocalized change with a known approach, limited risk, and straightforward verification.
    on Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdifficulty/easyLocalized change with a known approach, limited risk, and straightforward verification.triage/readyAccepted scope, current evidence, acceptance criteria, and validation path are recorded.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions