Skip to content

Commit

Permalink
Merge pull request #651 from no-chris/fix-repeat-directives
Browse files Browse the repository at this point in the history
Do not repeat trailing directives for a section
  • Loading branch information
no-chris committed Jan 24, 2024
2 parents 5f251ec + 8edccd0 commit d338c3e
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 46 deletions.
13 changes: 13 additions & 0 deletions packages/chord-mark/src/parser/helper/songs.js
@@ -1,4 +1,5 @@
import _cloneDeep from 'lodash/cloneDeep';
import _last from 'lodash/last';

import lineTypes from '../lineTypes';

Expand Down Expand Up @@ -85,5 +86,17 @@ export function getNthOfLabel(allLines, label, n) {
selected.push(line);
}
});

// remmove trailing directive lines
// because they likely apply to the next section
/**/
while (
_last(selected) &&
(_last(selected).type === lineTypes.KEY_DECLARATION ||
_last(selected).type === lineTypes.TIME_SIGNATURE)
) {
selected.pop();
}
/** */
return selected;
}
47 changes: 18 additions & 29 deletions packages/chord-mark/src/parser/songLinesFactory.js
Expand Up @@ -82,9 +82,7 @@ export default function songLinesFactory() {

let blueprint = [];
let blueprintIndex = 0;
let blueprintLine = '';

let isRepeatingChords = false;
let shouldMultiplySection = false;
let shouldCopySection = false;

Expand Down Expand Up @@ -141,13 +139,12 @@ export default function songLinesFactory() {
shouldMultiplySection = currentSection.multiplyTimes > 0;
previousSectionLabelLine = _cloneDeep(line);

if (!isFirstOfLabel(currentSection, allLines)) {
blueprint = getNthOfLabel(allLines, currentSection.label, 1);
blueprintIndex = 0;
isRepeatingChords = true;
} else {
isRepeatingChords = false;
}
blueprint =
currentSectionStats.count > 1
? getNthOfLabel(allLines, currentSection.label, 1)
: [];
blueprintIndex = 0;

return line;
}

Expand Down Expand Up @@ -239,8 +236,8 @@ export default function songLinesFactory() {
}

function repeatLinesFromBlueprint(line) {
if (isRepeatingChords && line.type !== lineTypes.SECTION_LABEL) {
blueprintLine = blueprint[blueprintIndex];
if (blueprint.length && line.type !== lineTypes.SECTION_LABEL) {
let blueprintLine = blueprint[blueprintIndex];
let repeatedLine;

while (shouldRepeatLineFromBlueprint(blueprintLine, line)) {
Expand All @@ -259,6 +256,16 @@ export default function songLinesFactory() {
}
}

function shouldRepeatLineFromBlueprint(blueprintLine, currentLine) {
const nonRepeatableLinesTypes = [lineTypes.LYRIC, lineTypes.EMPTY_LINE];
return (
blueprintLine &&
!nonRepeatableLinesTypes.includes(blueprintLine.type) &&
blueprintLine.type !== currentLine.type &&
currentLine.type !== lineTypes.EMPTY_LINE
);
}

function copySection() {
if (shouldCopySection) {
const toCopy = getNthOfLabel(
Expand Down Expand Up @@ -399,24 +406,6 @@ export default function songLinesFactory() {
};
}

function isFirstOfLabel(currentLabel, allLines) {
return allLines.every(
(line) =>
line.type === lineTypes.SECTION_LABEL &&
line.model.label !== currentLabel.label
);
}

function shouldRepeatLineFromBlueprint(blueprintLine, currentLine) {
return (
blueprintLine &&
blueprintLine.type !== lineTypes.LYRIC &&
blueprintLine.type !== lineTypes.EMPTY_LINE &&
blueprintLine.type !== currentLine.type &&
currentLine.type !== lineTypes.EMPTY_LINE
);
}

function isLastLineOfSection(lineIndex, allSrcLines) {
const nextLine = allSrcLines[lineIndex + 1];
return typeof nextLine === 'undefined' || isSectionLabel(nextLine);
Expand Down
7 changes: 6 additions & 1 deletion packages/chord-mark/tests/unit/parser/helpers/song.spec.js
Expand Up @@ -152,20 +152,24 @@ describe('getNthOfLabel', () => {
expect(getNthOfLabel).toBeInstanceOf(Function);
});

test('Should return the section identified by its label and index', () => {
test('Should return the section identified by its label and index, without trailing directives', () => {
const song2 = `
#v
Verse1-line1
Verse1-line2
Verse1-line3
Verse1-line4
4/4
#v
Verse2-line1
Verse2-line2
Verse2-line3
Verse2-line4
key B
3/4
#v
Verse3-line1
Verse3-line2
Expand All @@ -186,6 +190,7 @@ Verse3-line4`;
'Verse2-line3',
'Verse2-line4',
'',
'',
];
const v3 = [
'Verse3-line1',
Expand Down
58 changes: 42 additions & 16 deletions packages/documentation/docs/reference/chords.mdx
Expand Up @@ -9,9 +9,9 @@ To create a chord line, simply write chords symbols one after the other on a emp

Chord lines are special lines that have the following characteristics:

- they will be rendered with bar separators `|`
- if followed by a lyric line with chord position markers, the chords will be placed over the relevant lyrics
- if not, they will be neatly aligned between them across the whole song
- they will be rendered with bar separators `|`
- if followed by a lyric line with chord position markers, the chords will be placed over the relevant lyrics
- if not, they will be neatly aligned between them across the whole song

Leave as many spaces as you want between symbols, but know that <CM/> will ignore them and use its own algorithms to position the chords.

Expand All @@ -20,7 +20,9 @@ Leave as many spaces as you want between symbols, but know that <CM/> will ignor
If you write anything else than chord symbols on a line, then it won't be considered as a chord line and won't benefit from any of the special features mentioned above.
That is also true if a chord symbol is not recognized as such.

<TabbedChordMark src={'Extra "|" character:\nA7 | D7\nInvalid chord symbol:\nA7 D7(b9'} />
<TabbedChordMark
src={'Extra "|" character:\nA7 | D7\nInvalid chord symbol:\nA7 D7(b9'}
/>

## Chord duration

Expand All @@ -31,30 +33,45 @@ Use one dot per beat.
<TabbedChordMark src={'A7.. D7.. E7... A7.'} />

The example above creates two bars:
- the first bar has 2 chords (`A7` and `D7`) lasting 2 beats each
- the second bar has one chord lasting 3 beats (`E7`) and another one on fourth beat (`A7`).

By default, <CM/> renders the duration markers only when a bar contains chords with uneven duration.
- the first bar has 2 chords (`A7` and `D7`) lasting 2 beats each
- the second bar has one chord lasting 3 beats (`E7`) and another one on fourth beat (`A7`).

By default, <CM/> renders the duration markers only when a bar contains chords with uneven duration.
If all chords in a bar share the same duration, then the dots are not rendered.

Be careful that the math needs to be correct **on a per-bar basis**.
Be careful that the math needs to be correct **on a per-bar basis**.
If the chords durations don't add up, the line won't be considered as a chord line.

<TabbedChordMark src={'Invalid beat count:\nA7... D7..\nIf a chord spans multiple bar, specify it for each bar:\nA7.. D7.... E7..\nA7.. D7.. D7.. E7..'} />
<TabbedChordMark
src={
'Invalid beat count:\nA7... D7..\nIf a chord spans multiple bar, specify it for each bar:\nA7.. D7.... E7..\nA7.. D7.. D7.. E7..'
}
/>

## Sub-beat chord duration

If some chords last less than one beat, you can use the sub-beat delimiters to create a group of chords to play during the same beat.

<TabbedChordMark src={'C.. G..\nWhen I _find myself in _times of trouble\nAm. [Am Am/G] FM7. F6.\n_ Mother _Ma_ry _comes to _me'} />
<TabbedChordMark
src={
'C.. G..\nWhen I _find myself in _times of trouble\nAm. [Am Am/G] FM7. F6.\n_ Mother _Ma_ry _comes to _me'
}
/>

Within the sub-beat delimiters, it is the number of chords in the groups that define the chord duration, meaning it is not necessary to use chord duration markers:

<TabbedChordMark src={'8th notes:\nC.. G. [Am Am/G]\n8th notes triplet:\nC.. G. [Am Am/G F]\n16th notes:\nC.. G. [Am Am/G F C]'} />
<TabbedChordMark
src={
'8th notes:\nC.. G. [Am Am/G]\n8th notes triplet:\nC.. G. [Am Am/G F]\n16th notes:\nC.. G. [Am Am/G F C]'
}
/>

A sub-beat group definition is invalid if it contains chord duration markers, a single chord, or more than four chords:

<TabbedChordMark src={'Invalid:\nC.. G. [B. A7.]\nC.. G. [B]\nC.. G. [B C/E Am G F]'} />
<TabbedChordMark
src={'Invalid:\nC.. G. [B. A7.]\nC.. G. [B]\nC.. G. [B C/E Am G F]'}
/>

## Time signature

Expand All @@ -64,22 +81,31 @@ If your song is not in 4/4, you can just specify the time signature on a separat

You can mix multiple time signatures per song.

<TabbedChordMark src={'3/4\nDm Gm.. C.\n\n4/4\nA7.. D7.. E7... A7.\n\n5/4\nGm... C. Dm7.'} />
<TabbedChordMark
src={'3/4\nDm Gm.. C.\n\n4/4\nA7.. D7.. E7... A7.\n\n5/4\nGm... C. Dm7.'}
/>

The most commonly used time signatures are recognized by <CM/>.

It is possible to declare a time signature change inside a chord line.
The new time signature will last until a new one is declared on the same line, or until the end of the line.

<TabbedChordMark src={'4/4\n#i\nA.. Dsus2.. A\nGood _morning, good _morning, good _morning, ah!\n\n#v\n5/4 A... G.. G... A..\n_Nothing to do to _save his life, _call his wife _in,\n5/4 A... G.. 3/4 G 4/4 A\n_Nothing to say, but, "_What a day! _How\'s your boy _been?"\n5/4 D 4/4 E.. E7..\n_Nothing to do, it\'s up to _you,\n3/4 A G\nI\'ve got _nothing to say, but _it\'s okay,\n\n#c\nA.. Dsus2.. A\nGood _morning, good _morning, good _morning, ah!'} />
<TabbedChordMark
src={
"4/4\n#i\nA.. Dsus2.. A\nGood _morning, good _morning, good _morning, ah!\n\n#v\n5/4 A... G.. G... A..\n_Nothing to do to _save his life, _call his wife _in,\n5/4 A... G.. 3/4 G 4/4 A\n_Nothing to say, but, \"_What a day! _How's your boy _been?\"\n5/4 D 4/4 E.. E7..\n_Nothing to do, it's up to _you,\n3/4 A G\nI've got _nothing to say, but _it's okay,\n\n#c\nA.. Dsus2.. A\nGood _morning, good _morning, good _morning, ah!"
}
/>

If a time signature change occurs before a section change, either declare it directly in the new section, or on the line immediately preceding the section label,
to avoid that it would be repeated for the previous section when using the automatic chord repetition feature.

## Repeating bars

You can use the `%` symbol to repeat the last declared bar on the current line.

<TabbedChordMark src={'A7 %%%\nD7 % A7 %\nE7 D7 A7 %'} />

Whenever possible, <CM/> will render repeated bars with the `%` symbols, even if they were not explicitly specified as such.
Whenever possible, <CM/> will render repeated bars with the `%` symbols, even if they were not explicitly specified as such.

<TabbedChordMark src={'A7.. D7.. %\nD7.. E7. A7. D7.. E7. A7.'} />

Expand Down Expand Up @@ -110,7 +136,7 @@ For more consistency, <CM/> normalizes the chords symbols at rendering time.
<TabbedChordMark src={'CMAJ7\nC^\nCM7\n\nCmajor7'} />

Under the hood, <CM/> uses the powerful <CS/> library, which is capable of parsing virtually any chord symbol
and performs the normalization based on [widely recognized conventions](https://github.com/no-chris/chord-symbol#background-information).
and performs the normalization based on [widely recognized conventions](https://github.com/no-chris/chord-symbol/tree/master/packages/chord-symbol#background-information).

This normalization can also, sometimes, speed up the writing process:

Expand Down
3 changes: 3 additions & 0 deletions packages/documentation/docs/reference/keys.mdx
Expand Up @@ -9,6 +9,9 @@ It is possible to declare the key of any part of the song using they `key` keywo

<TabbedChordMark src={'key C\nC F G C'} />

If a key change occurs before a section change, either declare it directly in the new section, or on the line immediately preceding the section label,
to avoid that it would be repeated for the previous section when using the automatic chord repetition feature.

Appart from bringing clarity to the chord chart's reader, which is always a good thing, declaring one or multiple keys will bring at least 3 benefits.

### 1/ Automatic transposition
Expand Down

0 comments on commit d338c3e

Please sign in to comment.