js303/assets/javascripts/app.js.coffee
2015-01-14 20:12:11 +01:00

257 lines
5.8 KiB
CoffeeScript

#= require jquery/dist/jquery
#= require handlebars/handlebars
#= require emblem/dist/emblem
#= require ember/ember
#= require jquery-knob/dist/jquery.knob.min
#= require synth
lin2exp = (x, inMin, inMax, outMin, outMax) ->
tmp = (x - inMin) / (inMax - inMin)
outMin * Math.exp(tmp * Math.log(outMax / outMin))
lin2lin = (x, inMin, inMax, outMin, outMax) ->
tmp = (x - inMin) / (inMax - inMin)
outMin + tmp * (outMax - outMin)
randomBool = -> !!Math.round(Math.random())
Ember.Handlebars.registerHelper "group", (options) ->
data = options.data
fn = options.fn
view = data.view
childView = view.createChildView Ember._MetamorphView,
context: Ember.get(view, "context")
template: (context, options) ->
options.data.insideGroup = true
return fn(context, options)
view.appendChild childView
Step = Ember.Object.extend
pitch: 50
gate: true
slide: false
accent: false
up: false
down: false
randomize: ->
@set "pitch", 40 + [0, 2, 3, 5, 7, 8, 10, 12][Math.round(7 * Math.random())]
@set "gate", randomBool()
@set "slide", randomBool()
@set "accent", randomBool()
@set "up", randomBool()
@set "down", randomBool()
clear: ->
@set "pitch", 50
@set "gate", true
@set "slide", false
@set "accent", false
@set "up", false
@set "down", false
Pattern = Ember.Object.extend
numberOfSteps: 16
init: ->
@set "steps", (new Step for _ in [0..15])
randomize: ->
step.randomize() for step in @get("steps")
clear: ->
step.clear() for step in @get("steps")
window.App = App = Ember.Application.create()
App.KnobView = Ember.View.extend
tagName: "input"
min: 0
max: 1
value: 0.5
step: 0.01
initKnob: (->
@$().knob
fgColor: "#fc3932"
bgColor: "#cccccc"
inputColor: "#000000"
font: "Abel"
fontWeight: "normal"
min: Number(@get("min"))
max: Number(@get("max"))
step: Number(@get("step"))
width: 106
height: 85
angleOffset: -125
angleArc: 250
change: (value) =>
@set "value", value
@$().val(@get("value")).trigger("change")
).on('didInsertElement')
valueChanged: (->
@$().val(@get("value")).trigger("change")
@trigger "change"
).observes("value")
App.ToggleButtonView = Ember.View.extend
tagName: "button"
classNames: ["toggle-button"]
classNameBindings: ["active:on"]
templateName: "toggle_button"
active: true
title: ""
mouseDown: ->
@set "active", !@get("active")
App.ButtonView = Ember.View.extend Ember.TargetActionSupport,
tagName: "button"
classNames: ["button"]
templateName: "button"
title: ""
click: ->
@triggerAction
action: @get("action")
target: @get("target")
App.SelectorButtonView = App.ButtonView.extend
classNameBindings: ["isSelected::grey"]
isSelected: (->
@get("value") == @get("variable")
).property("value", "variable")
click: -> @set "variable", @get("value")
App.PitchButtonView = App.SelectorButtonView.extend
templateName: null
mouseDown: -> @set "variable", @get("value")
click: null
App.ApplicationController = Ember.ObjectController.extend
patterns: (new Pattern for _ in [0..7])
allowedPitches: [52..40]
tempo: 120
cutoff: 0.5
resonance: 0.5
envmod: 0.0
decay: 0.2
accent: 0.5
distortion: 0.0
foldback: 0.0
delaySteps: 3
delayMix: 0
delayFeedback: 0.5
waveform: 0
cutoffChanged: (->
@synth.setcutoff lin2exp(@get("cutoff"), 0, 1, 20, 20000)
).observes("cutoff").on("init")
resonanceChanged: (->
@synth.setresonance @get("resonance")
).observes("resonance").on("init")
envmodChanged: (->
@synth.setenvmod @get("envmod")
).observes("envmod").on("init")
decayChanged: (->
@synth.decay = lin2lin(@get("decay"), 0, 1, 20, 4000)
).observes("decay").on("init")
accentChanged: (->
@synth.accent = @get("accent")
).observes("accent").on("init")
tempoChanged: (->
@synth.settempo @get("tempo")
).observes("tempo").on("init")
waveformChanged: (->
@synth.waveform = @get("waveform") == 1
).observes("waveform").on("init")
distortionChanged: (->
@synth.setdistthreshold @get("distortion")
).observes("distortion").on("init")
foldbackChanged: (->
@synth.dist_shape = @get("foldback")
).observes("foldback").on("init")
delayStepsChanged: (->
@synth.delay_length = @synth.steplength * Math.round(@get("delaySteps"))
).observes("delaySteps", "tempo").on("init")
delayMixChanged: (->
@synth.delay_send = @get("delayMix")
).observes("delayMix").on("init")
delayFeedbackChanged: (->
@synth.delay_feedback = @get("delayFeedback")
).observes("delayFeedback").on("init")
columns: (->
@get("currentPattern").get("steps").map (step, index) ->
step: step
index: index + 1
highlight: index % 4 == 0
).property("currentPattern.steps.@each")
init: ->
@_super.apply this, arguments
@synth = new TB303 genwavetable()
@synth.waveform = 1
@synth.onStepChanged = => @stepChanged()
@set "audioManager", new AudioManager(@synth)
@set "currentPattern", @patterns[0]
stepChanged: ->
$(".playing").removeClass("playing")
$(".step-column").eq(@synth.pos + 1).addClass("playing")
currentPatternChanged: (->
@synth.pattern ||= @get("currentPattern")
@synth.nextPattern = @get("currentPattern")
).observes("currentPattern").on("init")
class AudioManager
constructor: (@synth) ->
@context = new AudioContext
@bufferSize = 4096
@processor = @context.createScriptProcessor @bufferSize, 0, 1
@processor.onaudioprocess = @audioCallback
@processor.connect @context.destination
audioCallback: (e) =>
output = e.outputBuffer.getChannelData 0
for i in [0..@bufferSize] by 1
output[i] = @synth.render()
start: ->
@synth.reset()
@synth.running = true
stop: ->
@synth.running = false
$(".playing").removeClass("playing")