songsheet 7.1.0 → 7.2.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/package.json +1 -1
- package/src/playback.js +38 -6
- package/test/playback.test.js +14 -0
package/package.json
CHANGED
package/src/playback.js
CHANGED
|
@@ -63,6 +63,43 @@ function detectInterBarlines(markers) {
|
|
|
63
63
|
return false
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Detect consecutive barlines (e.g., C || D), which usually indicate
|
|
68
|
+
* repeated held measures rather than explicit bar-delimited grouping.
|
|
69
|
+
*/
|
|
70
|
+
function hasConsecutiveBars(markers) {
|
|
71
|
+
let prevWasBar = false
|
|
72
|
+
for (const m of markers) {
|
|
73
|
+
if (m.type === 'bar') {
|
|
74
|
+
if (prevWasBar) return true
|
|
75
|
+
prevWasBar = true
|
|
76
|
+
} else {
|
|
77
|
+
prevWasBar = false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Decide whether a line should be grouped by explicit barline boundaries.
|
|
85
|
+
*
|
|
86
|
+
* Heuristic:
|
|
87
|
+
* - require at least one barline between chords
|
|
88
|
+
* - require barline density to be at least chord density
|
|
89
|
+
* - reject consecutive barlines (treated as hold repeats)
|
|
90
|
+
*
|
|
91
|
+
* Sparse barlines like "C C/B Am G F | Fsus4 F" stay in chord-per-measure mode.
|
|
92
|
+
*/
|
|
93
|
+
function shouldGroupByBarlines(markers) {
|
|
94
|
+
const chordCount = markers.filter(m => m.type === 'chord').length
|
|
95
|
+
const barCount = markers.length - chordCount
|
|
96
|
+
if (chordCount === 0 || barCount === 0) return false
|
|
97
|
+
if (!detectInterBarlines(markers)) return false
|
|
98
|
+
if (barCount < chordCount) return false
|
|
99
|
+
if (hasConsecutiveBars(markers)) return false
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
66
103
|
/**
|
|
67
104
|
* Build the playback timeline from the song's structure array.
|
|
68
105
|
*
|
|
@@ -94,12 +131,7 @@ export function buildPlaybackTimeline(structure, timeSignature) {
|
|
|
94
131
|
}
|
|
95
132
|
markers.sort((a, b) => a.col - b.col)
|
|
96
133
|
|
|
97
|
-
|
|
98
|
-
// Pattern: chord...bar...chord means barlines are measure separators.
|
|
99
|
-
// Pattern: chord chord... bar (trailing only) means barlines are phrase markers.
|
|
100
|
-
const hasInterBarlines = detectInterBarlines(markers)
|
|
101
|
-
|
|
102
|
-
if (hasInterBarlines) {
|
|
134
|
+
if (shouldGroupByBarlines(markers)) {
|
|
103
135
|
// Group chords by barline boundaries
|
|
104
136
|
let currentChords = []
|
|
105
137
|
for (const m of markers) {
|
package/test/playback.test.js
CHANGED
|
@@ -61,6 +61,20 @@ describe('buildPlaybackTimeline', () => {
|
|
|
61
61
|
expect(playback[1].chords[0].root).toBe('G')
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
it('sparse inter-barline usage stays chord-per-measure with bar repeat', () => {
|
|
65
|
+
const song = parse('TITLE\n(3/4 time)\n\nC C/B Am G F | Fsus4 F\n Lyrics')
|
|
66
|
+
const playback = song.playback
|
|
67
|
+
expect(playback.length).toBe(8)
|
|
68
|
+
const roots = playback.map(m => m.chords[0].root + (m.chords[0].bass ? '/' + m.chords[0].bass : ''))
|
|
69
|
+
expect(roots).toEqual(['C', 'C/B', 'A', 'G', 'F', 'F', 'F', 'F'])
|
|
70
|
+
for (const m of playback) {
|
|
71
|
+
expect(m.chords.length).toBe(1)
|
|
72
|
+
expect(m.chords[0].durationInBeats).toBe(3)
|
|
73
|
+
}
|
|
74
|
+
expect(playback[6].chords[0].type).toBe('sus4')
|
|
75
|
+
expect(playback[7].chords[0].type).toBe('')
|
|
76
|
+
})
|
|
77
|
+
|
|
64
78
|
it('D | creates 2 measures: D then barline repeats D', () => {
|
|
65
79
|
const song = parse('TITLE\n\nD |\n Lyrics')
|
|
66
80
|
const playback = song.playback
|