Skip to content

Instantly share code, notes, and snippets.

@djfm
Created October 11, 2019 09:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djfm/6588b961d89edb8aba1ce6c436c32123 to your computer and use it in GitHub Desktop.
Save djfm/6588b961d89edb8aba1ce6c436c32123 to your computer and use it in GitHub Desktop.
const scale = {
c: 0,
'c#': 1,
d: 2,
'd#': 3,
e: 4,
f: 5,
'f#': 6,
g: 7,
'g#': 8,
a: 9,
'a#': 10,
b: 11
}
const freqs = [16.35, 17.32, 18.35, 19.45, 20.60, 21.83, 23.12, 24.50, 25.96, 27.50, 29.14, 30.87]
const baseFreq = note => {
const m = /^([a-g]#?)/.exec(note)
return freqs[scale[m[1]]]
}
const octaveMod = note => {
const m = /(\d+)$/.exec(note)
if(m) {
return +m[1]
}
return 0
}
const adsr = (
attack = 0.1,
release = 0.05
) => fn => (t, d) => {
if (t < attack * d) {
return t / (attack * d) * fn(t, d)
}
if (t > d * (1 - release)) {
return (1 - (t - d * (1 - release)) / (d * release)) * fn(t, d)
}
return fn(t, d)
}
const sin = (note, octave = 4) => adsr()((t, d) =>
Math.sin(
2 * Math.PI *
t * baseFreq(note) *
(2 ** (octave + octaveMod(note)))
))
const msin = (note, octave = 4) => (t, d) => {
let sample = 0;
for (let i = 0; i <= octave; i += 1) {
sample += sin(note, octave - i)(t, d) / (i + 1)
}
return 2 * sample / (octave + 1)
}
const mix = (...fns) => (t, d) =>
fns.reduce(
(sum, fn) => sum + fn(t, d),
0
) / fns.length
const seq = duration => (...fns) => t => {
const offset = Math.floor(t / duration * fns.length)
return fns[offset](t - offset * duration / fns.length, duration / fns.length)
}
const rep = (duration, times) => fn =>
t => fn(t - Math.floor(t / duration * times) * duration / times, duration / times)
const Am = mix(
msin('a'),
msin('c1'),
msin('e1')
)
const F = mix(
msin('f'),
msin('a'),
msin('c1')
)
const C = mix(
msin('c'),
msin('e'),
msin('g')
)
const G = mix(
msin('g'),
msin('d1'),
msin('b1')
)
const verseDuration = 8
const nVerses = 4
const duration = verseDuration * nVerses
const verseA = seq(verseDuration)(Am, F, C, G)
const verseB = seq(verseDuration)(
sin('c1'), sin('e1'), sin('g1'),
sin('a1'), sin('f1'), sin('c1'),
sin('c1'), sin('e1'), sin('g1'),
sin('g1'), sin('d1'), sin('b1')
)
const verse = mix(verseA, verseB)
const song = rep(duration, nVerses)(verse)
const ctx = new window.AudioContext()
const osc = ctx.createOscillator()
const buf = ctx.createBuffer(
2,
ctx.sampleRate * duration,
ctx.sampleRate
)
for(let c = 0; c < 2; c += 1) {
const data = buf.getChannelData(c)
for (let s = 0; s < data.length; s += 1) {
data[s] = song(s / ctx.sampleRate)
}
}
const src = ctx.createBufferSource()
src.buffer = buf
src.connect(ctx.destination)
src.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment