songsheet 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -19,9 +19,14 @@ export interface Character {
19
19
  barLine?: true
20
20
  }
21
21
 
22
+ export interface BarLine {
23
+ column: number
24
+ chord?: Chord
25
+ }
26
+
22
27
  export interface Line {
23
28
  chords: PositionedChord[]
24
- barLines: number[]
29
+ barLines: BarLine[]
25
30
  lyrics: string
26
31
  characters: Character[]
27
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "songsheet",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "A songsheet interpreter",
5
5
  "type": "module",
6
6
  "exports": "./index.js",
package/src/parser.js CHANGED
@@ -158,8 +158,10 @@ function collectExprMarkers(expr, sections) {
158
158
  if (chord.splitMeasure) marker.splitMeasure = chord.splitMeasure
159
159
  lineMarkers.push(marker)
160
160
  }
161
- for (const col of line.barLines) {
162
- lineMarkers.push({ col, type: 'bar' })
161
+ for (const bar of line.barLines) {
162
+ const marker = { col: bar.column, type: 'bar' }
163
+ if (bar.chord) marker.chord = bar.chord
164
+ lineMarkers.push(marker)
163
165
  }
164
166
  lineMarkers.sort((a, b) => a.col - b.col)
165
167
  result.push(...lineMarkers)
@@ -185,6 +187,7 @@ function compactMarkersToLine(markers) {
185
187
  const chords = []
186
188
  const barLines = []
187
189
  let col = 0
190
+ let lastChord = null
188
191
  for (const m of markers) {
189
192
  if (col > 0) col += 1
190
193
  if (m.type === 'chord') {
@@ -197,9 +200,15 @@ function compactMarkersToLine(markers) {
197
200
  if (m.stop) chord.stop = true
198
201
  if (m.splitMeasure) chord.splitMeasure = m.splitMeasure
199
202
  chords.push(chord)
203
+ lastChord = { root: m.root, type: m.quality }
204
+ if (m.bass) lastChord.bass = m.bass
205
+ if (m.nashville) lastChord.nashville = true
200
206
  col += name.length
201
207
  } else {
202
- barLines.push(col)
208
+ const bar = { column: col }
209
+ if (m.chord) bar.chord = m.chord
210
+ else if (lastChord) bar.chord = lastChord
211
+ barLines.push(bar)
203
212
  col += 1
204
213
  }
205
214
  }
@@ -251,6 +260,7 @@ function parseChordLyricBlock(text) {
251
260
  const allChords = []
252
261
  const allLyrics = []
253
262
  let i = 0
263
+ let lastChord = null
254
264
 
255
265
  while (i < rawLines.length) {
256
266
  const line = rawLines[i]
@@ -260,7 +270,23 @@ function parseChordLyricBlock(text) {
260
270
  // This is a chord line — next non-chord line is its paired lyric
261
271
  const chordTokens = tokens
262
272
  const chords = tokens.filter(t => t.type === 'CHORD').map(t => tokenToPositionedChord(t))
263
- const barLines = tokens.filter(t => t.type === 'BAR_LINE').map(t => t.column)
273
+
274
+ // Build barLines as objects with chord context
275
+ // Sort all tokens by column, walk left-to-right tracking currentChord
276
+ const sortedTokens = [...tokens].sort((a, b) => a.column - b.column)
277
+ let currentChord = lastChord
278
+ const barLines = []
279
+ for (const tok of sortedTokens) {
280
+ if (tok.type === 'CHORD') {
281
+ currentChord = tokenToChord(tok)
282
+ } else if (tok.type === 'BAR_LINE') {
283
+ const bar = { column: tok.column }
284
+ if (currentChord) bar.chord = currentChord
285
+ barLines.push(bar)
286
+ }
287
+ }
288
+ // Update lastChord for the next line
289
+ if (currentChord) lastChord = currentChord
264
290
 
265
291
  i++
266
292
  // Find the paired lyric line (next line that isn't a chord line)
@@ -306,7 +306,12 @@ describe('bar line parsing', () => {
306
306
  it('parses bar lines in chord lines', () => {
307
307
  const song = parse('TEST SONG - AUTHOR\n\n| G | C | D |\nSome lyrics here')
308
308
  const line = song.structure[0].lines[0]
309
- expect(line.barLines).toEqual([0, 4, 8, 12])
309
+ expect(line.barLines).toEqual([
310
+ { column: 0 },
311
+ { column: 4, chord: { root: 'G', type: '' } },
312
+ { column: 8, chord: { root: 'C', type: '' } },
313
+ { column: 12, chord: { root: 'D', type: '' } },
314
+ ])
310
315
  expect(line.chords.map(c => c.root)).toEqual(['G', 'C', 'D'])
311
316
  })
312
317
 
@@ -316,4 +321,16 @@ describe('bar line parsing', () => {
316
321
  expect(chars[0].barLine).toBe(true)
317
322
  expect(chars[4].barLine).toBe(true)
318
323
  })
324
+
325
+ it('carries chord across lines for leading bar lines', () => {
326
+ const song = parse('TEST SONG - AUTHOR\n\n C | F C\nline one\n | | G |\nline two')
327
+ const lines = song.structure[0].lines
328
+ // Line 1: C at col 2, | at col 6, F at col 10, C at col 13
329
+ expect(lines[0].chords.map(c => c.root)).toEqual(['C', 'F', 'C'])
330
+ // Line 2 starts with | — should carry C from end of line 1
331
+ expect(lines[1].barLines[0].chord).toEqual({ root: 'C', type: '' })
332
+ expect(lines[1].barLines[1].chord).toEqual({ root: 'C', type: '' })
333
+ // After G chord, the trailing | should carry G
334
+ expect(lines[1].barLines[2].chord).toEqual({ root: 'G', type: '' })
335
+ })
319
336
  })