Skip to content

Shape drum and impact peaks at the source so the limiter stops pumping the mix - #3

Merged
MaxGhenis merged 3 commits into
mainfrom
drum-bus
Sep 30, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
drum-bus

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

The master's −2 dBTP true-peak limiter was doing the gain staging for the drums and impacts, so the whole mix pumped with it. This PR shapes those peaks where they are made, and the limiter now only catches strays. Hold for Max's by-ear OK before merging.

The finding, reproduced and corrected

Measured on v1.1.0 (c592471) with the method in the brief: 10 ms RMS of the mastered mix against hp(bed + sweeps, 30), relative to the median gain. 975572b gives the same rounded figures: its three whoosh gains differ by ≤ 0.015 dB, and its riser and swell by 0.30 and 0.52 dB.

  • 13.4% of windows are cut more than 1 dB, and 4.3% more than 3 dB. Reproduced exactly.
  • The 16.2 dB "worst cut" is the 30 ms fade-in (window at 0.005 s), not the limiter. With the edge windows excluded: 13.2%, 4.2%, worst 7.1 dB.
  • The limiter's own worst cut is 7.55 dB, at 14.00 s (the drop). This comes from its exact gain envelope, recovered by re-running the master; the re-run matches the build sample for sample.

Where the cuts came from, as whole-mix true peak past the ceiling:

  • Backbeats, where kick and clap land together: 4.3–6.5 dB. The drum bus on its own reaches 4.5–5.6; the music adds little.
  • Kicks alone, away from cues and impact tails: up to about 1.6 dB.
  • The hit and drop: the complete impact (boom, chord and noise) goes 3.6 and 4.6 dB past on its own, and 6.4 and 7.6 dB together with the downbeat kick. The boom waveforms alone stay under the ceiling.
  • The land: 1.3 dB on its own.

Audible? Likely, yes

  • Backbeats. On every backbeat the limiter took the whole mix down with the drums: 5.1 dB on average and up to 6.5. It stayed more than 1 dB down for about 200 ms. That gain change is the largest limiter cut from 5 ms before each backbeat to 200 ms after it, minus the smallest cut 300–100 ms before it. The kick's intended sidechain duck on the music bus comes on top of this.
  • Hit and drop. After them the mix spent 0.66 and 0.75 s more than 1 dB down, in bursts. The limiter re-triggered on every boom cycle, so the gain rippled 2.1 and 3.3 dB within 30 ms spans.
  • Cues. Sixteen felt-mallet cues start in a dip deeper than 0.5 dB, and nine in one deeper than 1 dB. The third statistic lands on a backbeat and loses 6.1 dB where its siblings lose 0.6 and 1.8.

