Skip to content

Commit

Permalink
Add support for define and chord directives
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnversluis committed Nov 10, 2024
1 parent 9e8318d commit fb0fa99
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 14 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ use those to change the generated output.

### Chord diagrams

| Directive | Support |
|:--------- |:------------------------:|
| define | :heavy_multiplication_x: |
| chord | :heavy_multiplication_x: |
| Directive | Support |
|:--------- |:------------------:|
| define | :heavy_check_mark: |
| chord | :heavy_check_mark: |

### Fonts, sizes and colours

Expand Down
8 changes: 4 additions & 4 deletions doc/README.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ use those to change the generated output.

### Chord diagrams

| Directive | Support |
|:--------- |:------------------------:|
| define | :heavy_multiplication_x: |
| chord | :heavy_multiplication_x: |
| Directive | Support |
|:--------- |:------------------:|
| define | :heavy_check_mark: |
| chord | :heavy_check_mark: |

### Fonts, sizes and colours

Expand Down
20 changes: 20 additions & 0 deletions src/chord_sheet/chord_pro/chord_definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Fret } from '../../constants';

class ChordDefinition {
name: string;

baseFret: number;

frets: Fret[];

fingers: number[];

constructor(name: string, baseFret: number, frets: Fret[], fingers?: number[]) {
this.name = name;
this.baseFret = baseFret;
this.frets = frets;
this.fingers = fingers || [];
}
}

export default ChordDefinition;
3 changes: 3 additions & 0 deletions src/chord_sheet/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
import AstComponent from './ast_component';
import TraceInfo from './trace_info';
import ChordDefinition from './chord_pro/chord_definition';

export const ALBUM = 'album';

