tonal-fretboard
Version:
Filter notes
145 lines (132 loc) • 5.18 kB
JavaScript
/**
* This module has functions to create fretboards and query for instrument
* tunnings.
*
* __Currently is NOT part of the tonal distribution__
*
* @module fretboard
*/
import { dictionary } from 'tonal-dictionary'
import { fromSemitones } from 'tonal-interval'
import { transpose } from 'tonal-transpose'
import { pc } from 'tonal-note'
import { asArr, compact, map } from 'tonal-array'
import { numeric } from 'tonal-range'
import { includes } from 'tonal-pcset'
import DATA from './tunings.json'
var dict = dictionary(DATA, function (s) { return s.split(' ') })
/**
* Given a tuning name, returns the notes of the strings in the open position
* @function
* @param {String} name - the tuning name
* @return {Array} the string notes or null if not valid tuning name
* @example
* var fret = require('tonal-fretboard')
* fret.tuning('guitar') // => [ 'E2', 'A2', 'D3', 'G3', 'B3', 'E4' ]
* fret.tuning('charango') // => [ 'G4', 'G4', 'C5', 'C5', 'E5', 'E4', 'A4', 'A4', 'E5', 'E5' ]
*/
export var tuning = dict.get
/**
* Given a tuning name returns the notes of the strings in open position
* as pitch classes removing doubled strings.
* @param {String} name - the tuning name or notes of the strings in open position
* @return {Array} the string notes as pitch classes
* @example
* fret.simpleTuning('guitar') => [ 'E', 'A', 'D', 'G', 'B', 'E' ]
* fret.simpleTuning('charango') => [ 'G', 'C', 'E', 'A', 'E' ]
*/
export function simpleTuning (src) {
var pcs = map(pc, tuning(src) || src)
var simple = pcs.reduce(function (s, pc, i) {
if (s === false) return s
else if (i % 2 === 0) s.push(pc)
else if (s[s.length - 1] !== pc) return false
return s
}, [])
return simple || pcs
}
/**
* Get all available tuning names
* @function
* @param {Boolean} aliases - get aliases or not
* @return {Array} an array of tuning names
*/
export var names = dict.keys
/**
* Build a fretboard using a given tuning (or tuning name), first and last
* fret numbers and optionally a chord or scale
*
* It returns an array of arrays, where each sub-array is the notes of
* a string.
*
* @param {String|Array} tuning - the tuning name or notes
* @param {Integer} first - the first fret number
* @param {Integer} last - the last fret number
* @param {Array|String} set - a scale or chord to filter the fretboard
* @return {Array} An array of arrays, one for each string
*/
export function notes (tun, first, last, set) {
first = first || 0
last = last || first
var ivls = numeric([first, last]).map(fromSemitones)
var notes = tuning(tun) || asArr(tun)
var filterFn = set ? map(includedIn(set)) : id
return notes.map(function (b) {
return ivls.map(transpose(b))
}).map(filterFn)
}
function id (o) { return o }
function includedIn (set) {
var isInSet = includes(set)
return function (n) { return isInSet(n) ? n : null }
}
/**
* Build a fretboard only showing the notes for the given scale.
* @param {String|Array} tuning - the tuning name or notes
* @param {String|Array} scale - the scale notes
* @param {Integer} first - the first fret number
* @param {Integer} last - the last fret number
* @return {Array} An array of arrays, one for each string
*/
export function scale (tuning, scale, first, last) {
return notes(tuning, first, last, scale)
}
/**
* Build an array of reachable chord shapes based on given notes and tuning.
* @param {String|Array} tuning - the tuning name or notes
* @param {Array} notes - an array of chord notes
* @param {Integer} first - the first fret number. Default 0.
* @param {Integer} last - the last fret number. Default 12.
* @param {Integer} span - how many frets to include per position. Default 4.
* @return {Array} An array of arrays, one for each possible shape. Element index is string number [ '0', '2', '2', '1', '0', '0' ]
*/
export function chordShapes (tuning, notes, first, last, span) {
// Set defaults
first = first || 0;
last = last || 12;
span = span || 4;
var fretboard = scale(tuning, notes, first, last)
var positions = []
// Break each string array into {fretSpan} frets overlapping sections
var strings = fretboard.map(function (string, stringIndex) {
return string.map(function (fret, fretIndex) {
return compact(string.slice(fretIndex, fretIndex + span).map(function (slicedFret, slicedFretIndex) {
// Convert note names to fret numbers
return slicedFret !== null ? fretIndex + slicedFretIndex : null
}))
})
})
// Build positions
strings.forEach(function (string) {
string.forEach(function (fretGroup, fretGroupIndex) {
if (!Array.isArray(positions[fretGroupIndex])) positions[fretGroupIndex] = []
if (fretGroup.length > 1) positions[fretGroupIndex].push(fretGroup)
else positions[fretGroupIndex].push(fretGroup.toString() ? fretGroup.toString() : null)
})
})
// Remove null, neighboring duplicate arrays, and arrays with a only one non-null value
return positions.filter(function (position, i) {
if (compact(position).length < 2) return false
return i === 0 ? position : positions[i].toString() !== positions[i - 1].toString()
})
}