Tape Loops
In 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. | |
Preamble
We 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 -> |
Player
AudioBufferSourceNodes work in a slightly counter-intuitive way
for performance reasons. Once a sound has been triggered with a
start message, it cannot be re-triggered. The
FAQ on
HTML5 Rocks discusses the reasons
behind this. To make the sample player more natural to work with
in this application we wrap an AudioBufferSourceNode in a custom
class. | 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 loadBuffer . | @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 url , decode and store in
an instance variable. | 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() |
TapeMachine
A class that, given a DOM element el and a Player , simulates
a tape machine. | class TapeMachine
constructor: (@el, @player) ->
@setupSpeedToggle()
@setupFineSpeed()
@setupPlayStop() |
The tape speed switch toggles the base speed of the player
between normal speed half speed. | |
Bind a Switch to the double-speed
element within the current el . | speed_toggle = new Switch(el: $(@el).find('.speed'), states: ['normal', 'half']) |
The switch fires normal and half events. We bind
these events to the setBaseSpeed method of the player. | 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 valueChanged events
when turned. We send the value to the setSpeedFine method
on the player. | 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)
)
|