songsheet 7.2.0 → 7.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/package.json +1 -1
- package/src/notation.js +3 -1
- package/src/playback.js +28 -11
- package/src/transpose.js +3 -1
- package/test/playback.test.js +26 -0
package/package.json
CHANGED
package/src/notation.js
CHANGED
|
@@ -173,7 +173,9 @@ function convertSong(song, key, toNNS) {
|
|
|
173
173
|
...measure,
|
|
174
174
|
chords: measure.chords.map(c => {
|
|
175
175
|
const converted = convertChord(c, keySemitone, toNNS, preferFlats)
|
|
176
|
-
|
|
176
|
+
const playbackChord = { ...converted, beatStart: c.beatStart, durationInBeats: c.durationInBeats }
|
|
177
|
+
if (c.markerIndex !== undefined) playbackChord.markerIndex = c.markerIndex
|
|
178
|
+
return playbackChord
|
|
177
179
|
}),
|
|
178
180
|
}))
|
|
179
181
|
: undefined
|
package/src/playback.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Expand a chord into PlaybackChord(s). If the chord has a splitMeasure,
|
|
3
3
|
* each sub-chord becomes a separate PlaybackChord; otherwise a single one.
|
|
4
4
|
*/
|
|
5
|
-
function expandChord(chord) {
|
|
5
|
+
function expandChord(chord, markerIndex) {
|
|
6
6
|
if (chord.splitMeasure && chord.splitMeasure.length > 0) {
|
|
7
7
|
return chord.splitMeasure.map(sc => {
|
|
8
8
|
const pc = { root: sc.root, type: sc.type }
|
|
@@ -11,6 +11,7 @@ function expandChord(chord) {
|
|
|
11
11
|
if (sc.diamond) pc.diamond = true
|
|
12
12
|
if (sc.push) pc.push = true
|
|
13
13
|
if (sc.stop) pc.stop = true
|
|
14
|
+
if (markerIndex !== undefined) pc.markerIndex = markerIndex
|
|
14
15
|
return pc
|
|
15
16
|
})
|
|
16
17
|
}
|
|
@@ -20,6 +21,7 @@ function expandChord(chord) {
|
|
|
20
21
|
if (chord.diamond) pc.diamond = true
|
|
21
22
|
if (chord.push) pc.push = true
|
|
22
23
|
if (chord.stop) pc.stop = true
|
|
24
|
+
if (markerIndex !== undefined) pc.markerIndex = markerIndex
|
|
23
25
|
return [pc]
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -127,16 +129,17 @@ export function buildPlaybackTimeline(structure, timeSignature) {
|
|
|
127
129
|
markers.push({ col: chord.column, type: 'chord', chord })
|
|
128
130
|
}
|
|
129
131
|
for (const bar of line.barLines) {
|
|
130
|
-
markers.push({ col: bar.column, type: 'bar' })
|
|
132
|
+
markers.push({ col: bar.column, type: 'bar', bar })
|
|
131
133
|
}
|
|
132
134
|
markers.sort((a, b) => a.col - b.col)
|
|
133
135
|
|
|
134
136
|
if (shouldGroupByBarlines(markers)) {
|
|
135
137
|
// Group chords by barline boundaries
|
|
136
138
|
let currentChords = []
|
|
137
|
-
for (
|
|
139
|
+
for (let mi = 0; mi < markers.length; mi++) {
|
|
140
|
+
const m = markers[mi]
|
|
138
141
|
if (m.type === 'chord') {
|
|
139
|
-
currentChords.push(...expandChord(m.chord))
|
|
142
|
+
currentChords.push(...expandChord(m.chord, mi))
|
|
140
143
|
} else {
|
|
141
144
|
// Bar line: flush accumulated chords as a measure
|
|
142
145
|
if (currentChords.length > 0) {
|
|
@@ -156,22 +159,36 @@ export function buildPlaybackTimeline(structure, timeSignature) {
|
|
|
156
159
|
// No inter-barlines: each chord is its own measure.
|
|
157
160
|
// Trailing barlines repeat the preceding chord for one additional measure.
|
|
158
161
|
let lastExpanded = null
|
|
159
|
-
for (
|
|
162
|
+
for (let mi = 0; mi < markers.length; mi++) {
|
|
163
|
+
const m = markers[mi]
|
|
160
164
|
if (m.type === 'chord') {
|
|
161
|
-
lastExpanded = expandChord(m.chord)
|
|
165
|
+
lastExpanded = expandChord(m.chord, mi)
|
|
162
166
|
measures.push(buildMeasure(lastExpanded, measureIndex, si, li, ts))
|
|
163
167
|
measureIndex++
|
|
164
|
-
} else if (m.type === 'bar'
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
} else if (m.type === 'bar') {
|
|
169
|
+
// Use parser-provided bar chord context when available.
|
|
170
|
+
// This enables leading bars on a new line (e.g. "| | G |")
|
|
171
|
+
// to repeat the previous line's carried chord.
|
|
172
|
+
let repeatedAtBar = null
|
|
173
|
+
if (m.bar && m.bar.chord) {
|
|
174
|
+
repeatedAtBar = expandChord(m.bar.chord, mi)
|
|
175
|
+
} else if (lastExpanded) {
|
|
176
|
+
repeatedAtBar = lastExpanded.map(c => ({ ...c, markerIndex: mi }))
|
|
177
|
+
}
|
|
178
|
+
if (repeatedAtBar) {
|
|
179
|
+
measures.push(buildMeasure(repeatedAtBar, measureIndex, si, li, ts))
|
|
180
|
+
measureIndex++
|
|
181
|
+
lastExpanded = repeatedAtBar
|
|
182
|
+
}
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
187
|
} else {
|
|
172
188
|
// Expression-only entries (FILL, INSTRUMENTAL, etc.) — chords only, no lines
|
|
173
|
-
for (
|
|
174
|
-
const
|
|
189
|
+
for (let ci = 0; ci < entry.chords.length; ci++) {
|
|
190
|
+
const chord = entry.chords[ci]
|
|
191
|
+
const expanded = expandChord(chord, ci)
|
|
175
192
|
measures.push(buildMeasure(expanded, measureIndex, si, -1, ts))
|
|
176
193
|
measureIndex++
|
|
177
194
|
}
|
package/src/transpose.js
CHANGED
|
@@ -114,7 +114,9 @@ export function transpose(song, semitones, options = {}) {
|
|
|
114
114
|
...measure,
|
|
115
115
|
chords: measure.chords.map(c => {
|
|
116
116
|
const transposed = transposeChordObj(c, semitones, preferFlats)
|
|
117
|
-
|
|
117
|
+
const playbackChord = { ...transposed, beatStart: c.beatStart, durationInBeats: c.durationInBeats }
|
|
118
|
+
if (c.markerIndex !== undefined) playbackChord.markerIndex = c.markerIndex
|
|
119
|
+
return playbackChord
|
|
118
120
|
}),
|
|
119
121
|
}))
|
|
120
122
|
: undefined
|
package/test/playback.test.js
CHANGED
|
@@ -75,6 +75,18 @@ describe('buildPlaybackTimeline', () => {
|
|
|
75
75
|
expect(playback[7].chords[0].type).toBe('')
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
+
it('leading bars on next line repeat carried chord context', () => {
|
|
79
|
+
const song = parse(
|
|
80
|
+
'TITLE\n(3/4 time)\n\nC | F C\nA way out on-line\n | | G |\nA thousand miles from what I know as mine'
|
|
81
|
+
)
|
|
82
|
+
const playback = song.playback
|
|
83
|
+
const roots = playback.map(m => m.chords[0].root + (m.chords[0].bass ? '/' + m.chords[0].bass : ''))
|
|
84
|
+
expect(roots).toEqual(['C', 'C', 'F', 'C', 'C', 'C', 'G', 'G'])
|
|
85
|
+
for (const m of playback) {
|
|
86
|
+
expect(m.chords[0].durationInBeats).toBe(3)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
78
90
|
it('D | creates 2 measures: D then barline repeats D', () => {
|
|
79
91
|
const song = parse('TITLE\n\nD |\n Lyrics')
|
|
80
92
|
const playback = song.playback
|
|
@@ -171,6 +183,20 @@ describe('buildPlaybackTimeline', () => {
|
|
|
171
183
|
expect(song.playback[0].measureIndex).toBe(0)
|
|
172
184
|
expect(song.playback[1].measureIndex).toBe(1)
|
|
173
185
|
})
|
|
186
|
+
|
|
187
|
+
it('assigns playback markerIndex values for chords and bar repeats', () => {
|
|
188
|
+
const song = parse('TITLE\n\nC D |\n Lyrics')
|
|
189
|
+
expect(song.playback[0].chords[0].markerIndex).toBe(0) // C marker
|
|
190
|
+
expect(song.playback[1].chords[0].markerIndex).toBe(1) // D marker
|
|
191
|
+
expect(song.playback[2].chords[0].markerIndex).toBe(2) // trailing bar marker
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('assigns markerIndex for grouped barline measures', () => {
|
|
195
|
+
const song = parse('TITLE\n\n| C D | G |\n Lyrics')
|
|
196
|
+
expect(song.playback[0].chords[0].markerIndex).toBe(1) // C marker
|
|
197
|
+
expect(song.playback[0].chords[1].markerIndex).toBe(2) // D marker
|
|
198
|
+
expect(song.playback[1].chords[0].markerIndex).toBe(4) // G marker
|
|
199
|
+
})
|
|
174
200
|
})
|
|
175
201
|
|
|
176
202
|
describe('fixture: spent-some-time-in-buffalo.txt', () => {
|