Skip to content

Commit

Permalink
Merge 412664e into 33d527e
Browse files Browse the repository at this point in the history
  • Loading branch information
no-chris committed Feb 9, 2023
2 parents 33d527e + 412664e commit 94cc0d8
Show file tree
Hide file tree
Showing 38 changed files with 1,469 additions and 932 deletions.
2 changes: 1 addition & 1 deletion packages/chord-mark-converters/package.json
Expand Up @@ -25,7 +25,7 @@
},
"dependencies": {
"chord-mark": "^0.13.0-beta.3",
"chord-symbol": "4.0.0-beta.0",
"chord-symbol": "4.0.0-beta.3",
"chord-symbol-ultimateguitar": "3.0.0",
"dompurify": "^2.3.4"
},
Expand Down
17 changes: 4 additions & 13 deletions packages/chord-mark-converters/yarn.lock
Expand Up @@ -2,26 +2,17 @@
# yarn lockfile v1


chord-mark@^0.13.0-beta.3:
version "0.13.0-beta.3"
resolved "https://registry.yarnpkg.com/chord-mark/-/chord-mark-0.13.0-beta.3.tgz#53953d533e79f97f4114618a9d8be69055995d6c"
integrity sha512-7eW+9uFRIFi55Re+9OFF55v2+73Csd2i6yFpyKhe0HO+vqI8Mj0iIRo4NaaLvcvkixEb76H8A/0JtPQmiD9AHQ==
dependencies:
chord-symbol "4.0.0-beta.0"
dompurify "^2.3.4"
lodash "^4.17.21"

chord-symbol-ultimateguitar@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chord-symbol-ultimateguitar/-/chord-symbol-ultimateguitar-3.0.0.tgz#fba93b5baf8d503f72dda2760956759c903bd6d3"
integrity sha512-Sbi3StI+obO5j/t6piv/+vzmABf+4TiwWiCrD+iMfFBtO88sBx8vmzOhuFkgBeChyCvhAJgt0s8DwuutRP84rw==
dependencies:
chord-symbol "^3.0.0"

chord-symbol@4.0.0-beta.0:
version "4.0.0-beta.0"
resolved "https://registry.yarnpkg.com/chord-symbol/-/chord-symbol-4.0.0-beta.0.tgz#dd350d2a9c97ef8e0620a518e1a9c5bdd23f4642"
integrity sha512-9HeCfZc79mmqJWBY8JABQx5QzIqqjxxCejcFQ67wgdNkvywPnu2nrZsOGzcpWT91vxuKoQ1BWuUgXy+ilLWX8g==
chord-symbol@4.0.0-beta.3:
version "4.0.0-beta.3"
resolved "https://registry.yarnpkg.com/chord-symbol/-/chord-symbol-4.0.0-beta.3.tgz#19e83198f83addaf627404708b1041344067111e"
integrity sha512-dOpcMDijtfkKNThPyTgzztbrhEKiX7NZcPsO5V3Y88866O+7Z/pORQUlJhP/j2T9Mfzg/aafO5imp620eeU0AA==
dependencies:
lodash "^4.17.21"

Expand Down
20 changes: 20 additions & 0 deletions packages/chord-mark-themes/scss/themes/dark1.scss
Expand Up @@ -52,4 +52,24 @@ $pink: rgb(237, 123, 255);
opacity: 0.8;
font-weight: bold;
}

.cmChordLine-romanNumeral,
.cmRomanNumeral {
color: $bitterSweet;
}

.cmRomanNumeral-diatonic {
font-weight: bold;
color: $ccs-background;
background-color: $bitterSweet;
}

.cmRomanNumeral-borrowed {
color: $ccs-background;
background-color: rgba($bitterSweet, 0.7);
}

.cmRomanNumeral-unknown {
opacity: 0.8;
}
}
2 changes: 1 addition & 1 deletion packages/chord-mark/package.json
Expand Up @@ -18,7 +18,7 @@
},
"homepage": "https://github.com/no-chris/chord-mark/tree/main/packages/chord-mark#readme",
"dependencies": {
"chord-symbol": "4.0.0-beta.0",
"chord-symbol": "4.0.0-beta.3",
"dompurify": "^2.3.4",
"lodash": "^4.17.21"
},
Expand Down
8 changes: 7 additions & 1 deletion packages/chord-mark/src/parser/getAllChordsInSong.js
@@ -1,5 +1,6 @@
import _findIndex from 'lodash/findIndex';
import _isEqual from 'lodash/isEqual';
import _omit from 'lodash/omit';
import _cloneDeep from 'lodash/cloneDeep';

