/**
* This module prodives a few helper functions for the other modules
* @module utils
*/
const sharpNotes = ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B']
const flatNotes = ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb']
const specs = {
note: '(?<note>([A-G]([b#]{0,1})){1})',
degree: '(?<degree>[b#]*[0-9]+)',
type: '(?<type>([a,c-z,A-Z,0-9,/][a-z,A-Z,0-9,#,/]*){0,1})',
nType: '((\\^(?<type>([a,c-z,A-Z,0-9,/][a-z,A-Z,0-9,#,/]*))){0,1})',
octave: '((:(?<octave>(-{0,1}[0-9].*))){0,1})',
qualifiers: '(?<qualifiers>[b#]*)(?<base>[0-9]+)'
}
const res = {
note: new RegExp(specs.note + specs.octave),
degree: new RegExp(specs.degree + specs.octave),
chord: new RegExp(specs.note + specs.type + specs.octave),
nashvilleChord: new RegExp(specs.degree + specs.nType + specs.octave),
qualifiers: new RegExp(specs.qualifiers)
}
/**
* What is chromatic number of a note (such as 'C', 'D#', or 'Ab') in the chromatic scale?
* @param {string} note The note to compute the chromatic number of
* @returns {number} the integer index into the chromatic scale
* @static
*/
function noteIndex (note) {
let result = sharpNotes.indexOf(note)
if (result < 0) result = flatNotes.indexOf(note)
return result
}
/**
* Parses a note specification such as 'C#:3' into a *note* and an *octave*
* @param {string} spec <note name>[:<octave>]
* @returns {Object} the *note* name and the *octave*
* @static
*/
function parseNote (spec) {
const { groups: { note, octave } } = res.note.exec(spec)
return { note, octave }
}
/**
* Parses a degree specification such as '1:5' into a *degree* and an *octave*
* @param {string|number} spec <degree>[:<octave>]
* @returns {Object} the *degree* and the *octave*
* @static
*/
function parseDegree (spec) {
if (Number.isInteger(spec)) return { degree: spec }
const { groups: { degree, octave } } = res.degree.exec(spec)
return { degree, octave }
}
/**
* Parses a chord specification such as 'C#7b5:3' into an object
* @param {string} spec <note name>[<chord type>][:<octave>]
* @returns {Object} *note*: the name of the note, eg 'C#'; *type*:
* the chord type, eg '7b5'; and the chord's *octave*
* @static
*/
function parseChord (spec) {
const { groups: { note, type, octave } } = res.chord.exec(spec)
return { note, type, octave }
}
/**
* Parse a nashville chord specification such as '1', or '2^M7:3',
* or even '#bb##2^dim:5'
* @param {string} spec <degree>[^<chord type>][:<octave>]
* @returns {Object} *nashNum*: the chord number; *offset*: number
* of half-steps from the chord number determined by the leading sharp/flat
* designators; *type*: the chord type, eg '7b5'; and the chord's *octave*
* @static
*/
function parseNashvilleChord (spec) {
const { groups: { degree, type, octave } } = res.nashvilleChord.exec(spec)
const { base: nashNum, offset } = resolveQualifiers(degree)
return { nashNum, offset, type, octave }
}
/**
* Resolve a sequence of sharp/flat symbols into an integer *offset* from the
* *base* degree.
* @param {string} spec [<#/b>...<#/b>]<base>
* @returns {Object} the *base and the *offset*
* @static
*/
function resolveQualifiers (spec) {
if (Number.isInteger(spec)) return { base: spec, offset: 0 }
// eslint-disable-next-line
let { groups: { qualifiers, base } } = res.qualifiers.exec(spec)
base = parseInt(base)
const offset = (qualifiers.match(/#/g) || []).length -
(qualifiers.match(/b/g) || []).length
return { base, offset }
}
export {
noteIndex,
parseDegree,
parseNote,
parseChord,
parseNashvilleChord,
resolveQualifiers
}