diff options
| author | Jules Laplace <jules@okfoc.us> | 2017-04-22 00:43:57 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2017-04-22 00:43:57 -0400 |
| commit | a6581c55ba134b3c9547968315c8915ae52fe016 (patch) | |
| tree | 2accb1cd88357446e06fbc77634a4bc7b43c3e72 /client/lib/intonation.js | |
microtonal sampler
Diffstat (limited to 'client/lib/intonation.js')
| -rw-r--r-- | client/lib/intonation.js | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/client/lib/intonation.js b/client/lib/intonation.js new file mode 100644 index 0000000..e5465af --- /dev/null +++ b/client/lib/intonation.js @@ -0,0 +1,162 @@ +module.exports = (function(){ + var Intonation = function(opt){ + opt = this.opt = Object.assign({ + name: "", + 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.name = this.opt.name || "interval list" + 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-1; i++) { + n *= ratio + scale.push(n) + } + this.name = this.opt.name || tet + "-tone equal temperament" + 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.name = this.opt.name || scl.description + 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 +})() |
