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(){
root = parseFloat( $("#root").val() )
var intervals = $("#intervals").val().split(/\s/).map(function(v){
if (v.indexOf("/") !== -1) return parseInterval(v) // intervals
if (v.indexOf("f") !== -1) return parseFloat(v) // pure frequencies
return parseFloat(v)
}).filter(function(v){
return !! v
})
if (! intervals.length) return
var last_interval = intervals[ intervals.length-1 ]
if (last_interval > 20) {
interval = last_interval / intervals[0]
}
else {
interval = intervals.pop()
}
scale = intervals.map(function(v){
if (v < 20) {
return v * root
}
else {
return v
}
})
}
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 = intervals[ intervals.length - 1 ]
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-1, scale.length)|0
}
environment.index_to_frequency = function(index){
var f = scale[ mod(index, scale.length)|0 ]
// if (f < 20) {
var pow = Math.floor(norm(index-1, 0, scale.length)) - 2
f *= Math.pow(interval, pow)
// }
// console.log(index, f, 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()
// console.log(index, scale.length, index / scale.length)
var color = Snap.hsl(mod(index / scale.length * 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 = "qwertyuiopasdfghjklzxcvbnm"
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
return num / den
}
return environment
})()