This project is maintained by kenakofer

Removing the \bar "" / \bar " " break workarounds

LilyPond 2.24 changed \break to always insert a break, bypassing the default rule that breaks only happen at barlines. From the 2.24 changelog:

The \break command now always inserts a break, bypassing all default decisions about break points. For example, it is no longer necessary to insert \bar "" to obtain a mid-measure break.

The corpus predates that change in habit, not in version: songs written after the 2.24.4 switch still carry the workaround, because it was copied forward from older songs. Everything below is about removing it safely.

What the workaround looks like

Two spellings, same purpose — give \break a barline to attach to so a mid-measure break is not discarded:

%% \bar " " (a space) — 11 files, all converted
\relative g' { ... | \partial 2 d2 \bar " " | } \break

%% \bar "" (empty), at a block boundary — 25 files
\relative g' { ... | b2. \bar "" } \break

%% \bar "" (empty), inline mid-stream — 39 files, the easy majority
  ... | fs'4 d'4 \bar "" \break

There was also a bb = { \bar "" \break } shortcut in lib/hymn-common.ily, used by three songs. Retiring the workaround there is a one-line library edit rather than three file edits.

Both are now redundant. \break alone does the job.

The recipe

  1. Remove the \bar "" / \bar " ". Leave every other \bar alone — \bar "|." and \bar "||" are real barlines that must survive.
  2. Remove the trailing | bar check at each block boundary — in all four voices, plus the chord line if it has one. Once the invisible barline is gone, the break point is no longer a measure boundary, so a bar check sitting there fails.
  3. Do not touch the \relative blocks at all. Not merging, not splitting, not adjusting octave marks, not changing a reference pitch. The note content stays byte-for-byte as it was; the only edits are removing the invisible barline and the trailing bar checks. See the trap below for why this is a hard rule and not a preference.
  4. Leave \partial alone unless it is provably redundant. A \partial that declares a real anacrusis is load-bearing; one that only existed to split a measure across a system break can go, but only if step 2 is applied consistently. See “Repeated \partial” below — “provably” means built and verified, not reasoned about.
  5. Keep \bar "|." on its own line after the last block. Moving it inside the final \relative block shortens the last system so it no longer justifies to the right margin.

Verification — all three, every song

Build to a scratch directory, never into docs/local-lilypond-outputs.

Check How Pass condition
Bar checks grep -ic barcheck build.log same count as baseline (some songs have pre-existing ones)
Pitches/rhythms cmp base/out.midi new/out.midi byte-identical
Engraving pixel-diff the -trad PNG against baseline see below

MIDI equality is necessary but not sufficient. It proves no note or duration changed; it says nothing about what is printed. The engraving diff is what catches accidentals, beams, and justification.

Interpreting the engraving diff:

Traps, all of which bit us

Converting soprano only. Barlines and breaks are Score-level and driven from soprano, but bar checks are per-voice. Editing soprano alone changes the measure structure that alto/tenor/bass are still checking against, and they fail — in files you never touched. Symptom: dozens of barcheck failed in voices you did not edit. Always do all four.

Editing \relative blocks at all. Each block resets its octave reference. Concatenating two blocks re-anchors block 2’s first note to block 1’s last note, silently transposing whole phrases by an octave. Bar checks still pass — durations are unaffected — so only the MIDI comparison catches it.

Hand-computing replacement octave marks to compensate does not work either: it was attempted here across several rounds and produced wrong pitches every time, each caught only by MIDI. The rule that survives is simply to leave the blocks alone. Removing \bar "" needs no \relative change, so there is never a reason to make one.

help-us-to-help-each-other-lord.ly is the one file in the corpus with merged blocks, from before this rule existed. It was verified clean and is deliberately kept that way — it is not a template to copy.

Moving \bar "|." inside the final block. Shortens the last system. Keep it on its own line.

Assuming bar-check failures mean \break is not working. They do not. \break works mid-measure under 2.24. Bar-check failures after removing a \partial mean the measure is genuinely short and nothing declares it — a different problem with a different fix.

Stale comments. Some songs carry comments asserting \bar "" is required for a mid-measure break (e.g. have-thine-own-way.ly). These describe pre-2.24 behaviour and should be deleted as the file is converted. dear-lord-and-father-of-mankind.ly has the correct post-2.24 note.

Delegating this

The mechanical part is safe to hand to a cheaper model; the judgement is not. A workable split:

MIDI differing is always a bug, never a judgement call — the agent should revert and report rather than escalate.

The prompt to hand an agent, with the thresholds written as a decision table, is agent-prompt-break-workaround.md.

Status

What the \bar "" sweep actually needed

Far less than the \bar " " set. Two thirds of the files spell it inline as \bar "" \break mid-stream, where deleting the \bar "" is the entire edit — no block boundary, so no trailing bar checks to remove and no \relative anywhere near it. The rest sit at a block boundary and match the \bar " " shape. No file needed a \partial removed, and none needed a \relative touched.

Results across all 64: MIDI byte-identical everywhere, bar-check counts unchanged (including the six songs with pre-existing failures), and lyric/marker content identical in every book. The visible change is horizontal respacing, plus the two deliberate improvements below.

Two output changes, both kept

Stem directions flip on some pickup notes. Removing the invisible barline changes which measure an anacrusis belongs to, and LilyPond chooses stem direction per measure context. Clearest in when-jesus-wept, where the canon-entry pickups flip from down-stem to up-stem. Approved and kept.

praise-god-old-hundredth’s slide deck goes from 9 slides to 8. The two French-verse systems now share a slide, comfortably spaced. Character content is identical (724 either way); nothing was dropped.

Repeated \partial — the other half of the workaround

A \partial repeated at every system break was the companion trick: it split a measure so the next phrase could open on its upbeat. Now that \break breaks wherever it is written, those repeats are often dead weight. Often, not always — and the split is not predictable from reading the file.

Of 51 songs with a repeated \partial in soprano, 20 tolerate dropping all but the first and 31 do not. Where it fails it fails loudly: a \partial declares a genuinely short measure, so removing it leaves the next measure overfull and every later bar check in that voice fails — 141 of them in a-mighty-fortress-is-our-god, 132 in take-my-life-and-let-it-be.

Two things that look like they should predict the split and don’t:

The only reliable method is to drop them, build, and compare. Bar checks alone separate the two groups cleanly here, which makes this cheap to test even though it is impossible to predict.

scripts/drop-repeated-partial.py does the mechanical edit — soprano only, since a \partial in a lower voice is that voice’s own pickup.

On the verification thresholds

The ink-percentage rule below is not a sufficient gate on its own. The when-jesus-wept stem flip moved 18 px on a 30 000 px page — 0.06%, well inside the “benign respacing” band — while being a real, visible change. Ink volume tells you how much moved, never what. Compare lyric/marker text separately, and look at a render when anything is flagged.

Two pixel-forensics traps, both hit during this sweep:

pdftotext needs care too: word order and word grouping both shift with spacing on pixel-identical pages, and LilyPond glyphs extract as control bytes. Compare the multiset of alphanumeric characters, not the word sequence.

See also lilypond-upgrade-notes.md for the 2.24.4 switch itself.