Generates Bach chorales using the Coconet neural chorale model in Magenta.js. And plays them very slowly.
https://magenta.tensorflow.org/coconet https://github.com/tensorflow/magenta-js
A Pen by Tero Parviainen on CodePen.
| <canvas></canvas> | |
| <button id="start" disabled>Loading…</button> |
Generates Bach chorales using the Coconet neural chorale model in Magenta.js. And plays them very slowly.
https://magenta.tensorflow.org/coconet https://github.com/tensorflow/magenta-js
A Pen by Tero Parviainen on CodePen.
| 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; | |
| } |