The fix (tools/soundtrack.py)

  • soft_clip on the kick-and-clap bus. It is a tanh shoulder, C1 at the knee, run at 4×. It finds peaks through the master's own 30 Hz high-pass and takes off only the excess.
    • A signal that never reaches the knee comes back bit for bit, and so does every sample more than 10 samples (the resampling filter's reach) from a peak.
    • The excess comes off the dry signal, which the master's high-pass then reshapes, so the score's lone kick lands 1.1 dB past DRUM_CEIL. It does not land under it.
    • 4× oversampling reduces aliasing; it does not remove it.
    • The hats go around the clip. They were never the problem, and the open hat's right channel was grazing the knee.
  • Backbeats: kick and clap play at 2/3. The limiter held their first 100 ms down by 3.6 dB on average. Kicks under the plink ripple (15–17 s) play at 0.75; the arrangement already rests the clap and open hat there.
  • Hit and drop: a smooth room. They and the music dip to 0.55 and 0.5 on a 3 ms ramp, then recover with a 0.6 s time constant: more than 1 dB down for 0.85–0.9 s. The drums recover with a 1.5 s time constant: more than 1 dB down from 12.0 s to about 16.3 s, as the two rooms overlap. Their boom takes the downbeat kick. All of it is derived from the events; no timestamps are added.
  • The land plays 1.9 dB down.
  • Unchanged: the master's code (one stale comment in it is corrected), SWEEP_UNDER and level_sweeps, and the random draws. Astra logged all 1,139 calls and they match main; Opus confirmed this by reading. With DRUM_CEIL=10, BACKBEAT=1, IMPACT={}, LAND=1 and the ripple kick's 0.75 put back to 1.0, the build reproduces main to within 1e-12.

This design was chosen after a four-way fan-out: static trims, a drum-bus soft clip, a drum-bus compressor and a hybrid. Each was re-measured independently on the US and UK event logs. The compressor moved the limiting onto the drums (20:1, mean error −17 dB). The two clip variants waveshaped the land and the drop's boom hard. This design keeps the clean drum clip and replaces impact clipping with a smooth room.

Before → after

Astra reproduced the limiter figures by executing both builds. Opus reviewed by reading only.

US before US after UK after
10 ms windows cut >1 dB (exact envelope) 14.6% 0.57% 0.53%
… cut >3 dB 5.0% 0 0
worst limiter cut 7.55 dB 1.41 dB 2.27 dB
brief's RMS method, edges excluded: >1 dB / >3 dB / worst 13.2% / 4.2% / 7.1 dB 0.4% / 0 / 1.4 dB 0.4% / 0 / 2.2 dB
whole-mix dip at a backbeat, mean (method above) 5.14 dB 0.17 dB 0.26 dB
worst felt-mallet cue cut in its first 20 ms (exact envelope) 6.11 dB 0.49 dB 0.78 dB
WAV: integrated loudness, true peak (4× meter) −14.2 LUFS, −2.00 dBTP −14.00, −2.00 −14.00, −2.00
WAV true peak at 16× −1.87 dBTP −1.84 −1.94
AAC (check_outputs.py) −14.2 LUFS, −1.5 dBTP −14.0, −1.8 —

The 16× row is the master's own 4× meter under-reading intersample peaks. It was already there before this PR (0.035 dB worse after), and the master is out of scope. The README now says "as its 4× meter reads it".

Heard balance

These are K-weighted loudness through the master's gain envelope, in LU against v1.1.0 (US / UK):

  • clap (drum bus over the 100 ms after each backbeat) minus the rest of the mix over the 400 ms before: −0.45 / −0.58
  • kicks, the same way, excluding the two downbeats the booms now take: −0.80 / −0.82
  • drums vs the rest over 8.5–26 s: −0.61 / −0.62
  • the hit (sfx bus, first 400 ms): +0.18 / +0.39
  • the drop, the same way: −0.26 / −0.26

Later in their tails the hit and drop run louder than in v1.1.0, where the limiter kept holding them down. Across 400 ms windows up to 2.5 s after each lands, the largest change is +1.6 LU, 1.5–1.9 s after the hit.

Drum distortion

Error-to-signal over the first 100 ms of the 27 drum hits, gain-matched against v1.1.0's dry drums:

  • this PR: −26.1 dB mean, −19.2 dB worst
  • v1.1.0's limiter gain applied to the same drums: −25.7 dB mean, −17.3 dB worst. It also moved everything else with them.

Listen for (Max's call)

  • Greatly reduced: the whole mix's dip with the limiter on beats 2 and 4, from 5.1 dB to 0.2–0.3 dB on average. Residual limiting reaches 1.4 dB (US) and 2.3 dB (UK) at most. The kick's intended sidechain duck stays. The hit and drop no longer ripple the mix.
  • The 12–16 s climax is less prominent. It now sits 1.4 LU over the mean of its neighbouring four-second sections, down from 2.5. Every other section is about 0.5 dB louder, because the normalization rose from 5.99 to 6.50 dB. The old prominence came from fast limiting that let the gain bounce back between boom cycles. Of about 20 smooth-envelope settings tried, none got it back without the limiter working 3 dB or more again. Loudness range (ffmpeg ebur128 on the AAC) goes from 6.4 to 5.4 LU.
  • Downbeats under the impacts: the hit and drop now land without a kick, and the drums stay more than 1 dB down from 12.0 s to about 16.3 s.
  • The swell now sits 5.9 LU under the music after the master, where it was 4.8, so it is a little less forward.
  • Not held to the cue bound: a typing click at 7.03 s, on a kick, loses 1.2 dB, as it did before.

Tests (tests/test_soundtrack.py)

Every new test measures the returned audio or an independent reference.

  • test_the_limiter_only_catches_stray_peaks: no 10 ms window is cut more than 3 dB, and at most 1% more than 1 dB. Main fails it at 7.1 dB and 13.8%.
  • test_no_felt_mallet_cue_lands_in_a_limiter_dip: no felt-mallet cue starts in a dip deeper than 1 dB. Main fails it at the plink at 15.0 s (3.2 dB).
  • soft_clip tests:
    • It returns a signal that never reaches the knee bit for bit.
    • With one burst in quiet noise, it leaves every sample outside the filter's reach untouched.
    • A fixed-seed kick and backbeat land within 1.5 dB of DRUM_CEIL.
  • test_impacts_take_their_room_and_give_it_back: a differential test against a piecewise reference written independently in integer samples, plus a bound on the steepest step. It catches all three reviewer mutations (no ramp, wrong depth, early cut-off). Stress-run locally at 1,500 examples on each of three seeds; Astra separately checked 936 float-boundary cases.
  • test_sweeps_sit_under_the_music_in_the_mastered_score: tightened to 6 ± 0.3 LU for every sweep. Main fails it at the swell's 4.84.
  • Suites: pytest -m "not slow": 51 passed. Vitest: 34 passed. UK: the limiter, cue and sweep assertions also hold on audio/uk/events.json, checked here and by Astra. Every UK sweep is within 0.08 LU of 6.

Outputs (local; not published)

  • audio/score.wav is regenerated (sha256 56b9b2a9…).
  • out/*.mp4 are remuxed with -c:v copy -c:a aac -b:a 320k. All three video streams are bit-identical to v1.1.0.
  • tools/check_outputs.py: ALL CHECKS PASS (30.000 s, 0 glitches, −14.0 LUFS, −1.8 dBTP).
  • No release was made and nothing was posted.

Review

Two independent reviewers looked at the first commit: Opus 5.5 read the code (it had no shell), and GPT-6 Astra executed it. Both requested changes, and fabb636 addresses them. Astra then re-reviewed fabb636 by executing it: approve with nits. Those nits are fixed in ae3a45f and in this description. The first round's fixes:

  • the hats go around the clip;
  • the tests are tightened as above;
  • the claims are corrected, including the land at 1.3 dB, not 1.9, and whole-mix rather than drum-bus attribution;
  • the true-peak and cue wording is narrowed to what holds.

Overlap with the UK cut (#2)

  • A merge with uk-version conflicts in README.md and tests/test_soundtrack.py. Both sides add rows and tests, so the resolution is to keep both.
  • audio/uk/score.wav needs regenerating after the rebase.
  • The UK sweep test's 4.3 LU swell floor can then drop to 6 ± 0.3: the UK swell measures 5.93 LU with this change.

axiom: n/a: audio-only change to the video's score, no policy encoded

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits September 27, 2026 22:42
…g the mix

The master's -2 dBTP limiter cut 13% of 10 ms windows by more than 1 dB and
4% by more than 3 dB. Every kick-plus-clap backbeat went 4.3-6.5 dB past its
ceiling and the hit and drop 6.4-7.6 dB, so the whole mix (pad, arp, bass,
cues) dipped about 5 dB on beats 2 and 4 and rippled 2-3 dB for most of a
second after each impact. The finding's 16.2 dB worst cut is the 30 ms
fade-in, not the limiter; the limiter's own worst was 7.55 dB, at the drop.

- soft_clip: a 4x-oversampled tanh soft clip on the dry drum bus, with the
  peaks found through the master's 30 Hz high-pass; bit-exact below the knee
- kick and clap at 2/3 on backbeats (the limiter held them ~3.6 dB down),
  kicks at 0.75 under the plink ripple
- the hit and drop take their room smoothly: they and the music dip to
  0.55/0.5 and recover over 0.6 s, the drums over 1.5 s, and their boom
  takes the downbeat kick; the land plays 2 dB down
- the master is unchanged: -14 LUFS, true peak <= -2 dBTP, every sweep
  exactly SWEEP_UNDER under the bed, deterministic, no rng draw moved

Tests bound the limiter on the returned audio (no 10 ms window cut past
3 dB, at most 1% past 1 dB; no cue starting in a >1 dB dip), check the
clip's identity below the knee and impact_gain's shape, and tighten the
mastered sweep margin to 6 +- 0.3 LU now that the swell is no longer
limited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he numbers

Review of the first commit by an Opus 5.5 and a GPT-6 Astra reviewer:

- The open hat's right channel reached the clip's knee (a -41 dB change,
  one channel only). The hats now go round the clip; only kick and clap
  are shaped. Limiter, balance and cue numbers are unchanged.
- impact_gain's test now compares against a piecewise reference written
  out independently (integer sample offsets), so a missing 3 ms ramp, a
  wrong depth or an early cut-off fails; it also bounds the steepest step.
- A new test puts one loud burst in quiet noise and checks soft_clip
  leaves every sample outside the resampling filter's reach untouched.
- The drum-clip test draws its kick and clap from a fixed-seed generator
  swapped in under try/finally, and says so, instead of claiming the
  score's own hits.
- Comments and README say what was measured: the whole mix, not the drum
  bus alone, went 4.3-6.5 dB past the ceiling; the land went 1.3 dB past,
  not 1.9; the score's kick is lifted 1.6 dB by the high-pass; the music
  stays over 1 dB down for 0.85-0.9 s after an impact and the drums for
  2.1-2.3 s; true peak is <= -2 dBTP as the master's 4x meter reads it
  (about -1.8 at 16x, as before); the cue bound covers the felt-mallet
  cues; the master's stale "glue + soft clip" comment is corrected (its
  code is unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…igures in test docstrings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit 2f24de6 into main Sep 30, 2026
2 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged on Max's ruling (d518, 2026-09-29): after a labelled A/B of the backbeats, the fixed mix is better. Gates: CI green on ae3a45f, MERGEABLE, reviewed independently by Opus 5.5 and GPT-6 Astra (twice; listening pack and reviews in the PR description). The US and UK films take this mix in release v1.2.0.

MaxGhenis added a commit that referenced this pull request Sep 30, 2026
Conflicts: README.md keeps main's soundtrack description and adds the
compare_revisions line; tests/test_soundtrack.py takes main's limiter
tests. The UK score now shares the new mix, so the tests that bound the
master (platform targets, the limiter only catching stray peaks, no story
cue in a limiter dip, heavy cues on downbeats) run on both scores through
an each_score fixture, and the UK sweeps meet the same 6 ± 0.3 LU bound
after the master as the US ones (their 4.3 LU swell floor was the pumping).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Sep 30, 2026
…o the whole UK (#2)

A UK cut of the 30-second film (?country=uk; tools/render_all.sh uk): the Income Tax Act 2007 personal allowance raised from £12,570 to £15,000 in 2026-27, followed from statute to the policyengine-uk parameter file, to a single parent in Manchester across earnings, to all 650 constituencies.

- National figures from policyengine.py 6.2.0 (policyengine-uk 2.102.3) on the enhanced FRS 2024-25: £22bn net cost, 75.9% of households gain, 40,000 fewer children in poverty (absolute, before housing costs, on DWP's FYE 2025 line). Every on-screen number is asserted against the run.
- The FRS microdata and the map's record-level draws stay in git-ignored data/uk/private/. national.json holds aggregates only: no minimums or maximums, and no sum resting on fewer than 10 records (tools/uk/disclosure.py). The FRS is cited on screen as its UK Data Service catalogue entry specifies.
- The family curve is asserted at every £1 and matches the previous pin (policyengine.py 6.1.1) and policyengine.py's household calculator. Decile groups are rebuilt from their definition and match for all 52,846 households.
- The citation chip moves to the nearest spot clear of text where the UK statute and YAML are denser (the US film is unchanged: 602 of 602 frames identical). Frames depend only on t, for both countries and both layouts.
- The UK score shares the limiter fix from #3; the soundtrack tests run on both scores.

Reviewed in three adversarial rounds (16 findings, all fixed) and by independent reviewers on each later commit. Max approved the cut after watching it (d498).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis deleted the drum-bus branch September 30, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant