Commit d17228a1 by PLN (Algolia)

feat(bounds): a `noise` mark, because a defect between two tracks belonged to neither

PLN, auditioning CosmicFest: "at 22:42 its end of reose_rouge, theres a loud blip
noise, at 22:44.9 its the start of rose rouge, could we cut noise and blend
better?"

The lab could only ever answer one question — where does the next track start —
so garbage living in the 2.9 s BETWEEN two tracks had nowhere to go. It survived
as prose in a comment box, which no downstream tool reads.

`n` marks the playhead as noise and deliberately leaves the cut call untouched: a
blip between two tracks is not an opinion about where either begins, and forcing
it through the cut field would corrupt the one number the mastering chain trusts.
`N` clears them, because a mis-tapped defect marker you cannot remove is worse
than no marker at all.

They render in warm orange, distinct from every existing mark colour: those are
all CANDIDATES for a cut, and this one is the opposite — a place to avoid.

Exported from both the decided and skipped blocks, so a seam PLN rejects can still
carry "and there is garbage at 22:42". That combination is exactly what happened
here and exactly what the old export could not represent.
parent 7b74d8b3
......@@ -37,7 +37,14 @@ interface Boundary {
}
interface BoundsDoc { gig: string; title: string; pad: number; boundaries: Boundary[] }
// `noise` came from PLN auditioning CosmicFest: "at 22:42 its end of reose_rouge,
// theres a loud blip noise, at 22:44.9 its the start of rose rouge, could we cut
// noise and blend better?" — a defect in the 2.9 s BETWEEN two tracks, belonging
// to neither. Until now the lab could only answer "where does the next track
// start", so a transition carrying garbage had nowhere to be recorded, and the
// note survived only as prose in a comment box.
const MARK_COLOR: Record<string, string> = {
noise: 'rgba(255, 96, 64, 0.95)', // a defect, not a candidate — warm and loud
takeover: 'rgba(217, 0, 255, 0.9)', // brand magenta — the one to try first
mid: 'rgba(120, 170, 255, 0.75)',
onset: 'rgba(120, 170, 255, 0.45)',
......@@ -46,7 +53,7 @@ const MARK_COLOR: Record<string, string> = {
nominal: 'rgba(255, 255, 255, 0.35)',
}
type Call = { t: number | null; comment: string; source: string }
type Call = { t: number | null; comment: string; source: string; noise?: number[] }
export function BoundaryLab({ gig }: { gig: string }) {
const [doc, setDoc] = useState<BoundsDoc | null>(null)
......@@ -75,13 +82,16 @@ export function BoundaryLab({ gig }: { gig: string }) {
const markers = useMemo(() => {
if (!b) return []
const all = [...b.marks, { key: 'nominal', label: 'nominal', t: b.nominal }]
const mine = (calls[b.track]?.noise ?? []).map((t, n) => ({
key: 'noise', label: `noise ${n + 1}`, t,
}))
const all = [...b.marks, ...mine, { key: 'nominal', label: 'nominal', t: b.nominal }]
return all.map((m) => ({
t: toClip(m.t),
label: m.label,
color: MARK_COLOR[m.key] ?? MARK_COLOR.nominal,
}))
}, [b, toClip])
}, [b, toClip, calls])
const jump = useCallback((tMaster: number, lead = 4) => {
// land a few seconds BEFORE the candidate: a cut is judged by what leads
......@@ -101,6 +111,21 @@ export function BoundaryLab({ gig }: { gig: string }) {
if (e.key === 'j') setI((v) => Math.min(doc.boundaries.length - 1, v + 1))
else if (e.key === 'k') setI((v) => Math.max(0, v - 1))
else if (e.key === 'Enter' && b) setCall(toMaster(now), 'playhead')
// `n` marks garbage AT the playhead and leaves the cut call untouched: a
// blip between two tracks is not an opinion about where either begins.
else if (e.key === 'n' && b) {
const t = Number(toMaster(now).toFixed(2))
setCalls((c) => {
const cur = c[b.track] ?? { t: null, source: 'noise', comment: '' }
const noise = [...(cur.noise ?? []), t].sort((x, y) => x - y)
return { ...c, [b.track]: { ...cur, noise } }
})
}
// `N` clears them, because a mis-tapped defect marker you cannot remove is
// worse than none.
else if (e.key === 'N' && b) {
setCalls((c) => (c[b.track] ? { ...c, [b.track]: { ...c[b.track], noise: [] } } : c))
}
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
......@@ -120,7 +145,10 @@ export function BoundaryLab({ gig }: { gig: string }) {
decided: Object.fromEntries(
Object.entries(calls)
.filter(([, c]) => c.t !== null)
.map(([k, c]) => [k, { start: Number(c.t!.toFixed(2)), source: c.source, note: c.comment }]),
.map(([k, c]) => [k, {
start: Number(c.t!.toFixed(2)), source: c.source, note: c.comment,
...(c.noise?.length ? { noise: c.noise } : {}),
}]),
),
// Skipped boundaries keep their NOTE. They used to export as bare integers,
// which silently threw away the most valuable thing in the file: PLN skipped
......@@ -134,7 +162,10 @@ export function BoundaryLab({ gig }: { gig: string }) {
skipped: Object.fromEntries(
Object.entries(calls)
.filter(([, c]) => c.t === null)
.map(([k, c]) => [k, { source: c.source, note: c.comment }]),
.map(([k, c]) => [k, {
source: c.source, note: c.comment,
...(c.noise?.length ? { noise: c.noise } : {}),
}]),
),
}
const url = URL.createObjectURL(new Blob([JSON.stringify(out, null, 1)], { type: 'application/json' }))
......@@ -283,7 +314,7 @@ export function BoundaryLab({ gig }: { gig: string }) {
</section>
<p className="mt-6 font-mono text-[10px] text-ink-faint/70">
j/k next-prev · space play/pause · Enter = take the playhead · drag to loop a span ·
n = mark noise (N clears) · j/k next-prev · space play/pause · Enter = take the playhead · drag to loop a span ·
clip is ±{doc.pad}s around the current cut, all exported times are master time
</p>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment