// Generate samples based on the "time" parameter.
var sample = sin(220.0 * 2*PI*time);
// Store final sample values in the "left" and "right" variables.
left = sample;
right = sample;
Help
Use the editor to write Javascript code to generate an audio signal in real time.
The following built-in variables are available:
time [input] -- elapsed time in seconds
left [output] -- store the sample value for the left channel here. Range is [-1.0...1.0].
right [output] -- store the sample value for the right channel here. Range is [-1.0...1.0].
For the sake of convenience, the following Math functions are defined at the global scope (no "Math." prefix required:
PI, abs(x), sign(x), sqrt(x), cbrt(x), min(x,y), max(x,y), clamp(x,minX,maxX), ceil(x), floor(x), trunc(x), fract(x), log(x), log10(x),
exp(x), pow(x,y), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x)
Tutorial:
Basic Wave Generation Let's start with the simplest example possible: a sine wave:
var sample = sin(time);
left = sample;
right = sample;
The resulting wave undergoes a single cycle once every 2*PI
(=6.28...) seconds. This gives a frequency of 1/6.28 = 0.159
Hz, which is well beyond the limits of human hearing (roughly
20Hz through 20,000 Hz). We'll need a much higher frequency if
we want to hear anything. First, let's generate a 1Hz wave, by
multiplying the time by 2*PI:
var sample = sin(2*PI*time);
left = sample;
right = sample;
Next, we declare a freq variable to store the desired
wave frequency. We'll start at 1.0 for now:
var freqHz = 1.0;
var sample = sin(freqHz * 2*PI*time);
left = sample;
right = sample;
This is still inaudible, but if we increase the value of
freqHz, we should eventually start to hear a low bass hum
at around 20Hz. Try it! For reference, the pitch most
orchestras tune to (A above middle C) is 440Hz.
var freqHz = 20.0;
var sample = sin(freqHz * 2*PI*time);
left = sample;
right = sample;
Adding Rhythm We can add a decay to the sound by scaling the sample
values by exp(-x). At x=0, exp(0) returns
1.0; as x gets larger (and -x gets closer to -infinity),
exp(-x) approaches zero. By scaling the input to
exp(), we get a longer or shorter decay.
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var sample = sin(freqHz * 2*PI*time) * exp(-decaySpeed*time);
left = sample;
right = sample;
Now let's add some rhythm to the sound. Instead of passing
time directory to exp(), we use fract() to
only pass the fractional part of time. This
forces the decay factor to reset to 1.0 every second. We
can go one step further and define a scale factor for the fract() term
so that we can express the tempo in terms of a more
familiar unit like beats per minute (bpm).
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
var sample = sin(freqHz * 2*PI*time) * exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
Advanced Wave Generation Sine waves are boring. Let's generate a more interesting
waveform, like a square wave. First let's pull the wave
generator into its own function.
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
function waveSine(freq,t) { return sin(freq * 2*PI*t); }
var sample = waveSine(freqHz,time) * exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
We can build a square wave a few different ways. The first
uses Fourier analysis to
break a square wave into an infinite series of sine waves
(for more details, see the Wikipedia entry for square waves).
The more terms we add, the more closely we approximate a
true square wave. Try slowly increasing the value of
numHarmonics below, or jump straight to around
50 for a pretty good approximation of a square wave.
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
function waveSine(freq,t) { return sin(freq * 2*PI*t); }
function waveSquare(freq,t) {
var sample=0;
var numHarmonics=1; // higher values -> better approximation
var iHarm;
for(iHarm=0; iHarm<numHarmonics; iHarm += 1) {
sample += sin(freq * (2*iHarm+1)*2*PI*t) / (2*iHarm+1);
}
sample *= 4/PI;
return sample;
}
var sample = waveSquare(freqHz,time) * exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
...or we could just take the high road, and observe
that it's quicker and easier to generate a perfect square
wave by simply taking the sign (+1 or -1) of a regular
sine wave. But the Fourier approach is still worth knowing
about!
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
function waveSine(freq,t) { return sin(freq * 2*PI*t); }
function waveSquare(freq,t) { return sign( sin(freq * 2*PI*t) ); }
var sample = waveSquare(freqHz,time) * exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
Phase Modulation Another way we can change the timbre of our waveform is to use a technique called phase modulation. We start by adding a second sine wave as an offset to the parameter of the main sine wave, with a scale factor called the "modulation index". Try increasing the modulation index to hear the difference in the resulting tone.
var freqHz = 440.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
var modulationIndex = 1.0;
var sample = sin(freqHz * 2*PI*time + modulationIndex*sin(freqHz * 2*PI*time))
* exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
...and once you have a modulation index, the obvious next step is to modulate it (with yet ANOTHER sine wave). As usual, try playing with the constants to see what effect they have on the output.
var freqHz = 220.0; // higher value -> higher pitch
var decaySpeed = 4.0; // higher value -> faster decay (shorter notes)
var tempoBpm = 60; // higher value -> faster rhythm
var modulationIndex = 10.0 + 15.0*sin(3.0*time);
var sample = sin(freqHz * 2*PI*time + modulationIndex*sin(freqHz * 2*PI*time))
* exp(-decaySpeed*fract(tempoBpm/60 * time));
left = sample;
right = sample;
Examples:
var modulationIndex = 10*sin(time);
var freqHz = 220;
var drumDistortion = 1000.0;
var phaseOffset = modulationIndex * sin(freqHz * 2*PI*time);
var sample = sin(freqHz * 2*PI*time + phaseOffset) * exp(-4.0*fract(4*time));
var kick = clamp( drumDistortion*sin( 100*exp( -10*fract(2.0*time ))), -1, 1);
var hat = clamp( drumDistortion*sin( 1000*exp(-100*fract(2.0*time + 0.5))), -1, 1);
sample += kick;
sample += hat;
left = sample;
right = sample;
TODO:
UI controls for:
Audio buffer size
Save current program to LocalStorage (with name)
Load program from LocalStorage (by name)
Delete program from LocalStorage (by name)
Move keyboard shortcuts from editor to window, so
they're active without editor focus.
Move editor and wave display into a frame?
Error reporting (syntax error, failure to set
left/right, etc.)