tonal-pitchset
Advanced tools
Comparing version 0.68.0 to 0.68.1
@@ -5,182 +5,30 @@ 'use strict'; | ||
var tonalPitch = require('tonal-pitch'); | ||
var tonalNote = require('tonal-note'); | ||
var tonalArray = require('tonal-array'); | ||
var tonalTranspose = require('tonal-transpose'); | ||
function toInt (set) { return parseInt(chroma(set), 2) } | ||
function pitchChr (p) { p = tonalPitch.asPitch(p); return p ? tonalPitch.chr(p) : null } | ||
/** | ||
* Given a list of notes, return the notes of the pitchset | ||
* starting with the first note of the list | ||
*/ | ||
function notes (notes) { | ||
var pcs = tonalArray.map(tonalNote.pc, notes) | ||
if (!pcs.length) return pcs | ||
var tonic = pcs[0] | ||
// since the first note of the chroma is always C, we have to rotate it | ||
var rotated = tonalArray.rotate(pitchChr(tonic), chroma(pcs).split('')).join('') | ||
return fromChroma(rotated, tonic) | ||
} | ||
/** | ||
* Given a pitch set (a list of notes or a pitch set chroma), produce the 12 rotations | ||
* of the chroma (and discard the ones that starts with '0') | ||
* Functions to create and manipulate pitch sets | ||
* | ||
* This can be used, for example, to get all the modes of a scale. | ||
* @example | ||
* var pitchset = require('tonal-pitchset') | ||
* | ||
* @param {Array|String} set - the list of notes or pitchChr of the set | ||
* @param {Boolean} normalize - (Optional, true by default) remove all | ||
* the rotations that starts with '0' | ||
* @return {Array<String>} an array with all the modes of the chroma | ||
* | ||
* @example | ||
* @module pitchset | ||
*/ | ||
function chromaModes (set, normalize) { | ||
normalize = normalize !== false | ||
var binary = chroma(set).split('') | ||
return tonalArray.compact(binary.map(function (_, i) { | ||
var r = tonalArray.rotate(i, binary) | ||
return normalize && r[0] === '0' ? null : r.join('') | ||
})) | ||
} | ||
var REGEX = /^[01]{12}$/ | ||
/** | ||
* Test if the given string is a pitch set chroma. | ||
* @param {String} chroma - the pitch set chroma | ||
* @return {Boolean} true if its a valid pitchset chroma | ||
* @example | ||
* pitchset.isChroma('101010101010') // => true | ||
* pitchset.isChroma('101001') // => false | ||
*/ | ||
function isChroma (set) { | ||
return REGEX.test(set) | ||
} | ||
/** | ||
* Get chroma of a pitch set. A chroma identifies each pitch set uniquely. | ||
* It's a 12-digit binary each presenting one semitone of the octave. | ||
* Get the notes of a pitch set. The notes in the set are sorted in asceding | ||
* pitch order, and no repetitions are allowed. | ||
* | ||
* Note that this function accepts a chroma as parameter and return it | ||
* without modification. | ||
* Note that it creates pitch sets and NOT picth class sets. This functionallity | ||
* resides inside `tonal-pcset` module. | ||
* | ||
* @param {Array|String} set - the pitch set | ||
* @return {String} a binary representation of the pitch set | ||
* @param {String|Array} notes - the notes to create the pitch set from | ||
* @return {Array<String>} the ordered pitch set notes | ||
* @example | ||
* pitchset.chroma('C D E') // => '1010100000000' | ||
* pitchset.notes('C4 c3 C5 c4') // => ['C3', 'C4', 'C5'] | ||
*/ | ||
function chroma (set) { | ||
if (isChroma(set)) return set | ||
var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | ||
tonalArray.map(pitchChr, set).forEach(function (i) { | ||
b[i] = 1 | ||
function notes (notes) { | ||
return tonalArray.sort(notes).filter(function (n, i, arr) { | ||
return i === 0 || n !== arr[i - 1] | ||
}) | ||
return b.join('') | ||
} | ||
var IVLS = '1P 2m 2M 3m 3M 4P 5d 5P 6m 6M 7m 7M'.split(' ') | ||
/** | ||
* Given a pitch set in binary notation it returns the intervals or notes | ||
* (depending on the tonic) | ||
* @param {String} binary - the pitch set in binary representation | ||
* @param {String|Pitch} tonic - the pitch set tonic | ||
* @return {Array} a list of notes or intervals | ||
* @example | ||
* pitchset.fromChroma('101010101010', 'C') // => ['C', 'D', 'E', 'Gb', 'Ab', 'Bb'] | ||
*/ | ||
function fromChroma (binary, tonic) { | ||
if (arguments.length === 1) return function (t) { return fromChroma(binary, t) } | ||
if (!isChroma(binary)) return null | ||
tonic = tonic || 'P1' | ||
return tonalArray.compact(binary.split('').map(function (d, i) { | ||
return d === '1' ? tonalTranspose.transpose(IVLS[i], tonic) : null | ||
})) | ||
} | ||
/** | ||
* Test if two pitch sets are identical | ||
* | ||
* @param {Array|String} set1 - one of the pitch sets | ||
* @param {Array|String} set2 - the other pitch set | ||
* @return {Boolean} true if they are equal | ||
* @example | ||
* pitchset.equal('c2 d3', 'c5 d2') // => true | ||
*/ | ||
function equal (s1, s2) { | ||
if (arguments.length === 1) return function (s) { return equal(s1, s) } | ||
return chroma(s1) === chroma(s2) | ||
} | ||
/** | ||
* Test if a pitch set is a subset of another | ||
* | ||
* @param {Array|String} set - the base set to test against | ||
* @param {Array|String} test - the set to test | ||
* @return {Boolean} true if the test set is a subset of the set | ||
* @example | ||
* pitchset.subset('c d e', 'C2 D4 D5 C6') // => true | ||
*/ | ||
function subset (set, test) { | ||
if (arguments.length === 1) return function (t) { return subset(set, t) } | ||
test = toInt(test) | ||
return (test & toInt(set)) === test | ||
} | ||
/** | ||
* Test if a pitch set is a superset | ||
* @param {Array|String} set - the base set to test against | ||
* @param {Array|String} test - the set to test | ||
* @return {Boolean} true if the test set is a superset of the set | ||
* @example | ||
* pitchset.subset('c d e', 'C2 D4 F4 D5 E5 C6') // => true | ||
*/ | ||
function superset (set, test) { | ||
if (arguments.length === 1) return function (t) { return superset(set, t) } | ||
test = toInt(test) | ||
return (test | toInt(set)) === test | ||
} | ||
/** | ||
* Test if a given pitch set includes a note | ||
* @param {Array|String} set - the base set to test against | ||
* @param {String|Pitch} note - the note to test | ||
* @return {Boolean} true if the note is included in the pitchset | ||
* @example | ||
* pitchset.includes('c d e', 'C4') // =A true | ||
* pitchset.includes('c d e', 'C#4') // =A false | ||
*/ | ||
function includes (set, note) { | ||
if (arguments.length > 1) return includes(set)(note) | ||
set = chroma(set) | ||
return function (note) { return set[pitchChr(note)] === '1' } | ||
} | ||
/** | ||
* Filter a list with a pitch set | ||
* | ||
* @param {Array|String} set - the pitch set | ||
* @param {Array|String} notes - the note list to be filtered | ||
* @return {Array} the filtered notes | ||
* | ||
* @example | ||
* pitchset.filter('c d e', 'c2 c#2 d2 c3 c#3 d3') // => [ 'c2', 'd2', 'c3', 'd3' ]) | ||
*/ | ||
function filter (set, notes) { | ||
if (arguments.length === 1) return function (n) { return filter(set, n) } | ||
return tonalArray.asArr(notes).filter(includes(set)) | ||
} | ||
exports.notes = notes; | ||
exports.chromaModes = chromaModes; | ||
exports.isChroma = isChroma; | ||
exports.chroma = chroma; | ||
exports.fromChroma = fromChroma; | ||
exports.equal = equal; | ||
exports.subset = subset; | ||
exports.superset = superset; | ||
exports.includes = includes; | ||
exports.filter = filter; |
175
index.js
@@ -6,176 +6,23 @@ /** | ||
* var pitchset = require('tonal-pitchset') | ||
* pitchset.equal('c2 d5 e6', 'c6 e3 d1') // => true | ||
* | ||
* @module pitchset | ||
*/ | ||
import { chr, asPitch } from 'tonal-pitch' | ||
import { pc } from 'tonal-note' | ||
import { map, asArr, rotate, compact } from 'tonal-array' | ||
import { transpose } from 'tonal-transpose' | ||
import { sort } from 'tonal-array' | ||
function toInt (set) { return parseInt(chroma(set), 2) } | ||
function pitchChr (p) { p = asPitch(p); return p ? chr(p) : null } | ||
/** | ||
* Given a list of notes, return the notes of the pitchset | ||
* starting with the first note of the list | ||
*/ | ||
export function notes (notes) { | ||
var pcs = map(pc, notes) | ||
if (!pcs.length) return pcs | ||
var tonic = pcs[0] | ||
// since the first note of the chroma is always C, we have to rotate it | ||
var rotated = rotate(pitchChr(tonic), chroma(pcs).split('')).join('') | ||
return fromChroma(rotated, tonic) | ||
} | ||
/** | ||
* Given a pitch set (a list of notes or a pitch set chroma), produce the 12 rotations | ||
* of the chroma (and discard the ones that starts with '0') | ||
* Get the notes of a pitch set. The notes in the set are sorted in asceding | ||
* pitch order, and no repetitions are allowed. | ||
* | ||
* This can be used, for example, to get all the modes of a scale. | ||
* Note that it creates pitch sets and NOT picth class sets. This functionallity | ||
* resides inside `tonal-pcset` module. | ||
* | ||
* @param {Array|String} set - the list of notes or pitchChr of the set | ||
* @param {Boolean} normalize - (Optional, true by default) remove all | ||
* the rotations that starts with '0' | ||
* @return {Array<String>} an array with all the modes of the chroma | ||
* | ||
* @param {String|Array} notes - the notes to create the pitch set from | ||
* @return {Array<String>} the ordered pitch set notes | ||
* @example | ||
* pitchset.chromaModes('C E G') | ||
* pitchset.notes('C4 c3 C5 c4') // => ['C3', 'C4', 'C5'] | ||
*/ | ||
export function chromaModes (set, normalize) { | ||
normalize = normalize !== false | ||
var binary = chroma(set).split('') | ||
return compact(binary.map(function (_, i) { | ||
var r = rotate(i, binary) | ||
return normalize && r[0] === '0' ? null : r.join('') | ||
})) | ||
} | ||
var REGEX = /^[01]{12}$/ | ||
/** | ||
* Test if the given string is a pitch set chroma. | ||
* @param {String} chroma - the pitch set chroma | ||
* @return {Boolean} true if its a valid pitchset chroma | ||
* @example | ||
* pitchset.isChroma('101010101010') // => true | ||
* pitchset.isChroma('101001') // => false | ||
*/ | ||
export function isChroma (set) { | ||
return REGEX.test(set) | ||
} | ||
/** | ||
* Get chroma of a pitch set. A chroma identifies each pitch set uniquely. | ||
* It's a 12-digit binary each presenting one semitone of the octave. | ||
* | ||
* Note that this function accepts a chroma as parameter and return it | ||
* without modification. | ||
* | ||
* @param {Array|String} set - the pitch set | ||
* @return {String} a binary representation of the pitch set | ||
* @example | ||
* pitchset.chroma('C D E') // => '1010100000000' | ||
*/ | ||
export function chroma (set) { | ||
if (isChroma(set)) return set | ||
var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | ||
map(pitchChr, set).forEach(function (i) { | ||
b[i] = 1 | ||
export function notes (notes) { | ||
return sort(notes).filter(function (n, i, arr) { | ||
return i === 0 || n !== arr[i - 1] | ||
}) | ||
return b.join('') | ||
} | ||
var IVLS = '1P 2m 2M 3m 3M 4P 5d 5P 6m 6M 7m 7M'.split(' ') | ||
/** | ||
* Given a pitch set in binary notation it returns the intervals or notes | ||
* (depending on the tonic) | ||
* @param {String} binary - the pitch set in binary representation | ||
* @param {String|Pitch} tonic - the pitch set tonic | ||
* @return {Array} a list of notes or intervals | ||
* @example | ||
* pitchset.fromChroma('101010101010', 'C') // => ['C', 'D', 'E', 'Gb', 'Ab', 'Bb'] | ||
*/ | ||
export function fromChroma (binary, tonic) { | ||
if (arguments.length === 1) return function (t) { return fromChroma(binary, t) } | ||
if (!isChroma(binary)) return null | ||
tonic = tonic || 'P1' | ||
return compact(binary.split('').map(function (d, i) { | ||
return d === '1' ? transpose(IVLS[i], tonic) : null | ||
})) | ||
} | ||
/** | ||
* Test if two pitch sets are identical | ||
* | ||
* @param {Array|String} set1 - one of the pitch sets | ||
* @param {Array|String} set2 - the other pitch set | ||
* @return {Boolean} true if they are equal | ||
* @example | ||
* pitchset.equal('c2 d3', 'c5 d2') // => true | ||
*/ | ||
export function equal (s1, s2) { | ||
if (arguments.length === 1) return function (s) { return equal(s1, s) } | ||
return chroma(s1) === chroma(s2) | ||
} | ||
/** | ||
* Test if a pitch set is a subset of another | ||
* | ||
* @param {Array|String} set - the base set to test against | ||
* @param {Array|String} test - the set to test | ||
* @return {Boolean} true if the test set is a subset of the set | ||
* @example | ||
* pitchset.subset('c d e', 'C2 D4 D5 C6') // => true | ||
*/ | ||
export function subset (set, test) { | ||
if (arguments.length === 1) return function (t) { return subset(set, t) } | ||
test = toInt(test) | ||
return (test & toInt(set)) === test | ||
} | ||
/** | ||
* Test if a pitch set is a superset | ||
* @param {Array|String} set - the base set to test against | ||
* @param {Array|String} test - the set to test | ||
* @return {Boolean} true if the test set is a superset of the set | ||
* @example | ||
* pitchset.subset('c d e', 'C2 D4 F4 D5 E5 C6') // => true | ||
*/ | ||
export function superset (set, test) { | ||
if (arguments.length === 1) return function (t) { return superset(set, t) } | ||
test = toInt(test) | ||
return (test | toInt(set)) === test | ||
} | ||
/** | ||
* Test if a given pitch set includes a note | ||
* @param {Array|String} set - the base set to test against | ||
* @param {String|Pitch} note - the note to test | ||
* @return {Boolean} true if the note is included in the pitchset | ||
* @example | ||
* pitchset.includes('c d e', 'C4') // =A true | ||
* pitchset.includes('c d e', 'C#4') // =A false | ||
*/ | ||
export function includes (set, note) { | ||
if (arguments.length > 1) return includes(set)(note) | ||
set = chroma(set) | ||
return function (note) { return set[pitchChr(note)] === '1' } | ||
} | ||
/** | ||
* Filter a list with a pitch set | ||
* | ||
* @param {Array|String} set - the pitch set | ||
* @param {Array|String} notes - the note list to be filtered | ||
* @return {Array} the filtered notes | ||
* | ||
* @example | ||
* pitchset.filter('c d e', 'c2 c#2 d2 c3 c#3 d3') // => [ 'c2', 'd2', 'c3', 'd3' ]) | ||
*/ | ||
export function filter (set, notes) { | ||
if (arguments.length === 1) return function (n) { return filter(set, n) } | ||
return asArr(notes).filter(includes(set)) | ||
} |
{ | ||
"name": "tonal-pitchset", | ||
"version": "0.68.0", | ||
"version": "0.68.1", | ||
"description": "Pitch set utilities", | ||
@@ -5,0 +5,0 @@ "keywords": [], |
@@ -5,88 +5,4 @@ var test = require('tape') | ||
test('pitchset: notes', function (t) { | ||
t.deepEqual(pitchset.notes('g4 f5 g3 d3 a3 a4 c6 a1'), | ||
[ 'G', 'A', 'C', 'D', 'F' ]) | ||
t.deepEqual(pitchset.notes('C4 c3 C5 C4 c4'), ['C3', 'C4', 'C5']) | ||
t.end() | ||
}) | ||
test('pitchset: chroma', function (t) { | ||
t.equal(pitchset.chroma('c d e'), '101010000000') | ||
t.equal(pitchset.chroma('g g#4 a bb5'), '000000011110') | ||
t.equal(pitchset.chroma('P1 M2 M3 P4 P5 M6 M7'), | ||
pitchset.chroma('c d e f g a b')) | ||
t.equal(pitchset.chroma('101010101010'), '101010101010') | ||
t.end() | ||
}) | ||
test('pitchset: fromChroma', function (t) { | ||
t.deepEqual(pitchset.fromChroma('101010101010', 'C'), | ||
[ 'C', 'D', 'E', 'Gb', 'Ab', 'Bb' ]) | ||
t.deepEqual(pitchset.fromChroma('101010101010', null), | ||
[ '1P', '2M', '3M', '5d', '6m', '7m' ]) | ||
t.end() | ||
}) | ||
test('pitchset: modes', function (t) { | ||
// TODO: fixme, the 4th mode should have F# instead of Gb | ||
t.deepEqual(pitchset.chromaModes('c d e f g a b').map(function (chroma, i) { | ||
return pitchset.fromChroma(chroma, 'C') | ||
}), [ [ 'C', 'D', 'E', 'F', 'G', 'A', 'B' ], | ||
[ 'C', 'D', 'Eb', 'F', 'G', 'A', 'Bb' ], | ||
[ 'C', 'Db', 'Eb', 'F', 'G', 'Ab', 'Bb' ], | ||
[ 'C', 'D', 'E', 'Gb', 'G', 'A', 'B' ], | ||
[ 'C', 'D', 'E', 'F', 'G', 'A', 'Bb' ], | ||
[ 'C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb' ], | ||
[ 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab', 'Bb' ] ]) | ||
t.end() | ||
}) | ||
test('pitchset: isChroma', function (t) { | ||
t.equal(pitchset.isChroma('101010101010'), true) | ||
t.equal(pitchset.isChroma('1010101'), false) | ||
t.equal(pitchset.isChroma('blah'), false) | ||
t.equal(pitchset.isChroma('c d e'), false) | ||
t.end() | ||
}) | ||
test('pitchset: subset', function (t) { | ||
t.equal(pitchset.subset('c4 d5 e6', 'c2 d3'), true) | ||
t.equal(pitchset.subset('c4 d5 e6', 'c2 d3 e5'), true) | ||
t.equal(pitchset.subset('c d e', 'c d e f'), false) | ||
t.equal(pitchset.subset('c d e', 'c2 d3 f6'), false) | ||
t.end() | ||
}) | ||
test('pitchset: superset', function (t) { | ||
t.equal(pitchset.superset('c d e', 'c2 d3 e4 f5'), true) | ||
t.equal(pitchset.superset('c d e', 'e f g'), false) | ||
t.equal(pitchset.superset('c d e', 'd e'), false) | ||
t.end() | ||
}) | ||
test('pitchset: equal', function (t) { | ||
t.ok(pitchset.equal('c2 d3 e7 f5', 'c4 c d5 e6 f1')) | ||
t.end() | ||
}) | ||
test('pitchset: includes', function (t) { | ||
t.equal(pitchset.includes('c d e', 'C4'), true) | ||
t.equal(pitchset.includes('c d e', 'C#4'), false) | ||
t.end() | ||
}) | ||
test('pitchset: filter', function (t) { | ||
t.deepEqual(pitchset.filter('c d e', 'c2 c#2 d2 c3 c#3 d3'), | ||
[ 'c2', 'd2', 'c3', 'd3' ]) | ||
t.end() | ||
}) | ||
test('pitchset: chromaModes', function (t) { | ||
t.deepEqual(pitchset.chromaModes('c d e f g a b'), | ||
[ '101011010101', '101101010110', '110101011010', '101010110101', | ||
'101011010110', '101101011010', '110101101010' ]) | ||
t.deepEqual(pitchset.chromaModes('c d e f g a b', false), | ||
[ '101011010101', '010110101011', '101101010110', '011010101101', | ||
'110101011010', '101010110101', '010101101011', '101011010110', | ||
'010110101101', '101101011010', '011010110101', '110101101010' ]) | ||
t.deepEqual(pitchset.chromaModes('blah bleh'), []) | ||
t.end() | ||
}) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
2929
61
1