08 · The grid follows the file
Chapter 07 warped the file onto a rigid grid, and that direction has a price: varispeed shifts the pitch, and avoiding the shift means time-stretch DSP. This chapter pays nothing. Run the conform the other way — make the project's tempo map be the file's beat map — and the audio plays raw at rate 1.0, bit-identical, while the metronome, the bar ruler, and every MIDI clip follow the file's wobble instead. This is the other half of every DAW's warp workflow: Ableton's Set tempo from clip, Logic's adapt project tempo to recording.
It is also this tutorial's first advanced chapter: playback runs through @dawcore/transport, a production Web Audio transport whose TempoMap implements precisely the regimes chapter 01 derived — 'step' is the piecewise-constant rectangle sum, 'linear' is the 'curve' integrates with the trapezoidal rule. The chapters stop deriving and start proving: the tests check that chapter-01 math and the production TempoMap agree at every beat, to
A transport schedules in ticks (chapter 07) against a tempo map (chapter 01). To make the grid follow the file, feed it one tempo event per file beat, holding each segment's BPM as a step:
event n: tick = (n - 1) * PPQN, bpm = segmentBpm(m[n], m[n+1])
The tick is chapter 07's anchor verbatim -- tick 0 is beat 1 -- and the bpm is chapter 05's instantaneous BPM, the per-gap rate the detectors already compute. Nothing new is derived; two existing layers are plugged into each other.
One number-system wrinkle remains, and it is the lead-in again. The transport anchors tick 0 at transport second 0, but the file's beat 1 sits at t_first > 0 in the audio -- and the file may open with PICKUP beats, the tail of a bar that began before the recording. The grid itself is non-negotiable: a DAW's grid is always FULL BARS from tick 0. So the placement must satisfy two constraints at once:
- the first DOWNBEAT lands exactly on a bar boundary (tick = k * beatsPerBar * PPQN), and
- the pickup beats fill the END of the bar before it.
With p pickup beats, the first bar boundary with room for them is
firstDownbeatTick = ceil(p * PPQN / ticksPerBar) * ticksPerBar
firstBeatTick = firstDownbeatTick - p * PPQN
and the file's beat n then lives at firstBeatTick + (n-1) * PPQN -- chapter 07's anchor, shifted whole. The grid before the first beat (the empty lead-in bars the metronome counts through) runs at the first segment's tempo, and the CLIP is placed so the audio's beat-1 sample sounds exactly at firstBeatTick's second: any audio before t_first now PLAYS during the lead-in instead of being trimmed. With no pickup, firstBeatTick = 0 and this degenerates to the plain clip offset: trim t_first, beat 1 at tick 0.
And the bars themselves come from the FILE, not from an assumption. Real tracker output contains irregular bars -- a lone 5-beat bar in the middle of a 4/4 song is common -- so counting "every 4th beat is a downbeat" drifts one beat off at the first irregularity and stays wrong forever after. The declared beatInBar column is authoritative: each run of equal bar lengths becomes one meter entry (5 beats = one bar of 5/4, then back), each entry placed at its downbeat's tick. That is the meter layer of chapter 04, feeding the transport's MeterMap instead of barPositionOf.
grid conformity learn more ↗
That rule is the whole chapter: the production transport's clock, evaluated at beat n's tick, must reproduce the beat tracker's timestamp shifted by where the clip sits -- through chapter-01 math on one side and @dawcore/transport's TempoMap on the other. The tests assert both, with and without pickups -- and assert the meter side too: every declared downbeat tick must satisfy MeterMap.isBarBoundary.
What the conversion actually is
Nothing new. One tempo event per file segment:
| layer | supplies | from chapter |
|---|---|---|
event tick | 07 | |
event bpm | segmentBpm(m[n], m[n+1]) — the instantaneous per-gap BPM | 01 / 05 |
| event semantics | 'step' interpolation — hold until the next event | 01 (regime 2) |
tempoEventsFromMarkers(markers, ppqn) is that conversion in its purest form — events anchored at tick 0 = beat 1, plus clipOffsetSec = markers[0].second to trim so the audio's beat-1 sample plays at transport second 0. gridPlanFromBeats(parsedBeats, ppqn) is the production-shaped version on top of it: it reads the .beats file's beatInBar column, detects pickups, and bar-aligns everything (next section).
The equivalence, proven three ways
The test suite sweeps every quarter-beat of a wobbly fixture through three independent clocks and demands agreement to
- Chapter 01 —
piecewiseConstantMap(markers).beatsToSeconds(β) - Production —
new TempoMap(ppqn)fed our events,ticksToSeconds((β−1)·ppqn) + clipOffset - Reference — this chapter's
gridSecondForBeat, ten lines of pure arithmetic
The same residual check runs live in the demo's table: transport.tickToTime(tick) + clipOffset − beatTime, evaluated against the actual transport instance that is playing, shows 0.00e+0 down the column while conformed.
Full bars, pickups, and the lead-in
The recurring number-system lesson closes the loop here, under one non-negotiable constraint: a DAW's grid is always full bars — the first downbeat must land on a bar boundary. With
so the pickup fills the end of the bar before the first downbeat, the empty lead-in bars tick at the first segment's tempo, and the clip is placed so the audio's beat-1 sample sounds exactly at firstBeatTick's second — meaning the file's own lead-in audio plays during the count-in bars instead of being trimmed. With no pickup this degenerates to the plain clip offset (trim gridPlanFromBeats implements all of it, and the test suite checks the conformity rule in both shapes through the production TempoMap.
See and hear it
Open the standalone demo ↗ — the UI is a full editor — a bar ruler, grid, waveform, and playhead drawn over a production Web Audio transport. Load a .beats file (six bundled samples: synthetic wobbly and pickup fixtures, real tracker output for otherside — as tracked and repaired per the chapter 06 pipeline — scar tissue, and the genuinely mixed-meter bastard, 73 meter regions) and optionally its audio. The file is scheduled once and never touched; the conform toggle only swaps the tempo map. Rigid: the bar lines march evenly and the waveform's beats drift across them while the metronome fights the music. Conformed: the grid bends to the waveform and every beat sits on a bar line. The signal is identical both ways — compare with chapter 07's demo, where locking the beats changed the sound.
What this chapter does not cover
Meter changes. The grid here is fixed 4/4; mapping a file whose bar length changes mid-way onto the transport's MeterMap (via meterMapFromBeats from the shared parser) is the same plug-two-layers-together move and makes a good exercise. And as ever: no DSP — this chapter's entire point is that none was needed.