diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..edc784f
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,8 @@
+# Copilot instructions — shanties
+
+**The primary artifact is the website** (`src/`): an interactive ABC-notation songbook — pick or type a song and abcjs live-renders staff notation, with transpose and lyrics / verses / guitar-tab / trumpet-fingering toggles. It deploys to https://brewingcode.github.io/shanties. The two printable **booklets** (`booklet/` = lyrics, `musicbook/` = staff) are secondary artifacts derived from the same `songs/`. **Read `AGENTS.md` for site architecture, URL params/filters, and the booklet/music-capture pipelines before any non-trivial change.**
+
+- Build the site: `npm run build` (Node-only, cross-platform → `dist/`). Serve with live reload: `npx pug-pack src -w` (http://localhost:3000); `npm run dev` rebuilds but does **not** serve.
+- `src/songs.json` + `src/by-title.json` are generated & gitignored — run a build if they're missing.
+- The Windows build is already fixed via `patches/pug-pack+1.11.0.patch` (applied by `postinstall`/patch-package). Don't re-diagnose or revert it.
+- Prefer Node built-ins over new deps; don't leave throwaway scripts; this is a PowerShell box — use single-line commands.
diff --git a/.vscode/mcp.json b/.vscode/mcp.json
new file mode 100644
index 0000000..137db1f
--- /dev/null
+++ b/.vscode/mcp.json
@@ -0,0 +1,9 @@
+{
+ "servers": {
+ "playwright": {
+ "command": "npx",
+ "args": ["-y", "@playwright/mcp", "--headless", "--browser", "chromium"]
+ }
+ },
+ "inputs": []
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f5e51f1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,25 @@
+{
+ // Workspace-level auto-approve list. Lives in the repo so it roams across
+ // all machines via git. Per-machine overrides go in %APPDATA%\Code\User\settings.json.
+ //
+ // Design rule: ONLY read-only / stdout-only cmdlets and a CLOSED, enumerated
+ // list of helper scripts may auto-approve. Nothing that writes to disk,
+ // executes an arbitrary script block, or names an unknown script belongs here.
+ //
+ // See .github/skills/_shared/scripts/README.md for the rationale and full mechanism.
+ "chat.tools.terminal.autoApprove": {
+ // --- Read-only inspection (no side effects) ---
+ "Test-Path": true,
+ "Get-Item": true,
+ "Get-ItemProperty": true,
+ "Get-Date": true,
+ // --- Read-only data shaping (stdout only; no -FilePath/-Path writes,
+ // no script-block execution) ---
+ "Group-Object": true,
+ "ConvertFrom-Json": true,
+ "ConvertTo-Json": true,
+ "ConvertFrom-Csv": true,
+ "ConvertTo-Csv": true,
+ "Out-String": true,
+ }
+}
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..1a1c5db
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,44 @@
+# AGENTS.md — shanties
+
+Fuller agent guide for the sea-shanties project. The always-loaded essentials live in `.github/copilot-instructions.md`; read this file for anything beyond a trivial change.
+
+The repo's **primary purpose is the website** (`src/`) — an interactive ABC-notation songbook. The two printable saddle-stitch booklets are secondary artifacts derived from the same `songs/`. The site deploys to https://brewingcode.github.io/shanties by `.github/workflows/gh-pages.yml` (Ubuntu, Node 20, `npm install` + `npm run build`).
+
+## Build & run
+- `npm run build` — site → `dist/`: runs `songs-to-json.js`, then `pug-pack src`, then copies `Songbook.pdf` + `qr-code.svg`. Cross-platform (Node only — no python/`cp`).
+- Serve locally with live reload: `npx pug-pack src -w` → http://localhost:3000 (browserSync). `npm run dev` watches + rebuilds but does not serve.
+- `npm run booklet` / `booklet:proof` / `booklet:single` — lyrics booklet (`booklet/`).
+- `npm run musicbook` / `musicbook:proof` — staff-notation booklet (`musicbook/`).
+- Syntax-check CoffeeScript snippets by piping (dedented) to `npx coffee -s -c -p`.
+
+## The website (`src/`) — the primary artifact
+Interactive ABC songbook: type ABC into `#abc` (or pick a song from the dropdown, or click "add a song" → GitHub `songs/`), and abcjs live-renders staff notation into `#paper`. Settings toggle inline lyrics (`#show-lyrics`), extended verses (`#show-verses`), trumpet fingering (`#show-trumpet`), and guitar tab (`#show-gtab`); `#transpose` shifts the key and shows from/to in `#fromKey`/`#toKey`. Parse warnings render to `#warnings`; on iPad the textarea sits behind an Edit button.
+- `src/index.pug` is the whole page (extends pug-pack `_base`). All logic is CoffeeScript in a `:coffeescript` filter; abcjs/jquery/bootstrap are `include`d and inlined into one big `dist/index.html`.
+- `songs/*.txt` are ABC songs. `songs-to-json.js` generates `src/songs.json` (keyed by filename) + `src/by-title.json` (keyed by `T:` title) — both are **generated and gitignored**; run a build if they're missing.
+- abcjs renders into `#paper`. UI chrome is tagged `.noprint` (hidden in print and when `body.fullscreen`).
+- URL params: `title=` / `file=` pick a song; `fullscreen=true` hides chrome; `lyrics=false`, `verses=false` hide inline / extended lyrics (read before first render).
+- `setSong` filters ABC lines pre-render: lowercase `w:` = inline lyrics (or trumpet fingering if all digits), uppercase `W:` = extended verses.
+- ABC songs use `%%` directives (e.g. `%%notelabels`, `%%notecolors`, `%%vocalfont`) alongside standard fields (`T:`, `M:`, `K:`, `w:`/`W:`).
+
+## Booklets (markdown-booklet)
+- Engine: `node node_modules/markdown-booklet/src/cli.js build
/book.yaml [--out … | --reading | --single]`.
+- `booklet/` = lyrics (Markdown, two-page spreads). `musicbook/` = staff notation (single `type: html` pages, no spreads), parallel to the lyrics book.
+- Song list source of truth is `booklet/book.yaml`. Before assuming a song has staff notation, confirm its `songs/.txt` exists (e.g. `skipper-jan-rebec` was booklet-only for a while).
+
+## Music-book staff capture (`musicbook/pages/*.svg`)
+Each staff SVG is captured from the running site. To (re)generate one:
+1. Serve the site (`npx pug-pack src -w`).
+2. Headlessly load `?file=.txt&fullscreen=true&verses=false`, grab `#paper svg` outerHTML (strip its inline `style`, set `width=100%`, remove `height`).
+3. **Post-process — required, or ` ` renders broken:** add `xmlns="http://www.w3.org/2000/svg"` to the root `` (abcjs omits it, so the standalone file is invalid XML) and replace ` ` → ` ` (` ` is undefined in standalone XML/SVG). Write UTF-8, no BOM.
+The abcjs SVG already renders the `T:` title, so pages need no extra heading; `.staff img` fit-contains within the page (`music.css`). Each `.html` is just ``.
+
+## Cross-platform — do not undo
+- Windows builds work via **patch-package**: `patches/pug-pack+1.11.0.patch` (applied by the `postinstall` script) fixes pug-pack's Unix-`find` call, its Windows path/regex `srcname` bug, the git `cwd`, and registers a `coffeescript` pug filter (so it no longer depends on hoisting `jstransformer-coffeescript`). Don't revert these or re-diagnose "pug-pack broken on Windows".
+- `pug-pack` is an **unpinned git-branch** dependency — if it changes upstream the patch may need refreshing (`npx patch-package pug-pack`).
+
+## Conventions & gotchas
+- Prefer Node built-ins over new dependencies (e.g. `fs.cpSync`, not `shx`).
+- Don't write throwaway scripts merely to execute them; validate by reasoning or a one-off inline command. Delete one-shot data-fix scripts after use.
+- Don't hard-wrap Markdown to a fixed column width.
+- This is a Windows/PowerShell dev box — prefer single-line commands; multi-line array/here-string pastes can mangle in the persistent shell.
+- `.playwright-mcp/` is scratch output and is gitignored.
diff --git a/musicbook/all-for-me-grog.html b/musicbook/all-for-me-grog.html
new file mode 100644
index 0000000..d128a26
--- /dev/null
+++ b/musicbook/all-for-me-grog.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/barretts-privateers.html b/musicbook/barretts-privateers.html
new file mode 100644
index 0000000..7a9420b
--- /dev/null
+++ b/musicbook/barretts-privateers.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/book.yaml b/musicbook/book.yaml
new file mode 100644
index 0000000..6bb8116
--- /dev/null
+++ b/musicbook/book.yaml
@@ -0,0 +1,38 @@
+title: "Sea Shanties — Music"
+
+# Logical page + margins, matching the lyrics songbook so the two fold alike.
+# The physical sheet is derived as two pages wide.
+config:
+ page:
+ width: 5.5in
+ height: 8.5in
+ margins:
+ inner: 0.75in
+ outer: 0.5in
+ top: 0.6in
+ bottom: 0.6in
+ signatureSize: all
+ stylesheet: music.css
+
+# Staff notation captured from the running site at
+# ?file=.txt&fullscreen=true&verses=false
+# Each song is a single page (no spreads). Order mirrors the lyrics songbook,
+# with Old Maui filling what was the blank second page (cover + 15 = 16 pages).
+# (skipper-jan-rebec now has ABC on master but is not included here yet.)
+pages:
+ - { file: cover.html, showPageNumber: false }
+ - { file: old-maui.html }
+ - { file: all-for-me-grog.html }
+ - { file: barretts-privateers.html }
+ - { file: bully-boys.html }
+ - { file: bully-in-the-alley.html }
+ - { file: drunken-sailor.html }
+ - { file: health-to-the-company.html }
+ - { file: joli-rouge.html }
+ - { file: leave-her-johnny.html }
+ - { file: mingulay-boat-song.html }
+ - { file: randy-dandy-o.html }
+ - { file: roll-the-old-chariot-along.html }
+ - { file: south-australia.html }
+ - { file: wellerman.html }
+ - { file: whiskey-johnny.html }
diff --git a/musicbook/bully-boys.html b/musicbook/bully-boys.html
new file mode 100644
index 0000000..74e7751
--- /dev/null
+++ b/musicbook/bully-boys.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/bully-in-the-alley.html b/musicbook/bully-in-the-alley.html
new file mode 100644
index 0000000..fb2c78d
--- /dev/null
+++ b/musicbook/bully-in-the-alley.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/cover.html b/musicbook/cover.html
new file mode 100644
index 0000000..5fd9ad2
--- /dev/null
+++ b/musicbook/cover.html
@@ -0,0 +1,12 @@
+
+
+
Sea Shanties
+
Music Edition
+
+
Staff notation for the crew
+
Chris Glein and Alex Deakin
+
+
diff --git a/musicbook/drunken-sailor.html b/musicbook/drunken-sailor.html
new file mode 100644
index 0000000..b4bc508
--- /dev/null
+++ b/musicbook/drunken-sailor.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/health-to-the-company.html b/musicbook/health-to-the-company.html
new file mode 100644
index 0000000..97b2188
--- /dev/null
+++ b/musicbook/health-to-the-company.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/joli-rouge.html b/musicbook/joli-rouge.html
new file mode 100644
index 0000000..467eed8
--- /dev/null
+++ b/musicbook/joli-rouge.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/leave-her-johnny.html b/musicbook/leave-her-johnny.html
new file mode 100644
index 0000000..f4f87c1
--- /dev/null
+++ b/musicbook/leave-her-johnny.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/mingulay-boat-song.html b/musicbook/mingulay-boat-song.html
new file mode 100644
index 0000000..eee7338
--- /dev/null
+++ b/musicbook/mingulay-boat-song.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/music.css b/musicbook/music.css
new file mode 100644
index 0000000..42b73ce
--- /dev/null
+++ b/musicbook/music.css
@@ -0,0 +1,56 @@
+/* Sea Shanties — music (staff notation) edition.
+ Appended after markdown-booklet's base CSS. url() paths resolve relative to
+ the OUTPUT html file, so build into this folder and keep pages/ as a sibling. */
+
+:root {
+ --display: "Primitive", "UnifrakturCook", "Blackletter", serif;
+ --body: "Lucida Bright", "Lucida Serif", "Georgia", "Times New Roman", serif;
+}
+
+.page-content { font-family: var(--body); }
+
+/* ---- Staff pages ------------------------------------------------------- */
+/* Each song page is a single captured abcjs SVG (which already renders the
+ song title). Fit it within the page content box, preserving aspect ratio. */
+.staff {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.staff img {
+ max-width: 100%;
+ max-height: 100%;
+ width: auto;
+ height: auto;
+}
+
+/* ---- Cover ------------------------------------------------------------- */
+.cover {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+.cover-title {
+ font-family: var(--display);
+ font-weight: 400;
+ font-size: 56pt;
+ line-height: 1.02;
+}
+.cover-sub {
+ font-family: var(--body);
+ font-size: 15pt;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ margin-top: 0.2in;
+ color: #333;
+}
+.cover-byline {
+ font-family: var(--body);
+ font-size: 13pt;
+ margin-top: 0.6in;
+}
+.cover-byline p { margin: 0.06in 0; }
diff --git a/musicbook/old-maui.html b/musicbook/old-maui.html
new file mode 100644
index 0000000..0725e8a
--- /dev/null
+++ b/musicbook/old-maui.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/pages/all-for-me-grog.svg b/musicbook/pages/all-for-me-grog.svg
new file mode 100644
index 0000000..554d341
--- /dev/null
+++ b/musicbook/pages/all-for-me-grog.svg
@@ -0,0 +1 @@
+Sheet Music for "All For Me Grog" All For Me Grog Chorus All C E for E me D grog C me B jol- F A ly B jol- C ly A grog C G All 12 C G for 12 G me 12 G beer 12 G and 0 F to- 2 E bac- 2 G E co 12 D D For E I F spent C G all G me E tin C on C the B las- F A sies B drin- C kin' A gin C G And C a- D cross E the G west- F ern E o- E cean D I C must B wan- G7 D der C C
\ No newline at end of file
diff --git a/musicbook/pages/barretts-privateers.svg b/musicbook/pages/barretts-privateers.svg
new file mode 100644
index 0000000..11b869e
--- /dev/null
+++ b/musicbook/pages/barretts-privateers.svg
@@ -0,0 +1 @@
+Sheet Music for "Barrett's Privateers" Barrett's Privateers Verse Oh, C the E year C G was G se- F F ven- F teen E sev- G D en- D ty D eight, C C how G I G wish C G I G was Dm (F?) A in A Sher- C G brooke G now! G D A A let- C C ter C of C marque F B came A from G G the E D king C C to C the D scum- E mi- E est E ves- D sel C I've C e- E ver G seen, F A God F damn C E them G D all! C C Chorus I C E was G told F A we'd A cruise G G the G seas F A for B A- B mer- C C i- G can G gold, F A we'd F fire C E no G D guns. C C Shed G no B tears, F C now G I'm A a B bro- C C ken G man F A on G a F Hal- C E i- E fax G pier, F A the A last Dm (F?) A of G Bar- F rett's E pri- G E D va- C C
\ No newline at end of file
diff --git a/musicbook/pages/bully-boys.svg b/musicbook/pages/bully-boys.svg
new file mode 100644
index 0000000..c5c3f8c
--- /dev/null
+++ b/musicbook/pages/bully-boys.svg
@@ -0,0 +1 @@
+Sheet Music for "Bully Boys" Bully Boys EEADAE = 140 And E it's F row C G me G bul- G ly F boys E we're F F in F a F hur- F ry E boys D we've C E got E a E long E way D to C go G7 D And G we'll G sing C G and C we'll C dance C and D bid E fare- F F well E to D France, D row G7 G me E bul- F ly E boys D row C C And See, A And G we now, wee we G sailed how dram sailed C C a- we of a- C way feast whis- way C in on key in D the phea- for the E rough- sants e- rough- F F est by ver- est E of a y of D wa- flock, man, wa- D ter, ter, C row row row row C E me me me me E bul- bul- bul- bul- E ly ly ly ly D boys boys boys boys C row row row row G7 D But It's And And E now a a now G we long, bar- we C G re- long rel re- G turn way of turn G in from rum and G the the for so F most gruel the lock E ro- and shan- up F F yal the your E ty D quar- stocks, daugh- D ters, man, ters, C row row row row G7 G me me me me E bul- bul- bul- bul- F ly ly ly ly E boys boys boys boys D row row row row C C
\ No newline at end of file
diff --git a/musicbook/pages/bully-in-the-alley.svg b/musicbook/pages/bully-in-the-alley.svg
new file mode 100644
index 0000000..0373d9a
--- /dev/null
+++ b/musicbook/pages/bully-in-the-alley.svg
@@ -0,0 +1 @@
+Sheet Music for "Bully in the Alley" Bully in the Alley EEADAE = 150 Sal- Se- Sal- Well G G ly ven G is long ly I'll G the years leave D girl I- Brown Sal G that 've I and A I court- took I'll B love ed a go D dear- Sal- no- sail- B ly ly tion in' G way way way way C C hey hey hey hey G B bul- bul- bul- bul- D A ly ly ly ly A in in in in A the the the the B al- al- al- al- A ley ley ley ley D Sal- All To Leave G G ly she sail G is did my G the was a- D girl dil- cross gal G that ly this and A I and wide I'll B spliced dal- damn go D near- oc- whale- B ly ly ean in' G bul- bul- bul- bul- C C ly ly ly ly C down down down down C in in in in B shin- shin- shin- shin- G D bone bone bone bone D7 F al al al al G G So G G help D me G ba G ba A bul- B ly B in B the D al- B ley G way C C hey G B bul- D A ly A in A the B al- A ley D So G G help D me G ba G ba A bul- B ly B in B the D al- B ley G bul- C C ly C down C in B shin- G D bone D7 F al G G
\ No newline at end of file
diff --git a/musicbook/pages/drunken-sailor.svg b/musicbook/pages/drunken-sailor.svg
new file mode 100644
index 0000000..1b29441
--- /dev/null
+++ b/musicbook/pages/drunken-sailor.svg
@@ -0,0 +1 @@
+Sheet Music for "Drunken Sailor" Drunken Sailor What Shave Put him Stick him Put him That's Dm A will his in in in what A we bel- a a the we A do ly long scup- bed do A with with boat per with with A a a with a the a A drunk- rust- till hose- cap- drunk- A en y he's pipe tains en D sail- ra- so- bot- daugh- sail- F or? zor ber tom ter or A What Shave Put him Stick him Put him That's C G will his in in in what G we bel- a a the we G do ly long scup- bed do G with with boat per with with G a a with a the a G drunk- rust- till hose- cap- drunk- G en y he's pipe tains en C sail- ra- so- bot- daugh- sail- E or? zor ber tom ter or G What Shave Put him Stick him Put him That's Dm A will his in in in what A we bel- a a the we A do ly long scup- bed do A with with boat per with with A a a with a the a A drunk- rust- till hose- cap- drunk- A en y he's pipe tains en B sail- ra- so- bot- daugh- sail- C or? zor ber tom ter or D Ear- Ear- Ear- Ear- Ear- Ear- F C ly ly ly ly ly ly A in in in in in in C G the the the the the the E morn- morn- morn- morn- morn- morn- Dm D ing! ing! ing! ing! ing! ing! D Way Dm A hay A and A up A she D ris- F es A Way C G hay G and G up G she C ris- E es G Way Dm A hay A and A up A she B ris- C es D Ear- F C ly A in C G the E morn- Dm D ing! D
\ No newline at end of file
diff --git a/musicbook/pages/health-to-the-company.svg b/musicbook/pages/health-to-the-company.svg
new file mode 100644
index 0000000..97fe1aa
--- /dev/null
+++ b/musicbook/pages/health-to-the-company.svg
@@ -0,0 +1 @@
+Sheet Music for "Health to the Company" Health to the Company EEADAE = 110 Verse Kind E D friends Am E and A com- B pan- Em A i- G ons, E come G join G G me D in B C rhyme Am A Come A B lift Am C up D your E voi- C A G ces E in D chor- Am E us A with B mine G G A Come A B lift Am C up D your E vo- C A i- G ces, E all D grief Am E to A re- B frain G G A For E we D may Am E or A might B ne- Em A G ver E all G meet G G here D a- B C gain Am A
\ No newline at end of file
diff --git a/musicbook/pages/joli-rouge.svg b/musicbook/pages/joli-rouge.svg
new file mode 100644
index 0000000..c62935e
--- /dev/null
+++ b/musicbook/pages/joli-rouge.svg
@@ -0,0 +1 @@
+Sheet Music for "Joli Rouge" Joli Rouge Dm D F A F A Am A C E Dm D F A F A Am A C E Dm D F A F A Am A C E G From She's So So Dm A France called turn you A we the your can A get Dread- sails have G the nought over a Am A Bran- Ci- Mag- A dy, der, ners A from she's and and A Mar- pro- bring pour Dm A ti- per her it G nique and hard o- F the she's to ver G rum fine port ice Am A A Sweet And Find Or Dm F red when you F the that can F Cab- day lit- have C E er- is tle a D net over star Strong- C sure and bow C from I fly if D I- wish straight it's Dm C ta- that in- sad- D ly she to ness D does were the that you C come. mine. North like. C D D But Or The Or D the E fair- in wild join Dm F est the us E of dark sun up D them of up- the F all, win- on ri- C E me ter, your ver D boys back and C the or the we'll D one on wind set F A to a a- your G beat sum- blow- heart F the mer's ing a- A day, eve free glow C C C C is You're And C made One roll- how Dm D from hand ing you'll C up feel C ap- giv- the when F A ples eth river the G up and boys real F the the to A migh- oth- old ci- C7 G ty er Chi- der G Sa- does cou- starts F gue- re- ti- to E nay. ceive. mi. flow. F F So So So So A fol- Dm A low A me A lads A Am cause A this A ain't Dm A no G grog F or G ale Am A One Dm F pint F F down C E you'll D be C swing- Dm A in' D in D D the C gale C D Five Dm F pints F F bul- C E ly D you'll C be D shak- F A in' G in F your A shoes C C C C We're C half- Dm D seas C C ov- F A er G on F the A Jo- C G li C Rogue. Dm D
\ No newline at end of file
diff --git a/musicbook/pages/leave-her-johnny.svg b/musicbook/pages/leave-her-johnny.svg
new file mode 100644
index 0000000..fe97762
--- /dev/null
+++ b/musicbook/pages/leave-her-johnny.svg
@@ -0,0 +1 @@
+Sheet Music for "Leave Her Johnny" Leave Her Johnny I Oh I We C the D thought wind hate swear C E I was to by E heard foul sail rote E the and on for C the this D old sea rot- want E man was ten of D say high tub more C Leave Leave Leave Leave G D her her her her D John- John- John- John- D ny ny ny ny C leave leave leave leave C E her her her her G To- She No But G mor- shipped grog now F A row it a- we're A ye green lowed through C G will and and so we'll E get none rot- go G F yer went ten on F pay by grub shore C E and and and and C it's it's it's it's D time time time time E for for for for E us us us us E to to to to C leave leave leave leave G7 D her her her her C C Leave G D her D John- D ny C leave C E her G Oh C B leave F A her, A John- A ny G leave C A G her E For C the B voyage F A is A long C G and E the E winds G F don't F blow C E and C it's D time E for E us E to C leave G7 D her C C
\ No newline at end of file
diff --git a/musicbook/pages/mingulay-boat-song.svg b/musicbook/pages/mingulay-boat-song.svg
new file mode 100644
index 0000000..942263c
--- /dev/null
+++ b/musicbook/pages/mingulay-boat-song.svg
@@ -0,0 +1 @@
+Sheet Music for "Mingulay Boat Song" Mingulay Boat Song EEADAE = 140 Chorus Heave D D her E ho, G G boys, G let D her B go, B A boys G Swing D her B head D A round A in- G to B A the G wea- C E ther E Heave D D her E ho, G G boys, G let D her B go, B A boys G Sail- D ing B home- D A ward A to G Min- B gu- A lay G G Verse What D D care E we, G G though, G whi- D te B the B Minch A is? G What D care B we, D A boys, A for G win- B dy A G weath- C E er? E When D D we E know G G that G e- D ver- B y B inch A is G Sail- D ing B home- D A ward A to G Min- B gu- A lay G G
\ No newline at end of file
diff --git a/musicbook/pages/old-maui.svg b/musicbook/pages/old-maui.svg
new file mode 100644
index 0000000..35c1962
--- /dev/null
+++ b/musicbook/pages/old-maui.svg
@@ -0,0 +1 @@
+Sheet Music for "Old Maui" Old Maui Verse It's B a B damn Em E tough E life B D full B of B toil G G and F E strife D F we B A wha- Em G ler- F men E un- B F der- D E go, Em E and B we B don't Em E give E a E damn B D when B the B gale G G is F E done D F how B A hard Em G the E winds B F did D E blow, Em E for G we're A home- G B ward B bound B from A the G Arc- D A tic A ground A with G a F good Em G ship G taut G and E free, B F and B we B don't Em E give E a E damn B D when B we B drink G G our F E rum D F with B the A girls Em G of E old B F Mau- D E ---> Chorus i, Em E roll- G in' A down G B to B old B Mau- A G i, D A me A boys, A rol- B lin' A down Em G to G old G Mau- E F i, B F we're B home Em E ward E bound B D from B the B Arc- G G tic F E ground, D F rol- B lin' A down Em G to E old B F Mau- D E i Em E
\ No newline at end of file
diff --git a/musicbook/pages/randy-dandy-o.svg b/musicbook/pages/randy-dandy-o.svg
new file mode 100644
index 0000000..1f10578
--- /dev/null
+++ b/musicbook/pages/randy-dandy-o.svg
@@ -0,0 +1 @@
+Sheet Music for "Randy Dandy-O" Randy Dandy-O EEADAE = 140 Now Man Heave We Soon Come Am C we the a- are we'll breast C are stout way, out- be the C read- caps- bull- ward warp- bar B dy 'n ies, bound ing bull- C to and ye for her ies B sail heave par- Vall- out heave A for with ish- ip- through with B the a rigged o the a A Horn, will bums Bay locks will E Way Way Way Way Way Way A hey hey hey hey hey hey G B roll roll roll roll roll roll Am A and and and and and and G go go go go go go E Our For Take Get Where Oh E your the F boots soon hands crack- pret- soon C G an' we'll from in', ty we'll A our be your me young be G clothes, driv- pock- lads, girls roll- G boys, in' ets it's all ing F are a- and a come her E all way don't hell down down G D in up suck of in through D the the your a their the D pawn hill thumbs way! frocks bay D To To To To To To C me me me me me me D rol- rol- rol- rol- rol- rol- Am E lick- lick- lick- lick- lick- lick- F in' in' in' in' in' in' E ran- ran- ran- ran- ran- ran- E dy dy dy dy dy dy D dan- dan- dan- dan- dan- dan- E7 C dy dy dy dy dy dy A O O O O O O Am A Heave Am C a C pawl B oh B heave A a- A way E Way A hey G B roll Am A and G go E The E F an- C G chor's A on G board G and F the E cab- G D le's D all D stored D To C me D rol- Am E lick- F ing E ran- E dy D dan- E7 C dy A O Am A
\ No newline at end of file
diff --git a/musicbook/pages/roll-the-old-chariot-along.svg b/musicbook/pages/roll-the-old-chariot-along.svg
new file mode 100644
index 0000000..5d3b30d
--- /dev/null
+++ b/musicbook/pages/roll-the-old-chariot-along.svg
@@ -0,0 +1 @@
+Sheet Music for "Roll the Old Chariot Along" Roll the Old Chariot Along EEADAE = 140 We'd We'll Well Well Oh F be be a a a G all- all- nice night drop Dm A wash on of A bõ- the Nelson's F right right low town blood D if if would- would- would- F the we n't n't n't G wind make do do do A was it us us us A in round an- an- an- A our The y y y F sails. Horn. harm. harm. harm. D We'd We'll Well Well Oh A be be a a a A all- all- nice night drop C G wash on of G bõ- the Nelson's E right right low town blood C if if would- would- would- E the we n't n't n't F wind make do do do G was it us us us G in round an- an- an- G our The y y y E sails. Horn. harm. harm. harm. C We'd We'll Well Well Oh G be be a a a G all- all- nice night drop Dm A wash on of A bõ- the Nelson's F right right low town blood D if if would- would- would- F the we n't n't n't G wind make do do do A was it us us us A in round an- an- an- A our The y y y F sails. Horn. harm. harm. harm. D And F we'll G all F A hang G on A7 F be- E hind. Dm D And F we'll G roll Dm A the F old D char- A i- A ot A a- F long D A We'll C G roll E the C old G char- G i- G ot E a- C We'll G roll Dm A the F old D cha- A r- A iot A a- F long D And F we'll A all F A hang G on A7 F be- E hind Dm D
\ No newline at end of file
diff --git a/musicbook/pages/south-australia.svg b/musicbook/pages/south-australia.svg
new file mode 100644
index 0000000..1ded3bc
--- /dev/null
+++ b/musicbook/pages/south-australia.svg
@@ -0,0 +1 @@
+Sheet Music for "South Australia" South Australia Part A In B South E G Aus- B tral- B ia B I C was B born. B Heave A C a- B way E B Haul A C a- B way E B Heave C a- B way B Haul C a- B way B In A South E G Aus- B tral- B ia G 'round B7 F Cape G Horn, E E We E are F bound G for B South B Aus- G tral- B7 F ia. E E bound G for B South B Aus- G tral- F ia. E Part B Heave A E a- E way, C you E rol- C ling B kings. E B Heave A C a- B way, E B haul A C a- B way E B Heave A E a- E way, C you'll E hear C me B sing: E B We E are F bound G for B South B Aus- G tral- B7 F ia. E E
\ No newline at end of file
diff --git a/musicbook/pages/wellerman.svg b/musicbook/pages/wellerman.svg
new file mode 100644
index 0000000..c6ec366
--- /dev/null
+++ b/musicbook/pages/wellerman.svg
@@ -0,0 +1 @@
+Sheet Music for "Wellerman" Wellerman There She Be- No For As G once had fore line fort- far Cm C was not the was y as C a I've C ship been boat cut, days heard, C that two had no or the E put weeks hit whale ev- fight's G to from the was en still G sea, shore, water, freed, more, on G the when the The The The G name down whale's cap- line line's Fm A of the on tail tain's went not F ship her came mind slack cut F was a up was then and A the the B Billy right and not tight whale's Cm C o whale caught of once not G Tea. bore. her. greed. more. gone. G The The All But All The G winds cap- hands he boats Weller- C blew tain to the side, be- were man C up, called har- longed lost, makes C her all pooned to there his E bow hands and the were regular G dipped and fought Whaleman's only call G down. swore: her, creed. four, To G Oh "We'll when She but encourage G blow take she took still the G G my that dived that that captain F bully whale down ship whale crew E boys in be- in did and D blow. tow". low. tow. go. all. Cm C Soon Aâ™ C may C the A Wel- Eâ™ B ler- B man G come G to G bring Fm A us F su- F gar G and A tea Cm G and E rum C One Aâ™ C day C the A ton- Eâ™ B guin B is G done G we'll G take G G our F leave E and D go Cm C
\ No newline at end of file
diff --git a/musicbook/pages/whiskey-johnny.svg b/musicbook/pages/whiskey-johnny.svg
new file mode 100644
index 0000000..2ba3503
--- /dev/null
+++ b/musicbook/pages/whiskey-johnny.svg
@@ -0,0 +1 @@
+Sheet Music for "Whiskey Johnny" Whiskey Johnny Whisk- E B ey B is B the E life C of A man B Whisk- G ey, B John- G ny! B Whisk- G ey, B John- G ny! B I'll D B drink B whisk- B ey C while G I F can E Whisk- A B ey B for B my C John- G ny F Oh! E Whisk- B ey B for B my C John- G ny F Oh! E
\ No newline at end of file
diff --git a/musicbook/proof.html b/musicbook/proof.html
new file mode 100644
index 0000000..71821c5
--- /dev/null
+++ b/musicbook/proof.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+Sea Shanties — Music
+
+
+
+
+
Sea Shanties
+
Music Edition
+
+
Staff notation for the crew
+
Chris Glein and Alex Deakin
+
+
+
+
+
+
+
2
+
+
+
+
3
+
+
+
+
4
+
+
+
+
5
+
+
+
+
6
+
+
+
+
7
+
+
+
+
8
+
+
+
+
9
+
+
+
+
10
+
+
+
+
11
+
+
+
+
12
+
+
+
+
13
+
+
+
+
14
+
+
+
+
15
+
+
+
+
16
+
+
diff --git a/musicbook/randy-dandy-o.html b/musicbook/randy-dandy-o.html
new file mode 100644
index 0000000..3d30144
--- /dev/null
+++ b/musicbook/randy-dandy-o.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/roll-the-old-chariot-along.html b/musicbook/roll-the-old-chariot-along.html
new file mode 100644
index 0000000..9decb12
--- /dev/null
+++ b/musicbook/roll-the-old-chariot-along.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/sea-shanties-music.html b/musicbook/sea-shanties-music.html
new file mode 100644
index 0000000..b109b4e
--- /dev/null
+++ b/musicbook/sea-shanties-music.html
@@ -0,0 +1,192 @@
+
+
+
+
+
+Sea Shanties — Music
+
+
+
+
+
+
+
16
+
Sea Shanties
+
Music Edition
+
+
Staff notation for the crew
+
Chris Glein and Alex Deakin
+
+
+
+
+
+
+
2
+
+
+
15
+
+
+
+
14
+
+
+
3
+
+
+
+
4
+
+
+
13
+
+
+
+
12
+
+
+
5
+
+
+
+
6
+
+
+
11
+
+
+
+
10
+
+
+
7
+
+
+
+
8
+
+
+
9
+
+
diff --git a/musicbook/south-australia.html b/musicbook/south-australia.html
new file mode 100644
index 0000000..9ba6177
--- /dev/null
+++ b/musicbook/south-australia.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/wellerman.html b/musicbook/wellerman.html
new file mode 100644
index 0000000..180a91c
--- /dev/null
+++ b/musicbook/wellerman.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/musicbook/whiskey-johnny.html b/musicbook/whiskey-johnny.html
new file mode 100644
index 0000000..f72f7bc
--- /dev/null
+++ b/musicbook/whiskey-johnny.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/package.json b/package.json
index e39c5a7..cab83aa 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,21 @@
{
"scripts": {
- "build": "./songs-to-json && pug-pack src && cp Songbook.pdf dist && cp qr-code.svg dist",
+ "postinstall": "patch-package",
+ "build": "node songs-to-json.js && pug-pack src && node -e \"const fs=require('fs');for(const f of ['Songbook.pdf','qr-code.svg'])fs.cpSync(f,'dist/'+f)\"",
"booklet": "node node_modules/markdown-booklet/src/cli.js build booklet/book.yaml --out booklet/sea-shanties.html",
"booklet:proof": "node node_modules/markdown-booklet/src/cli.js build booklet/book.yaml --reading --out booklet/proof.html",
"booklet:single": "node node_modules/markdown-booklet/src/cli.js build booklet/book.yaml --single --out booklet/sea-shanties-single.html",
+ "musicbook": "node node_modules/markdown-booklet/src/cli.js build musicbook/book.yaml --out musicbook/sea-shanties-music.html",
+ "musicbook:proof": "node node_modules/markdown-booklet/src/cli.js build musicbook/book.yaml --reading --out musicbook/proof.html",
"scaleL": "coffee src/transform.coffee scaleL",
- "dev": "nodemon -w src -w songs -i src/songs.json -i src/by-title.json -e '*' -x npm run build"
+ "dev": "nodemon -w src -w songs -i src/songs.json -i src/by-title.json -e \"*\" -x npm run build"
},
"dependencies": {
"coffeescript": "^2.7.0",
"markdown-booklet": "chrisglein/markdown-booklet",
"mathjs": "^11.10.0",
"nodemon": "^2.0.22",
+ "patch-package": "^8.0.1",
"pug-pack": "brewingcode/pug-pack"
}
}
diff --git a/patches/pug-pack+1.11.0.patch b/patches/pug-pack+1.11.0.patch
new file mode 100644
index 0000000..f07fe03
--- /dev/null
+++ b/patches/pug-pack+1.11.0.patch
@@ -0,0 +1,67 @@
+diff --git a/node_modules/pug-pack/lib/build.coffee b/node_modules/pug-pack/lib/build.coffee
+index da9318b..359c63a 100644
+--- a/node_modules/pug-pack/lib/build.coffee
++++ b/node_modules/pug-pack/lib/build.coffee
+@@ -66,13 +66,18 @@ module.exports = self =
+
+ out
+
++ # compile :coffeescript blocks with our bundled compiler, so the build
++ # doesn't depend on pug hoisting jstransformer-coffeescript to top-level
++ coffeescript: (text, options) ->
++ coffeescript.compile text
++
+ # replacement for path.parse(), but in the context of this module
+ parsename: (f) ->
+ parts = path.parse f
+ parts.ext = parts.ext.replace /^\./, ''
+ parts.absfile = path.resolve self.vars.basedir, f
+ parts.absdir = path.resolve self.vars.basedir
+- parts.srcname = parts.absfile.replace(new RegExp(parts.absdir, 'i'), '').replace(/^\//, '')
++ parts.srcname = path.relative(parts.absdir, parts.absfile).split(path.sep).join('/')
+ self.vars.files[parts.srcname] = parts.absfile
+ parts
+
+@@ -176,23 +181,33 @@ module.exports = self =
+ # 1, for example
+ crawl: (rootDir, skipPug) ->
+ self.vars.basedir = path.resolve rootDir
++ gitOpts = { cwd: self.vars.basedir }
++
++ walkFiles = (dir) ->
++ results = []
++ for entry in fs.readdirSync dir
++ full = path.join dir, entry
++ if fs.statSync(full).isDirectory()
++ results = results.concat walkFiles full
++ else
++ results.push full
++ results
+
+- execAsync("cd '#{self.vars.basedir}' && git rev-parse --short HEAD").then (stdout) =>
++ execAsync("git rev-parse --short HEAD", gitOpts).then (stdout) =>
+ self.vars.src.GIT_HEAD = stdout.toString().trim()
+- .catch ->
++ .catch =>
+ self.vars.src.GIT_HEAD = null
+-
+- if head = self.vars.src.GIT_HEAD
+- execAsync("cd '#{self.vars.basedir}' && git tag --points-at #{head}").then (stdout) =>
++ .then =>
++ return unless head = self.vars.src.GIT_HEAD
++ execAsync("git tag --points-at #{head}", gitOpts).then (stdout) =>
+ self.vars.src.GIT_TAGS = stdout.toString().split('\n').filter (s) -> s.match(/\S/)
+- .catch ->
++ .catch =>
+ self.vars.src.GIT_TAGS = []
+-
+- execAsync("find '#{self.vars.basedir}' -type f -print0").then (stdout) =>
++ .then =>
+ pug_files = []
+ other_files = []
+
+- stdout.split('\0').forEach (f) =>
++ walkFiles(self.vars.basedir).forEach (f) =>
+ return unless f
+ { name, ext, srcname } = self.parsename f
+ if name.match(/^_/)
diff --git a/songs-to-json b/songs-to-json
deleted file mode 100755
index b5e59f6..0000000
--- a/songs-to-json
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python3
-
-import os, json, re
-songs = {}
-byTitle = {}
-for f in os.listdir('songs'):
- if not re.search(r'\.txt$', f, re.I):
- continue
-
- try:
- with open('songs/' + f) as fh:
- song = fh.read()
- except:
- with open('songs/' + f, encoding='latin-1') as fh:
- song = fh.read()
-
- songs[f] = song
-
- m = re.search(r'^T:\s*(.*)', song, re.MULTILINE)
- if m:
- title = m.group(1)
- byTitle[title] = song
-
- else:
- print('weird song:', f)
-with open('src/songs.json', 'w') as fh:
- fh.write(json.dumps(songs))
-with open('src/by-title.json', 'w') as fh:
- fh.write(json.dumps(byTitle))
diff --git a/songs-to-json.js b/songs-to-json.js
new file mode 100644
index 0000000..b2b0632
--- /dev/null
+++ b/songs-to-json.js
@@ -0,0 +1,27 @@
+const fs = require('fs');
+const path = require('path');
+
+const songsDir = 'songs';
+const songs = {};
+const byTitle = {};
+
+for (const f of fs.readdirSync(songsDir)) {
+ if (!/\.txt$/i.test(f)) continue;
+
+ const buf = fs.readFileSync(path.join(songsDir, f));
+ let song = buf.toString('utf8');
+ if (song.includes('\uFFFD')) song = buf.toString('latin1');
+ song = song.replace(/\r\n/g, '\n');
+
+ songs[f] = song;
+
+ const m = song.match(/^T:\s*(.*)/m);
+ if (m) {
+ byTitle[m[1]] = song;
+ } else {
+ console.log('weird song:', f);
+ }
+}
+
+fs.writeFileSync('src/songs.json', JSON.stringify(songs));
+fs.writeFileSync('src/by-title.json', JSON.stringify(byTitle));
diff --git a/src/index.pug b/src/index.pug
index b1a9f16..a8b91c8 100644
--- a/src/index.pug
+++ b/src/index.pug
@@ -22,6 +22,8 @@ append head
button { margin-right: 1ex }
+ body.fullscreen .noprint { display: none !important }
+
@media print {
.noprint { display: none !important }
}
@@ -55,6 +57,10 @@ append body
span Show lyrics
input#show-lyrics(type="checkbox" checked)
span
+ label
+ span Show verses
+ input#show-verses(type="checkbox" checked)
+ span
label
span Show trumpet fingering
input#show-trumpet(type="checkbox")
@@ -100,13 +106,16 @@ append body
s = song
.split /\n/
.filter (line) ->
- if m = line.match /^\s*w:(.*)/i
+ if m = line.match /^\s*w:(.*)/
lyrics = m[1]
if lyrics.match /^[\s\d\?]+$/
- # all digits are trumpet fingering, keep those
- return $('#show-trumpt').is(':checked')
+ # all digits are trumpet fingering
+ return $('#show-trumpet').is(':checked')
else
return $('#show-lyrics').is(':checked')
+ else if line.match /^\s*W:/
+ # W: lines are the extended verses below the music
+ return $('#show-verses').is(':checked')
else
return true
.join('\n')
@@ -158,7 +167,7 @@ append body
$('#toKey').text displayKey()
, 500
- $('#show-lyrics, #show-trumpet').on 'change', ->
+ $('#show-lyrics, #show-verses, #show-trumpet').on 'change', ->
setSong byTitle[document.title]
$('#show-gtab').on 'change', ->
@@ -173,6 +182,17 @@ append body
}
params = new URLSearchParams(window.location.search)
+
+ isFalse = (v) -> v? and v.toLowerCase() in ['false', '0', 'no', 'off']
+ isTrue = (v) -> v? and v.toLowerCase() in ['true', '1', 'yes', 'on']
+
+ if isTrue params.get('fullscreen')
+ $('body').addClass 'fullscreen'
+ if isFalse params.get('lyrics')
+ $('#show-lyrics').prop 'checked', false
+ if isFalse params.get('verses')
+ $('#show-verses').prop 'checked', false
+
file = params.get('file')
title = params.get('title')
if file and songs[file]