var environment = (function(){ var environment = {} var image_index = -1 var snap, dot_grid, polysynth var strided_wires = [], wires = [] var scale = [], root, tet, interval, duration var dot_color = "#333" var drawing = true var palette = [ "#ff0000", "#00ffff", "#0000ff", "#ff8800", "#ffff00", ] environment.init = function(){ if (is_mobile) { environment.request_audio_context() } else { environment.ready() } } environment.ready = function(){ environment.build() environment.bind() } environment.request_audio_context = function(){ var element = $("
", {"id" : "MobileStart"}).appendTo("body") var button = $("
").attr("id", "Button").html("Enter
(please unmute your phone)").appendTo(element) StartAudioContext.setContext(Tone.context) StartAudioContext.on(button) StartAudioContext.onStarted(function(){ element.remove() environment.ready() }) } environment.build = function(){ environment.scale() environment.duration() environment.set_intervals() snap = new Snap (window.innerWidth, window.innerHeight - $("#controls").height()) dot_grid = new DotGrid () dot_grid.load() polysynth = new Tone.PolySynth(8, Tone.synth) polysynth.set({ oscillator: { type: $("#waveform").val() }, envelope:{ attack: 0.01, decay: 2.5, sustain: 0.0, release: 0.1, } }) // polysynth = new Tone.PolySynth(8, Tone.FMSynth) // polysynth.set({ // harmonicity:2, // modulationIndex:3, // detune:0, // oscillator:{ // type:"sine", // }, // envelope:{ // attack:0.01, // decay:0.01, // sustain:1, // release:0.2, // }, // moduation:{ // type:"sawtooth", // }, // modulationEnvelope:{ // attack:0.1, // decay:0, // sustain:1, // release:0.2, // } // }) // polysynth.set({ // oscillator: { type: $("#waveform").val() }, // envelope:{ // attack: 0.01, // decay: 2.5, // sustain: 0.0, // release: 0.1, // } // }) var comp = new Tone.Compressor(-30, 3).toMaster() // var reverb = new Tone.Freeverb () // reverb.wet.value = 0.01 // polysynth.connect(reverb) // reverb.connect(comp) polysynth.connect(comp) // environment.stride() // environment.randomize() } environment.bind = function(){ $(window).focus(environment.focus) $(window).blur(environment.blur) window.addEventListener("keydown", environment.keydown, true) $("#waveform").on("input", environment.setWaveform) $("#x").on("keydown", environment.stride) $("#y").on("keydown", environment.stride) $("#rand").on("input", environment.randomize) $("#tet").on("input", environment.scale) $("#root").on("input", environment.scale) $("#interval").on("input", environment.scale) $("#intervals").on("input", environment.set_intervals) $("#duration").on("input", environment.duration) $("#use_scale").on("change", environment.use_scale) if (is_mobile) { snap.node.addEventListener("touchstart", getFirstTouch( dot_grid.mousedown.bind(dot_grid) )) document.addEventListener("touchmove", getFirstTouch( environment.mousemove)) document.addEventListener("touchend", dot_grid.mouseup.bind(dot_grid) ) $("#draw").on("touchstart", environment.check_drawing) $("#reset").on("touchstart", environment.reset) } else { $(snap.node).on("mousedown", dot_grid.mousedown.bind(dot_grid)) $(window).mousemove(environment.mousemove) $(window).on("mouseup", dot_grid.mouseup.bind(dot_grid)) } } environment.blur = function(){ last_p = null } environment.focus = function(){ } environment.use_scale = function(){ use_scale = $("#use_scale").get(0).checked } environment.set_intervals = function(){ scale = $("#intervals").val().split(" ").map(parseInterval).filter(function(v){ return !! v }).map(function(v){ return v * root }) } environment.check_drawing = function(){ drawing = ! drawing $("#draw").html( drawing ? "drawing" : "playing" ) } environment.reset = function(){ environment.reset_wires() } environment.duration = function(){ duration = parseInt( $("#duration").val() ) || 1000 duration /= 1000 } environment.keydown = function(e){ if (e.altKey || e.ctrlKey || e.metaKey) { e.stopPropagation() return } if (e.keyCode == 69) { // charcode: e e.preventDefault() } if (document.activeElement instanceof HTMLInputElement && (e.keyCode in key_numbers)) { e.stopPropagation() return } if (! (e.keyCode in keys)) return var i = keys[e.keyCode] if (e.shiftKey) i += letters.length var wire = wires[ i % wires.length ] wire && wire.play() } environment.setWaveform = function(){ var w = $("#waveform").val() polysynth.set({ oscillator: { type: w } }) } environment.last_p = { x: 0, y: 0 } environment.positionFromEvent = function(e){ var offset = $(snap.node).offset() var p = { x: e.pageX - offset.left, y: e.pageY - offset.top, } return p } environment.mousemove = function(e){ var p = environment.positionFromEvent(e) if (dot_grid.active) { dot_grid.active.update({ point: p, muted: false, moving: true, duration: 0.2, }) } else { wires.forEach(function(wire){ wire.intersect(p, environment.last_p) }) } environment.last_p = p } environment.stride = function(e){ var dx = parseFloat( $("#x").val() ) var dy = parseFloat( $("#y").val() ) environment.reset_strided_wires() if (! dx && ! dy) return if (dx < 1.1 && ! dy) return var x0 = 0, x1 = dx, y0 = 0, y1 = dy var wire do { wire = new Wire ({ head: { x: x0, y: y0 }, tail: { x: x1, y: y1 }, color: { h: Math.round(y0 / dot_grid.dots[0].length * 180 + 180), s: 100, l: 50 }, integer: true, }) strided_wires.push(wire) if (dx || ! dy) x0 += 1 y0 += 1 x1 += dy y1 += dx } while ( y0 < dot_grid.dots.length) } environment.reset_strided_wires = function(){ strided_wires.forEach(function(wire){ wire.remove() }) strided_wires = [] } environment.reset_wires = function(){ wires.forEach(function(wire){ wire.remove(true) }) wires.length = 0 } environment.randomize = function(){ var n = parseInt( $("#rand").val() ) environment.reset_strided_wires() var x0, y0, x1, y1 while (n--) { x0 = randint( dot_grid.dots.length ) x1 = randint( dot_grid.dots.length ) y0 = randint( dot_grid.dots[0].length ) y1 = randint( dot_grid.dots[0].length ) wire = new Wire ({ head: { x: x0, y: y0 }, tail: { x: x1, y: y1 }, color: { h: Math.round(y0 / dot_grid.dots[0].length * 180 + 180), s: 100, l: 50 }, integer: true, }) strided_wires.push(wire) } } environment.scale = function(e){ if (e && e.keyCode && ! key_numbers(e.keyCode)) { e.preventDefault() return } var ratio, n tet = parseFloat( $("#tet").val() ) root = parseFloat( $("#root").val() ) interval = parseInterval( $("#interval").val() ) ratio = Math.pow( interval, 1/tet ) n = root scale = [n] for (var i = 0; i < tet; i++) { n *= ratio scale.push(n) } wires.forEach(function(wire){ wire.updateColor(true) }) } // quantize a frequency to the scale environment.quantize_frequency = function(f, get_index){ if (f == 0) return 0 var scale_f = f var pow = 0 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 } if (get_index) { return i } scale_f *= Math.pow(2, pow) // console.log(scale_f) return scale_f } environment.quantize_index = function(index){ return mod(index, scale.length-1)|0 } environment.index_to_frequency = function(index){ var f = scale[ mod(index, scale.length-1)|0 ] var pow = Math.floor(norm(index, 0, scale.length-1)) - 2 f *= Math.pow(interval, pow) // console.log(index, scale.length, pow, f) return f } function DotGrid (opt){ this.opt = defaults(opt, { dot_radius: 4, dot_spacing: 20, mouse_radius: 200, wave_width: 200, fill: "#ffffff", speed: 4, ease: 10, }) this.active = null this.group = snap.g() this.wire_group = snap.g() this.wire_group.attr({ "style": "pointer-events: none" }) this.dots = [] } DotGrid.prototype.indexToPoint = function(p){ var dx = this.opt.dot_spacing var q = {} q.x = (p.x + 1/2) * dx q.y = (p.y + 1/2) * dx return q } DotGrid.prototype.quantize = function(p){ var dx = this.opt.dot_spacing var q = {} q.x = quantize(p.x - dx/2, dx) + dx/2 q.y = quantize(p.y - dx/2, dx) + dx/2 return q } DotGrid.prototype.load = function(){ var dots = this.dots var dx = dy = this.opt.dot_spacing var dot_radius = this.opt.dot_radius var x, y, i, j, u, r, a var w = this.w = window.innerWidth var h = this.h = window.innerHeight - $("#controls").height() - dy for (x = dx/2; x < w; x += dx) { a = [] dots.push(a) for (y = dy/2; y < h; y += dy) { dot = this.group.circle(x, y, dot_radius).attr({ fill: dot_color, }) a.push(dot) } } } DotGrid.prototype.mousedown = function(touch, e){ if (! drawing) return var p = environment.positionFromEvent(touch) var q = this.quantize(p) if (! this.active) { this.active = new Wire ({ head: q }) } else if (this.active.head.x == q.x && this.active.head.y == q.y) { return } else if (this.active) { (e || touch).preventDefault() this.active.setTail(q) this.active = null } } DotGrid.prototype.mouseup = function(e){ if (this.active && this.active.length > 0) { // var q = this.quantize(environment.last_p) this.active.setTail(environment.last_p) this.active = null } } function Wire (opt){ this.opt = opt if (opt.integer) { opt.head = dot_grid.indexToPoint(opt.head) if (opt.tail) { opt.tail = dot_grid.indexToPoint(opt.tail) } } if (opt.color && opt.color.h) { opt.color = Snap.hsl(opt.color.h, opt.color.s, opt.color.l) } else if (! opt.color) { opt.color = choice(palette) } this.length = 0 this.head = opt.head var path_str = "M" + 0 + "," + 0 this.path = dot_grid.wire_group.path(path_str).attr({ stroke: opt.color, fill: "none", strokeWidth: 3, }) if (opt.tail) { this.setTail(opt.tail, true) } } Wire.prototype.update = function(opt){ var p = opt.point var length = this.length = dist(this.head.x, this.head.y, p.x, p.y) var theta = angle(this.head.x, this.head.y, p.x, p.y) * 180 / Math.PI var index = Math.floor( length / dot_grid.opt.dot_spacing ) if (index < 0) return var length = this.length = (index * dot_grid.opt.dot_spacing) var q = { x: Math.sin(theta) * length + this.head.x, y: Math.cos(theta) * length + this.head.y, } // var q = dot_grid.quantize(p) var path_str = "M" + 0 + "," + 0 + "t" + length.toFixed(2) + "," + 0 var tran_str = "translate(" + this.head.x + "," + this.head.y + ") " + "rotate(" + theta + ")" var color = this.updateColor() this.path.attr({ d: path_str, transform: tran_str, stroke: color, }) if (! opt.muted && length > 0) { this.play(opt) } return length } Wire.prototype.updateColor = function(should_set){ var index = this.index() var color = Snap.hsl(mod(index / tet * 240 + 180, 360), 100, 50) if (should_set) { this.path.attr({ stroke: color, }) } return color } Wire.prototype.setTail = function(p, muted){ this.tail = p this.update({ point: p, muted: true }) wires.push(this) } Wire.prototype.intersect = function (a,b) { if (a.x - b.x == 0 || a.y - b.y == 0) return if (doLineSegmentsIntersect(a, b, this.head, this.tail)) { this.play() } } Wire.prototype.index = function(){ // var f = this.length / 340.29 * root // return environment.quantize( f, true ) return environment.quantize_index( this.length / dot_grid.opt.dot_spacing ) } Wire.prototype.frequency = function(){ var f if (use_scale) { f = environment.index_to_frequency( this.length / dot_grid.opt.dot_spacing - 1 ) // f = environment.quantize_frequency(f) } else { f = this.length / 340.29 * root } return f } Wire.prototype.play = function(opt){ opt = opt || {} var d = opt.duration || duration // console.log(d, duration) // var f = this.length / 340.29 * root var f = this.frequency() if (opt.moving && last_f == f) { return } last_f = f // console.log(f, duration) polysynth.triggerAttackRelease(f, d || randrange(1.0, 2.5)) } var last_f = 0 Wire.prototype.remove = function(dont_splice){ this.path.remove() if (! dont_splice) { wires.splice(wires.indexOf(this), 1) } } environment.update = function(t){ } var keys var keys = {} var key_numbers = {} var letters = "qwertyuiop[]\\asdfghjkl;'zxcvbnm,./" var numbers = "1234567890"; letters.toUpperCase().split("").map(function(k,i){ keys[k.charCodeAt(0)] = i }) numbers.split("").map(function(k,i){ keys[k.charCodeAt(0)] = true key_numbers[k.charCodeAt(0)] = true }) function parseInterval (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 2 if (isNaN(den) || den == 0) return num if (num == den) return 2 if (num < den) return den/num return num / den } return environment })()