Published
Edited
Mar 21, 2022
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
audioCtx = await new AudioContext()
Insert cell
analyzer = audioCtx.createAnalyser()
Insert cell
out = audioCtx.destination;
Insert cell
// Create a master gain node to limit the volume so as not to damage our ears :)
masterGain = audioCtx.createGain()
Insert cell
// Connect it to the audio output
masterGain.connect(out)
Insert cell
Insert cell
function fmSynth() {
let carrier, mod1;

// Envelope attack and release time in seconds
let attackTime = attack;
let releaseTime = release;
// let attackTime = 0.01;
// let releaseTime = 1;

// This function will play an enveloped note specified in semitones offset from A440
this.playNote = function (note) {
// Create oscillators
carrier = audioCtx.createOscillator();
mod1 = audioCtx.createOscillator();

// Set frequencies
carrier.frequency.value = getNoteFrequency(note);
mod1.frequency.value = mod1Freq + mod1FreqFine;

// Create a gain node to control modulation depth
var mod1Gain = audioCtx.createGain();
mod1Gain.gain.value = mod1Depth;

// Create a gain node to act as an envelope
let envelope = audioCtx.createGain();
envelope.gain.cancelScheduledValues(audioCtx.currentTime);
envelope.gain.setValueAtTime(0, audioCtx.currentTime);

// Set some parameter automation to actually generate the envelope
envelope.gain.linearRampToValueAtTime(1, audioCtx.currentTime + attackTime);
envelope.gain.linearRampToValueAtTime(
0,
audioCtx.currentTime + attackTime + releaseTime
);

// Connect the nodes
mod1.connect(mod1Gain);
mod1Gain.connect(carrier.detune); // This is the magic FM part!
carrier.connect(envelope);
envelope.connect(analyzer); // This lets us visualize the carrier wave
envelope.connect(masterGain);

// Make sound
mod1.start();
carrier.start();

// Schedule automatic oscillation stop
mod1.stop(audioCtx.currentTime + attackTime + releaseTime);
carrier.stop(audioCtx.currentTime + attackTime + releaseTime);
};
}
Insert cell
synth = new fmSynth();
Insert cell
Insert cell
note = {
while (play == "Playing") {
yield new Promise(resolve => {
setTimeout(() => {
let note = getRandomNote();
synth.playNote(note);
resolve(note);
}, (attack + release + spacing) * 1000);
})
}
}
Insert cell
getRandomNote = function () {
// Pick a random note from a scale
let pentatonic = [-24, -21, -19, -17, -14, -12, -9, -7, -5, -2, 0, 3, 5, 7, 10, 12, 15, 17, 19, 22, 24]
let randomIndex = Math.floor(Math.random() * pentatonic.length)
return pentatonic[randomIndex]
}
Insert cell
Insert cell
getNoteFrequency = function(semitones) {
// Get the frequency in Hz of a note given as semitones above or below A440
return 440 * ((2 ** (1/12)) ** semitones)
}
Insert cell
Insert cell
data = {
analyzer.fftSize = 256
const bufferLength = analyzer.fftSize
const dataArray = new Uint8Array(bufferLength)
return dataArray
}
Insert cell
{
let frame = 0
let ctx = canvas.getContext('2d')
let len = data.length

ctx.fillStyle = '#FFFFFF'
ctx.lineWidth = 1
ctx.strokeStyle = '#000000'

while(true) {
analyzer.getByteTimeDomainData(data)

ctx.fillRect(0, 0, width, height)
ctx.beginPath()
ctx.moveTo(0, height / 2)

for(let i = 1; i < len - 1; ++i) {
let x = i / len * width
let y = data[i] / 128 * height / 2
ctx.lineTo(x, y)
}

ctx.lineTo(width, height / 2)
ctx.stroke()

// This doesn't do anything interesting but the notebook crashes when I take it out.
// I think it's because it's the only thing limiting the speed of the canvas refresh?
// Needs more investigation...
yield d3.max(data)
}
}
Insert cell
// Set the chart height based on the width of the notebook
height = width * 0.2
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more