diff --git a/README.md b/README.md index f7c01e7..9adc3d2 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ first 3D prototype: https://www.youtube.com/watch?v=8QR59RP-Z_8 - Neural Drum Machine: https://codepen.io/teropa/pen/JLjXGK - Neural Melody Autocompletion: https://codepen.io/teropa/pen/gvwwZL - Performance RNN: https://magenta.tensorflow.org/performance-rnn -- Magenta demos: https://magenta.tensorflow.org/demos \ No newline at end of file +- Magenta demos: https://magenta.tensorflow.org/demos diff --git a/TODO.md b/TODO.md index 5bf5404..f2d7236 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,16 @@ ## Roadmap ### 0.4 -- [ ] map note length (triggerRelease) to bounce height, see addBody -> sphereRestitution or initContactMaterial restitutionValue param -- [ ] update instrumentMappings to use key of B major instead of C -- [ ] display key signature in UI +- [x] map note length (triggerRelease) to bounce height, see addBody -> sphereRestitution or initContactMaterial restitutionValue param ### 0.5 - [ ] rename state variables to store, separate property for UI related variables -- [ ] different iframes / canvas for each instrument type, separate routes with different globals can be used -- [ ] balls drop and hit key of 3D piano +- [ ] update instrumentMappings to use key of B major instead of C +- [ ] display key signature in UI ### 0.6 +- [ ] different iframes / canvas for each instrument type, separate routes with different globals can be used +- [ ] balls drop and hit key of 3D piano - [ ] instrument animations mapped to note sequences, ex: flamePhysics.create triggered on FD - FD -- A3F - A3F added to note sequence (use humanKeyAdds array? or buildNoteSequence function) ### 0.7 diff --git a/css/bounce.css b/css/bounce.css index 0d67c56..5c9b247 100644 --- a/css/bounce.css +++ b/css/bounce.css @@ -23,20 +23,21 @@ canvas { #controls-container { /* display: none; */ position: absolute; - top: 30px; - right: 0; - left: 5px; + top: 0; + left: 0; width: 200px; margin: 0; - padding: 8px; - background-color: rgba(160, 160, 160, 0.55); + padding: 0; color: #fff; text-align: center; - z-index: 1; + z-index: 10; } #settings-container { display: block; + padding: 10px 5px 0; + /* background: rgba(204, 204, 204, 0.3); */ + background-color: rgba(255, 255, 255, 0.2); } #settings-container.hidden { @@ -45,10 +46,23 @@ canvas { /* display: block; */ } +.toggle-wrapper { + margin: 0 0 0; +} button#settings-toggle-btn { - margin: 0 0 20px; + margin: 0 0 0; padding: 5px 10px; font-size: 16px; + color: #fff; + /* background-color: rgba(0, 0, 0, 0.30); */ + background-color: rgba(255, 255, 255, 0.2); + border: none; + -webkit-transition: all 0.3s; + transition: all 0.3s; +} +button#settings-toggle-btn:hover { + /* background-color: rgba(0, 0, 0, 0.90); */ + background-color: rgba(255, 255, 255, 0.3); } button#settings-toggle-btn.hidden-active { @@ -64,33 +78,37 @@ button { cursor: pointer; } -button#bounce, +button#btn-drums-1, .play { display: inline-block; - margin: auto 3px 30px auto; + margin: auto 3px 10px auto; padding: 6px 8px; background: none; font: 14px/1 Verdana, Geneva, sans-serif; letter-spacing: 1.2px; - color: #003366; - border: 1px solid #003366; + color: #fff; + border: 1px solid #fff; + /* color: #003366; */ + /* border: 1px solid #003366; */ text-transform: uppercase; cursor: pointer; -webkit-transition: all 0.3s; transition: all 0.3s; } -.play span.fa { +span.fa-headphones { padding: 0 5px 0 3px; } -button#bounce:hover, +button#btn-drums-1:hover, .play:hover { - background: #003366; - color: #fff; + /* background: #003366; */ + /* color: #fff; */ + background: #fff; + color: #003366; } -button#bounce { +button#btn-drums-1 { margin: auto 3px 10px auto; } @@ -127,6 +145,7 @@ button#bounce { position: absolute; left: 0; right: 0; + z-index: 9; } #legend-container.pos-top-left { margin: 0; @@ -177,10 +196,28 @@ button#bounce { letter-spacing: 1.25px; } +/*** MACHINE DATA ***/ +#machine-data { + margin: 10px 0 8px; + color: #ED4A82; + font-family: Verdana, sans-serif; +} + +#machine-state { + position: relative; + top: -1px; + color: #fff; + font-family: Verdana, sans-serif; + font-size: 10px; + font-style: italic; +} + #logo-container { position: fixed; - top: 15px; - right: 10vw; + /* top: 15px; */ + top: 213px; + /* right: 350px; */ + left: 55px; } #logo-container .logo { display: inline-block; @@ -194,11 +231,20 @@ button#bounce { opacity: 0; animation: fade-in 2.0s 1; animation-fill-mode: forwards; - /* animation-delay: 12s; */ - animation-delay: 1s; + animation-delay: 6s; + /* animation-delay: 1s; */ } @keyframes fade-in { from {opacity: 0;} to {opacity: 1;} } + + +.nsc-content-camera-container { + margin: 0 0 0 200px; +} + +.nsc-content-camera { + background: transparent; +} \ No newline at end of file diff --git a/index.html b/index.html index 56b9112..b8e21a4 100644 --- a/index.html +++ b/index.html @@ -3,16 +3,13 @@ - 3D Sheet Music Animation Machine + AI Duet 3D - - - @@ -20,26 +17,11 @@ - - - - - - - - - - + @@ -49,61 +31,24 @@ -
-
- Stop -
+ - -
    -
  • HUMAN

  • -
  • AI

  • - - +
  • HUMAN

  • +
  • AI

