Skip to content

Instantly share code, notes, and snippets.

@Nepi24
Created May 1, 2024 14:00
Show Gist options
  • Select an option

  • Save Nepi24/ae17db11e040eb2eec24c9bf96dd59c0 to your computer and use it in GitHub Desktop.

Select an option

Save Nepi24/ae17db11e040eb2eec24c9bf96dd59c0 to your computer and use it in GitHub Desktop.
Neural Bach Very Slowly
<canvas></canvas>
<button id="start" disabled>Loading&hellip;</button>
let Tone = mm.Player.tone;
let notes = Promise.all([
bounceChord(["C7"], fmDrone, 1, 2),
bounceChord(["F#7"], fmDrone, 1, 2),
bounceChord(["C8"], fmDrone, 1, 2),
bounceChord(["F#8"], fmDrone, 1, 2),
bounceChord(["C7"], fmBells, 1, 2),
bounceChord(["C8"], fmBells, 1, 2)
]),
stepDur = 25,
seqSteps = 32,
model = new mm.Coconet(
`https://storage.googleapis.com/magentadata/js/checkpoints/coconet/bach`
),
startButton = document.querySelector("#start"),
canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
minVisNote = 35,
maxVisNote = 75,
noteVisualization = [];
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
Promise.all([notes, model.initialize()]).then(([buffers]) => {
start.disabled = false;
start.textContent = "Play";
start.addEventListener("click", () => {
Tone.context.resume();
nextSequence();
nextFrame();
start.remove();
});
let bufs = [
[Tone.Frequency("C7").toMidi(), buffers[0]],
[Tone.Frequency("F#7").toMidi(), buffers[1]],
[Tone.Frequency("C8").toMidi(), buffers[2]],
[Tone.Frequency("F#8").toMidi(), buffers[3]],
[Tone.Frequency("C7").toMidi(), buffers[4]],
[Tone.Frequency("C8").toMidi(), buffers[5]]
];
async function nextSequence() {
// Start from a random pitch class
let seedNote = 60 + Math.floor(Math.random() * 12);
// Create a note sequence that plays that pitch on first time step
let ns = mm.NoteSequence.create();
ns.quantizationInfo = { stepsPerQuarter: 4 };
ns.totalQuantizedSteps = seqSteps;
let note = new mm.NoteSequence.Note();
note.pitch = seedNote;
note.instrument = 1; // alto voice
note.quantizedStartStep = 0;
note.quantizedEndStep = 1;
ns.notes.push(note);
// Fill in the rest
let output = await model.infill(ns);
for (let note of output.notes) {
let start = note.quantizedStartStep * stepDur;
let [bufNote, buffer] = bufs[Math.floor(Math.random() * bufs.length)];
let noteDistance = note.pitch - bufNote;
let playbackRate = 2 ** (noteDistance / 12);
let src = new Tone.BufferSource({ buffer, playbackRate }).toMaster();
src.start(Tone.now() + start);
let noteVis = {
pitch: note.pitch,
startAt: Tone.now() + start,
endAt: Tone.now() + start + 1 / playbackRate,
hue: Math.round(((Tone.now() + start + 1000) / 10) % 360)
};
noteVisualization.push(noteVis);
}
setTimeout(nextSequence, seqSteps * stepDur * 1000);
}
function nextFrame() {
let now = Tone.now();
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = noteVisualization.length - 1; i >= 0; i--) {
let noteVis = noteVisualization[i];
if (noteVis.endAt < now) {
noteVisualization.splice(i, 1);
} else if (noteVis.startAt < now) {
let noteHeight = canvas.height / (maxVisNote - minVisNote);
let noteY = canvas.height - noteHeight * (noteVis.pitch - minVisNote);
let noteHalfDur = (noteVis.endAt - noteVis.startAt) / 2;
let noteMidpoint = noteVis.startAt + noteHalfDur;
let distToMid = Math.abs(now - noteMidpoint) / noteHalfDur;
context.fillStyle = `hsla(${noteVis.hue}, 75%, 75%, ${1 - distToMid})`;
context.shadowColor = `hsla(${noteVis.hue}, 75%, 75%, 1)`;
context.shadowBlur = (now - noteVis.startAt) * 5;
context.fillRect(0, noteY, canvas.width, noteHeight);
}
}
requestAnimationFrame(nextFrame);
}
});
function bounceChord(notes, synthFn, playDuration, tailDuration) {
let playSeconds = Tone.Time(playDuration).toSeconds();
let tailSeconds = Tone.Time(tailDuration).toSeconds();
return Tone.Offline(
() => synthFn(notes, playSeconds, tailSeconds),
playSeconds + tailSeconds
);
}
function fmDrone(notes, playSeconds, tailSeconds) {
let reverb = new Tone.Reverb({ decay: playSeconds / 4, wet: 0.8 });
reverb.generate(); // Risky not to wait but ¯\_(ツ)_/¯
let synth = new Tone.PolySynth(notes.length, Tone.FMSynth).chain(
new Tone.Chorus({ frequency: 0.33, depth: 0.7, wet: 0.85 }),
new Tone.FeedbackDelay({
delayTime: playSeconds / 16,
feedback: 0.33,
wet: 0.66
}),
reverb,
Tone.Master
);
synth.set({
harmonicity: 0.5,
modulationIndex: 1,
oscillator: {
type: "sine"
},
envelope: {
attack: playSeconds / 4,
sustain: 1,
release: tailSeconds - 1,
attackCurve: "linear",
releaseCurve: "linear"
},
modulation: { type: "sine" },
modulationEnvelope: {
attack: playSeconds * 2,
sustain: 1,
release: tailSeconds,
releaseCurve: "linear"
},
volume: -25
});
synth.triggerAttackRelease(notes, playSeconds);
}
function fmBells(notes, playSeconds, tailSeconds) {
let delay = new Tone.FeedbackDelay({
delayTime: playSeconds / 8,
feedback: 0.88,
wet: 0.66
});
let flanger = new Tone.FeedbackDelay({
delayTime: 0.005,
feedback: 0.1,
wet: 0.33
});
new Tone.LFO(1, 0.003, 0.007).start().connect(flanger.delayTime);
let reverb = new Tone.Reverb({ decay: playSeconds / 4, wet: 0.8 });
reverb.generate(); // Risky not to wait but ¯\_(ツ)_/¯
let synth = new Tone.PolySynth(5, Tone.FMSynth).chain(
delay,
flanger,
reverb,
Tone.Master
);
synth.set({
harmonicity: 1.4,
modulationIndex: 1,
oscillator: {
type: "sine"
},
envelope: {
attack: 0.01,
decay: 0.3,
sustain: 0.6,
release: tailSeconds - 1
},
modulation: { type: "triangle" },
modulationEnvelope: {
attack: 0.01,
decay: 0.3,
sustain: 0.6,
release: tailSeconds
},
volume: -30
});
synth.triggerAttackRelease(notes, playSeconds);
}
<script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.7.0/dist/magentamusic.min.js"></script>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
background-image: linear-gradient(
to top,
#f3e7e9 0%,
#e3eeff 99%,
#e3eeff 100%
);
}
canvas {
position: fixed;
width: 100vw;
height: 100vh;
pointer-events: none;
}
button {
padding: 10px 20px;
font-size: 24px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment