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
|
var Intonation = (function(){
var Intonation = function(opt){
opt = this.opt = Object.assign({
root: 440,
octave: 0,
interval: 2,
tet: 0,
intervals: null,
}, opt || {})
this.generate()
}
Intonation.prototype.generate = function(opt){
opt = Object.assign(this.opt, opt || {})
if (opt.scl) {
this.generate_scl()
}
else if (opt.tet) {
this.generate_tet()
}
else if (opt.intervals) {
this.generate_intervals()
}
}
Intonation.prototype.generate_intervals = function(){
var root = this.opt.root
var interval_list = this.opt.intervals
if (typeof interval_list == "string") {
interval_list = interval_list.split(" ")
}
this.intervals = interval_list
this.interval = this.opt.interval = parseInterval.call(this, interval_list.pop() )
this.scale = interval_list.map( parseIntervalString.bind(this) ).filter(function(v){
return !! v
})
}
Intonation.prototype.generate_tet = function(){
var scale = this.scale = []
var root = this.opt.root
var tet = this.opt.tet
var interval = this.interval = this.opt.interval
var ratio = Math.pow( interval, 1/tet )
var n = root
scale.push(n)
for (var i = 0; i < tet; i++) {
n *= ratio
scale.push(n)
}
this.intervals = null
}
Intonation.prototype.generate_scl = function(){
var root = this.opt.root
var scl = this.parse_scl( this.opt.scl )
this.intervals = scl.notes
this.interval = scl.notes.pop()
this.scale = scl.notes.map(function(v){
return v * root
})
}
Intonation.prototype.parse_scl = function(s){
var scl = {}
scl.comments = []
scl.notes = []
s.trim().split("\n").forEach(function(line){
// Lines beginning with an exclamation mark are regarded as comments
// and are to be ignored.
if ( line.indexOf("!") !== -1 ) {
scl.comments.push(line)
}
// The first (non comment) line contains a short description of the scale.
// If there is no description, there should be an empty line. (nb: which is falsey)
else if ( ! ('description' in scl) ) {
scl.description = line
}
// The second line contains the number of notes.
// The first note of 1/1 or 0.0 cents is implicit and not in the files.
else if ( ! scl.notes.length) {
scl.notes.push(1)
}
else {
// If the value contains a period, it is a cents value, otherwise a ratio.
var note = line.replace(/^[^-\.0-9]+/,"").replace(/[^-\/\.0-9]+$/,"")
if ( note.indexOf(".") !== -1 ) {
note = Math.pow( 2, (parseFloat(note) / 1200) )
}
else {
note = parseInterval(note)
}
if (note) {
scl.notes.push(note)
}
}
})
return scl
}
Intonation.prototype.index = function(i, octave){
octave = octave || this.opt.octave
var f = this.scale[ mod(i, this.scale.length)|0 ]
var pow = Math.floor(norm(i, 0, this.scale.length)) + octave
f *= Math.pow(this.interval, pow)
return f
}
Intonation.prototype.range = function(min, max){
var a = []
for (var i = min; i < max; i++) {
a.push( this.index(i) )
}
return a
}
Intonation.prototype.set_root = function(f){
this.opt.root = f
this.generate()
}
Intonation.prototype.quantize_frequency = function(f){
if (f == 0) return 0
var scale_f = f
var pow = 0
var interval = this.interval
var scale = this.scale
while (scale_f < root) {
scale_f *= interval
pow -= 1
}
while (scale_f > root * interval) {
scale_f /= interval
pow += 1
}
for (var i = 0; i < scale.length; i++) {
if (scale_f > scale[i]) continue
scale_f = scale[i]
break
}
scale_f *= Math.pow(2, pow)
return scale_f
}
Intonation.prototype.quantize_index = function(i){
return mod(index-1, this.scale.length)|0
}
var parseInterval = Intonation.prototype.parse_interval = function (s) {
if (typeof s == "number") return s
if (! s.indexOf("/") == -1) return parseInt(s)
var pp = s.split("/")
var num = parseInt(pp[0])
var den = parseInt(pp[1])
if (isNaN(num)) return 1
if (isNaN(den) || den == 0) return num
if (num == den) return 1
return num / den
}
var parseIntervalString = Intonation.prototype.parse_interval_string = function(s){
if (s.indexOf("/") !== -1) return parseInterval(s) * this.opt.root // intervals
if (s.indexOf("f") !== -1) return parseFloat(s) // pure frequencies
return parseFloat(s)
}
function norm(n,a,b){ return (n-a) / (b-a) }
function mod(n,m){ return n-(m * Math.floor(n/m)) }
return Intonation
})()
|