Skip to content

Commit

Permalink
Implement Song#getChordDefinitions()
Browse files Browse the repository at this point in the history
`Song#getChordDefinitions()` returns all chord definitions from the song. Definitions are made using the `{chord}` or `{define}` directive. A chord definitions overrides a previous chord definition for the exact same chord.

See https://chordpro.org/chordpro/directives-define/ and https://chordpro.org/chordpro/directives-chord/
  • Loading branch information
martijnversluis committed Nov 12, 2024
1 parent 6690fe3 commit f596bad
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 4 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ the paragraph contents as one string where lines are separated by newlines.</p>
* [.changeMetadata(name, value)](#Song+changeMetadata)
* [.mapItems(func)](#Song+mapItems)[<code>Song</code>](#Song)
* [.getChords()](#Song+getChords) ⇒ <code>Array.&lt;string&gt;</code>
* [.getChordDefinitions()](#Song+getChordDefinitions) ⇒ <code>Record.&lt;string, ChordDefinition&gt;</code>
* [.mapLines(func)](#Song+mapLines)[<code>Song</code>](#Song)

<a name="new_Song_new"></a>
Expand Down Expand Up @@ -1102,6 +1103,20 @@ song.mapItems((item) => {

**Kind**: instance method of [<code>Song</code>](#Song)
**Returns**: <code>Array.&lt;string&gt;</code> - <p>the chords</p>
<a name="Song+getChordDefinitions"></a>

### song.getChordDefinitions() ⇒ <code>Record.&lt;string, ChordDefinition&gt;</code>
<p>Returns all chord definitions from the song.
Definitions are made using the <code>{chord}</code> or <code>{define}</code> directive.
A chord definitions overrides a previous chord definition for the exact same chord.</p>

**Kind**: instance method of [<code>Song</code>](#Song)
**Returns**: <code>Record.&lt;string, ChordDefinition&gt;</code> - <p>the chord definitions</p>
**See**

- https://chordpro.org/chordpro/directives-define/
- https://chordpro.org/chordpro/directives-chord/

<a name="Song+mapLines"></a>

### song.mapLines(func) ⇒ [<code>Song</code>](#Song)
Expand Down
39 changes: 39 additions & 0 deletions src/chord_sheet/chord_pro/chord_definition.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
import { Fret } from '../../constants';

/**
* Represents a chord definition.
*
* Definitions are made using the `{chord}` or `{define}` directive.
* A chord definitions overrides a previous chord definition for the exact same chord.
*
* @see https://chordpro.org/chordpro/directives-define/
* @see https://chordpro.org/chordpro/directives-chord/
*/
class ChordDefinition {
/**
* The chord name, e.g. `C`, `Dm`, `G7`.
* @type {string}
*/
name: string;

/**
* Defines the offset for the chord, which is the position of the topmost finger.
* The offset must be 1 or higher.
* @type {number}
*/
baseFret: number;

/**
* Defines the string positions.
* Strings are enumerated from left (lowest) to right (highest), as they appear in the chord diagrams.
* Fret positions are relative to the offset minus one, so with base-fret 1 (the default),
* the topmost fret position is 1. With base-fret 3, fret position 1 indicates the 3rd position.
* `0` (zero) denotes an open string. Use `-1`, `N` or `x` to denote a non-sounding string.
* @type {Fret[]}
*/
frets: Fret[];

/**
* defines finger settings. This part may be omitted.
*
* For the frets and the fingers positions, there must be exactly as many positions as there are strings,
* which is 6 by default. For the fingers positions, values corresponding to open or damped strings are ignored.
* Finger settings may be numeric (0 .. 9) or uppercase letters (A .. Z).
* Note that the values -, x, X, and N are used to designate a string without finger setting.
* @type {number[]}
*/
fingers: number[];

constructor(name: string, baseFret: number, frets: Fret[], fingers?: number[]) {
Expand All @@ -15,6 +50,10 @@ class ChordDefinition {
this.frets = frets;
this.fingers = fingers || [];
}

clone(): ChordDefinition {
return new ChordDefinition(this.name, this.baseFret, this.frets, this.fingers);
}
}

export default ChordDefinition;
27 changes: 27 additions & 0 deletions src/chord_sheet/song.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Tag, {
START_OF_CHORUS,
} from './tag';
import SongBuilder from '../song_builder';
import ChordDefinition from './chord_pro/chord_definition';

type EachItemCallback = (_item: Item) => void;

Expand Down Expand Up @@ -441,6 +442,32 @@ Or set the song key before changing key:
return Array.from(chords);
}

/**
* Returns all chord definitions from the song.
* Definitions are made using the `{chord}` or `{define}` directive.
* A chord definitions overrides a previous chord definition for the exact same chord.
* @returns {Record<string, ChordDefinition>} the chord definitions
* @see https://chordpro.org/chordpro/directives-define/
* @see https://chordpro.org/chordpro/directives-chord/
*/
getChordDefinitions(): Record<string, ChordDefinition> {
const chordDefinitions: Record<string, ChordDefinition> = {};

this.foreachItem((item: Item) => {
if (!(item instanceof Tag)) {
return;
}

const { chordDefinition } = (item as Tag);

if (chordDefinition) {
chordDefinitions[chordDefinition.name] = chordDefinition.clone();
}
});

return chordDefinitions;
}

/**
* Change the song contents inline. Return a new {@link Line} to replace it. Return `null` to remove it.
* @example
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Chord from './chord';
import ChordDefinition from './chord_sheet/chord_pro/chord_definition';
import ChordLyricsPair from './chord_sheet/chord_lyrics_pair';
import ChordProFormatter from './formatter/chord_pro_formatter';
import ChordProParser from './parser/chord_pro_parser';
Expand Down Expand Up @@ -30,6 +31,7 @@ import {
} from './constants';

export { default as Chord } from './chord';
export { default as ChordDefinition } from './chord_sheet/chord_pro/chord_definition';
export { default as ChordLyricsPair } from './chord_sheet/chord_lyrics_pair';
export { default as ChordProFormatter } from './formatter/chord_pro_formatter';
export { default as ChordProParser } from './parser/chord_pro_parser';
Expand Down Expand Up @@ -73,6 +75,7 @@ export {
export default {
CHORUS,
Chord,
ChordDefinition,
ChordLyricsPair,
ChordProFormatter,
ChordProParser,
Expand Down
44 changes: 43 additions & 1 deletion test/chord_sheet/song.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ChordLyricsPair, ChordSheetSerializer, Tag } from '../../src';
import { createChordLyricsPair, createLine, createSong } from '../utilities';

import {
createChordDefinition,
createChordLyricsPair,
createLine,
createSong, createTag,
} from '../utilities';

import { exampleSongSolfege, exampleSongSymbol } from '../fixtures/song';
import { serializedSongSolfege, serializedSongSymbol } from '../fixtures/serialized_song';
Expand Down Expand Up @@ -318,4 +324,40 @@ describe('Song', () => {
expect(song.getChords()).toEqual([]);
});
});

describe('#getChordDefinitions', () => {
it('returns the unique chord definitions in a song', () => {
const cm7 = createChordDefinition('CM7', 3, ['x', '0', 1]);
const dm = createChordDefinition('Dm', 3, ['x', 3, 5]);

const song = createSong([
createLine([
createTag('chord', 'CM7', cm7),
]),
createLine([]),
createLine([
createTag('define', 'Dm', dm),
]),
]);

expect(song.getChordDefinitions()).toEqual({
CM7: cm7,
Dm: dm,
});
});

it('returns an empty array if there are no chords in the song', () => {
const song = createSong([
createLine([
createTag('chord', 'CM7'),
]),
createLine([]),
createLine([
createChordLyricsPair('Am', 'be'),
]),
]);

expect(song.getChordDefinitions()).toEqual({});
});
});
});
3 changes: 3 additions & 0 deletions test/exports.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ChordSheetJS, {
Chord,
ChordDefinition,
ChordLyricsPair,
ChordProFormatter,
ChordProParser,
Expand Down Expand Up @@ -54,6 +55,7 @@ const {
describe('exports', () => {
it('supplies all required constants as named exports', () => {
expect(Chord).toBeDefined();
expect(ChordDefinition).toBeDefined();
expect(ChordLyricsPair).toBeDefined();
expect(ChordProFormatter).toBeDefined();
expect(ChordProParser).toBeDefined();
Expand Down Expand Up @@ -106,6 +108,7 @@ describe('exports', () => {
});

it('supplies all constants as properties of the default export', () => {
expect(ChordSheetJS.ChordDefinition).toBeDefined();
expect(ChordSheetJS.ChordProParser).toBeDefined();
expect(ChordSheetJS.ChordSheetParser).toBeDefined();
expect(ChordSheetJS.ChordsOverWordsParser).toBeDefined();
Expand Down
13 changes: 10 additions & 3 deletions test/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { LineType } from '../src/chord_sheet/line';
import Metadata from '../src/chord_sheet/metadata';
import { TernaryProperties } from '../src/chord_sheet/chord_pro/ternary';
import Item from '../src/chord_sheet/item';
import { ChordType, Modifier } from '../src/constants';
import { ChordType, Fret, Modifier } from '../src/constants';
import Key from '../src/key';
import ChordSheetSerializer from '../src/chord_sheet_serializer';
import ChordDefinition from '../src/chord_sheet/chord_pro/chord_definition';

import {
ContentType,
Expand Down Expand Up @@ -68,8 +69,14 @@ export function createChordLyricsPair(chords, lyrics) {
return new ChordLyricsPair(chords, lyrics);
}

export function createTag(name: string, value = '') {
return new Tag(name, value);
export function createTag(name: string, value = '', chordDefinition: ChordDefinition | null = null): Tag {
const newTag = new Tag(name, value);
if (chordDefinition) newTag.chordDefinition = chordDefinition;
return newTag;
}

export function createChordDefinition(name: string, baseFret: number, frets: Fret[], fingers: number[] = []) {
return new ChordDefinition(name, baseFret, frets, fingers);
}

export function createComposite(expressions) {
Expand Down

0 comments on commit f596bad

Please sign in to comment.