Skip to content

Commit e444d34

Browse files
author
Brian Kelly
authored
Adds support for Measures, Tablature and Comments (#25)
* Adds support for Measures, Tablature and Comments * Use simpler looping
1 parent 0ca06e5 commit e444d34

File tree

4 files changed

+128
-27
lines changed

4 files changed

+128
-27
lines changed

src/Line.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
export class Line {
22
constructor() {
33
this.parts = [];
4+
this.measures = null;
5+
this.tablature = null;
6+
this.comment = null;
7+
}
8+
9+
hasTablature() {
10+
return this.tablature != null;
11+
}
12+
13+
hasMeasures() {
14+
return this.measures != null;
15+
}
16+
17+
hasComment() {
18+
return this.comment != null;
419
}
520
}

src/Measure.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class Measure {
2+
constructor() {
3+
this.chords = [];
4+
}
5+
}

src/SongPro.js

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ import { Section } from "./Section";
33
import { Line } from "./Line";
44
import { chunk, flatten, trim } from "lodash";
55
import { Part } from "./Part";
6+
import { Measure } from "./Measure";
7+
8+
const SECTION_REGEX = /#\s*([^$]*)/;
9+
const ATTRIBUTE_REGEX = /@(\w*)=([^%]*)/;
10+
const CUSTOM_ATTRIBUTE_REGEX = /!(\w*)=([^%]*)/;
11+
const CHORDS_AND_LYRICS_REGEX = /(\[[\w#b/]+])?([\w\s',.!()_\-"]*)/gi;
12+
13+
const MEASURES_REGEX = /([[\w#b/\]+\]\s]+)[|]*/gi;
14+
const CHORDS_REGEX = /\[([\w#b+/]+)]?/gi;
15+
const COMMENT_REGEX = />\s*([^$]*)/;
616

717
export class SongPro {
818
static parse(text) {
@@ -11,9 +21,7 @@ export class SongPro {
1121

1222
const lines = text.split("\n");
1323

14-
for (let i = 0; i < lines.length; i++) {
15-
const text = lines[i];
16-
24+
for (const text of lines) {
1725
if (text.startsWith("@")) {
1826
this.processAttribute(song, text);
1927
} else if (text.startsWith("!")) {
@@ -29,7 +37,7 @@ export class SongPro {
2937
}
3038

3139
static processAttribute(song, line) {
32-
const matches = /@(\w*)=([^%]*)/.exec(line);
40+
const matches = ATTRIBUTE_REGEX.exec(line);
3341

3442
if (matches == null) {
3543
return;
@@ -39,7 +47,7 @@ export class SongPro {
3947
}
4048

4149
static processCustomAttribute(song, line) {
42-
const matches = /!(\w*)=([^%]*)/.exec(line);
50+
const matches = CUSTOM_ATTRIBUTE_REGEX.exec(line);
4351

4452
if (matches == null) {
4553
return;
@@ -49,7 +57,7 @@ export class SongPro {
4957
}
5058

5159
static processSection(song, line) {
52-
const matches = /#\s*([^$]*)/.exec(line);
60+
const matches = SECTION_REGEX.exec(line);
5361

5462
if (matches == null) {
5563
return;
@@ -74,33 +82,60 @@ export class SongPro {
7482

7583
const line = new Line();
7684

77-
let captures = this.scan(text, /(\[[\w#b/]+])?([\w\s',.!()_\-"]*)/gi);
78-
captures = flatten(captures);
79-
const groups = chunk(captures, 2);
85+
if (text.startsWith("|-")) {
86+
line.tablature = text;
87+
} else if (text.startsWith("| ")) {
88+
let captures = this.scan(text, MEASURES_REGEX);
89+
captures = flatten(captures);
8090

81-
for (let i = 0; i < groups.length; i++) {
82-
const group = groups[i];
83-
let chord = group[0];
84-
let lyric = group[1];
91+
const measures = [];
8592

86-
const part = new Part();
93+
for (const capture of captures) {
94+
let chords = this.scan(capture, CHORDS_REGEX);
95+
chords = flatten(chords);
8796

88-
if (chord) {
89-
chord = chord.replace("[", "").replace("]", "");
97+
const measure = new Measure();
98+
measure.chords = chords;
99+
measures.push(measure);
90100
}
91101

92-
if (chord === undefined) {
93-
chord = "";
94-
}
95-
if (lyric === undefined) {
96-
lyric = "";
102+
line.measures = measures;
103+
} else if (text.startsWith(">")) {
104+
const matches = COMMENT_REGEX.exec(text);
105+
106+
if (matches == null) {
107+
return;
97108
}
98109

99-
part.chord = trim(chord);
100-
part.lyric = trim(lyric);
110+
line.comment = matches[1].trim();
111+
} else {
112+
let captures = this.scan(text, CHORDS_AND_LYRICS_REGEX);
113+
captures = flatten(captures);
114+
const groups = chunk(captures, 2);
115+
116+
for (const group of groups) {
117+
let chord = group[0];
118+
let lyric = group[1];
119+
120+
const part = new Part();
121+
122+
if (chord) {
123+
chord = chord.replace("[", "").replace("]", "");
124+
}
125+
126+
if (chord === undefined) {
127+
chord = "";
128+
}
129+
if (lyric === undefined) {
130+
lyric = "";
131+
}
132+
133+
part.chord = trim(chord);
134+
part.lyric = trim(lyric);
101135

102-
if (!(part.chord === "" && part.lyric === "")) {
103-
line.parts.push(part);
136+
if (!(part.chord === "" && part.lyric === "")) {
137+
line.parts.push(part);
138+
}
104139
}
105140
}
106141

src/SongPro.test.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SongPro } from "./SongPro";
22

3-
test("attribute parsing", () => {
3+
test("attributes", () => {
44
const song = SongPro.parse(`
55
@title=Bad Moon Rising
66
@artist=Creedence Clearwater Revival
@@ -22,7 +22,7 @@ test("attribute parsing", () => {
2222
expect(song.attrs.tuning).toEqual("Eb Standard");
2323
});
2424

25-
test("custom attribute parsing", () => {
25+
test("custom attributes", () => {
2626
const song = SongPro.parse(`
2727
!difficulty=Easy
2828
!spotify_url=https://open.spotify.com/track/5zADxJhJEzuOstzcUtXlXv?si=SN6U1oveQ7KNfhtD2NHf9A
@@ -103,3 +103,49 @@ test("lyrics before chords", () => {
103103
"bound to take your life"
104104
);
105105
});
106+
107+
test("measures", () => {
108+
const song = SongPro.parse(`
109+
# Instrumental
110+
111+
| [A] [B] | [C] | [D] [E] [F] [G] |
112+
`);
113+
114+
expect(song.sections.length).toEqual(1);
115+
expect(song.sections[0].lines[0].hasMeasures()).toEqual(true);
116+
expect(song.sections[0].lines[0].measures.length).toEqual(3);
117+
expect(song.sections[0].lines[0].measures[0].chords).toEqual(["A", "B"]);
118+
expect(song.sections[0].lines[0].measures[1].chords).toEqual(["C"]);
119+
expect(song.sections[0].lines[0].measures[2].chords).toEqual([
120+
"D",
121+
"E",
122+
"F",
123+
"G",
124+
]);
125+
});
126+
127+
test("tablature", () => {
128+
const song = SongPro.parse(`
129+
# Riff
130+
131+
|-3---5-|
132+
|---4---|
133+
`);
134+
expect(song.sections.length).toEqual(1);
135+
expect(song.sections[0].lines[0].hasTablature()).toEqual(true);
136+
expect(song.sections[0].lines[0].tablature).toEqual("|-3---5-|");
137+
expect(song.sections[0].lines[1].hasTablature()).toEqual(true);
138+
expect(song.sections[0].lines[1].tablature).toEqual("|---4---|");
139+
});
140+
141+
test("comments", () => {
142+
const song = SongPro.parse(`
143+
# Comment
144+
145+
> This is a comment.
146+
`);
147+
148+
expect(song.sections.length).toEqual(1);
149+
expect(song.sections[0].lines[0].hasComment()).toEqual(true);
150+
expect(song.sections[0].lines[0].comment).toEqual("This is a comment.");
151+
});

0 commit comments

Comments
 (0)