tapeloops.coffee | |
---|---|
Tape LoopsIn the early days of the Radiophonics Workshop pieces of music were painstakingly composed a note at a time by recording and splicing together pieces of tapes in loops. In this video you can see Delia Derbyshire explaining the process. This demo is a simulation of three tape machines with variable speed controls that have to be triggered in time to build up a simple composition. It is a simple example of the use of the Web Audio API's AudioBufferSourceNode. This application is a simulation of three tape loop machines with variable speed controls using the Web Audio API. |
|
PreambleWe use jQuery, backbone.js and some custom UI elements (namely a knob and a switch) in this application. We make these libraries available to our application using require.js. | require(["jquery", "backbone", "knob", "switch"], ($, Backbone, Knob, Switch) ->
$(document).ready -> |
PlayerAudioBufferSourceNodes work in a slightly counter-intuitive way
for performance reasons. Once a sound has been triggered with a
| class Player
constructor: (@url) ->
this.loadBuffer() |
Set the default playback speed. | this.setBaseSpeed(1)
this.setSpeedFine(1)
play: ->
if @buffer |
Set the buffer of a new AudioBufferSourceNode equal to the
samples loaded by | @source = context.createBufferSource()
@source.buffer = @buffer
@source.connect context.destination
@source.loop = true
this.setSpeed() |
Trigger the source to play immediately. | @source.start 0
stop: ->
if @buffer && @source |
Stop the sample playback immediately. | @source.stop 0
setBaseSpeed: (speed) ->
@base_speed = speed
this.setSpeed()
setSpeedFine: (speed) ->
@fine_speed = speed
this.setSpeed() |
The playback speed is a combination of the "base speed" (normal or double speed playback) and a "fine speed" control. | setSpeed: ->
if @source
@source.playbackRate.value = @base_speed * @fine_speed |
Load the samples from the provided | loadBuffer: ->
self = this
request = new XMLHttpRequest()
request.open('GET', @url, true)
request.responseType = 'arraybuffer' |
Load the decoded sample into the buffer if the request is successful. | request.onload = =>
onsuccess = (buffer) ->
self.buffer = buffer
onerror = -> alert "Could not decode #{self.url}"
context.decodeAudioData request.response, onsuccess, onerror
request.send() |
TapeMachineA class that, given a DOM element | class TapeMachine
constructor: (@el, @player) ->
@setupSpeedToggle()
@setupFineSpeed()
@setupPlayStop() |
The tape speed switch toggles the base speed of the player between normal speed half speed. | setupSpeedToggle: () -> |
Bind a Switch to the | speed_toggle = new Switch(el: $(@el).find('.speed'), states: ['normal', 'half']) |
The switch fires | speed_toggle.on('normal', => @player.setBaseSpeed(1))
speed_toggle.on('half', => @player.setBaseSpeed(0.5)) |
Attach a Knob to the fine speed control to vary the playback speed by ±3%. | setupFineSpeed: () ->
fine_speed_control = new Knob(
el: $(@el).find('.fine-speed')
initial_value: 1
valueMin: 0.97
valueMax: 1.03
) |
The Knob triggers | fine_speed_control.on('valueChanged', (v) =>
@player.setSpeedFine(v)
) |
A switch to toggle the play state of the machine. | setupPlayStop: () ->
play_stop_control = new Switch(el: $(@el).find('.play'))
play_stop_control.on('on', => @player.play())
play_stop_control.on('off', => @player.stop()) |
Application Setup | |
Create an audio context for our application to use. | context = new AudioContext |
Instantiate three separate players with the three loops. | player1 = new Player('/audio/delia_loop_01.wav')
player2 = new Player('/audio/delia_loop_02.wav')
player3 = new Player('/audio/delia_loop_03.wav') |
Set up the UI. | new TapeMachine('#machine1', player1)
new TapeMachine('#machine2', player2)
new TapeMachine('#machine3', player3)
)
|