fixing stuck notes
This commit is contained in:
parent
fe2699b993
commit
7249b3eac7
159
static/src/keyboard/Element.js
Normal file
159
static/src/keyboard/Element.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import events from 'events'
|
||||||
|
import 'style/keyboard.css'
|
||||||
|
import 'pepjs'
|
||||||
|
import {Roll} from 'roll/Roll'
|
||||||
|
import {Note} from 'keyboard/Note'
|
||||||
|
|
||||||
|
const offsets = [0, 0.5, 1, 1.5, 2, 3, 3.5, 4, 4.5, 5, 5.5, 6]
|
||||||
|
|
||||||
|
class KeyboardElement extends events.EventEmitter {
|
||||||
|
|
||||||
|
constructor(container, lowest=36, octaves=4){
|
||||||
|
super()
|
||||||
|
this._container = document.createElement('div')
|
||||||
|
this._container.id = 'keyboard'
|
||||||
|
container.setAttribute('touch-action', 'none')
|
||||||
|
container.appendChild(this._container)
|
||||||
|
|
||||||
|
//some default menu stuff
|
||||||
|
container.addEventListener('pointerup', (e) => delete this._pointersDown[e.pointerId])
|
||||||
|
container.addEventListener('contextmenu', this._absorbEvent.bind(this))
|
||||||
|
|
||||||
|
this._keys = {}
|
||||||
|
|
||||||
|
this._pointersDown = {}
|
||||||
|
|
||||||
|
this.resize(lowest, octaves)
|
||||||
|
|
||||||
|
Roll.appendTo(container)
|
||||||
|
|
||||||
|
this._aiNotes = {}
|
||||||
|
this._notes = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
resize(lowest, octaves){
|
||||||
|
this._keys = {}
|
||||||
|
// clear the previous ones
|
||||||
|
this._container.innerHTML = ''
|
||||||
|
// each of the keys
|
||||||
|
const keyWidth = (1 / 7) / octaves
|
||||||
|
for (let i = lowest; i < lowest + octaves * 12; i++){
|
||||||
|
let key = document.createElement('div')
|
||||||
|
key.classList.add('key')
|
||||||
|
let isSharp = ([1, 3, 6, 8, 10].indexOf(i % 12) !== -1)
|
||||||
|
key.classList.add(isSharp ? 'black' : 'white')
|
||||||
|
this._container.appendChild(key)
|
||||||
|
// position the element
|
||||||
|
|
||||||
|
let noteOctave = Math.floor(i / 12) - Math.floor(lowest / 12)
|
||||||
|
let offset = offsets[i % 12] + noteOctave * 7
|
||||||
|
key.style.width = `${keyWidth * 100}%`
|
||||||
|
key.style.left = `${offset * keyWidth * 100}%`
|
||||||
|
key.id = i.toString()
|
||||||
|
key.setAttribute('touch-action', 'none')
|
||||||
|
|
||||||
|
const fill = document.createElement('div')
|
||||||
|
fill.id = 'fill'
|
||||||
|
key.appendChild(fill)
|
||||||
|
|
||||||
|
this._bindKeyEvents(key)
|
||||||
|
this._keys[i] = key
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_absorbEvent(event) {
|
||||||
|
const e = event || window.event;
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
e.stopPropagation && e.stopPropagation();
|
||||||
|
e.cancelBubble = true;
|
||||||
|
e.returnValue = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindKeyEvents(key){
|
||||||
|
|
||||||
|
key.addEventListener('pointerover', (e) => {
|
||||||
|
if (this._pointersDown[e.pointerId]){
|
||||||
|
const noteNum = parseInt(e.target.id)
|
||||||
|
this.emit('keyDown', noteNum)
|
||||||
|
} else {
|
||||||
|
key.classList.add('hover')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
key.addEventListener('pointerout', (e) => {
|
||||||
|
if (this._pointersDown[e.pointerId]){
|
||||||
|
const noteNum = parseInt(e.target.id)
|
||||||
|
this.emit('keyUp', noteNum)
|
||||||
|
} else {
|
||||||
|
key.classList.remove('hover')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
key.addEventListener('pointerdown', (e) => {
|
||||||
|
const noteNum = parseInt(e.target.id)
|
||||||
|
// this.keyDown(noteNum, false)
|
||||||
|
this.emit('keyDown', noteNum)
|
||||||
|
this._pointersDown[e.pointerId] = true
|
||||||
|
})
|
||||||
|
key.addEventListener('pointerup', (e) => {
|
||||||
|
const noteNum = parseInt(e.target.id)
|
||||||
|
// this.keyUp(noteNum, false)
|
||||||
|
this.emit('keyUp', noteNum)
|
||||||
|
delete this._pointersDown[e.pointerId]
|
||||||
|
})
|
||||||
|
|
||||||
|
// cancel all the pointer events to prevent context menu which keeps the key stuck
|
||||||
|
key.addEventListener('touchstart', this._absorbEvent.bind(this))
|
||||||
|
key.addEventListener('touchend', this._absorbEvent.bind(this))
|
||||||
|
key.addEventListener('touchmove', this._absorbEvent.bind(this))
|
||||||
|
key.addEventListener('touchcancel', this._absorbEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown(noteNum, ai=false){
|
||||||
|
// console.log('down', noteNum, ai)
|
||||||
|
if (this._keys.hasOwnProperty(noteNum)){
|
||||||
|
const key = this._keys[noteNum]
|
||||||
|
key.classList.remove('hover')
|
||||||
|
|
||||||
|
const note = new Note(key.querySelector('#fill'), ai)
|
||||||
|
|
||||||
|
const noteArray = ai ? this._aiNotes : this._notes
|
||||||
|
if (!noteArray[noteNum]){
|
||||||
|
noteArray[noteNum] = []
|
||||||
|
}
|
||||||
|
noteArray[noteNum].push(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp(noteNum, ai=false){
|
||||||
|
// console.log('up', noteNum, ai)
|
||||||
|
if (this._keys.hasOwnProperty(noteNum)){
|
||||||
|
const noteArray = ai ? this._aiNotes : this._notes
|
||||||
|
if (!(noteArray[noteNum] && noteArray[noteNum].length)){
|
||||||
|
// throw new Error('note off without note on')
|
||||||
|
// setTimeout(() => this.keyUp.bind(this, noteNum, ai), 100)
|
||||||
|
console.warn('note off before note on')
|
||||||
|
} else {
|
||||||
|
noteArray[noteNum].shift().noteOff()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {KeyboardElement}
|
162
static/src/keyboard/Keyboard.js
Normal file
162
static/src/keyboard/Keyboard.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AudioKeys from 'audiokeys'
|
||||||
|
import Tone from 'Tone/core/Tone'
|
||||||
|
import events from 'events'
|
||||||
|
import {KeyboardElement} from 'keyboard/Element'
|
||||||
|
import buckets from 'buckets-js'
|
||||||
|
import {Midi} from 'keyboard/Midi'
|
||||||
|
import Buffer from 'Tone/core/Buffer'
|
||||||
|
|
||||||
|
class Keyboard extends events.EventEmitter{
|
||||||
|
constructor(container){
|
||||||
|
super()
|
||||||
|
|
||||||
|
this._container = container
|
||||||
|
|
||||||
|
this._active = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The audio key keyboard
|
||||||
|
* @type {AudioKeys}
|
||||||
|
*/
|
||||||
|
this._keyboard = new AudioKeys({polyphony : 88, rows : 1, octaveControls : false})
|
||||||
|
this._keyboard.down((e) => {
|
||||||
|
this.keyDown(e.note)
|
||||||
|
this._emitKeyDown(e.note)
|
||||||
|
})
|
||||||
|
this._keyboard.up((e) => {
|
||||||
|
this.keyUp(e.note)
|
||||||
|
this._emitKeyUp(e.note)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The piano interface
|
||||||
|
*/
|
||||||
|
this._keyboardInterface = new KeyboardElement(container, 48, 2)
|
||||||
|
this._keyboardInterface.on('keyDown', (note) => {
|
||||||
|
this.keyDown(note)
|
||||||
|
this._emitKeyDown(note)
|
||||||
|
})
|
||||||
|
this._keyboardInterface.on('keyUp', (note) => {
|
||||||
|
this.keyUp(note)
|
||||||
|
this._emitKeyUp(note)
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('resize', this._resize.bind(this))
|
||||||
|
//size initially
|
||||||
|
this._resize()
|
||||||
|
|
||||||
|
//make sure they don't get double clicked
|
||||||
|
this._currentKeys = {}
|
||||||
|
|
||||||
|
//a queue of all of the events
|
||||||
|
this._eventQueue = new buckets.PriorityQueue((a, b) => b.time - a.time)
|
||||||
|
this._boundLoop = this._loop.bind(this)
|
||||||
|
this._loop()
|
||||||
|
|
||||||
|
const bottom = document.createElement('div')
|
||||||
|
bottom.id = 'bottom'
|
||||||
|
container.appendChild(bottom)
|
||||||
|
|
||||||
|
//the midi input
|
||||||
|
this._midi = new Midi()
|
||||||
|
this._midi.on('keyDown', (note) => {
|
||||||
|
this.keyDown(note)
|
||||||
|
this._emitKeyDown(note)
|
||||||
|
})
|
||||||
|
this._midi.on('keyUp', (note) => {
|
||||||
|
this.keyUp(note)
|
||||||
|
this._emitKeyUp(note)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_loop(){
|
||||||
|
requestAnimationFrame(this._boundLoop)
|
||||||
|
const now = Tone.now()
|
||||||
|
while(!this._eventQueue.isEmpty() && this._eventQueue.peek().time <= now){
|
||||||
|
const event = this._eventQueue.dequeue()
|
||||||
|
event.callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_emitKeyDown(note){
|
||||||
|
if (this._active){
|
||||||
|
this.emit('keyDown', note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_emitKeyUp(note){
|
||||||
|
if (this._active){
|
||||||
|
this.emit('keyUp', note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown(note, time=Tone.now(), ai=false){
|
||||||
|
if (!this._active){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this._currentKeys[note]){
|
||||||
|
this._currentKeys[note] = 0
|
||||||
|
}
|
||||||
|
this._currentKeys[note] += 1
|
||||||
|
this._eventQueue.add({
|
||||||
|
time : time,
|
||||||
|
callback : this._keyboardInterface.keyDown.bind(this._keyboardInterface, note, ai)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp(note, time=Tone.now(), ai=false){
|
||||||
|
if (!this._active){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//add a little time to it in edge cases where the keydown and keyup are at the same time
|
||||||
|
time += 0.01
|
||||||
|
if (this._currentKeys[note]){
|
||||||
|
this._currentKeys[note] -= 1
|
||||||
|
this._eventQueue.add({
|
||||||
|
time : time,
|
||||||
|
callback : this._keyboardInterface.keyUp.bind(this._keyboardInterface, note, ai)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_resize(){
|
||||||
|
const keyWidth = 24
|
||||||
|
let octaves = Math.round((window.innerWidth / keyWidth) / 12)
|
||||||
|
octaves = Math.max(octaves, 2)
|
||||||
|
octaves = Math.min(octaves, 7)
|
||||||
|
let baseNote = 48
|
||||||
|
if (octaves > 5){
|
||||||
|
baseNote -= (octaves - 5) * 12
|
||||||
|
}
|
||||||
|
this._keyboardInterface.resize(baseNote, octaves)
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(){
|
||||||
|
container.classList.add('focus')
|
||||||
|
this._active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivate(){
|
||||||
|
container.classList.remove('focus')
|
||||||
|
this._active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Keyboard}
|
59
static/src/keyboard/Midi.js
Normal file
59
static/src/keyboard/Midi.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import events from 'events'
|
||||||
|
import WebMidi from 'webmidi'
|
||||||
|
|
||||||
|
class Midi extends events.EventEmitter{
|
||||||
|
constructor(){
|
||||||
|
super()
|
||||||
|
|
||||||
|
this._isEnabled = false
|
||||||
|
|
||||||
|
WebMidi.enable((err) => {
|
||||||
|
if (!err){
|
||||||
|
this._isEnabled = true
|
||||||
|
if (WebMidi.inputs){
|
||||||
|
WebMidi.inputs.forEach((input) => this._bindInput(input))
|
||||||
|
}
|
||||||
|
WebMidi.addListener('connected', (device) => {
|
||||||
|
if (device.input){
|
||||||
|
this._bindInput(device.input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindInput(inputDevice){
|
||||||
|
if (this._isEnabled){
|
||||||
|
WebMidi.addListener('disconnected', (device) => {
|
||||||
|
if (device.input){
|
||||||
|
device.input.removeListener('noteOn')
|
||||||
|
device.input.removeListener('noteOff')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
inputDevice.addListener('noteon', 'all', (event) => {
|
||||||
|
this.emit('keyDown', event.note.number)
|
||||||
|
})
|
||||||
|
inputDevice.addListener('noteoff', 'all', (event) => {
|
||||||
|
this.emit('keyUp', event.note.number)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Midi}
|
38
static/src/keyboard/Note.js
Normal file
38
static/src/keyboard/Note.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {RollNote} from 'roll/RollNote'
|
||||||
|
|
||||||
|
export class Note{
|
||||||
|
constructor(container, ai){
|
||||||
|
this.element = document.createElement('div')
|
||||||
|
this.element.classList.add('highlight')
|
||||||
|
this.element.classList.add('active')
|
||||||
|
if (ai){
|
||||||
|
this.element.classList.add('ai')
|
||||||
|
}
|
||||||
|
container.appendChild(this.element)
|
||||||
|
|
||||||
|
this.rollNote = new RollNote(container, ai)
|
||||||
|
}
|
||||||
|
noteOff(){
|
||||||
|
this.element.classList.remove('active')
|
||||||
|
this.rollNote.noteOff()
|
||||||
|
setTimeout(() => {
|
||||||
|
this.element.remove()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
131
static/src/roll/Roll.js
Normal file
131
static/src/roll/Roll.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const THREE = require('three')
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry( 1, 1, 1 )
|
||||||
|
const material = new THREE.MeshBasicMaterial( {color: 0x1FB7EC, side: THREE.DoubleSide} )
|
||||||
|
const aiMaterial = new THREE.MeshBasicMaterial( {color: 0xFFB729, side: THREE.DoubleSide} )
|
||||||
|
|
||||||
|
window.zero = new THREE.Vector3(0, 0, 0)
|
||||||
|
|
||||||
|
function scale(value, inMin, inMax, min, max){
|
||||||
|
return ((value - inMin) / (inMax - inMin)) * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
class RollClass {
|
||||||
|
constructor(container){
|
||||||
|
this._element = document.createElement('div')
|
||||||
|
this._element.id = 'roll'
|
||||||
|
|
||||||
|
this._camera = new THREE.OrthographicCamera(0, 1, 1, 0, 1, 1000 )
|
||||||
|
this._camera.position.z = 1
|
||||||
|
this._camera.lookAt(new THREE.Vector3(0, 0, 0))
|
||||||
|
|
||||||
|
this._scene = new THREE.Scene()
|
||||||
|
|
||||||
|
this._renderer = new THREE.WebGLRenderer({alpha: true})
|
||||||
|
this._renderer.setClearColor(0x000000, 0)
|
||||||
|
this._renderer.setPixelRatio( window.devicePixelRatio )
|
||||||
|
this._renderer.sortObjects = false
|
||||||
|
this._element.appendChild(this._renderer.domElement)
|
||||||
|
|
||||||
|
this._currentNotes = {}
|
||||||
|
|
||||||
|
window.camera = this._camera
|
||||||
|
|
||||||
|
//start the loop
|
||||||
|
this._lastUpdate = Date.now()
|
||||||
|
this._boundLoop = this._loop.bind(this)
|
||||||
|
this._boundLoop()
|
||||||
|
window.addEventListener('resize', this._resize.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
get bottom(){
|
||||||
|
return this._element.clientHeight + this._camera.position.y
|
||||||
|
}
|
||||||
|
|
||||||
|
appendTo(container){
|
||||||
|
container.appendChild(this._element)
|
||||||
|
this._resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
add(element){
|
||||||
|
this._scene.add(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown(midi, box, ai=false){
|
||||||
|
const selector = ai ? `ai${midi}` : midi
|
||||||
|
if (!this._currentNotes.hasOwnProperty(selector)){
|
||||||
|
this._currentNotes[selector] = []
|
||||||
|
}
|
||||||
|
if (midi && box){
|
||||||
|
//translate the box coords to this space
|
||||||
|
const initialScaling = 10000
|
||||||
|
const plane = new THREE.Mesh( geometry, ai ? aiMaterial : material )
|
||||||
|
const margin = 4
|
||||||
|
const width = box.width - margin * 2
|
||||||
|
plane.scale.set(width, initialScaling, 1)
|
||||||
|
plane.position.z = 0
|
||||||
|
plane.position.x = box.left + margin + width / 2
|
||||||
|
plane.position.y = this._element.clientHeight + this._camera.position.y + initialScaling / 2
|
||||||
|
this._scene.add(plane)
|
||||||
|
|
||||||
|
this._currentNotes[selector].push({
|
||||||
|
plane : plane,
|
||||||
|
position: this._camera.position.y
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp(midi, ai=false){
|
||||||
|
const selector = ai ? `ai${midi}` : midi
|
||||||
|
if (this._currentNotes[selector] && this._currentNotes[selector].length){
|
||||||
|
const note = this._currentNotes[selector].shift()
|
||||||
|
const plane = note.plane
|
||||||
|
const position = note.position
|
||||||
|
// get the distance covered
|
||||||
|
plane.scale.y = Math.max(this._camera.position.y - position, 5)
|
||||||
|
plane.position.y = this._element.clientHeight + position + plane.scale.y / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_resize(){
|
||||||
|
const frustumSize = 1000
|
||||||
|
const aspect = this._element.clientWidth / this._element.clientHeight
|
||||||
|
//make it match the screen pixesl
|
||||||
|
this._camera.left = 0
|
||||||
|
this._camera.bottom = this._element.clientHeight
|
||||||
|
this._camera.right = this._element.clientWidth
|
||||||
|
this._camera.top = 0
|
||||||
|
|
||||||
|
//update things
|
||||||
|
this._camera.updateProjectionMatrix()
|
||||||
|
this._renderer.setSize( this._element.clientWidth, this._element.clientHeight )
|
||||||
|
}
|
||||||
|
|
||||||
|
_loop(){
|
||||||
|
const delta = Date.now() - this._lastUpdate
|
||||||
|
this._lastUpdate = Date.now()
|
||||||
|
requestAnimationFrame(this._boundLoop)
|
||||||
|
this._renderer.render( this._scene, this._camera )
|
||||||
|
this._camera.position.y += 1 / 10 * delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Roll = new RollClass()
|
||||||
|
export {Roll}
|
44
static/src/roll/RollNote.js
Normal file
44
static/src/roll/RollNote.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const THREE = require('three')
|
||||||
|
import {Roll} from 'roll/Roll'
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneBufferGeometry( 1, 1, 1 )
|
||||||
|
const material = new THREE.MeshBasicMaterial( {color: 0x1FB7EC, side: THREE.BackSide} )
|
||||||
|
const aiMaterial = new THREE.MeshBasicMaterial( {color: 0xFFB729, side: THREE.BackSide} )
|
||||||
|
|
||||||
|
export class RollNote {
|
||||||
|
constructor(element, ai){
|
||||||
|
this.element = element
|
||||||
|
const box = this.element.getBoundingClientRect()
|
||||||
|
const initialScaling = 3000
|
||||||
|
this.plane = new THREE.Mesh( geometry, ai ? aiMaterial : material )
|
||||||
|
const margin = 4
|
||||||
|
const width = box.width - margin * 2
|
||||||
|
this.plane.scale.set(width, initialScaling, 1)
|
||||||
|
this.plane.position.z = 0
|
||||||
|
this.plane.position.x = box.left + margin + width / 2
|
||||||
|
this.plane.position.y = Roll.bottom + initialScaling / 2
|
||||||
|
this.bottom = Roll.bottom
|
||||||
|
Roll.add(this.plane)
|
||||||
|
}
|
||||||
|
noteOff(bottom){
|
||||||
|
const dist = Roll.bottom - this.bottom
|
||||||
|
this.plane.scale.y = Math.max(dist, 5)
|
||||||
|
this.plane.position.y = this.bottom + this.plane.scale.y / 2
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user