|
//SEED GENERATOR: covert array to string, add together and then back to numbers |
|
|
|
Vue.component('item', { |
|
props: ['num', 'music', 'modearray'], |
|
template: '<div>{{noteName}}</div>', |
|
data() { |
|
return { |
|
filter: new Tone.Filter(800, 'lowpass'), |
|
synth: null |
|
} |
|
}, |
|
computed: { |
|
note() { |
|
if(this.num === 7) { |
|
return Tone.Frequency(this.music.key + '4'); |
|
} |
|
return Tone.Frequency(this.music.key + '3').transpose(this.modearray[this.num]); |
|
}, |
|
noteName() { |
|
return this.note.toNote(); |
|
}, |
|
height() { |
|
return `${30*(this.num+1)}px`; |
|
}, |
|
color() { |
|
let startColor = [25, 48, 115]; |
|
let endColor = [61, 121, 242]; |
|
let finalColor = []; |
|
startColor.forEach((color, i)=>{ |
|
let increment = (endColor[i]-color)/8; |
|
finalColor.push(color+(increment*this.num)); |
|
}); |
|
return `rgb(${finalColor[0]}, ${finalColor[1]}, ${finalColor[2]})`; |
|
|
|
} |
|
}, |
|
methods: { |
|
triggerSynth(time) { |
|
this.synth.triggerAttackRelease(this.note, '8n', time); |
|
}, |
|
schedule(iteration) { |
|
Tone.Transport.schedule(this.triggerSynth, iteration*Tone.Time('4n')); |
|
} |
|
}, |
|
beforeMount() { |
|
this.synth = new Tone.Synth({ |
|
oscillator: { |
|
type: 'square' |
|
} |
|
}).chain(this.filter, Tone.Master) |
|
}, |
|
mounted() { |
|
this.$el.style.height = this.height; |
|
this.$el.style.backgroundColor = this.color; |
|
this.synth.volume.value = -10; |
|
} |
|
}) |
|
|
|
let app = new Vue({ |
|
el: "#app", |
|
data: { |
|
unsorted: chance.unique(chance.integer, 8, {min: 0, max: 7}), |
|
startArray: null, |
|
animation: { |
|
active: 0, |
|
checking: 0, |
|
anchor: 0, |
|
iteration: 0, |
|
finished: false, |
|
animating: false, |
|
delay: 500, |
|
interval: null, |
|
color: 'rgb(242,29,228)' |
|
}, |
|
music: { |
|
key: 'C', |
|
mode: 'M', |
|
tempo: 120, |
|
bassPattern: [1,0,0,1,0,0,1,0,0,0,1,0,1,0,0,0], |
|
bass: new Tone.Synth({ |
|
type: 'triangle' |
|
}).toMaster(), |
|
kick: new Tone.MembraneSynth().toMaster() |
|
}, |
|
controls: true, |
|
toastMessage: 'Seed copied to clipboard!', |
|
toastActive: false, |
|
savedSeeds: { |
|
seeds: [], |
|
selected: null |
|
} |
|
}, |
|
computed: { |
|
modeArray() { |
|
switch(this.music.mode) { |
|
case 'M': |
|
return [0,2,4,5,7,9,11]; |
|
break; |
|
case 'm': |
|
return [0,2,3,5,7,8,10]; |
|
break; |
|
case 'L': |
|
return [0,1,3,5,6,8,10]; |
|
break; |
|
} |
|
}, |
|
seedBassPattern() { |
|
let temp = []; |
|
for(let note of this.music.bassPattern) { |
|
if(note) { |
|
temp.push(1); |
|
} else { |
|
temp.push(0); |
|
} |
|
} |
|
return temp; |
|
}, |
|
seed: { |
|
get: function() { |
|
let seed = ''; |
|
for(let num of this.unsorted) { |
|
seed += `${num}`; |
|
} |
|
let bassPattern = this.seedBassPattern.join(''); |
|
|
|
let stringTempo; |
|
if(this.music.tempo <= 9) { |
|
stringTempo = '00' + this.music.tempo; |
|
} else if(this.music.tempo <= 99) { |
|
stringTempo = '0' + this.music.tempo; |
|
} else { |
|
stringTempo = this.music.tempo; |
|
} |
|
|
|
|
|
seed += stringTempo; |
|
seed += bassPattern; |
|
seed += this.music.key; |
|
seed += this.music.mode; |
|
return seed; |
|
}, |
|
set: function(newValue) { |
|
|
|
} |
|
} |
|
}, |
|
watch: { |
|
'music.key': function(newVal, oldVal) { |
|
Tone.Transport.cancel(); |
|
this.presort(); |
|
}, |
|
'music.mode': function(newVal, oldVal) { |
|
Tone.Transport.cancel(); |
|
this.presort(); |
|
}, |
|
'music.tempo': function(newVal, oldVal) { |
|
Tone.Transport.bpm.value = newVal; |
|
} |
|
}, |
|
methods: { |
|
triggerToast(type, message) { |
|
|
|
if(this.toastActive != true) { |
|
setTimeout(()=>{ |
|
this.toastActive = false; |
|
}, 1000); |
|
} |
|
|
|
this.toastActive = true; |
|
|
|
switch (type) { |
|
case 'success': |
|
this.$refs.toast.style.backgroundColor = '#3D79F2'; |
|
break; |
|
case 'failure': |
|
this.$refs.toast.style.backgroundColor = '#F21DE4'; |
|
break; |
|
} |
|
|
|
this.toastMessage = message; |
|
}, |
|
selectSeedBox() { |
|
this.$refs.seed.select(); |
|
}, |
|
saveSeed() { |
|
let seed = this.$refs.seed.value; |
|
let invalid = false; |
|
|
|
for(let savedSeed of this.savedSeeds.seeds) { |
|
if(seed === savedSeed) { |
|
invalid = true; |
|
} |
|
} |
|
|
|
if(invalid) { |
|
this.triggerToast('failure', 'seed already in saved seeds!'); |
|
return; |
|
} |
|
|
|
this.savedSeeds.seeds.push(seed); |
|
localStorage.setItem('seeds', JSON.stringify(this.savedSeeds.seeds)); |
|
}, |
|
clearSavedSeeds() { |
|
this.savedSeeds.seeds = []; |
|
localStorage.clear(); |
|
}, |
|
copySeed() { |
|
this.$refs.seed.select(); |
|
document.execCommand('copy'); |
|
this.triggerToast('success', 'Seed copied to clipboard!'); |
|
}, |
|
setSeed(value) { |
|
let invalid = false; |
|
let data = value.split(''); |
|
|
|
let notes = data.splice(0,8); |
|
let tempo = data.splice(0,3); |
|
tempo = tempo.join(''); |
|
let bassPattern = data.splice(0,16); |
|
let key = data[0]; |
|
let mode = data[1]; |
|
if(data[1] === '#') { |
|
key += data[1]; |
|
mode = data[2]; |
|
} |
|
|
|
|
|
notes.forEach((note, i)=>{ |
|
notes[i] = parseInt(note); |
|
if(!(/[0-7]/.test(notes[i]))) { |
|
invalid = true; |
|
return; |
|
} |
|
}); |
|
|
|
bassPattern.forEach((note, i)=>{ |
|
bassPattern[i] = parseInt(note); |
|
if(!(/[0-1]/.test(bassPattern[i]))) { |
|
invalid = true; |
|
return; |
|
} |
|
}); |
|
|
|
if(!(/[m,M,L]/.test(mode))) { |
|
invalid = true; |
|
} |
|
|
|
if(invalid) { |
|
this.triggerToast('failure', 'Invalid seed!'); |
|
return; |
|
} |
|
|
|
this.unsorted = notes; |
|
this.music.key = key; |
|
this.music.mode = mode; |
|
this.music.tempo = tempo; |
|
this.music.bassPattern = bassPattern; |
|
this.presort(); |
|
}, |
|
handleSeedInput(e) { |
|
// console.log(e); |
|
if(e.code === "Enter") { |
|
e.preventDefault(); |
|
this.setSeed(this.$refs.seed.value); |
|
} |
|
}, |
|
//musical methods |
|
presort() { |
|
Tone.Transport.cancel(); |
|
let arr = this.unsorted.slice(0); |
|
let iteration = 0; |
|
for(let i = arr.length; i>0; i--) { |
|
for(let j=0; j<i-1; j++) { |
|
if(arr[j] > arr[j+1]) { |
|
//schedule musical event |
|
this.$refs[arr[j+1]][0].schedule(iteration); |
|
//swap |
|
let temp = arr[j]; |
|
arr[j] = arr[j+1]; |
|
arr[j+1] = temp; |
|
} |
|
iteration++; |
|
} |
|
} |
|
}, |
|
generateBassLoop(pattern, time) { |
|
let {bass, key} = this.music; |
|
for(let i=0; i<pattern.length; i++) { |
|
if(pattern[i]) { |
|
bass.triggerAttackRelease(`${key}2`, "8n", time + i*Tone.Time('16n')); |
|
} |
|
} |
|
}, |
|
randomizeBassLoop() { |
|
let bp = this.music.bassPattern; |
|
for(let i=0; i<bp.length; i++) { |
|
Vue.set(this.music.bassPattern, i, chance.integer({min: 0, max: 1})); |
|
} |
|
}, |
|
bassLoop() { |
|
let {bass, kick, key} = this.music; |
|
let animationCounter = 0; |
|
|
|
let loop = new Tone.Loop((time)=>{ |
|
this.generateBassLoop(this.music.bassPattern, time); |
|
|
|
kick.triggerAttackRelease(`${key}1`, "8n", time); |
|
kick.triggerAttackRelease(`${key}1`, "8n", time + Tone.Time("4n")); |
|
kick.triggerAttackRelease(`${key}1`, "8n", time + 2*Tone.Time("4n")); |
|
kick.triggerAttackRelease(`${key}1`, "8n", time + 3*Tone.Time("4n")); |
|
|
|
animationCounter = 0; |
|
}, "1m"); |
|
|
|
let animationLoop = new Tone.Loop((time)=>{ |
|
animationCounter++; |
|
|
|
if(this.music.bassPattern[animationCounter-1]) { |
|
let activeBox = this.$refs.bass[animationCounter-1]; |
|
|
|
function flashEnd() { |
|
TweenLite.to(activeBox, .1, {scale: 1}); |
|
} |
|
|
|
TweenLite.to(activeBox, .05, {scale: 1.2, onComplete: flashEnd}); |
|
} |
|
|
|
|
|
|
|
}, "16n"); |
|
|
|
loop.start(0).stop('8m'); |
|
animationLoop.start(0).stop('8m'); |
|
}, |
|
//styling methods |
|
arrayClass(i) { |
|
if(i>this.animation.anchor || this.animation.anchor == 0) { |
|
return 'complete'; |
|
} |
|
|
|
switch(i) { |
|
case this.animation.active: |
|
return 'active'; |
|
break; |
|
case this.animation.checking: |
|
return 'checking'; |
|
break; |
|
} |
|
}, |
|
//bubble sort methods |
|
randomize() { |
|
Tone.Transport.cancel(); |
|
this.unsorted = chance.unique(chance.integer, 8, {min: 0, max: 7}); |
|
this.presort(); |
|
}, |
|
firstPass() { |
|
this.animation.checking = this.animation.active+1; |
|
this.animation.anchor = (this.unsorted.length-1) - this.animation.iteration; |
|
}, |
|
nextPass() { |
|
this.animation.active = 0; |
|
this.animation.checking = 1; |
|
this.animation.iteration++; |
|
this.animation.anchor--; |
|
|
|
if(this.animation.anchor === 0) { |
|
this.animation.finished = true; |
|
clearInterval(this.animation.interval); |
|
} |
|
}, |
|
bubbleStep() { |
|
if(!this.animation.finished) { |
|
let arr = this.unsorted; |
|
let {active, checking, anchor} = this.animation; |
|
if(arr[active] > arr[checking]) { |
|
//animation |
|
let activeNote = this.$refs[arr[checking]][0].$el; |
|
let originalColor = this.$refs[checking][0].color; |
|
|
|
function flashFinish() { |
|
TweenLite.to(activeNote, .2, {backgroundColor: originalColor, zIndex: 0, delay: .2}); |
|
} |
|
|
|
TweenLite.to(activeNote, .05, {backgroundColor: this.animation.color, zIndex: 100, onComplete: flashFinish}); |
|
|
|
//swap |
|
let temp = arr[checking]; |
|
arr[checking] = arr[active]; |
|
arr[active] = temp; |
|
} |
|
|
|
this.animation.active++; |
|
this.animation.checking++; |
|
|
|
if(this.animation.checking === anchor+1) { |
|
this.nextPass(); |
|
} |
|
} |
|
}, |
|
beginSort() { |
|
if(this.controls) { |
|
this.animation.finished = false; |
|
this.startArray = this.unsorted.slice(0); |
|
this.controls = false; |
|
this.bassLoop(); |
|
|
|
let loop = new Tone.Loop((time)=>{ |
|
Tone.Draw.schedule(()=>{ |
|
this.bubbleStep(); |
|
}, time); |
|
}, "4n"); |
|
|
|
loop.start(0).stop('8m'); |
|
Tone.Transport.toggle(); |
|
} else { |
|
this.resetSort(); |
|
} |
|
}, |
|
resetSort() { |
|
Tone.Transport.toggle(); |
|
Tone.Transport.cancel(); |
|
this.unsorted = this.startArray; |
|
|
|
//reset animation back to original |
|
this.animation.active = 0; |
|
this.animation.checking = 1; |
|
this.animation.anchor = this.unsorted.length-1; |
|
this.animation.iteration = 0; |
|
|
|
this.presort(); |
|
|
|
this.controls = true; |
|
} |
|
}, |
|
beforeMount() { |
|
let seeds = JSON.parse(localStorage.getItem('seeds')); |
|
|
|
if(seeds) { |
|
this.savedSeeds.seeds = seeds; |
|
} |
|
}, |
|
mounted() { |
|
this.firstPass(); |
|
this.presort(); |
|
this.music.kick.volume.value = -9; |
|
} |
|
}) |