Home ~ Tutorial 1: Getting Started ~ Tutorial 2: Communicating with Chuck ~ Tutorial 3: Listening to Chuck
Audio programming in the browser unveils new modes of interaction between users and sounds on the web.
In this tutorial we will take a look at mapping user inputs (via buttons, key presses, and more) to sounds and their parameters in ChucK.
This ChucK program plays a sine oscillator at 220Hz for as long as it runs.
// Sine oscillator at 220 Hz SinOsc osc => dac; 220.0 => osc.freq; while (true) { 1::ms => now; }
What if we want to change the frequency of the oscillator while the program runs?
First, we need a frequency variable to update. The WebChucK javascript API will register the variable with the server if we declare it as global.
// Sine oscillator at 220 Hz (updating from global variable) SinOsc osc => dac; 220.0 => global float freq; while (true) { freq => osc.freq; 1::ms => now; }
Let's run this ChucK code from a file called interaction-1.ck, add an "Update Freq" button, and map clicks to randomize the freq variable in ChucK (between 220 and 440Hz).
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript" src="./js/webchuck_host.js"></script>
<h1>Interfacing With WebChucK</h1>
<h2>Update a variable</h2>
<input id="startButton" type="button" value="Start ChucK" />
<input id="updateFreq" type="button" value="Update Freq" />
<script>
var serverFilesToPreload = [
{
serverFilename: './interaction-1.ck',
virtualFilename: 'interaction-1.ck'
}
];
var startButton = document.getElementById( "startButton" );
startButton.addEventListener( "click", async function() {
await preloadFilenames( serverFilesToPreload );
await startChuck();
await theChuckReady;
await theChuck.runFile( "interaction-1.ck" );
startButton.disabled = true;
});
var updateFreqButton = document.getElementById( "updateFreq" );
updateFreqButton.addEventListener( "click", async function() {
// Randomize frequency between 220 and 440Hz
var newFreq = 220.0 + Math.random() * 220.0;
theChuck.setFloat("freq", newFreq);
});
</script>
</body>
</html>
We reference the ChucK code's freq variable as a string id ("freq") and request that it's value be updated via the WebChucK API's setFloat() function. Here is the website in action. Click away and you can tell we are really making "Computer Music" now ;)
There are several other set…() functions:
Let's take a look at an example that employs a few of these methods at once.
The following ChucK code is in a file called interaction-2.ck. It arpeggiates a chord upward continuously.
// Chord arpeggiator SinOsc osc => ADSR adsr => dac; adsr.set(10::ms, 400::ms, 0.0, 3::ms); 48.0 => global float fund; [0.0, 4.0, 7.0, 11.0, 14.0] @=> global float notes[]; 500 => global int period; 0 => int which; while (true) { Std.mtof(fund+notes[which++]) => osc.freq; if (which >= notes.size()) 0 => which; adsr.keyOn(); period::ms => now; }
Now run interaction-2.ck and map a few extra buttons to control the rate of the arpeggiation (period) using setInt(), the root note of the chord (fund) using setFloat(), and the quality of the chord itself (chords) using setFloatArray().
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript" src="./js/webchuck_host.js"></script>
<h1>Interfacing With WebChucK</h1>
<h2>Arpeggiator: updating several variables</h2>
<input id="startButton" type="button" value="Start ChucK" />
<input id="changeTempo" type="button" value="Change Tempo" />
<input id="changeRoot" type="button" value="Change Root" />
<input id="changeChord" type="button" value="Change Chord" />
<script>
var serverFilesToPreload = [
{
serverFilename: './interaction-2.ck',
virtualFilename: 'interaction-2.ck'
}
];
var startButton = document.getElementById( "startButton" );
startButton.addEventListener( "click", async function() {
await preloadFilenames( serverFilesToPreload );
await startChuck();
await theChuckReady;
await theChuck.runFile( "interaction-2.ck" );
startButton.disabled = true;
});
var changeRootButton = document.getElementById( "changeRoot" );
changeRootButton.addEventListener( "click", async function() {
// Randomize frequency between 220 and 440Hz
var newFund = 45 + Math.round(Math.random() * 24);
theChuck.setFloat("fund", newFund);
});
var changeTempoButton = document.getElementById( "changeTempo" );
changeTempoButton.addEventListener( "click", async function() {
// Randomize period of tempo from 25-500 ms
var newPeriod= 25.0 + Math.random() * 475.0;
theChuck.setInt("period", newPeriod);
});
var whichChord = 0;
var chord1 = [0.0, 4.0, 7.0, 11.0, 14.0];
var chord2 = [-1, 3, 6, 9, 12];
var changeChordButton = document.getElementById( "changeChord" );
changeChordButton.addEventListener( "click", async function() {
theChuck.setFloatArray("notes", ((whichChord++)%2) ? chord1 : chord2);
});
</script>
</body>
</html>
Check the working site out here.
What if we just want to trigger an event in ChucK? Like, play a note, start a sample, reset a metronome…
Simply create a global instance of the ChucK native Event class (in ChucK) and call broadcastEvent("event") (in JavaScript).
The following ChucK code is in a file called interaction-3.ck. It arpeggiates a chord upward continuously.
// Note triggered by global event global Event playNote; // Sawtooth through a low pass filter and envelope SawOsc osc => LPF filter => ADSR env => dac; env.set(2::ms, 500::ms, 0.0, 0::ms); // A little LFO modulation on the filter freq SinOsc filterMod => blackhole; 3.5 => filterMod.freq; function void filterModProcess() { while (true) { 300 + filterMod.last() * 150 => filter.freq; 1::samp => now; } } spork ~ filterModProcess(); while (true) { playNote => now; 0 => filterMod.phase; env.keyOn(); }
Below we run interaction-3.ck and broadcast a “playNote” event when a button labeled “Play Note” is clicked.
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript" src="./js/webchuck_host.js"></script>
<h1>Interfacing With WebChucK</h1>
<h2>Mapping button to Event</h2>
<input id="startButton" type="button" value="Start ChucK" />
<input id="playNote" type="button" value="Play Note" />
<script>
var serverFilesToPreload = [
{
serverFilename: './interaction-3.ck',
virtualFilename: 'interaction-3.ck'
}
];
var startButton = document.getElementById( "startButton" );
startButton.addEventListener( "click", async function() {
await preloadFilenames( serverFilesToPreload );
await startChuck();
await theChuckReady;
await theChuck.runFile( "interaction-3.ck" );
startButton.disabled = true;
});
var playNoteButton = document.getElementById( "playNote" );
playNoteButton.addEventListener( "click", async function() {
theChuck.broadcastEvent("playNote");
});
</script>
</body>
</html>
Note: As with setInt(), setFloat(), etc., make sure the name of the broadcasted event is identical to the global Event instance variable name.
Check the working site out here.
Home ~ Tutorial 1: Getting Started ~ Tutorial 2: Communicating with Chuck ~ Tutorial 3: Listening to Chuck