{"slug": "neural-drum-machine-density-remix", "title": "Neural Drum Machine Density Remix", "summary": "A developer built a neural drum machine using Magenta's MusicRNN and MusicVAE models, combined with Tone.js for audio synthesis. The system generates drum patterns with humanization and reverb effects, mapping MIDI notes to nine drum classes with velocity layers.", "body_md": "|\nconst DRUM_CLASSES = [ |\n|\n'Kick', |\n|\n'Snare', |\n|\n'Hi-hat closed', |\n|\n'Hi-hat open', |\n|\n'Tom low', |\n|\n'Tom mid', |\n|\n'Tom high', |\n|\n'Clap', |\n|\n'Rim' |\n|\n]; |\n|\nconst TIME_HUMANIZATION = 0.01; |\n|\n|\n|\nlet sampleBaseUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699'; |\n|\n|\n|\nlet reverb = new Tone.Convolver( |\n|\n`${sampleBaseUrl}/small-drum-room.wav` |\n|\n).toMaster(); |\n|\nreverb.wet.value = 0.35; |\n|\n|\n|\nlet snarePanner = new Tone.Panner().connect(reverb); |\n|\nnew Tone.LFO(0.13, -0.25, 0.25).connect(snarePanner.pan).start(); |\n|\n|\n|\nlet drumKit = [ |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/808-kick-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/808-kick-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/808-kick-vl.mp3` |\n|\n}).toMaster(), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/flares-snare-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/flares-snare-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/flares-snare-vl.mp3` |\n|\n}).connect(snarePanner), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/808-hihat-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/808-hihat-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/808-hihat-vl.mp3` |\n|\n}).connect(new Tone.Panner(-0.5).connect(reverb)), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/808-hihat-open-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/808-hihat-open-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/808-hihat-open-vl.mp3` |\n|\n}).connect(new Tone.Panner(-0.5).connect(reverb)), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/slamdam-tom-low-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/slamdam-tom-low-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/slamdam-tom-low-vl.mp3` |\n|\n}).connect(new Tone.Panner(-0.4).connect(reverb)), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/slamdam-tom-mid-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/slamdam-tom-mid-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/slamdam-tom-mid-vl.mp3` |\n|\n}).connect(reverb), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/slamdam-tom-high-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/slamdam-tom-high-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/slamdam-tom-high-vl.mp3` |\n|\n}).connect(new Tone.Panner(0.4).connect(reverb)), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/909-clap-vh.mp3`, |\n|\nmed: `${sampleBaseUrl}/909-clap-vm.mp3`, |\n|\nlow: `${sampleBaseUrl}/909-clap-vl.mp3` |\n|\n}).connect(new Tone.Panner(0.5).connect(reverb)), |\n|\nnew Tone.Players({ |\n|\nhigh: `${sampleBaseUrl}/909-rim-vh.wav`, |\n|\nmed: `${sampleBaseUrl}/909-rim-vm.wav`, |\n|\nlow: `${sampleBaseUrl}/909-rim-vl.wav` |\n|\n}).connect(new Tone.Panner(0.5).connect(reverb)) |\n|\n]; |\n|\nlet midiDrums = [36, 38, 42, 46, 41, 43, 45, 49, 51]; |\n|\nlet reverseMidiMapping = new Map([ |\n|\n[36, 0], |\n|\n[35, 0], |\n|\n[38, 1], |\n|\n[27, 1], |\n|\n[28, 1], |\n|\n[31, 1], |\n|\n[32, 1], |\n|\n[33, 1], |\n|\n[34, 1], |\n|\n[37, 1], |\n|\n[39, 1], |\n|\n[40, 1], |\n|\n[56, 1], |\n|\n[65, 1], |\n|\n[66, 1], |\n|\n[75, 1], |\n|\n[85, 1], |\n|\n[42, 2], |\n|\n[44, 2], |\n|\n[54, 2], |\n|\n[68, 2], |\n|\n[69, 2], |\n|\n[70, 2], |\n|\n[71, 2], |\n|\n[73, 2], |\n|\n[78, 2], |\n|\n[80, 2], |\n|\n[46, 3], |\n|\n[67, 3], |\n|\n[72, 3], |\n|\n[74, 3], |\n|\n[79, 3], |\n|\n[81, 3], |\n|\n[45, 4], |\n|\n[29, 4], |\n|\n[41, 4], |\n|\n[61, 4], |\n|\n[64, 4], |\n|\n[84, 4], |\n|\n[48, 5], |\n|\n[47, 5], |\n|\n[60, 5], |\n|\n[63, 5], |\n|\n[77, 5], |\n|\n[86, 5], |\n|\n[87, 5], |\n|\n[50, 6], |\n|\n[30, 6], |\n|\n[43, 6], |\n|\n[62, 6], |\n|\n[76, 6], |\n|\n[83, 6], |\n|\n[49, 7], |\n|\n[55, 7], |\n|\n[57, 7], |\n|\n[58, 7], |\n|\n[51, 8], |\n|\n[52, 8], |\n|\n[53, 8], |\n|\n[59, 8], |\n|\n[82, 8] |\n|\n]); |\n|\n|\n|\nlet temperature = 1.2; |\n|\n|\n|\nlet outputs = { |\n|\ninternal: { |\n|\nplay: (drumIdx, velocity, time) => { |\n|\ndrumKit[drumIdx].get(velocity).start(time); |\n|\n} |\n|\n} |\n|\n}; |\n|\n|\n|\nlet rnn = new mm.MusicRNN( |\n|\n'https://storage.googleapis.com/download.magenta.tensorflow.org/tfjs_checkpoints/music_rnn/drum_kit_rnn' |\n|\n); |\n|\nlet vae = new mm.MusicVAE( |\n|\n'https://storage.googleapis.com/download.magenta.tensorflow.org/tfjs_checkpoints/music_vae/drums_2bar_hikl_small' |\n|\n); |\n|\nPromise.all([ |\n|\nrnn.initialize(), |\n|\nvae.initialize(), |\n|\nnew Promise(res => Tone.Buffer.on('load', res)) |\n|\n]).then(([vars]) => { |\n|\n|\n|\n|\n|\nlet state = { |\n|\npatternLength: 32, |\n|\nseedLength: 4, |\n|\nswing: 0.55, |\n|\npattern: [[0], [], [2]].concat(_.times(32, i => [])), |\n|\ntempo: 120 |\n|\n}; |\n|\nlet stepEls = [], |\n|\nhasBeenStarted = false, |\n|\nsequence, |\n|\ndensityRange = null, |\n|\nactiveOutput = 'internal'; |\n|\n|\n|\nfunction generatePattern(seed, length) { |\n|\nlet seedSeq = toNoteSequence(seed); |\n|\nreturn rnn |\n|\n.continueSequence(seedSeq, length, temperature) |\n|\n.then(r => seed.concat(fromNoteSequence(r, length))); |\n|\n} |\n|\n|\n|\nfunction getStepVelocity(step) { |\n|\nif (step % 4 === 0) { |\n|\nreturn 'high'; |\n|\n} else if (step % 2 === 0) { |\n|\nreturn 'med'; |\n|\n} else { |\n|\nreturn 'low'; |\n|\n} |\n|\n} |\n|\n|\n|\nfunction humanizeTime(time) { |\n|\nreturn time - TIME_HUMANIZATION / 2 + Math.random() * TIME_HUMANIZATION; |\n|\n} |\n|\n|\n|\nfunction playPattern() { |\n|\nsequence = new Tone.Sequence( |\n|\n(time, { drums, stepIdx }) => { |\n|\nlet isSwung = stepIdx % 2 !== 0; |\n|\nif (isSwung) { |\n|\ntime += (state.swing - 0.5) * Tone.Time('8n').toSeconds(); |\n|\n} |\n|\nlet velocity = getStepVelocity(stepIdx); |\n|\ndrums.forEach(d => { |\n|\nlet humanizedTime = stepIdx === 0 ? time : humanizeTime(time); |\n|\noutputs[activeOutput].play(d, velocity, time); |\n|\nvisualizePlay(humanizedTime, stepIdx, d); |\n|\n}); |\n|\n}, |\n|\nstate.pattern.map((drums, stepIdx) => ({ drums, stepIdx })), |\n|\n'16n' |\n|\n).start(); |\n|\n} |\n|\n|\n|\nfunction visualizePlay(time, stepIdx, drumIdx) { |\n|\nTone.Draw.schedule(() => { |\n|\nif (!stepEls[stepIdx]) return; |\n|\nlet animTime = Tone.Time('2n').toMilliseconds(); |\n|\nlet cellEl = stepEls[stepIdx].cellEls[drumIdx]; |\n|\nif (cellEl.classList.contains('on')) { |\n|\nlet baseColor = stepIdx < state.seedLength ? '#e91e63' : '#64b5f6'; |\n|\ncellEl.animate( |\n|\n[ |\n|\n{ |\n|\ntransform: 'translateZ(-100px)', |\n|\nbackgroundColor: '#fad1df' |\n|\n}, |\n|\n{ |\n|\ntransform: 'translateZ(50px)', |\n|\noffset: 0.7 |\n|\n}, |\n|\n{ transform: 'translateZ(0)', backgroundColor: baseColor } |\n|\n], |\n|\n{ duration: animTime, easing: 'cubic-bezier(0.23, 1, 0.32, 1)' } |\n|\n); |\n|\n} |\n|\n}, time); |\n|\n} |\n|\n|\n|\nfunction renderPattern(regenerating = false) { |\n|\nlet seqEl = document.querySelector('.sequencer .steps'); |\n|\nwhile (stepEls.length > state.pattern.length) { |\n|\nlet { stepEl, gutterEl } = stepEls.pop(); |\n|\nstepEl.remove(); |\n|\nif (gutterEl) gutterEl.remove(); |\n|\n} |\n|\nfor (let stepIdx = 0; stepIdx < state.pattern.length; stepIdx++) { |\n|\nlet step = state.pattern[stepIdx]; |\n|\nlet stepEl, gutterEl, cellEls; |\n|\nif (stepEls[stepIdx]) { |\n|\nstepEl = stepEls[stepIdx].stepEl; |\n|\ngutterEl = stepEls[stepIdx].gutterEl; |\n|\ncellEls = stepEls[stepIdx].cellEls; |\n|\n} else { |\n|\nstepEl = document.createElement('div'); |\n|\nstepEl.classList.add('step'); |\n|\nstepEl.dataset.stepIdx = stepIdx; |\n|\nseqEl.appendChild(stepEl); |\n|\ncellEls = []; |\n|\n} |\n|\n|\n|\nstepEl.style.flex = stepIdx % 2 === 0 ? state.swing : 1 - state.swing; |\n|\n|\n|\nif (!gutterEl && stepIdx < state.pattern.length - 1) { |\n|\ngutterEl = document.createElement('div'); |\n|\ngutterEl.classList.add('gutter'); |\n|\nseqEl.insertBefore(gutterEl, stepEl.nextSibling); |\n|\n} else if (gutterEl && stepIdx >= state.pattern.length) { |\n|\ngutterEl.remove(); |\n|\ngutterEl = null; |\n|\n} |\n|\n|\n|\nif (gutterEl && stepIdx === state.seedLength - 1) { |\n|\ngutterEl.classList.add('seed-marker'); |\n|\n} else if (gutterEl) { |\n|\ngutterEl.classList.remove('seed-marker'); |\n|\n} |\n|\n|\n|\nfor (let cellIdx = 0; cellIdx < DRUM_CLASSES.length; cellIdx++) { |\n|\nlet cellEl; |\n|\nif (cellEls[cellIdx]) { |\n|\ncellEl = cellEls[cellIdx]; |\n|\n} else { |\n|\ncellEl = document.createElement('div'); |\n|\ncellEl.classList.add('cell'); |\n|\ncellEl.classList.add(_.kebabCase(DRUM_CLASSES[cellIdx])); |\n|\ncellEl.dataset.stepIdx = stepIdx; |\n|\ncellEl.dataset.cellIdx = cellIdx; |\n|\nstepEl.appendChild(cellEl); |\n|\ncellEls[cellIdx] = cellEl; |\n|\n} |\n|\nif (step.indexOf(cellIdx) >= 0) { |\n|\ncellEl.classList.add('on'); |\n|\n} else { |\n|\ncellEl.classList.remove('on'); |\n|\n} |\n|\n} |\n|\nstepEls[stepIdx] = { stepEl, gutterEl, cellEls }; |\n|\n|\n|\nlet stagger = stepIdx * (300 / (state.patternLength - state.seedLength)); |\n|\nsetTimeout(() => { |\n|\nif (stepIdx < state.seedLength) { |\n|\nstepEl.classList.add('seed'); |\n|\n} else { |\n|\nstepEl.classList.remove('seed'); |\n|\nif (regenerating) { |\n|\nstepEl.classList.add('regenerating'); |\n|\n} else { |\n|\nstepEl.classList.remove('regenerating'); |\n|\n} |\n|\n} |\n|\n}, stagger); |\n|\n} |\n|\n|\n|\nsetTimeout(repositionRegenerateButton, 0); |\n|\n} |\n|\n|\n|\nfunction repositionRegenerateButton() { |\n|\nlet regenButton = document.querySelector('.regenerate'); |\n|\nlet sequencerEl = document.querySelector('.sequencer'); |\n|\nlet seedMarkerEl = document.querySelector('.gutter.seed-marker'); |\n|\nlet regenLeft = |\n|\nsequencerEl.offsetLeft + |\n|\nseedMarkerEl.offsetLeft + |\n|\nseedMarkerEl.offsetWidth / 2 - |\n|\nregenButton.offsetWidth / 2; |\n|\nlet regenTop = |\n|\nsequencerEl.offsetTop + |\n|\nseedMarkerEl.offsetTop + |\n|\nseedMarkerEl.offsetHeight / 2 - |\n|\nregenButton.offsetHeight / 2; |\n|\nregenButton.style.left = `${regenLeft}px`; |\n|\nregenButton.style.top = `${regenTop}px`; |\n|\nregenButton.style.visibility = 'visible'; |\n|\n} |\n|\n|\n|\nfunction regenerate() { |\n|\nlet seed = _.take(state.pattern, state.seedLength); |\n|\nrenderPattern(true); |\n|\nreturn generatePattern(seed, state.patternLength - seed.length).then( |\n|\nresult => { |\n|\nstate.pattern = result; |\n|\nonPatternUpdated(); |\n|\nsetDensityValue(); |\n|\nupdateDensityRange(); |\n|\n} |\n|\n); |\n|\n} |\n|\n|\n|\n|\n|\nfunction onPatternUpdated() { |\n|\nif (sequence) { |\n|\nsequence.dispose(); |\n|\nsequence = null; |\n|\n} |\n|\nrenderPattern(); |\n|\n} |\n|\n|\n|\nfunction toggleStep(cellEl) { |\n|\nif (state.pattern && cellEl.classList.contains('cell')) { |\n|\nlet stepIdx = +cellEl.dataset.stepIdx; |\n|\nlet cellIdx = +cellEl.dataset.cellIdx; |\n|\nlet isOn = cellEl.classList.contains('on'); |\n|\nif (isOn) { |\n|\n_.pull(state.pattern[stepIdx], cellIdx); |\n|\ncellEl.classList.remove('on'); |\n|\n} else { |\n|\nstate.pattern[stepIdx].push(cellIdx); |\n|\ncellEl.classList.add('on'); |\n|\n} |\n|\nif (sequence) { |\n|\nsequence.at(stepIdx, { stepIdx, drums: state.pattern[stepIdx] }); |\n|\n} |\n|\nsetDensityValue(); |\n|\ndensityRange = null; |\n|\n} |\n|\n} |\n|\n|\n|\nfunction setDensityValue() { |\n|\nlet totalCellCount = state.pattern.length * 9; |\n|\nlet activeCellCount = _.sum(state.pattern.map(p => p.length)); |\n|\nlet density = activeCellCount / totalCellCount; |\n|\nlet roundedDensity = Math.round(density / 0.05) * 0.05; |\n|\ndocument.querySelector('#density').value = roundedDensity; |\n|\n} |\n|\n|\n|\nfunction updateDensityRange( |\n|\ndensity = +document.querySelector('#density').value |\n|\n) { |\n|\nlet stepsDown = density / 0.05; |\n|\nlet stepsUp = (0.75 - density) / 0.05; |\n|\nlet stepsBeyond = 0.25 / 0.05; |\n|\n|\n|\nlet emptySeq = toNoteSequence([]); |\n|\nlet fullSeq = toNoteSequence( |\n|\n_.times(state.pattern.length, () => _.range(9)) |\n|\n); |\n|\nlet currentSeq = toNoteSequence(state.pattern); |\n|\n|\n|\ndensityRange = []; |\n|\nlet interpsUp = stepsDown > 0 ? vae.interpolate([emptySeq, currentSeq], stepsDown) : Promise.resolve([]); |\n|\nlet interpsDown = stepsUp > 0 ? vae.interpolate( |\n|\n[currentSeq, fullSeq], |\n|\nstepsUp + stepsBeyond |\n|\n) : Promise.resolve([]); |\n|\n|\n|\ninterpsDown.then(interps => { |\n|\nfor (let noteSeq of interps) { |\n|\ndensityRange.push(fromNoteSequence(noteSeq, state.pattern.length)); |\n|\n} |\n|\n}).then(() => densityRange.push(state.pattern)) |\n|\n.then(() => interpsUp) |\n|\n.then(interps => { |\n|\nfor (let noteSeq of interps) { |\n|\nif (stepsUp-- > 0) { |\n|\ndensityRange.push(fromNoteSequence(noteSeq, state.pattern.length)); |\n|\n} |\n|\n} |\n|\n}); |\n|\n} |\n|\n|\n|\nfunction toNoteSequence(pattern) { |\n|\nreturn mm.sequences.quantizeNoteSequence( |\n|\n{ |\n|\nticksPerQuarter: 220, |\n|\ntotalTime: pattern.length / 2, |\n|\ntimeSignatures: [ |\n|\n{ |\n|\ntime: 0, |\n|\nnumerator: 4, |\n|\ndenominator: 4 |\n|\n} |\n|\n], |\n|\ntempos: [ |\n|\n{ |\n|\ntime: 0, |\n|\nqpm: 120 |\n|\n} |\n|\n], |\n|\nnotes: _.flatMap(pattern, (step, index) => |\n|\nstep.map(d => ({ |\n|\npitch: midiDrums[d], |\n|\nstartTime: index * 0.5, |\n|\nendTime: (index + 1) * 0.5 |\n|\n})) |\n|\n) |\n|\n}, |\n|\n1 |\n|\n); |\n|\n} |\n|\n|\n|\nfunction fromNoteSequence({ notes }, patternLength) { |\n|\nlet res = _.times(patternLength, () => []); |\n|\nfor (let { pitch, quantizedStartStep } of notes) { |\n|\nres[quantizedStartStep].push(reverseMidiMapping.get(pitch)); |\n|\n} |\n|\nreturn res; |\n|\n} |\n|\n|\n|\nfunction setSwing(newSwing) { |\n|\nstate.swing = newSwing; |\n|\nrenderPattern(); |\n|\n} |\n|\n|\n|\nfunction setPatternLength(newPatternLength) { |\n|\nif (newPatternLength < state.patternLength) { |\n|\nstate.pattern.length = newPatternLength; |\n|\n} else { |\n|\nfor (let i = state.pattern.length; i < newPatternLength; i++) { |\n|\nstate.pattern.push([]); |\n|\n} |\n|\n} |\n|\nlet lengthRatio = newPatternLength / state.patternLength; |\n|\nstate.seedLength = Math.max( |\n|\n1, |\n|\nMath.min(newPatternLength - 1, Math.round(state.seedLength * lengthRatio)) |\n|\n); |\n|\nstate.patternLength = newPatternLength; |\n|\nonPatternUpdated(); |\n|\nif (Tone.Transport.state === 'started') { |\n|\nplayPattern(); |\n|\n} |\n|\n} |\n|\n|\n|\nfunction updatePlayPauseIcons() { |\n|\nif (Tone.Transport.state === 'started') { |\n|\ndocument.querySelector('.playpause .pause-icon').style.display = null; |\n|\ndocument.querySelector('.playpause .play-icon').style.display = 'none'; |\n|\n} else { |\n|\ndocument.querySelector('.playpause .play-icon').style.display = null; |\n|\ndocument.querySelector('.playpause .pause-icon').style.display = 'none'; |\n|\n} |\n|\n} |\n|\n|\n|\nfunction encodeState() { |\n|\nreturn Object.keys(state) |\n|\n.reduce((a, k) => { |\n|\na.push(k + '=' + JSON.stringify(state[k])); |\n|\nreturn a; |\n|\n}, []) |\n|\n.join('&'); |\n|\n} |\n|\n|\n|\nWebMidi.enable(err => { |\n|\nif (err) { |\n|\nconsole.error('WebMidi could not be enabled', err); |\n|\nreturn; |\n|\n} |\n|\ndocument.querySelector('.webmidi-enabled').style.display = 'block'; |\n|\nlet outputSelector = document.querySelector('.midi-output'); |\n|\n|\n|\nfunction onOutputsChange() { |\n|\nwhile (outputSelector.firstChild) { |\n|\noutputSelector.firstChild.remove(); |\n|\n} |\n|\nlet internalOption = document.createElement('option'); |\n|\ninternalOption.value = 'internal'; |\n|\ninternalOption.innerText = 'Internal drumkit'; |\n|\noutputSelector.appendChild(internalOption); |\n|\nfor (let output of WebMidi.outputs) { |\n|\nlet option = document.createElement('option'); |\n|\noption.value = output.id; |\n|\noption.innerText = output.name; |\n|\noutputSelector.appendChild(option); |\n|\n} |\n|\nonActiveOutputChange('internal'); |\n|\n} |\n|\n|\n|\nfunction onActiveOutputChange(id) { |\n|\nif (activeOutput !== 'internal') { |\n|\noutputs[activeOutput] = null; |\n|\n} |\n|\nactiveOutput = id; |\n|\nif (activeOutput !== 'internal') { |\n|\nlet output = WebMidi.getOutputById(id); |\n|\noutputs[id] = { |\n|\nplay: (drumIdx, velo, time) => { |\n|\nlet delay = (time - Tone.now()) * 1000; |\n|\nlet duration = Tone.Time('16n').toMilliseconds(); |\n|\nlet velocity = { high: 1, med: 0.75, low: 0.5 }; |\n|\noutput.playNote(midiDrums[drumIdx], 1, { |\n|\ntime: delay > 0 ? `+${delay}` : WebMidi.now, |\n|\nvelocity, |\n|\nduration |\n|\n}); |\n|\n} |\n|\n}; |\n|\n} |\n|\nfor (let option of Array.from(outputSelector.children)) { |\n|\noption.selected = option.value === id; |\n|\n} |\n|\n} |\n|\n|\n|\nonOutputsChange(); |\n|\nWebMidi.addListener('connected', onOutputsChange); |\n|\nWebMidi.addListener('disconnected', onOutputsChange); |\n|\n$(outputSelector) |\n|\n.on('change', evt => onActiveOutputChange(evt.target.value)) |\n|\n.material_select(); |\n|\n}); |\n|\n|\n|\ndocument.querySelector('.app').addEventListener('click', event => { |\n|\nif (event.target.classList.contains('cell')) { |\n|\ntoggleStep(event.target); |\n|\n} |\n|\n}); |\n|\ndocument.querySelector('.regenerate').addEventListener('click', event => { |\n|\nevent.preventDefault(); |\n|\nevent.currentTarget.classList.remove('pulse'); |\n|\ndocument.querySelector('.playpause').classList.remove('pulse'); |\n|\nregenerate().then(() => { |\n|\nif (!hasBeenStarted) { |\n|\nTone.context.resume(); |\n|\nTone.Transport.start(); |\n|\nupdatePlayPauseIcons(); |\n|\nhasBeenStarted = true; |\n|\n} |\n|\nif (Tone.Transport.state === 'started') { |\n|\nsetTimeout(() => playPattern(), 0); |\n|\n} |\n|\n}); |\n|\n}); |\n|\ndocument.querySelector('.playpause').addEventListener('click', event => { |\n|\nevent.preventDefault(); |\n|\ndocument.querySelector('.playpause').classList.remove('pulse'); |\n|\nif (Tone.Transport.state !== 'started') { |\n|\nTone.context.resume(); |\n|\nTone.Transport.start(); |\n|\nplayPattern(); |\n|\nupdatePlayPauseIcons(); |\n|\nhasBeenStarted = true; |\n|\n} else { |\n|\nif (sequence) { |\n|\nsequence.dispose(); |\n|\nsequence = null; |\n|\n} |\n|\nTone.Transport.pause(); |\n|\nupdatePlayPauseIcons(); |\n|\n} |\n|\n}); |\n|\n|\n|\nlet draggingSeedMarker = false; |\n|\ndocument.querySelector('.app').addEventListener('mousedown', evt => { |\n|\nlet el = evt.target; |\n|\nif ( |\n|\nel.classList.contains('gutter') && |\n|\nel.classList.contains('seed-marker') |\n|\n) { |\n|\ndraggingSeedMarker = true; |\n|\nevt.preventDefault(); |\n|\n} |\n|\n}); |\n|\ndocument.querySelector('.app').addEventListener('mouseup', () => { |\n|\ndraggingSeedMarker = false; |\n|\n}); |\n|\ndocument.querySelector('.app').addEventListener('mouseover', evt => { |\n|\nif (draggingSeedMarker) { |\n|\nlet el = evt.target; |\n|\nwhile (el) { |\n|\nif (el.classList.contains('step')) { |\n|\nlet stepIdx = +el.dataset.stepIdx; |\n|\nif (stepIdx > 0) { |\n|\nstate.seedLength = stepIdx; |\n|\nrenderPattern(); |\n|\n} |\n|\nbreak; |\n|\n} |\n|\nel = el.parentElement; |\n|\n} |\n|\n} |\n|\n}); |\n|\ndocument.querySelector('#density').addEventListener('input', evt => { |\n|\nlet newDensity = +evt.target.value; |\n|\nlet patternIndex = Math.round(newDensity / 0.05); |\n|\nif (_.isNull(densityRange)) { |\n|\nupdateDensityRange(newDensity); |\n|\n} |\n|\nif ( |\n|\ndensityRange && |\n|\npatternIndex >= 0 && |\n|\npatternIndex < densityRange.length - 1 |\n|\n) { |\n|\nstate.pattern = densityRange[patternIndex]; |\n|\nrenderPattern(); |\n|\nif (sequence) { |\n|\nstate.pattern.forEach((drums, stepIdx) => |\n|\nsequence.at(stepIdx, { stepIdx, drums }) |\n|\n); |\n|\n} |\n|\n} |\n|\n}); |\n|\ndocument |\n|\n.querySelector('#swing') |\n|\n.addEventListener('input', evt => setSwing(+evt.target.value)); |\n|\ndocument |\n|\n.querySelector('#temperature') |\n|\n.addEventListener('input', evt => |\n|\ntemperature = +evt.target.value |\n|\n); |\n|\ndocument |\n|\n.querySelector('#tempo') |\n|\n.addEventListener( |\n|\n'input', |\n|\nevt => (Tone.Transport.bpm.value = state.tempo = +evt.target.value) |\n|\n); |\n|\n|\n|\nwindow.addEventListener('resize', repositionRegenerateButton); |\n|\n|\n|\nrenderPattern(); |\n|\nsetDensityValue(); |\n|\n|\n|\ndocument.querySelector('.progress').remove(); |\n|\ndocument.querySelector('.app').style.display = null; |\n|\n}); |", "url": "https://wpnews.pro/news/neural-drum-machine-density-remix", "canonical_source": "https://gist.github.com/chance600/74f2891d33b7fa9de299069c9187fd02", "published_at": "2026-06-26 04:51:58+00:00", "updated_at": "2026-06-26 05:03:25.324853+00:00", "lang": "en", "topics": ["machine-learning", "large-language-models", "ai-tools", "developer-tools"], "entities": ["Magenta", "MusicRNN", "MusicVAE", "Tone.js", "TensorFlow.js"], "alternates": {"html": "https://wpnews.pro/news/neural-drum-machine-density-remix", "markdown": "https://wpnews.pro/news/neural-drum-machine-density-remix.md", "text": "https://wpnews.pro/news/neural-drum-machine-density-remix.txt", "jsonld": "https://wpnews.pro/news/neural-drum-machine-density-remix.jsonld"}}