Expand Down Expand Up @@ -406,6 +407,8 @@ class Tag extends AstComponent {

_value = '';

chordDefinition?: ChordDefinition;

constructor(name: string, value: string | null = null, traceInfo: TraceInfo | null = null) {
super(traceInfo);
this.parseNameValue(name, value);
Expand Down
33 changes: 31 additions & 2 deletions src/chord_sheet_serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Item from './chord_sheet/item';
import Evaluatable from './chord_sheet/chord_pro/evaluatable';

import {
SerializedChordDefinition,
SerializedChordLyricsPair,
SerializedComment,
SerializedComponent,
Expand All @@ -21,6 +22,7 @@ import {
} from './serialized_types';
import SoftLineBreak from './chord_sheet/soft_line_break';
import { warn } from './utilities';
import ChordDefinition from './chord_sheet/chord_pro/chord_definition';

const CHORD_LYRICS_PAIR = 'chordLyricsPair';
const CHORD_SHEET = 'chordSheet';
Expand Down Expand Up @@ -83,12 +85,27 @@ class ChordSheetSerializer {
throw new Error(`Don't know how to serialize ${item.constructor.name}`);
}

serializeTag(tag: Tag): SerializedTag {
serializeChordDefinition(chordDefinition: ChordDefinition): SerializedChordDefinition {
return {
name: chordDefinition.name,
baseFret: chordDefinition.baseFret,
frets: chordDefinition.frets,
fingers: chordDefinition.fingers,
};
}

serializeTag(tag: Tag): SerializedTag {
const serializedTag: SerializedTag = {
type: TAG,
name: tag.originalName,
value: tag.value,
};

if (tag.chordDefinition) {
serializedTag.chordDefinition = this.serializeChordDefinition(tag.chordDefinition);
}

return serializedTag;
}

serializeChordLyricsPair(chordLyricsPair: ChordLyricsPair) {
Expand Down Expand Up @@ -194,8 +211,20 @@ class ChordSheetSerializer {
name,
value,
location: { offset = null, line = null, column = null } = {},
chordDefinition,
} = astComponent;
return new Tag(name, value, { line, column, offset });
const tag = new Tag(name, value, { line, column, offset });

if (chordDefinition) {
tag.chordDefinition = new ChordDefinition(
chordDefinition.name,
chordDefinition.baseFret,
chordDefinition.frets,
chordDefinition.fingers,
);
}

return tag;
}

parseComment(astComponent: SerializedComment): Comment {
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,9 @@ export const MINOR = 'm';
export const MAJOR = 'M';

export type Mode = 'M' | 'm';

type FretNumber = number;
type OpenFret = '0';
type NonSoundingString = '-1' | 'N' | 'x';

export type Fret = FretNumber | OpenFret | NonSoundingString;
69 changes: 66 additions & 3 deletions src/parser/chord_pro/grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ Line
}

Token
= Tag
= ChordDefinition
/ Tag
/ AnnotationLyricsPair
/ chordLyricsPair:ChordLyricsPair
/ MetaTernary
Expand Down Expand Up @@ -220,6 +221,62 @@ WordChar
return sequence;
}

ChordDefinition
= "{" _ name:("chord" / "define") _ ":" _ value:ChordDefinitionValue _ "}" {
const { text, ...chordDefinition } = value;

return {
type: 'tag',
name,
value: text,
chordDefinition,
location: location().start,
};
}

ChordDefinitionValue
= name:$([A-Za-z0-9]+) _ "base-fret" __ baseFret:FretNumber __ "frets" frets:FretWithLeadingSpace+ fingers:ChordFingersDefinition? {
return { name, baseFret, frets, fingers, text: text() };
}

ChordFingersDefinition
= __ "fingers" fingers:FingerNumberWithLeadingSpace+ {
return fingers;
}

FingerNumberWithLeadingSpace
= __ finger:FingerNumber {
return finger;
}

FingerNumber
= number:[1-9] {
return parseInt(number, 10);
}

FretWithLeadingSpace
= __ fret:Fret {
return fret;
}

Fret
= _ fret:(FretNumber / OpenFret / NonSoundingString) {
return fret;
}

FretNumber
= number:[1-9] {
return parseInt(number, 10);
}

OpenFret
= "0" {
return 0;
}

NonSoundingString
= "-1" / "N" / "x"

Tag
= "{" _ tagName:$(TagName) _ tagColonWithValue: TagColonWithValue? _ "}" {
return {
Expand Down Expand Up @@ -252,8 +309,14 @@ TagValueChar
return sequence;
}

_ "whitespace"
= [ \t\n\r]*
__ "whitespace"
= WhitespaceCharacter+

_ "optional whitespace"
= WhitespaceCharacter*

WhitespaceCharacter
= [ \t\n\r]

Space "space"
= [ \t]+
Expand Down
10 changes: 9 additions & 1 deletion src/serialized_types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChordType, Modifier } from './constants';
import { ChordType, Fret, Modifier } from './constants';

export interface SerializedTraceInfo {
location?: {
Expand Down Expand Up @@ -26,10 +26,18 @@ export interface SerializedChordLyricsPair {
annotation?: string | null,
}

export interface SerializedChordDefinition {
name: string,
baseFret: number,
frets: Fret[],
fingers?: number[],
}

export type SerializedTag = SerializedTraceInfo & {
type: 'tag',
name: string,
value: string,
chordDefinition?: SerializedChordDefinition,
};

export interface SerializedComment {
Expand Down
77 changes: 77 additions & 0 deletions test/parser/chord_pro_parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
LILYPOND,
NONE,
TAB,
Tag,
Ternary,
VERSE,
} from '../../src';
Expand Down Expand Up @@ -652,4 +653,80 @@ Let it [Am]be
expect(items[1]).toBeChordLyricsPair('', 'it be, let it ');
expect(items[2]).toBeChordLyricsPair('C/G', 'be');
});

describe('{define} chord definitions', () => {
it('parses chord definitions with finger numbers', () => {
const chordSheet = '{define: D7 base-fret 3 frets x 3 2 3 1 x fingers 1 2 3 4 5 6 }';

const parser = new ChordProParser();
const song = parser.parse(chordSheet);
const tag = song.lines[0].items[0];
const { chordDefinition } = (tag as Tag);

expect(tag).toBeTag('define', 'D7 base-fret 3 frets x 3 2 3 1 x fingers 1 2 3 4 5 6');

expect(chordDefinition).toEqual({
name: 'D7',
baseFret: 3,
frets: ['x', 3, 2, 3, 1, 'x'],
fingers: [1, 2, 3, 4, 5, 6],
});
});

it('parses chord definitions without finger numbers', () => {
const chordSheet = '{define: D7 base-fret 3 frets x 3 2 3 1 x }';

const parser = new ChordProParser();
const song = parser.parse(chordSheet);
const tag = song.lines[0].items[0];
const { chordDefinition } = (tag as Tag);

expect(tag).toBeTag('define', 'D7 base-fret 3 frets x 3 2 3 1 x');

expect(chordDefinition).toEqual({
name: 'D7',
baseFret: 3,
frets: ['x', 3, 2, 3, 1, 'x'],
fingers: [],
});
});
});

describe('{chord} chord definitions', () => {
it('parses chord definitions with finger numbers', () => {
const chordSheet = '{chord: D7 base-fret 3 frets x 3 2 3 1 x fingers 1 2 3 4 5 6 }';

const parser = new ChordProParser();
const song = parser.parse(chordSheet);
const tag = song.lines[0].items[0];
const { chordDefinition } = (tag as Tag);

expect(tag).toBeTag('chord', 'D7 base-fret 3 frets x 3 2 3 1 x fingers 1 2 3 4 5 6');

expect(chordDefinition).toEqual({
name: 'D7',
baseFret: 3,
frets: ['x', 3, 2, 3, 1, 'x'],
fingers: [1, 2, 3, 4, 5, 6],
});
});

it('parses chord definitions without finger numbers', () => {
const chordSheet = '{chord: D7 base-fret 3 frets x 3 2 3 1 x }';

const parser = new ChordProParser();
const song = parser.parse(chordSheet);
const tag = song.lines[0].items[0];
const { chordDefinition } = (tag as Tag);

expect(tag).toBeTag('chord', 'D7 base-fret 3 frets x 3 2 3 1 x');

expect(chordDefinition).toEqual({
name: 'D7',
baseFret: 3,
frets: ['x', 3, 2, 3, 1, 'x'],
fingers: [],
});
});
});
});

0 comments on commit fb0fa99

Please sign in to comment.