import { forEachChordInSong } from './helper/songs';
Expand All @@ -15,7 +16,12 @@ export default function getAllChordsInSong(allLines) {

forEachChordInSong(allLines, (chord) => {
if (chord.model !== syntax.noChord) {
i = _findIndex(allChords, (o) => _isEqual(o.model, chord.model));
i = _findIndex(allChords, (o) =>
_isEqual(
_omit(o.model, ['numeral']),
_omit(chord.model, ['numeral'])
)
);

if (i === -1) {
allChords.push({
Expand Down
115 changes: 79 additions & 36 deletions packages/chord-mark/src/parser/helper/keyHelpers.js
@@ -1,32 +1,51 @@
import _cloneDeep from 'lodash/cloneDeep';
import _findIndex from 'lodash/findIndex';
import _invert from 'lodash/invert';

const allNotesSharp = 'A,A#,B,C,C#,D,D#,E,F,F#,G,G#'.split(',');
const allFlats = 'Ab,Bb,Db,Eb,Gb'.split(',');
const allNotes = [...allNotesSharp, ...allFlats];
const allKeys = [...allNotes, ...allNotes.map((note) => note + 'm')];

const flatsToSharps = {
Ab: 'G#',
Bb: 'A#',
Db: 'C#',
Eb: 'D#',
Gb: 'F#',
};
const sharpsToFlats = _invert(flatsToSharps);

// We use chord symbol to manipulate key declarations even though they are not chords per se
// But we benefit from the chord-symbol parser to validate the key declaration
import { chordParserFactory, chordRendererFactory } from 'chord-symbol';
/**
* Check if the given string is a valid key
* @param {String} keyString
* @returns {Boolean}
*/
export function isKey(keyString) {
return allKeys.includes(keyString);
}

/**
* Returns the accidental of a given key
*
* @param {string} keyString
* @returns {('flat'|'sharp')}
*/
export function getKeyAccidental(keyString) {
const sharpKeys = [
'G', // 1 sharp
'Emi',
'Em',
'D', // 2 sharps
'Bmi',
'Bm',
'A', // 3 sharps
'F#mi',
'F#m',
'E', // 4 sharps
'C#mi',
'C#m',
'B', // 5 sharps
'G#mi',
'G#m',
'F#', // 6 sharps
'D#mi',
'D#m',
'C#', // 7 sharps
'A#mi',
'A#m',

// Theoretical keys
'G#', // 8 sharps
Expand All @@ -38,59 +57,83 @@ export function getKeyAccidental(keyString) {
}

/**
*
* Transpose a key, trying to avoid theoretical keys when `accidentalsType` is 'auto'.
* Otherwise, the transposed key will use the given `accidentalsType`, e.g. 'sharp' or 'flat'.
* @param {KeyDeclaration} keyModel
* @param {number} transposeValue
* @param {boolean} avoidTheoreticalKeys
* @param {('auto'|'sharp'|'flat')} accidentalsType
* @returns {KeyDeclaration}
*/
export function transposeKey(keyModel, transposeValue, avoidTheoreticalKeys) {
export function transposeKey(keyModel, transposeValue, accidentalsType) {
const theoreticalKeys = {
'G#': 'Ab',
'D#': 'Eb',
'A#': 'Bb',
Dbmi: 'C#mi',
Gbmi: 'F#mi',
Dbm: 'C#m',
Gbm: 'F#m',
};

const parseKeyChord = chordParserFactory();
const renderKeyChord = chordRendererFactory({
transposeValue,
accidentals:
transposeValue === 0
? 'original'
: transposeValue < 0
? 'flat'
: 'sharp',
});

const tempKey = renderKeyChord(keyModel.chordModel);
let keyTemp;

if (transposeValue === 0 && accidentalsType === 'auto') {
keyTemp = keyModel.string;
} else {
const accidental =
accidentalsType === 'auto'
? transposeValue < 0
? 'flat'
: 'sharp'
: accidentalsType;
keyTemp = doTranspose(keyModel.string, transposeValue, accidental);
}

const transposedKey =
avoidTheoreticalKeys && theoreticalKeys[tempKey]
? theoreticalKeys[tempKey]
: tempKey;
accidentalsType === 'auto' && theoreticalKeys[keyTemp]
? theoreticalKeys[keyTemp]
: keyTemp;

return {
string: transposedKey,
chordModel: parseKeyChord(transposedKey),
accidental: getKeyAccidental(transposedKey),
};
}

function doTranspose(key, value, accidental) {
const isMinor = key.endsWith('m');
const note = key.replace('m', '');

const noteSharp = flatsToSharps[note] || note;
const noteIndex = allNotesSharp.indexOf(noteSharp);

let transposedIndex = noteIndex + value;

if (transposedIndex < 0) {
transposedIndex += allNotesSharp.length;
} else if (transposedIndex >= allNotesSharp.length) {
transposedIndex -= allNotesSharp.length;
}

const transposedSharp = allNotesSharp[transposedIndex];

const transposed =
accidental === 'flat'
? sharpsToFlats[transposedSharp] || transposedSharp
: transposedSharp;

return isMinor ? transposed + 'm' : transposed;
}

/**
* Try to guess the key of a song based on the chords
*
* Try to guess the key of a song based on its chords
* @param {SongChord[]} allChords
* @returns {(KeyDeclaration|undefined)}
*/
export function guessKey(allChords) {
const keyString = inferKeyFromChords(allChords);
const parseKeyChord = chordParserFactory();

return keyString
? {
string: keyString,
chordModel: parseKeyChord(keyString),
accidental: getKeyAccidental(keyString),
}
: undefined;
Expand Down
19 changes: 2 additions & 17 deletions packages/chord-mark/src/parser/matchers/isKeyDeclaration.js
@@ -1,7 +1,7 @@
import _escapeRegExp from 'lodash/escapeRegExp';
import syntax from '../syntax';
import clearSpaces from '../helper/clearSpaces';
import { chordParserFactory } from 'chord-symbol';
import { isKey } from '../helper/keyHelpers';

export const keyDeclarationRegexp = new RegExp(
'^' + _escapeRegExp(syntax.keyDeclarationPrefix) + '([ABCDEFG].*)$'
Expand All @@ -11,20 +11,5 @@ export default function isKeyDeclaration(string) {
const found = clearSpaces(string).match(keyDeclarationRegexp);
if (found === null) return false;

// We use chord symbol to manipulate key declarations even though they are not chords per se
// But we benefit from the chord-symbol parser to validate the key declaration
const parseChord = chordParserFactory({ notationSystems: ['english'] });
const chord = parseChord(found[1]);

if (chord.error) return false;

const chordIntervals = chord.normalized.intervals;

return (
// again, chords are not key, so we just want major or minor triads in here
chordIntervals.length === 3 &&
['b3', '3'].includes(chordIntervals[1]) &&
chordIntervals[2] === '5' &&
chordIntervals[0] === '1'
);
return isKey(found[1]);
}
7 changes: 4 additions & 3 deletions packages/chord-mark/src/parser/parseChord.js
@@ -1,10 +1,11 @@
import { chordParserFactory } from 'chord-symbol';

/**
* @param chordString
* @param {String} chordString
* @param {KeyDeclaration} key
* @returns {Chord}
*/
export default function parseChordWrapper(chordString) {
const parseChord = chordParserFactory();
export default function parseChordWrapper(chordString, key = {}) {
const parseChord = chordParserFactory({ key: key.string });
return parseChord(chordString);
}
8 changes: 5 additions & 3 deletions packages/chord-mark/src/parser/parseChordLine.js
Expand Up @@ -58,12 +58,14 @@ const barRepeatSymbols = new RegExp(

/**
* @param {String} chordLine
* @param {TimeSignature} timeSignature
* @param {Object} options
* @param {TimeSignature} options.timeSignature
* @param {KeyDeclaration} options.key
* @returns {ChordLine}
*/
export default function parseChordLine(
chordLine,
{ timeSignature = defaultTimeSignature } = {}
{ timeSignature = defaultTimeSignature, key = {} } = {}
) {
let { beatCount } = timeSignature;

Expand Down Expand Up @@ -149,7 +151,7 @@ export default function parseChordLine(
duration: getChordDuration(token, beatCount, isInSubBeatGroup),
model: isNoChordSymbol(cleanedToken)
? syntax.noChord
: parseChord(cleanedToken),
: parseChord(cleanedToken, key),
beat: currentBeatCount + 1,
isInSubBeatGroup,
};
Expand Down
9 changes: 1 addition & 8 deletions packages/chord-mark/src/parser/parseKeyDeclaration.js
Expand Up @@ -2,14 +2,12 @@ import clearSpaces from './helper/clearSpaces';
import isKeyDeclaration, {
keyDeclarationRegexp,
} from './matchers/isKeyDeclaration';
import { chordParserFactory, chordRendererFactory } from 'chord-symbol';
import { getKeyAccidental } from './helper/keyHelpers';

/**
* @typedef {Object} KeyDeclaration
* @type {Object}
* @property {String} string
* @property {ChordDef} chordModel
* @property {('flat'|'sharp')} accidental
*/

Expand All @@ -22,16 +20,11 @@ export default function parseKeyDeclaration(string) {
throw new TypeError('Expected key declaration, received: ' + string);
}

const parseChord = chordParserFactory();
const renderChord = chordRendererFactory();

const found = clearSpaces(string).match(keyDeclarationRegexp);
const chordModel = parseChord(found[1]);
const keyString = renderChord(chordModel);
const keyString = found[1];

return {
string: keyString,
chordModel,
accidental: getKeyAccidental(keyString),
};
}

0 comments on commit 94cc0d8

Please sign in to comment.