Skip to content

Instantly share code, notes, and snippets.

@Fordi
Last active April 3, 2021 06:31
Show Gist options
  • Save Fordi/de824c6c72b70d1bc979b501b95bd0ef to your computer and use it in GitHub Desktop.
Save Fordi/de824c6c72b70d1bc979b501b95bd0ef to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script>
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
function playSound(arr) {
var buf = new Float32Array(arr.length)
for (var i = 0; i < arr.length; i++) buf[i] = arr[i]
var buffer = context.createBuffer(1, buf.length, context.sampleRate)
buffer.copyToChannel(buf, 0)
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}
var generateSound = ({
// Total Volume of the sound
volume,
// Length of the sound in seconds
length,
// How many tones to generate in parallel
steps,
// The interval between the lowest and highest tone
interval,
// Zero to full in what part of the whole sound
attack = 0.1,
// Full to zero in what part of the whole sound
decay = 0.5,
// How far to sweep the whole thing
sweepInterval = 3,
// How to ease the sweep (^1/x -> more r shaped; ^x -> more j shaped)
sweepOrder = 1,
}) => {
const arr = [];
// The set of tones
const tones = new Array(steps + 1).join('.').split('.').map((_, i) => 440 * Math.pow(Math.pow(2, interval / 12), i / steps));
// Length of the sound in samples
const sampleLength = context.sampleRate * length;
// Base volume for a single tone
const baseVol = volume / tones.length;
for (var i = 0; i < sampleLength; i++) {
const sweep = Math.pow(2, Math.pow(i / sampleLength, sweepOrder) * sweepInterval/12);
const sampleVolume = (
// Calculate the non-wave amplitude for all the tones in this sample...
// The base volume for one tone
baseVol
// Positional attack / decay (no one likes a "click" to start their tone)
* Math.min(
1,
i / (sampleLength * attack),
(sampleLength - i) / (sampleLength * decay)
)
);
arr[i] = tones.reduce((sum, tone, ti) => (
sum
+ Math.cos(sweep * i / (context.sampleRate / tone / (Math.PI * 2))) * sampleVolume
), 0);
}
return arr;
};
const playIt = () => {
playSound(generateSound({
volume: parseFloat(document.getElementById('volume').value),
length: parseFloat(document.getElementById('length').value),
steps: parseFloat(document.getElementById('steps').value),
interval: parseFloat(document.getElementById('interval').value),
sweepInterval: parseFloat(document.getElementById('sweepInterval').value),
sweepOrder: parseFloat(document.getElementById('sweepOrder').value)
}));
};
</script>
</head>
<body>
<label for="volume">
Volume:<br />
<input id="volume" type="number" min="0" max="1" step="0.1" value="1"/>
</label><hr />
<label for="length">
Length:<br />
<input id="length" type="number" min="0" step="0.5" value="5" />
seconds
</label><hr />
<label for="steps">
# of Tones:<br />
<input id="steps" type="number" min="1" step="1" value="120" />
</label><hr />
<label for="interval">
Interval between lowest and highest tone in semitones (float allowed):<br />
<input id="interval" type="number" min="1" step="1" value="12" />
</label><hr />
<label for="sweepInterval">
How far to sweep in semitones:<br />
<input id="sweepInterval" type="number" min="0" step="1" value="0" />
</label><hr />
<label for="sweepOrder">
How to ease the sweep; 1 is flat. >1 is more 'j' shaped. 0..1 is more 'r' shaped:<br />
<input id="sweepOrder" type="number" min="0" step="0.1" value="1" />
</label><hr />
<button onclick="playIt()">Play</button><br />
Depending on your config, this may take a moment.
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment