mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
* feat(cli): audio answers beats, downbeats, bpm, sections and describe through a provider ladder Beats and downbeats try api, apple-mu, beat-this, then signal; sections try api, apple-mu, then signal; describe tries api, then apple-sound. bpm is read off whichever provider placed the beats. Each provider says whether it can run here and why not, and the ladder warns when it passes one over. The signal rung is a TypeScript tracker: log-band spectral flux, autocorrelation tempo with a log-normal prior, Ellis dynamic-programming beats with a windowed local tempo, a low-band bar phase, and Foote novelty sections snapped to beats. It reports no steady beat when the onsets do not repeat. The api rung posts the audio to HYPERFRAMES_AUDIO_API_URL with HYPERFRAMES_AUDIO_API_KEY and moves each answered time onto the nearest signal beat within a quarter beat, reporting what moved and what did not. beat-this runs Beat This! from a configured Python and local weights and never downloads. apple-sound runs macOS's sound classifier through a Swift helper. apple-mu reports that Music Understanding needs macOS 27. words are no longer accepted: they come from hyperframes transcribe. * docs(cli): audio's beats, downbeats, bpm, sections, describe and their provider ladder * feat(cli): beats tracks the music with the TypeScript beat tracker, without Chrome hyperframes beats and Studio's beat detection now share analyzeMusicFromSignal: band energies, then the spectral-flux tracker in audioRhythm.ts, then each beat's loudness for its strength. The CLI decodes with the same ffmpeg path as hyperframes audio, so beats no longer launches headless Chrome. Studio still decodes in the browser and passes every channel. The old analyzer averaged bpm-detective's tempo with an energy-peak tempo it had only compared after folding both into one octave, and phased a rigid grid from noisy peaks. On 22 real tracks with exact script grids it found the tempo 18% of the time with beat F 0.437; this finds it 91% of the time with F 0.887, and refuses the other two rather than guess. Below a tracker confidence of 0.2 no beats are reported, so ambient or rubato music gets no invented grid and the CLI still writes nothing. bpm-detective, the energy-peak grid, the headless analyzer and its prebuilt bundle are gone. * fix(core): tempo reads 96 BPM off beats on 10 ms frames, not 95.2 The tracker places beats on 10 ms frames, so a 625 ms beat lands 620 or 630 ms apart and the median interval reported 95.2 or 96.8 BPM. Tempo now averages the intervals within 10% of the median: as robust to a skipped or doubled beat, without the frame-sized step. Both hyperframes audio and hyperframes beats read it. * fix(cli): remove redundant audio context comment * fix(core): remove redundant tempo interval comment * fix(ci): record beat analyzer removals and browser evidence * fix(core): keep flat audio novelty in one section * chore: keep PR capture evidence out of the source tree * fix(cli): honor BPM provider pins before analysis
71 lines
2.9 KiB
JavaScript
71 lines
2.9 KiB
JavaScript
import { strict as assert } from "node:assert";
|
|
import { test } from "node:test";
|
|
|
|
import { ALLOWED_DELETIONS, classify, parseBase } from "./check-no-main-deletions.mjs";
|
|
|
|
test("a branch that only adds reports nothing", () => {
|
|
const { deleted, renamed } = classify("A\tpackages/cli/src/new.ts\nM\tpackages/cli/src/old.ts\n");
|
|
assert.deepEqual(deleted, []);
|
|
assert.deepEqual(renamed, []);
|
|
});
|
|
|
|
test("a deleted file is named", () => {
|
|
const { deleted } = classify("D\t.agents/skills/README.md\nA\tsrc/new.ts\n");
|
|
assert.deepEqual(deleted, [".agents/skills/README.md"]);
|
|
});
|
|
|
|
test("a rename is not reported as a deletion", () => {
|
|
// The false alarm worth avoiding: in a name-only diff a rename looks exactly
|
|
// like loss, and a check that cried wolf on every move would be turned off.
|
|
const { deleted, renamed } = classify("R096\tsrc/old/name.ts\tsrc/new/name.ts\n");
|
|
assert.deepEqual(deleted, []);
|
|
assert.deepEqual(renamed, [{ from: "src/old/name.ts", to: "src/new/name.ts" }]);
|
|
});
|
|
|
|
test("deletions and renames are separated in one diff", () => {
|
|
const { deleted, renamed } = classify(
|
|
"D\tdocs/gone.md\nR100\ta.ts\tb.ts\nM\tc.ts\nD\tdocs/also-gone.md\n",
|
|
);
|
|
assert.deepEqual(deleted, ["docs/gone.md", "docs/also-gone.md"]);
|
|
assert.equal(renamed.length, 1);
|
|
});
|
|
|
|
test("the base ref defaults, and an explicit one is honoured", () => {
|
|
assert.equal(parseBase([]), "origin/main");
|
|
assert.equal(parseBase(["--base", "origin/release"]), "origin/release");
|
|
});
|
|
|
|
test("a --base with no value fails rather than silently defaulting", () => {
|
|
// Silently falling back would diff against the wrong ref and report a pass.
|
|
assert.throws(() => parseBase(["--base"]), /needs a ref/);
|
|
assert.throws(() => parseBase(["--base", "--other"]), /needs a ref/);
|
|
});
|
|
|
|
test("every agreed deletion names a path and says why", () => {
|
|
// The guard has no blanket override on purpose: a flag or an env var would
|
|
// be reached for by the branch deleting something by accident. An entry has
|
|
// to be written down, so the removal shows up in review.
|
|
for (const [path, reason] of ALLOWED_DELETIONS) {
|
|
assert.ok(path.length > 0, "an allowed deletion needs a path");
|
|
assert.ok(
|
|
reason && reason.length > 10,
|
|
`${path} needs a reason, got ${JSON.stringify(reason)}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("the allowlist does not silence an unrelated deletion", () => {
|
|
const { deleted } = classify("D\tpackages/cli/src/something-else.ts\n");
|
|
assert.deepEqual(deleted, ["packages/cli/src/something-else.ts"]);
|
|
assert.equal(ALLOWED_DELETIONS.has("packages/cli/src/something-else.ts"), false);
|
|
});
|
|
|
|
test("the Chrome beat analyzer removal is explicitly scoped", () => {
|
|
for (const path of [
|
|
"packages/cli/src/beats/headlessAnalyzer.ts",
|
|
"packages/cli/scripts/build-beat-analyzer.mjs",
|
|
])
|
|
assert.match(ALLOWED_DELETIONS.get(path) ?? "", /TypeScript beat tracker/);
|
|
assert.equal(ALLOWED_DELETIONS.has("packages/core/src/beats/beatDetection.ts"), false);
|
|
});
|