+ +
@@ -112,7 +57,6 @@
- diff --git a/src/js/Input.js b/src/js/Input.js index 79338f8..66d12c5 100644 --- a/src/js/Input.js +++ b/src/js/Input.js @@ -144,17 +144,39 @@ function onActiveInputChange(id) { } let input = WebMidi.getInputById(id); if (input) { + console.log({input}); + + let noteStartTime = 0; + input.addListener('noteon', 1, e => { + // console.log('noteon listener -> e: ', e); humanKeyDown(e.note.number, e.velocity); - // hideUI(); + // uiHidden(); + + noteStartTime = e.timestamp; }); input.addListener('controlchange', 1, e => { + // console.log('controlchange listener -> e: ', e); if (e.controller.number === TEMPO_MIDI_CONTROLLER) { Tone.Transport.bpm.value = (e.value / 128) * MAX_MIDI_BPM; echo.delayTime.value = Tone.Time('8n.').toSeconds(); } }); - input.addListener('noteoff', 1, e => humanKeyUp(e.note.number)); + + // TODO: how to map humanKeyUp to floor / ball restitution bounciness, how to map arpeggiator toggle to start or reset generatedSequence + // input.addListener('noteoff', 1, e => humanKeyUp(e.note.number)); + input.addListener('noteoff', 1, e => { + // console.log('noteoff listener -> e: ', e); + // TODO: access pitchbend using: e.target._userHandlers.channel.pitchbend.1 + + const tempNoteLength = e.timestamp - noteStartTime; + humanKeyUp(e.note.number, tempNoteLength) + }); + + // input.addListener('pitchbend', 1, e => { + // console.log('pitchbend listener -> e: ', e); // no effect + // }); + // for (let option of Array.from(inputSelector.children)) { // option.selected = option.value === id; // } @@ -226,24 +248,46 @@ let humanKeyAdds = [], humanKeyRemovals = []; function humanKeyDown(note, velocity = 0.7) { console.log('(humanKeyDown) -> note: ', note); + // console.log('(humanKeyDown) -> velocity: ', velocity); if (note < MIN_NOTE || note > MAX_NOTE) return; - let tonalNote = Tonal.Note.fromMidi(note); - let tonalFreq = Tonal.Note.midiToFreq(note); - updateChord({ add: note }); + // if (note === 60) { // C4 + // if (note === 72) { // C5 + if (note === 72 || note === 67) { // C5, G5 + globals.machineTrigger = true; + } else { + humanKeyAdds.push({ note, velocity }); + // globals.machineTrigger = false; + } + + if (note === 71) { // High B + globals.machineTrigger = false; + } +} + +function humanKeyUp(note, timestampLength) { + // console.log('humanKeyUp -> note: ', note); + // console.log('humanKeyUp -> timestampLength: ', timestampLength); + if (note < MIN_NOTE || note > MAX_NOTE) return; + let tonalNote = Tonal.Note.fromMidi(note); + let tonalFreq = Tonal.Note.midiToFreq(note); const instrMapped = getInstrByInputNote(tonalNote); instrMapped.color = '#64b5f6'; // med blue - - physics.addBody(true, globals.dropPosX, instrMapped); - if (note < MIN_NOTE || note > MAX_NOTE) return; - humanKeyAdds.push({ note, velocity }); -} + // instrMapped.length = timestampLength; + // console.log({timestampLength}); + const maxNoteLength = 500; + timestampLength = timestampLength > maxNoteLength ? maxNoteLength : timestampLength; + instrMapped.length = timestampLength / 1000; // IMPORTANT - so length is in milliseconds + + // if (note !== 60) { + // if (note !== 72 && note !== 71) { + if (note !== 72 && note !== 71 && note !== 67) { // High G, B, C + physics.addBody(true, globals.dropPosX, instrMapped); + } -function humanKeyUp(note) { - if (note < MIN_NOTE || note > MAX_NOTE) return; humanKeyRemovals.push({ note }); updateChord({ remove: note }); } @@ -251,7 +295,10 @@ function humanKeyUp(note) { function machineKeyDown(note = 60, time = 0) { // console.log('(machineKeyDown) -> note: ', note); // console.log('(machineKeyDown) -> time: ', time); - if (note < MIN_NOTE || note > MAX_NOTE) return; + + // const TEMP_MIN_NOTE = 60; // C4 + const TEMP_MIN_NOTE = 64; // E4 + if (note < TEMP_MIN_NOTE || note > MAX_NOTE) return; let tonalNote = Tonal.Note.fromMidi(note); let instrMapped = getInstrByInputNote(tonalNote); @@ -265,12 +312,13 @@ function machineKeyDown(note = 60, time = 0) { } if (instrMapped === undefined) { - instrMapped = getInstrByInputNote('C4'); + // instrMapped = getInstrByInputNote('C4'); + } else { + // console.log('(machineKeyDown) -> instrMapped: ', instrMapped); + instrMapped.color = '#ED4A82'; // pink + physics.addBody(true, globals.dropPosX, instrMapped); } - // console.log('(machineKeyDown) -> instrMapped: ', instrMapped); - instrMapped.color = '#ED4A82'; // pink - physics.addBody(true, globals.dropPosX, instrMapped); } function buildNoteSequence(seed) { @@ -375,9 +423,10 @@ function startSequenceGenerator(seed) { let generatedSequence = Math.random() < 0.7 ? _.clone(seedSeq.notes.map(n => n.pitch)) : []; - console.log('(startSequenceGenerator) -> generatedSequence: ', generatedSequence); + // console.log('(startSequenceGenerator) -> generatedSequence: ', generatedSequence); let launchWaitTime = getSequenceLaunchWaitTime(seed); // returns 1 or 0.3 + launchWaitTime = 0.1; let playIntervalTime = getSequencePlayIntervalTime(seed); // 0.25 let generationIntervalTime = playIntervalTime / 2; @@ -390,11 +439,13 @@ function startSequenceGenerator(seed) { generatedSequence = generatedSequence.concat( genSeq.notes.map(n => n.pitch) ); - console.log('(generateNext) .then -> generatedSequence: ', generatedSequence); + // console.log('(generateNext) .then -> generatedSequence: ', generatedSequence); + updateUI(generatedSequence); + setTimeout(generateNext, generationIntervalTime * 1000); }); } else { - console.log('(generateNext) ELSE -> generatedSequence: ', generatedSequence); + // console.log('(generateNext) ELSE -> generatedSequence: ', generatedSequence); setTimeout(generateNext, generationIntervalTime * 1000); } } @@ -404,8 +455,9 @@ function startSequenceGenerator(seed) { if (generatedSequence.length) { console.log('consumeNext -> generatedSequence: ', generatedSequence); let note = generatedSequence.shift(); - if (note > 0) { - machineKeyDown(note, time); + // if (note > 0) { + if (note > 0 && globals.machineTrigger === true) { + machineKeyDown(note, time); // IMPORTANT } } } @@ -417,6 +469,8 @@ function startSequenceGenerator(seed) { playIntervalTime, Tone.Transport.seconds + launchWaitTime ); + + // updateUI(generatedSequence); return () => { running = false; @@ -424,6 +478,49 @@ function startSequenceGenerator(seed) { }; } +function updateUI(machineSequence) { + // console.log('updateUI -> machineSequence: ', machineSequence); + + // if (globals.ui.machine.currentSequence.length > 0) { + // if (machineSequence.length > 1) { + if (machineSequence.length > 0) { + globals.ui.machine.currentSequence = machineSequence; + } +} +let machineDataId = document.getElementById('machine-data'); +setInterval(() => { + if (globals.ui) { + if (globals.ui.machine.currentSequence.length > 0) { + + // mappedNotes = notes.map(n => Tonal.Note.pc(Tonal.Note.fromMidi(n.note))).sort(); + // let mappedNotes = globals.ui.machine.currentSequence.map(n => Tonal.Note.pc(Tonal.Note.fromMidi(n.note))); + let mappedNotes = globals.ui.machine.currentSequence.map(note => Tonal.Note.fromMidi(note)); + + // console.log('mappedNotes: ', mappedNotes); + // console.log(globals.ui.machine.currentSequence); + // machineDataId.innerHTML = globals.ui.machine.currentSequence; + + // https://love2dev.com/blog/javascript-remove-from-array/ + if (mappedNotes.length > 6) { + mappedNotes.length = 6; + } + + if (globals.machineTrigger === true) { + machineDataId.innerHTML = mappedNotes.join(', '); + } else { + machineDataId.innerHTML = ''; + } + } + + let machineStateId = document.getElementById('machine-state'); + if (globals.machineTrigger === true) { + machineStateId.innerHTML = '- ON'; + } else { + machineStateId.innerHTML = '- OFF'; + } + } +}, 500); + function generateDummySequence(seed = SEED_DEFAULT) { const sequence = rnn.continueSequence( buildNoteSequence(seed), // TODO: fix err - seed.map is not a function @@ -435,8 +532,9 @@ function generateDummySequence(seed = SEED_DEFAULT) { } /* AYSNC - AWAIT VERSION */ -// rnn.initialize(); -initRNN(); // TODO: move to async function +// if (globals.drumsOnly !== true) { + initRNN(); +// } function resolveDummyPattern() { return new Promise(resolve => { @@ -452,9 +550,11 @@ function initRNN() { rnn.initialize(); console.log('initRNN -> rnn: ', rnn); resolve('resolved'); - if (Tone.Transport.state !== 'started') { + + // if (globals.autoStart === true && Tone.Transport.state !== 'started') { Tone.Transport.start(); - } + // } + // console.log('initRNN -> Tone.Transport.state: ', Tone.Transport.state); //'started' or 'stopped' }); } diff --git a/src/js/InstrumentMappings.js b/src/js/InstrumentMappings.js index 3b58bed..0afb8bf 100644 --- a/src/js/InstrumentMappings.js +++ b/src/js/InstrumentMappings.js @@ -23,16 +23,20 @@ export default class InstrumentMappings { }, hiHatClosed: { ballDesc: 'H', - color: '#ff0000', //red + // color: '#ff0000', //red + color: '#64b5f6', // human (lt blue) keyInput: '1', movement: movement, //default: 'physics', or 'static' type: 'drum', variation: 'hihat', - originalPosition: { x: 0, y: 0, z: -3 } + // originalPosition: { x: 0, y: 0, z: -3 } + originalPosition: { x: -3, y: 1.5, z: 1 } }, hiHatOpen: { - ballDesc: 'H+', - color: '#990000', //dkred + ballDesc: 'H', + // ballDesc: 'H+', + // color: '#990000', //dkred + color: '#64b5f6', // human (lt blue) keyInput: '2', // movement: 'static', type: 'drum', @@ -41,7 +45,8 @@ export default class InstrumentMappings { }, snarePrimary: { ballDesc: 'S', - color: '#FFFF00', //yellow + // color: '#FFFF00', //yellow + color: '#64b5f6', // human (lt blue) keyInput: '3', type: 'drum', variation: 'snare', @@ -51,25 +56,30 @@ export default class InstrumentMappings { kickPrimary: { // ballDesc: 'K', // beat-v1 ballDesc: 'B', - color: '#003366', //midnight blue + // color: '#003366', //midnight blue + color: '#64b5f6', // human (lt blue) keyInput: '4', type: 'drum', variation: 'kick', - originalPosition: { x: 0, y: 0, z: 5 } + // originalPosition: { x: 0, y: 0, z: 5 } // drum staff + // originalPosition: { x: 0, y: 0, z: 2 } + originalPosition: { x: 0, y: 0, z: 1 } }, crashPrimary: { ballDesc: 'Cr', // color: '#FFA500', //orange color: '#8B008B', //darkmagenta + color: '#64b5f6', // human (lt blue) keyInput: '5', type: 'drum', variation: 'crash', //aka clap - originalPosition: { x: 0, y: 0, z: -4 } + // originalPosition: { x: 0, y: 0, z: -4 } + originalPosition: { x: 0, y: 0, z: 2 } }, ridePrimary: { ballDesc: 'R', - color: '#FFD700', //gold - // color: '#800080', //purple + // color: '#FFD700', //gold + color: '#64b5f6', // human (lt blue) keyInput: '6', type: 'drum', variation: 'ride', @@ -77,8 +87,8 @@ export default class InstrumentMappings { }, tomHigh: { ballDesc: 'T', - // color: '#800080', //purple - color: '#006400', //dkgreen + // color: '#006400', //dkgreen + color: '#64b5f6', // human (lt blue) keyInput: '7', type: 'drum', variation: 'tom-high', @@ -262,6 +272,7 @@ export default class InstrumentMappings { octave: 4, chord: ['C4', 'E4', 'G4'], type: 'chord', + length: '8n', // '4n', '2n' originalPosition: { x: 0, y: 0, z: -2 } }, sphereChordD4: { @@ -344,6 +355,16 @@ export default class InstrumentMappings { type: 'chord', originalPosition: { x: 0, y: 0, z: -10 } }, + sphereChordE5: { + ballDesc: 'E', + color: '#FF001F', //III - redorange + keyInput: 'F', + note: 'E', + octave: 5, + chord: ['E5', 'G5', 'B5'], + type: 'chord', + originalPosition: { x: 0, y: 0, z: -10 } + }, // Db2, Eb2, Gb2, Ab2, Bb2, Db3, Eb3, Gb3, Ab3, Bb3 }; } diff --git a/src/js/Physics.js b/src/js/Physics.js index 6d3550d..658db0d 100644 --- a/src/js/Physics.js +++ b/src/js/Physics.js @@ -32,34 +32,41 @@ export default class Physics { globals.world.gravity.set(0, -10, 0); this.debugRenderer = new THREE.CannonDebugRenderer(globals.scene, globals.world); - const groundShape = new CANNON.Plane(); - const groundMaterial = new CANNON.Material(); //http://schteppe.github.io/cannon.js/docs/classes/Material.html - const groundBody = new CANNON.Body({ mass: 0, material: groundMaterial }); - groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); - groundBody.addShape(groundShape); - globals.world.add(groundBody); - - // if (this.useVisuals) this.helper.this.addVisual(groundBody, 'ground', false, true); - this.addVisual(groundBody, 'ground', false, true); - this.shapes = {}; this.shapes.sphere = new CANNON.Sphere(0.5); this.shapes.box = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); - this.groundMaterial = groundMaterial; - // this.animate(); - this.initContactMaterial(0.3); + this.initGroundContactMaterial(0.3); + // this.initGroundContactMaterial(0.3, [0, 10, 0]); + // this.initGroundContactMaterial(0.3, [0, 5, 0], [2, 2, 0.1]); + + // this.addSpinner(); } - initContactMaterial(restitutionValue = 0.3) { + initGroundContactMaterial(restitutionValue = 0.3, posArr=[0, -6, 0], sizeArr=[2500, 20, 5]) { //TODO: add colored ground on contact here //http://schteppe.github.io/cannon.js/docs/classes/ContactMaterial.html - const groundShape = new CANNON.Plane(); - const tempMaterial = new CANNON.Material(); //http://schteppe.github.io/cannon.js/docs/classes/Material.html + // const groundShape = new CANNON.Plane(); // invisible plane across entire screen + + // const groundShape = new CANNON.Box(new CANNON.Vec3(10, 10, 0.1)); + // const groundShape = new CANNON.Box(new CANNON.Vec3(15, 15, 5)); // 0.3 + // const groundShape = new CANNON.Box(new CANNON.Vec3(1500, 20, 5)); + const groundShape = new CANNON.Box(new CANNON.Vec3(...sizeArr)); + + // http://schteppe.github.io/cannon.js/docs/classes/Material.html + const tempMaterial = new CANNON.Material({ restitution: 1, friction: 1 }); + // const tempMaterial = new CANNON.Material(); + // console.log({tempMaterial}); + const groundBody = new CANNON.Body({ mass: 0, material: tempMaterial }); - groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); + groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); //PREV + // groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(0.5, 0, 0), -Math.PI / 2); // invisible giant hill + + // groundBody.position.set(0, -6, 0); + groundBody.position.set(...posArr); + // console.log({groundBody}); groundBody.addShape(groundShape); globals.world.add(groundBody); @@ -67,16 +74,16 @@ export default class Physics { // if (this.useVisuals) this.helper.this.addVisual(groundBody, 'ground', false, true); this.addVisual(groundBody, 'ground', false, true); - this.shapes = {}; - this.shapes.sphere = new CANNON.Sphere(0.5); - this.shapes.box = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); - - const material = new CANNON.Material(); //why both tempMaterial and material needed? - const materialGround = new CANNON.ContactMaterial(tempMaterial, material, { friction: 0.0, restitution: restitutionValue }); - globals.world.addContactMaterial(materialGround); + // https://github.com/schteppe/cannon.js/issues/300 + // https://github.com/schteppe/cannon.js/blob/master/demos/bounce.html + // restitutionValue = 200; + // const material = new CANNON.Material(); //why both tempMaterial and material needed? + // const restitutionGround = new CANNON.ContactMaterial(tempMaterial, material, { friction: 0.0, restitution: restitutionValue }); + // globals.world.addContactMaterial(restitutionGround); } - addBody(sphere = true, xPosition = 5.5, options = '', timeout = 0) { + // addBody(sphere = true, xPosition = 5.5, options = '', timeout = 0) { + addBody(sphere = true, xPosition=5.5, options = '', index=0) { // TODO: take yPosition from globals.dropCoordCircleInterval[] loop, swap yPos to zPos if (options === '') { const instrument = new InstrumentMappings(); @@ -84,15 +91,29 @@ export default class Physics { options = defaultInstr.hiHatClosed; } + // console.log('addBody -> options: ', options); + const trigger = new Trigger(); - const material = new CANNON.Material(); + let sphereRestitution = 0.1; + if (options.type === 'drum') { + sphereRestitution = 0.3; //prev: 0.9, 0.1 = one bounce + } else { + if (options.length > 0) { + sphereRestitution = options.length / 2; + } + // console.log({sphereRestitution}); + } + const material = new CANNON.Material({ restitution: sphereRestitution, friction: 1 }); + + // https://schteppe.github.io/cannon.js/docs/classes/Body.html const body = new CANNON.Body({ mass: 5, material: material }); // const body = new CANNON.Body({ mass: 1, material: material }); //no effect - + this.shapes = {}; this.shapes.sphere = new CANNON.Sphere(0.5); this.shapes.box = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); + if (sphere) { body.addShape(this.shapes.sphere); } else { @@ -101,7 +122,7 @@ export default class Physics { let xRand = Math.random() * (15 - 1) + 1; //rdm b/w 1 and 15 let xPos = xPosition; //TODO: remove xPosition param if not used - + if (globals.autoScroll === true) { if (options.type === 'drum') { xPos = -(globals.ticks); @@ -119,108 +140,97 @@ export default class Physics { let zPos; zPos = options.originalPosition !== undefined ? options.originalPosition.z : Math.random() * (15 - 5) - 2; + // zPos = globals.dropPosY; // drum spinner (v0.3) + + // body.mass = 1; // feather light + // body.mass = 8; // heavy if (options.type === 'drum') { // TODO: new drum machine paradigm - use rotating clock hand to hit drums // https://codepen.io/danlong/pen/LJQYYN - zPos += 10; // see globals.staffLineInitZ and globals.staffLineSecondZ + // zPos += 10; // PREV: see globals.staffLineInitZ and globals.staffLineSecondZ + + zPos -= 8; } else { - // zPos -= 10; //PREV zPos -= 3; //PREV + // zPos = 0; } + + if (globals.cameraCircularAnimation === true) { + globals.dropOffset = options.variation === 'snare' ? globals.dropOffset -= 0.8 : 0; + globals.dropOffset = options.variation === 'kick' ? globals.dropOffset -= 1.2 : 0; + globals.dropOffset = options.variation === 'hihat' ? globals.dropOffset -= 1.6 : 0; + // console.log('globals.dropOffset: ', globals.dropOffset); // DEBUG + // xPos += globals.dropOffset; + // zPos += globals.dropOffset; + + xPos = globals.dropCoordCircleInterval[index].px; + zPos = globals.dropCoordCircleInterval[index].py; + } + // zPos = options.originalPosition.z; body.position.set((sphere) ? -xPos : xPos, yPos, zPos); - body.linearDamping = globals.damping; + body.linearDamping = globals.damping; // 0.01 + // body.linearDamping = 0.01; // v0.2, v0.3 // body.angularVelocity.z = 12; //too much rotation - hard to read note letter // body.angularVelocity.z = 6; //prev body.angularVelocity.z = 0; if (options.type === 'animation') { - // console.log('addBody -> animation: ', options); - - // if (globals.flameCounter % 2 === 0) { - // if (globals.flameCounter % 3 === 0) { - // console.log(globals.flameCounter); - // if (globals.flameCounter === 4) { - // if (globals.flameCounter % 2 === 1) { //is flame is called odd num of times - // if (globals.flameCounter === 1) { - // flamePhysics.create({x: -xPos}); - // globals.flameCounter = 0; - // } flamePhysics.create({x: -xPos}); - globals.flameCounter++; return; } - // setTimeout(function() { //TODO: remove setTimeout param if not needed anymore - globals.world.add(body); - // }, timeout); - - // if (this.useVisuals) this.helper.this.addVisual(body, (sphere) ? 'sphere' : 'box', true, false); + globals.world.add(body); body.userData = { opts: options }; this.addVisual(body, (sphere) ? 'sphere' : 'box', true, false, options); + let notePlayed = false; let bodyCollideCount = 0; + let spinnerCollideCount = 0; body.addEventListener('collide', function(ev) { + // console.log('body collide ev: ', ev); // console.log('body collide event: ', ev.body); // console.log('body collide INERTIA: ', ev.body.inertia); // console.log('contact between two bodies: ', ev.contact); // console.log(bodyCollideCount); + if (ev.contact) { + // console.log('ev.contact.ni', ev.contact.ni); // DEBUG USE + // console.log('ev.contact.rj', ev.contact.rj); + + //TODO: determine best way to convert from negative scientific notation without rounding to -0, ex: -2.220446049250313e-16 + // const roundedHitMetric = parseInt(ev.contact.ni.z); + // if (ev.contact.ni.x !== -0 || roundedHitMetric !== -2) { + if (ev.contact.ni.x !== -0) { + // console.log('HIT ev.contact.ni', ev.contact.ni); + spinnerCollideCount++; + } else { + // console.log('MISS ev.contact.ni', ev.contact.ni); + // console.log('MISS roundedHitMetric', roundedHitMetric); + } + bodyCollideCount++; + } - if (options.type === 'drum') { - // if (bodyCollideCount <= 1) { //play note two times on collide - if (bodyCollideCount <= 0) { - // console.log('DRUM ev: ', ev); - // if (isNaN(ev.body.inertia.x)) { //hack works - - // if (ev.body.initPosition.x === 0) { - // since ground is stationary at 0, must be hidden contact body above origin drop point + if (globals.triggerOn === 'contact') { + if (bodyCollideCount === 1) { trigger.triggerNote(body); - // } + notePlayed = true; } - } else { //regular spheres - if (bodyCollideCount <= 0) { //play note one time on collide - // console.log('REGULAR ev: ', ev); + } else if (globals.triggerOn === 'spinner') { + if (spinnerCollideCount === 1 && notePlayed !== true) { // 0.3 trigger.triggerNote(body); + notePlayed = true; } } - - // console.log(bodyCollideCount); - // setTimeout(() => { - // if (bodyCollideCount >= 3) { //play note one time on collide - // console.log({ body }); - // globals.world.remove(body); - // // http://www.html5gamedevs.com/topic/28819-solved-how-dispose-mesh-in-oncollideevent/ - // } - // }, 2000); //does not work - - bodyCollideCount++; }); - const defaultRestitution = 0.3; //bounciness - - let sphereRestitution; - if (options.type === 'drum') { - sphereRestitution = 0.5; //prev: 0.9, 0.1 = one bounce - } else { - // TODO: map note duration to sphereRestitution so longer note length = bouncier - // 1/4 note = 0.25, 1/2 = 0.50 ??? - sphereRestitution = 0.1; - } - - if (globals.cameraPositionBehind === true) { - // body.quaternion.x = 11; //sideways spin - // body.quaternion.y = 11; - // body.quaternion.z = 0.5; - // console.log(body); //TODO: rotate adjust HERE!!! - } } addVisual(body, name, castShadow = true, receiveShadow = true, options = 'Z') { @@ -273,40 +283,6 @@ export default class Physics { } } - addSpinner() { - // DRUM MACHINE WHEEL: - // https://codepen.io/danlong/pen/LJQYYN?editors=1010 - - // CANNON (PHYSICS) - let boxShape = new CANNON.Box(new CANNON.Vec3(12.25, 0.5, 0.5)); - let spinnerBody = new CANNON.Body({ - mass: 1000, - angularVelocity: new CANNON.Vec3(0,5,0), - fixedRotation: true, - }); - spinnerBody.addShape(boxShape); - spinnerBody.position.set(0,0.25,0); - spinnerBody.name = 'spinner'; - - // THREE JS (VISUAL) - var geometry = new THREE.BoxBufferGeometry( 24.5, 0.5, 0.5 ); - geometry.rotateX(THREE.Math.degToRad(90)); // TODO: animate rotation so rect goes in circle - var material = new THREE.MeshBasicMaterial( {color: 0xff0000} ); - let spinner = new THREE.Mesh( geometry, material ); - spinner.position.y = 0; - - // push to meshes & bodies - // this.meshes.push(spinner); - // this.bodies.push(this.spinnerBody); - // this.scene.add(spinner); - // this.world.addBody(this.spinnerBody); - // this.bodies.push(this.spinnerBody); - - globals.world.bodies.push(spinnerBody); - globals.scene.add(spinner); - globals.world.addBody(spinnerBody); - } - shape2Mesh(body, castShadow, receiveShadow, options) { const helpers = new Helpers(); @@ -355,7 +331,8 @@ export default class Physics { case CANNON.Shape.types.PLANE: // geometry = new THREE.PlaneGeometry(10, 10, 4, 4); // too short - geometry = new THREE.PlaneGeometry(20, 10, 4, 4); + // geometry = new THREE.PlaneGeometry(20, 10, 4, 4); + geometry = new THREE.PlaneGeometry(0, 0, 0, 0); mesh = new THREE.Object3D(); // TODO: try changing mesh.name to fix no color update @@ -374,10 +351,8 @@ export default class Physics { material.color = defaultColor; const ground = new THREE.Mesh(geometry, material); - // ground.scale.set(100, 100, 100); // ORIG ground aka floor size - // ground.scale.set(100, 6, 100); //PREV - - ground.scale.set(500, 6, 100); //PREV + // ground.scale.set(500, 6, 100); // PREV + ground.scale.set(10, 10, 10); // no effect ground.name = 'groundMesh'; //TODO: use correctly - https://threejs.org/docs/#manual/en/introduction/How-to-update-things @@ -388,10 +363,19 @@ export default class Physics { break; case CANNON.Shape.types.BOX: - const box_geometry = new THREE.BoxGeometry(shape.halfExtents.x * 2, + // NEW Ground for drum spinner, PLANE no longer used since infinite invisible contact not needed + const boxGeometry = new THREE.BoxGeometry(shape.halfExtents.x * 2, shape.halfExtents.y * 2, shape.halfExtents.z * 2); - mesh = new THREE.Mesh(box_geometry, material); + + console.log({shape}); + + // const boxGeometry = new THREE.BoxGeometry(25, 25, 0.5); // does not coincide with contact surface size + + material.color = new THREE.Color(globals.activeInstrColor);; + + // boxGeometry.scale.set(10, 10, 10); // not a function + mesh = new THREE.Mesh(boxGeometry, material); break; case CANNON.Shape.types.CONVEXPOLYHEDRON: @@ -537,6 +521,68 @@ export default class Physics { return new CANNON.ConvexPolyhedron(vertices, faces); } + addSpinner() { + // DRUM MACHINE WHEEL: + // https://codepen.io/danlong/pen/LJQYYN?editors=1010 + // FORK: https://codepen.io/sjcobb/pen/vYYpKMv + + // const rotationSpeed = globals.bpm * 0.011; + // const rotationSpeed = globals.bpm * 0.019; + // const rotationSpeed = globals.bpm * 0.027; // prev + const rotationSpeed = globals.bpm * 0.025; + // console.log({rotationSpeed}); + + const spinnerLength = 28; + + // CANNON (PHYSICS) + let boxShape = new CANNON.Box(new CANNON.Vec3(12.25, 0.5, 0.5)); // no effect + + // https://schteppe.github.io/cannon.js/docs/classes/Body.html + globals.spinnerBody = new CANNON.Body({ + // mass: 1000, + mass: 1000, + // angularVelocity: new CANNON.Vec3(0, 5 ,0), + angularVelocity: new CANNON.Vec3(0, rotationSpeed, 0), // TODO: spinner speed (2nd param, y) map to Tone.Transport bpm + // angularVelocity: new CANNON.Vec3(12, rotationSpeed, 0), // wave shutter up & down + // angularVelocity: new CANNON.Vec3(0, rotationSpeed, 10), // vertical clock tower - USE + angularDamping: 0, // default=0.01 + // linearDamping: 0.01, + fixedRotation: true, // IMPORTANT + // boundingRadius: 2 + // interpolatedPosition: {x: 100, y: 100, z: 100} + }); + // globals.spinnerBody.quaternion = new CANNON.Quaternion(-0.5, -0.5, 0.5, 0.5); // rotate standing up + // globals.spinnerBody.quaternion = new CANNON.Quaternion(0.5, 0.5, 0.5, 0.5); // rotate standing up + // globals.spinnerBody.quaternion = new CANNON.Quaternion(0, 0.5, 0.5, 0.5); // woah + + globals.spinnerBody.quaternion = new CANNON.Quaternion(0, 0.5, 0.05, 0.5); // decent - stage under - wobbly + + globals.spinnerBody.addShape(boxShape); + // console.log('globals.spinnerBody: ', globals.spinnerBody); + console.log(globals.spinnerBody); + + // globals.spinnerBody.position.set(0, 0.25, 0); // no effect + + globals.spinnerBody.name = 'spinner'; + + // THREE JS (VISUAL) + // var geometry = new THREE.BoxBufferGeometry( 24.5, 0.5, 0.5 ); + var geometry = new THREE.BoxBufferGeometry(spinnerLength, 0.5, 0.5); + // geometry.rotateX(THREE.Math.degToRad(90)); // TODO: animate rotation so rect goes in circle + // geometry.rotateY(THREE.Math.degToRad(45)); // no effect + // console.log({geometry}); + + // var material = new THREE.MeshBasicMaterial({color: 0xff0000}); red + var material = new THREE.MeshBasicMaterial({color: 0x003366}); //midnight blue + let spinner = new THREE.Mesh(geometry, material); + // console.log({spinner}); + + globals.meshes.push(spinner); + globals.bodies.push(globals.spinnerBody); + globals.scene.add(spinner); + globals.world.addBody(globals.spinnerBody); + } + updatePhysics() { // TODO: uncomment debugRenderer after fix scene undef err if (this.physics.debugRenderer !== undefined) { @@ -544,46 +590,16 @@ export default class Physics { } } + // updateMeshPositions() { + // for (var i = 0; i !== this.meshes.length; i++) { + // globals.meshes[i].position.copy(this.bodies[i].position); + // globals.meshes[i].quaternion.copy(this.bodies[i].quaternion); + // } + // }, + updateBodies(world) { - // globals.lastColor = globals.activeInstrColor; //remove? - - if (globals.configColorAnimate === true) { - - //TODO: fix activeInstrColor by simpifying nested forEach calls - // console.log('globals.scene.children -> ', globals.scene.children); - - globals.scene.children.forEach((child) => { - if (child.name && child.name === 'physicsParent') { - child.children.forEach((child) => { - if (child.name && child.name === 'groundPlane') { - child.children.forEach((child) => { - child.children.forEach((child) => { - if (child.name && child.name === 'groundMesh') { - if (globals.groundMeshIncrementer % 10 === 0) { - const tempColor = globals.activeInstrColor.substr(0, 1) === '#' ? globals.activeInstrColor.slice(1, globals.activeInstrColor.length) : 0x191CAC; - const intColor = parseInt('0x' + tempColor, 16); - - if (globals.lastColor !== globals.activeInstrColor) { - child.material.color = new THREE.Color(intColor); - - // console.log({child}); //{r: 1, g: 0.5294117647058824, b: 0.16862745098039217} - // console.log({tempColor}); - // console.log({intColor}); - // console.log(globals.lastColor); - } - - globals.lastColor = globals.activeInstrColor; - - } - globals.groundMeshIncrementer++; - } - }); - }); - } - }); - } - }); - } + + // globals.spinnerBody.position.set(0, -1.5, 0); // IMPORTANT: cannon.js boilerplate // world.bodies.forEach(function(body) { @@ -593,6 +609,12 @@ export default class Physics { body.threemesh.quaternion.copy(body.quaternion); } }); + + // TODO: standard way to update bodies? globals.bodies and globals.meshes shouldn't only be for spinner + for (var i = 0; i !== globals.meshes.length; i++) { + globals.meshes[i].position.copy(globals.bodies[i].position); + globals.meshes[i].quaternion.copy(globals.bodies[i].quaternion); + } } } \ No newline at end of file diff --git a/src/js/Trigger.js b/src/js/Trigger.js index 14bf2cc..29d9bec 100644 --- a/src/js/Trigger.js +++ b/src/js/Trigger.js @@ -10,9 +10,15 @@ import Physics from './Physics.js'; import Flame from './Flame.js'; //-----TONE------// -Tone.Transport.bpm.value = 200; +// Tone.Transport.bpm.value = 200; //PREV +// Tone.Transport.bpm.value = 120; +Tone.Transport.bpm.value = globals.bpm; // Tone.Transport.bpm.rampTo(120, 10); -Tone.Transport.timeSignature = 12; // https://tonejs.github.io/docs/r13/Transport#timesignature + +// https://tonejs.github.io/docs/r13/Transport#timesignature +// Tone.Transport.timeSignature = 12; // PREV +Tone.Transport.timeSignature = 4; // DEFAULT + // Tone.Transport.setLoopPoints(0, "13m"); //starts over at beginning // Tone.Transport.loop = true; //TODO: *** clear all addBody objects if Transport loop true @@ -110,18 +116,21 @@ export default class Trigger { } else { } - // let triggerObj = instrument.getNoteMapping(obj); //ORIG - - // console.log('Trigger -> addBody - note: ', obj.userData.opts.note); - const triggerNote = obj.userData.opts.note ? (obj.userData.opts.note + obj.userData.opts.octave) : 'C4'; - let triggerObj = instrument.getInstrByNote(triggerNote); - - let combinedNote = triggerObj.note + triggerObj.octave; + // console.log('Trigger -> addBody - opts: ', obj.userData.opts); + + let triggerObj = {}; + let combinedNote = 'C1'; + if (obj.userData.opts.type !== 'drum') { + const triggerNote = obj.userData.opts.note ? (obj.userData.opts.note + obj.userData.opts.octave) : 'C4'; + // combinedNote = triggerObj.note + triggerObj.octave; + combinedNote = triggerNote; + triggerObj = instrument.getInstrByNote(triggerNote); + } else { + triggerObj = instrument.getNoteMapping(obj); //ORIG + } + // console.log('Trigger -> combinedNote: ', combinedNote); - // console.log('triggerObj: ', triggerObj); - let drumIndex = 0; - // TODO: is if else performance causing sound bug? if (triggerObj.type === 'drum') { if (triggerObj.variation === 'kick') { // console.log('trigger -> playerKick: ', playerKick); @@ -142,15 +151,16 @@ export default class Trigger { playerTomHigh.start(); // key: 7 // flameFirst.create(obj.initPosition); } else { - console.log('UNDEF variation - triggerNote() -> triggerObj (drum): ', triggerObj); + // console.log('UNDEF variation - triggerNote() -> triggerObj (drum): ', triggerObj); playerHiHat.start(); } drumIndex++; } else if (triggerObj.type === 'chord') { // TODO: rename, universal chord / note accessor - // console.log('triggerObj -> chord: ', triggerObj.chord); - // polySynth.triggerAttackRelease(triggerObj.chord, '4n'); - // polySynth.triggerAttackRelease(combinedNote, '4n'); - polySynth.triggerAttackRelease(combinedNote, '8n'); + // console.log('triggerNote (chord) -> combinedNote: ', combinedNote); + // console.log('triggerNote (chord) -> triggerObj: ', triggerObj); + // console.log('triggerNote (chord) -> obj.userData.opts.length: ', obj.userData.opts.length); + const noteLength = obj.userData.opts.length ? obj.userData.opts.length : 0.15; + polySynth.triggerAttackRelease(combinedNote, noteLength); } else { bounceSynth.triggerAttackRelease(combinedNote, "8n"); // console.log('triggerNote -> ballDesc: ', triggerObj.ballDesc, ', note: ', combinedNote); @@ -164,23 +174,6 @@ export default class Trigger { globals.activeInstrColor = triggerObj.color; } } - - // humanKeyDown(52); // TODO: add humanKeyDown as shared method - - // switch (obj.userData.opts.ballDesc) { - // case ('A'): - // bounceSynth.triggerAttackRelease("A3", "8n"); - // console.log('triggerNote -> poolBalls.ballA'); - // break; - // default: - // // debugger; - // bounceSynth.toMaster(); - // bounceSynth.triggerAttackRelease("A2", "8n"); - // console.log('default case'); - // } - // //bounceSynth.triggerRelease(); - // //Tone.Transport.stop(); - } } \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index be3d50c..9166194 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -37,8 +37,6 @@ globals.instr = instrument.getInstrumentMappingTemplate(); const globalBallTextureWidth = 512; const globalCollisionThreshold = 4; //prev: 3.4 -let globalDropPosX = 5.5; - // TODO: remove all globalLetterNumArr calls const globalLetterNumArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG']; //TODO: remove globalLetterNumArr array, only instrumentMapping obj needed @@ -69,11 +67,13 @@ if (globals.cameraLookUp === true) { } if (globals.keysOnly === true) { - globals.camera.position.z -= 6; // middle of first keyboard staff - // globals.camera.position.z -= 8; // middle of first keyboard staff - // globals.camera.position.z -= 14; // between each keyboard staff (dashed line C) + globals.camera.position.z -= 6; // PREV, middle of first keyboard staff + globals.posBehindX -= 10; +} - // globals.camera.position.x -= 30; // remove, no difference +if (globals.drumsOnly === true) { + // globals.camera.position.z -= 10; // only see top half of spinner + globals.camera.position.z += 8; globals.posBehindX -= 10; } @@ -220,10 +220,10 @@ function addStaffLines(color = 0x000000, offset, posXstart, posXend, posY, posZ, if (dashedLines === true) { // if (i <= 1) { if (i === 0 && middleC === true) { - staffLine = new THREE.Line(staffLineGeo, new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 1, gapSize: 5 } )); // blue: 0x0000ff + staffLine = new THREE.Line(staffLineGeo, new THREE.LineDashedMaterial( { color: 0xffffff, dashSize: 1, gapSize: 5 } )); // blue: 0x0000ff staffLine.computeLineDistances(); } else if (i === 3 || i === 4) { - staffLine = new THREE.Line(staffLineGeo, new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 1, gapSize: 5 } )); // blue: 0x0000ff + staffLine = new THREE.Line(staffLineGeo, new THREE.LineDashedMaterial( { color: 0xffffff, dashSize: 1, gapSize: 5 } )); // blue: 0x0000ff staffLine.computeLineDistances(); } else { staffLine = new THREE.Line(); // empty line @@ -235,12 +235,14 @@ function addStaffLines(color = 0x000000, offset, posXstart, posXend, posY, posZ, const staffLineLengthEnd = 8000; if (globals.keysOnly !== true) { - addStaffLines(0x000000, globals.staffLineInitZ, -1000, staffLineLengthEnd, 0.08, 0, 2); + // addStaffLines(0x000000, globals.staffLineInitZ, -1000, staffLineLengthEnd, 0.08, 0, 2); } else if (globals.keysOnly === true) { - addStaffLines(0x000000, globals.staffLineSecondZ, -1000, staffLineLengthEnd, 0.08, 0, 2); + + const lineYHeight = -0.95; + addStaffLines(0xffffff, globals.staffLineSecondZ, -1000, staffLineLengthEnd, lineYHeight, 0, 2); // two dashed lines above treble clef - addStaffLines(0x0000ff, globals.staffLineSecondZ - 10, -1000, staffLineLengthEnd, 0.08, 0, 2, true, true); + addStaffLines(0xffffff, globals.staffLineSecondZ - 10, -1000, staffLineLengthEnd, lineYHeight, 0, 2, true, true); } else {} @@ -360,6 +362,43 @@ function moveObject(object, motionActive, positionUp, threshold) { // }, 1); // var clock = new THREE.Clock(); +let dropAngle = 0; +function rotateCalc(a) { + // https://stackoverflow.com/a/35672783 + let x = 0 + let y = 0; // lower = closer to center of spinner + // let y = 8; + + let r = 10.25; // radius + + // let a = 0; // angle (from 0 to Math.PI * 2) + + // x += globals.dropOffset; + // y += globals.dropOffset; + + var px = x + r * Math.cos(a); + var py = y + r * Math.sin(a); + return { + 'px': px, + 'py': py + } +} +// for (var i=0; i<719; i++) { +for (var i=0; i<720; i++) { + dropAngle = (dropAngle + Math.PI / 360) % (Math.PI * 2); + let dropCoord = rotateCalc(dropAngle); + globals.dropCoordCircle.push(dropCoord); +} +const dropInterval = globals.dropCoordCircle.length / 4; +globals.dropCoordCircleInterval = [globals.dropCoordCircle[0], globals.dropCoordCircle[dropInterval], globals.dropCoordCircle[dropInterval * 2], globals.dropCoordCircle[dropInterval * 3]] + +console.log('globals.dropCoordCircleInterval: ', globals.dropCoordCircleInterval); + +// console.log({dropInterval}); +// console.log('INIT -> globals.dropCoordCircle: ', globals.dropCoordCircle); +// console.log('globals.dropCoordCircle[0]: ', globals.dropCoordCircle[0]); +// console.log('globals.dropCoordCircle.length ', globals.dropCoordCircle[globals.dropCoordCircle.length - 1]); + //-----ANIMATION------// let animate = () => { requestAnimationFrame(animate); @@ -383,7 +422,7 @@ let animate = () => { //ENABLE HORIZONTAL SCROLL if (globals.autoScroll === true) { - const ticksMultiplier = 9; + const ticksMultiplier = 9; // 0.3, 0.2 // // globals.ticks = Tone.Transport.ticks * 0.014; //old // globals.ticks += (delta * 5); //PREV @@ -395,32 +434,24 @@ let animate = () => { globals.camera.position.x = globals.posBehindX + (globals.ticks); // console.log(globals.camera); } else { - // globals.camera.position.x = (globals.ticks) - 12; // prev, works the (delta * 5) - globals.camera.position.x = (globals.ticks) - 30; + // globals.camera.position.x = (globals.ticks) - 30; // 0.3, 0.2 + globals.camera.position.x = (globals.ticks) - 25; } } - // if (triggerAnimationTime === Tone.Transport.position & flameActive === false) { - // if (Tone.Transport.position === "5:8:0" & flameActive === false) { - // if (Tone.Transport.position === "6:5:0" & flameActive === false) { - // if (Tone.Transport.seconds > 23 & flameActive === false) { - if (flameActive === false) { - // console.log('addFire active -> position: ', Tone.Transport.position); - // flameFirst.addFire(globals.ticks); - // flameActive = true; - } + // if (globals.cameraCircularAnimation === true) { + // // 3D z axis rotation: https://jsfiddle.net/prisoner849/opau47vk/ + // // camera Three.js ex: https://stackoverflow.com/a/10342429 + // // USE - simple trig ex: https://stackoverflow.com/a/35672783 + // dropAngle = (dropAngle + Math.PI / 360) % (Math.PI * 2); + // let dropCoord = rotateCalc(dropAngle); + // globals.dropPosX = dropCoord.px; + // globals.dropPosY = dropCoord.py; + // } - if (Tone.Transport.seconds > 41 && Tone.Transport.seconds < 42) { - flameActive = false; - } + // to reinit flame animation, see: https://github.com/sjcobb/music360js/blob/v4-fire/src/js/app.js + // if (flameActive === false) {} - // TODO: readd after webpack setup - var flameRate = globals.clock.getElapsedTime() * 2.0; - // volumetricFire.update(flameRate); - if (globals.flameArr.length > 0) { - globals.flameArr[0].update(flameRate); - } - physics.updateBodies(globals.world); globals.world.step(globals.fixedTimeStep); @@ -450,8 +481,8 @@ window.onload = () => { switch (keyName) { case ('z'): - // physics.addBody(true, globalDropPosX, keyMapped); - // globalDropPosX -= 1.3; + // physics.addBody(true, globals.dropPosX, keyMapped); + // globals.dropPosX -= 1.3; break; default: // console.log('keydown -> DEFAULT...', event); @@ -466,8 +497,8 @@ window.onload = () => { if (keyName === keyMapped.keyInput) { //*** IMPORTANT *** // console.log({keyMapped}); - physics.addBody(true, globalDropPosX, keyMapped); - globalDropPosX -= 1.3; //TODO: how to manipulate Y drop position? + physics.addBody(true, globals.dropPosX, keyMapped); + // globals.dropPosX -= 1.3; //TODO: how to manipulate Y drop position? console.log('keydown -> keyMapped, event: ', keyMapped, event); } else { console.log('keyMapped UNDEF -> else: ', event); diff --git a/src/js/audio.js b/src/js/audio.js index 9a19472..dcdd8e4 100644 --- a/src/js/audio.js +++ b/src/js/audio.js @@ -9,56 +9,78 @@ let flameAudio = new Flame(); *** AUDIO *** */ -/* + const physics = new Physics(); //-----INSTRUMENT PARTS------// var allDrumsPart = new Tone.Part(function(time, instr) { - physics.addBody(true, time * globals.multiplierPosX, instr); + // physics.addBody(true, time * globals.multiplierPosX, instr); + physics.addBody(true, globals.dropPosX, instr); }, [ - ["0:0:0", globals.instr.kickPrimary], - ["0:6:0", globals.instr.kickPrimary], - ["0:7:0", globals.instr.kickPrimary], + // ["0:0:0", globals.instr.kickPrimary], + // ["0:0:0", globals.instr.snarePrimary], + + // ["0:6:0", globals.instr.kickPrimary], + // ["0:6:0", globals.instr.snarePrimary], + + // ["0:10:0", globals.instr.kickPrimary], + // ["0:10:0", globals.instr.snarePrimary], - ["0:10:0", globals.instr.snarePrimary], + // ["0:10:0", globals.instr.snarePrimary], - ["0:4:0", globals.instr.crashPrimary], + // ["0:8:0", globals.instr.crashPrimary], + // ["0:8:0", globals.instr.snarePrimary], - ["0:4:0", globals.instr.tomHigh], + // ["0:4:0", globals.instr.tomHigh], ]); allDrumsPart.loop = true; // allDrumsPart.start("0:0:0"); -allDrumsPart.start("2:0:0"); +// // allDrumsPart.start("1:0:0"); +// // allDrumsPart.start("2:0:0"); -var secondVerseDrumsPart = new Tone.Part(function(time, instr) { - physics.addBody(true, time * globals.multiplierPosX, instr); +var introPart = new Tone.Part(function(time, instr) { + // TODO: use globals.dropCoordCircle [0] and [719] for dropPosX and dropPosY (must be added as param to addBody) coordinates + // console.log('introPart -> dropPosX, dropPosY: ', globals.dropPosX, '-', globals.dropPosY); + physics.addBody(true, globals.dropPosX, instr, 0); + // physics.addBody(true, time * globals.multiplierPosX, instr); // sine wave }, [ - ["0:0:0", globals.instr.ridePrimary], - ["0:2:0", globals.instr.ridePrimary], - ["0:4:0", globals.instr.ridePrimary], - ["0:6:0", globals.instr.ridePrimary], + // ["0:0:0", globals.instr.hiHatClosed], + ["0:0:0", globals.instr.hiHatOpen], + // ["0:11:0", globals.instr.hiHatClosed], ]); -secondVerseDrumsPart.loop = 4; -secondVerseDrumsPart.start("7:0:0"); +// introPart.loop = 6; +introPart.loop = true; +// introPart.start("0:0:0"); -var introPart = new Tone.Part(function(time, instr) { - physics.addBody(true, time * globals.multiplierPosX, instr); +var secPosPart = new Tone.Part(function(time, instr) { + physics.addBody(true, globals.dropPosX, instr, 1); }, [ - ["0:0:0", globals.instr.hiHatClosed], - ["0:2:0", globals.instr.hiHatClosed], - ["0:2:3", globals.instr.hiHatClosed], - ["0:2:6", globals.instr.hiHatClosed], - ["0:2:9", globals.instr.hiHatClosed], - - ["0:6:0", globals.instr.hiHatClosed], - ["0:6:3", globals.instr.hiHatClosed], - ["0:6:6", globals.instr.hiHatClosed], - ["0:6:9", globals.instr.hiHatClosed], - ["0:10:0", globals.instr.hiHatOpen], + //[ "0:0:0", globals.instr.hiHatClosed], + // ["0:2:0", globals.instr.kickPrimary], + ["0:0:0", globals.instr.snarePrimary], ]); -introPart.loop = 6; -introPart.start("0:0:0"); +secPosPart.loop = true; +// secPosPart.start("0:0:0"); + +var thirdPosPart = new Tone.Part(function(time, instr) { + physics.addBody(true, globals.dropPosX, instr, 2); +}, [ + ["0:0:0", globals.instr.kickPrimary], +]); +thirdPosPart.loop = true; +// thirdPosPart.start("0:0:0"); + +var fourthPosPart = new Tone.Part(function(time, instr) { + physics.addBody(true, globals.dropPosX, instr, 3); +}, [ + ["0:0:0", globals.instr.kickPrimary], +]); +fourthPosPart.loop = true; +// fourthPosPart.start("0:0:0"); + + +/* var groovePart = new Tone.Part(function(time, instr) { physics.addBody(true, time * globals.multiplierPosX, instr); }, [ @@ -168,5 +190,4 @@ export default class Audio { } } - */ \ No newline at end of file diff --git a/src/js/globals.js b/src/js/globals.js index 832172b..c9a002f 100644 --- a/src/js/globals.js +++ b/src/js/globals.js @@ -13,29 +13,46 @@ export default { // activeInstrColor: '#e5b38f', //PREV - sand (md2) // activeInstrColor: '#d8d8d8', // activeInstrColor: '#00A29C', // teal: https://www.color-hex.com/color-palette/4666 - activeInstrColor: '#66b2b2', // lt teal - autoScroll: true, + // activeInstrColor: '#66b2b2', // lt teal + // activeInstrColor: '#003366', // spinner midnight blue + // activeInstrColor: '#001f3e', + // activeInstrColor: '#1f1f1f', + activeInstrColor: '#343434', + autoScroll: true, // true - v0.1, v0.2 autoStart: false, - autoStartTime: 9000, + autoStartTime: 4500, + bpm: 120, + //bpm: 160, camera: new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000), + cameraCircularAnimation: false, // true - drum spinner (v0.3) cameraPositionBehind: false, cameraLookUp: false, clock: new THREE.Clock(), configColorAnimate: true, controls: '', // controls: new FlyControls(camera), - damping: 0.01, - dropPosX: 5.5, + // currentNoteLength: 0, // not needed + damping: 0.01, // effects bounciness, lag + dropCoordCircle: [], + dropCoordCircleInterval: [], + dropOffset: 0, + // dropPosX: 5.5, //prev + dropPosX: 0, + dropPosY: 0, + // dropPosZ: 0, // should z be swapped with y? + drumsOnly: false, fixedTimeStep: 1.0 / 60.0, flameArr: [], flameCounter: 0, - hideUI: true, inputMidi: false, instr: {}, instrumentCounter: 0, keysOnly: true, lastColor: '#000000', loader: new THREE.TextureLoader(), + machineTrigger: false, + meshes: [], + bodies: [], multiplierPosX: -2.5, musicActive: false, patternInfinite: false, @@ -45,12 +62,22 @@ export default { groundMeshIncrementer: 0, renderer: new THREE.WebGLRenderer(), scene: new THREE.Scene(), + spinnerBody: {}, staffLineInitZ: 8, staffLineSecondZ: -8, // showStaticRows: false, // old static animation + tempPos: 0, ticks: 0, triggerAnimationTime: '4:0:0', + triggerOn: 'contact', + // triggerOn: 'spinner', // Transport: Tone.Transport, //TODO: add Transport here for logging ticks and position + uiHidden: false, + ui: { + machine: { + currentSequence: [] + } + }, world: new CANNON.World(), }; diff --git a/src/js/ui.js b/src/js/ui.js index 8849312..e7a4566 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -22,65 +22,56 @@ document.documentElement.addEventListener( /* stop all sounds */ var button = document.querySelector(".play"); button.addEventListener("click", function() { - console.log('Tone.Transport STOPPED... bounceSynth disconnected...'); + console.log('... ... ... Tone.Transport STOPPED ... ... ...'); Tone.Transport.stop(); // bounceSynth.disconnect(); - // //bounceSynth.dispose(); + // bounceSynth.dispose(); }); -let bounce = document.getElementById('bounce'); -// bounce.innerHTML = 'BOUNCE!'; -bounce.onclick = () => { +let drumId = document.getElementById('btn-drums-1'); +drumId.onclick = () => { + console.log('#btn-drums-1 clicked... Tone.Transport started...') Tone.Transport.start(); // Tone.Transport.start("+0.1", 0); - - // if (bounceControl === false) { - // bounceControl = true; - // bounce.innerHTML = 'STOP'; - // } - // else { - // bounceControl = false; - // bounce.innerHTML = 'BOUNCE!'; - // } }; -document.getElementById('shape-form').onchange = (evt) => { - switch (evt.target.value) { - case 'box': - currentShape = box; - break; - case 'sphere': - currentShape = sphere; - break; - case 'torus': - currentShape = torus; - break; - default: - currentShape = box; - break; - } - obj.geometry = currentShape; - obj.geometry.buffersNeedUpdate = true; -}; - -document.getElementById('mesh-form').onchange = (evt) => { - switch (evt.target.value) { - case 'phong': - currentMesh = phong; - break; - case 'basic': - currentMesh = basic; - break; - case 'lambert': - currentMesh = lambert; - break; - default: - currentMesh = box; - break; - } - obj.material = currentMesh; - obj.material.needsUpdate = true; -}; +// document.getElementById('shape-form').onchange = (evt) => { +// switch (evt.target.value) { +// case 'box': +// currentShape = box; +// break; +// case 'sphere': +// currentShape = sphere; +// break; +// case 'torus': +// currentShape = torus; +// break; +// default: +// currentShape = box; +// break; +// } +// obj.geometry = currentShape; +// obj.geometry.buffersNeedUpdate = true; +// }; + +// document.getElementById('mesh-form').onchange = (evt) => { +// switch (evt.target.value) { +// case 'phong': +// currentMesh = phong; +// break; +// case 'basic': +// currentMesh = basic; +// break; +// case 'lambert': +// currentMesh = lambert; +// break; +// default: +// currentMesh = box; +// break; +// } +// obj.material = currentMesh; +// obj.material.needsUpdate = true; +// }; let controlsId = document.getElementById('controls-container'); let settingsId = document.getElementById('settings-container'); @@ -88,17 +79,18 @@ let toggleId = document.getElementById('settings-toggle-btn'); toggleId.onclick = (el) => { - // console.log('RUN toggleId'); + console.log('toggleId clicked -> el: ', el); toggleId.classList.toggle('hidden-active'); settingsId.classList.toggle('hidden'); }; -let addShapeId = document.getElementById('call-add-shape'); -addShapeId.onclick = (el) => { - addBody(); -}; +// let addShapeId = document.getElementById('call-add-shape'); +// addShapeId.onclick = (el) => { +// addBody(); +// }; -if (globals.autoStart === true || globals.hideUI === true) { +// if (globals.autoStart === true || globals.uiHidden === true) { +if (globals.uiHidden === true) { controlsId.classList.toggle('hidden'); } diff --git a/webpack.config.js b/webpack.config.js index f4b3561..a5c2b5d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ // https://github.com/johndatserakis/modern-webpack-starter/blob/master/webpack.config.js +// https://github.com/edwinwebb/three-seed/blob/master/webpack.config.js // https://github.com/webpack/webpack-dev-server/blob/master/examples/api/simple/server.js#L16 // https://medium.com/code-oil/burning-questions-with-answers-to-why-webpack-dev-server-live-reload-does-not-work-6d6390277920 // USE: