aiexperiments-ai-duet/static/app/keyboard/Keyboard.js
2016-11-14 16:35:58 -08:00

149 lines
3.5 KiB
JavaScript

/**
* 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._active = false
/**
* The audio key keyboard
* @type {AudioKeys}
*/
this._keyboard = new AudioKeys({polyphony : 88, rows : 1, octaveControls : false, rootNote : 48})
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, 36, 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
}
if (this._currentKeys[note]){
this._currentKeys[note] -= 1
this._currentKeys[note] = Math.max(this._currentKeys[note], 0)
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)
this._keyboardInterface.resize(36, octaves)
}
activate(){
this._active = true
}
}
export {Keyboard}