-
Notifications
You must be signed in to change notification settings - Fork 207
/
index.js
183 lines (170 loc) · 5.65 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { isNum, isArr, asPitch, id, isPitch, toPitchStr } from 'tonal-pitches'
import { tr } from 'tonal-distances'
// items can be separated by spaces, bars and commas
const SEP = /\s*\|\s*|\s*,\s*|\s+/
/**
* Convert anything to array. Speifically, split string separated by spaces,
* commas or bars. The arrays are passed without modifications and the rest of
* the objects are wrapped.
*
* This function always returns an array (null or undefined values are converted
* to empty arrays)
*
* Thanks to this function, the rest of the functions of this module accepts
* any object (or more useful: strings) as an array parameter.
*
* @param {*} source - the thing to get an array from
* @return {Array} the object as an array
*
* @example
* import { asArr } from 'tonal-arrays'
* asArr('C D E F G') // => ['C', 'D', 'E', 'F', 'G']
*/
export function asArr (src) {
return isArr(src) ? src
: typeof src === 'string' ? src.trim().split(SEP)
: (src === null || typeof src === 'undefined') ? []
: [ src ]
}
/**
* Map an array with a function. Basically the same as the JavaScript standard
* `array.map` but with two enhacements:
* - Arrays can be expressed as strings (see [asArr])
* - This function can be partially applied. This is useful to create _mapped_
* versions of single element functions. For an excellent introduction of
* the adventages [read this](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch4.html)
*
* @param {Function} fn - the function
* @param {Array|String} arr - the array to be mapped
* @return {Array}
* @example
* var arr = require('tonal-arr')
* var toUp = arr.map(function(e) { return e.toUpperCase() })
* toUp('a b c') // => ['A', 'B', 'C']
*
* @example
* var tonal = require('tonal')
* tonal.map(tonal.transpose('M3'), 'C D E') // => ['E', 'F#', 'G#']
*/
export function map (fn, list) {
return arguments.length > 1 ? map(fn)(list) : (l) => asArr(l).map(fn)
}
/**
* Filter an array with a function. Again, almost the same as JavaScript standard
* filter function but:
* - It accepts strings as arrays
* - Can be partially applied
*
* @param {Function} fn
* @param {String|Array} arr
* @return {Array}
*/
export function filter (fn, list) {
return arguments.length > 1 ? filter(fn)(list) : (l) => asArr(l).filter(fn)
}
// #### Transform lists in array notation
const listToStr = (v) => isPitch(v) ? toPitchStr(v) : isArr(v) ? v.map(toPitchStr) : v
/**
* Decorates a function to so it's first parameter is an array of pitches in
* array notation. Also, if the return value is a pitch or an array of pitches
* in array notation, it convert backs to strings.
*
* @function
* @param {Function} fn - the function to decorate
* @return {Function} the decorated function
* @example
* import { listFn } from 'tonal-arrays'
* const octUp = listFn((p) => { p[2] = p[2] + 1; return p[2] })
* octUp('C2 D2 E2') // => ['C3', 'D3', 'E3']
*/
export const listFn = (fn) => (src) => {
const param = asArr(src).map(asPitch)
const result = fn(param)
return listToStr(result)
}
/**
* Given an array of intervals, create a function that harmonizes a
* note with this intervals.
*
* @function
* @param {Array|String} ivls - the array of intervals
* @return {Function} The harmonizer
* @example
* import { harmonizer } from 'tonal-arrays'
* var maj7 = harmonizer('P1 M3 P5 M7')
* maj7('C') // => ['C', 'E', 'G', 'B']
*/
export const harmonizer = (list) => (pitch) => {
return listFn((list) => list.map(tr(pitch || 'P1')).filter(id))(list)
}
/**
* Harmonizes a note with an array of intervals. It's a layer of sintatic
* sugar over `harmonizer`.
*
* @function
* @param {String|Array} ivl - the array of intervals
* @param {String|Pitch} note - the note to be harmonized
* @return {Array} the resulting notes
* @example
* var tonal = require('tonal')
* tonal.harmonise('P1 M3 P5 M7', 'C') // => ['C', 'E', 'G', 'B']
*/
export const harmonize = function (list, pitch) {
return arguments.length > 1 ? harmonizer(list)(pitch) : harmonizer(list)
}
// a custom height function that
// - returns -Infinity for non-pitch objects
// - assumes pitch classes has octave -10 (so are sorted before that notes)
const objHeight = function (p) {
if (!p) return -Infinity
const f = p[1] * 7
const o = isNum(p[2]) ? p[2] : -Math.floor(f / 12) - 10
return f + o * 12
}
const ascComp = (a, b) => objHeight(a) - objHeight(b)
const descComp = (a, b) => -ascComp(a, b)
/**
* Sort an array or notes or intervals. It uses the JavaScript standard sort
* function.
*
* @param {Boolean|Function} comp - the comparator. `true` means use an
* ascending comparator, `false` a descending comparator, or you can pass a
* custom comparator (that receives pitches in array notation)
* @param {Array|String} arr - the array of notes or intervals
* @example
* import { sort } from 'tonal-arrays'
* sort(true, 'D E C') // => ['C', 'D', 'E']
* @example
* var tonal = require('tonal')
* tonal.sort(false, 'D E C') // => ['E', 'D', 'C']
*/
export function sort (comp, list) {
if (arguments.length > 1) return sort(comp)(list)
const fn = comp === true || comp === null ? ascComp
: comp === false ? descComp : comp
return listFn((arr) => arr.sort(fn))
}
/**
* Randomizes the order of the specified array using the Fisher–Yates shuffle.
*
* @function
* @param {Array|String} arr - the array
* @return {Array} the shuffled array
*
* @example
* import { shuffle } from 'tonal-arrays'
* @example
* var tonal = require('tonal')
* tonal.shuffle('C D E F')
*/
export const shuffle = listFn((arr) => {
var i, t
var m = arr.length
while (m) {
i = Math.random() * m-- | 0
t = arr[m]
arr[m] = arr[i]
arr[i] = t
}
return arr
})