// 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;

Use the editor to write Javascript code to generate an audio signal in real time.

The following built-in variables are available:

[input] -- elapsed time in seconds**time**[output] -- store the sample value for the left channel here. Range is [-1.0...1.0].**left**[output] -- store the sample value for the right channel here. Range is [-1.0...1.0].**right**

**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;

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;

- 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.)