/** * 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' 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.addEventListener('pointerup', () => this._mousedown = false) container.appendChild(this._container) this._keys = {} this._mousedown = false this.resize(lowest, octaves) /** * The piano roll * @type {Roll} */ this._roll = new Roll(container) } 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 } } _bindKeyEvents(key){ key.addEventListener('pointerover', (e) => { if (this._mousedown){ const noteNum = parseInt(e.target.id) // this.keyDown(noteNum, false) this.emit('keyDown', noteNum) } else { key.classList.add('hover') } }) key.addEventListener('pointerout', (e) => { if (this._mousedown){ const noteNum = parseInt(e.target.id) // this.keyUp(noteNum, false) this.emit('keyUp', noteNum) } else { key.classList.remove('hover') } }) key.addEventListener('pointerdown', (e) => { e.preventDefault() const noteNum = parseInt(e.target.id) // this.keyDown(noteNum, false) this.emit('keyDown', noteNum) this._mousedown = true }) key.addEventListener('pointerup', (e) => { e.preventDefault() const noteNum = parseInt(e.target.id) // this.keyUp(noteNum, false) this.emit('keyUp', noteNum) this._mousedown = false }) } keyDown(noteNum, ai=false){ // console.log('down', noteNum, ai) if (this._keys.hasOwnProperty(noteNum)){ const key = this._keys[noteNum] key.classList.remove('hover') const highlight = document.createElement('div') highlight.classList.add('highlight') highlight.classList.add('active') if (ai){ highlight.classList.add('ai') } key.querySelector('#fill').appendChild(highlight) this._roll.keyDown(noteNum, this._getNotePosition(noteNum), ai) } } keyUp(noteNum, ai=false){ // console.log('up', noteNum, ai) if (this._keys.hasOwnProperty(noteNum)){ const query = ai ? '.highlight.active.ai' : '.highlight.active' const highlight = this._keys[noteNum].querySelector(query) if (highlight){ highlight.classList.remove('active') setTimeout(() => highlight.remove(), 2000) //and up on the roll this._roll.keyUp(noteNum, ai) } else { // console.log(this._keys[noteNum].querySelector(query)) //try again without ai this.keyUp(noteNum) } } } _getNotePosition(key){ if (this._keys.hasOwnProperty(key)){ return this._keys[key].querySelector('#fill').getBoundingClientRect() } } } export {KeyboardElement}