mirror of
https://github.com/Tonejs/Tone.js
synced 2024-11-16 16:48:00 +00:00
501 lines
15 KiB
JavaScript
501 lines
15 KiB
JavaScript
|
/*
|
||
|
* Qwerty Hancock keyboard library v0.4.3
|
||
|
* The web keyboard for now people.
|
||
|
* Copyright 2012-14, Stuart Memo
|
||
|
*
|
||
|
* Licensed under the MIT License
|
||
|
* http://opensource.org/licenses/mit-license.php
|
||
|
*
|
||
|
* http://stuartmemo.com/qwerty-hancock
|
||
|
*/
|
||
|
|
||
|
(function (window, undefined) {
|
||
|
var version = '0.4.3',
|
||
|
settings = {},
|
||
|
mouse_is_down = false,
|
||
|
keysDown = {},
|
||
|
key_map = {
|
||
|
65: 'Cl',
|
||
|
87: 'C#l',
|
||
|
83: 'Dl',
|
||
|
69: 'D#l',
|
||
|
68: 'El',
|
||
|
70: 'Fl',
|
||
|
84: 'F#l',
|
||
|
71: 'Gl',
|
||
|
89: 'G#l',
|
||
|
90: 'G#l',
|
||
|
72: 'Al',
|
||
|
85: 'A#l',
|
||
|
74: 'Bl',
|
||
|
75: 'Cu',
|
||
|
79: 'C#u',
|
||
|
76: 'Du',
|
||
|
80: 'D#u',
|
||
|
59: 'Eu',
|
||
|
186: 'Eu',
|
||
|
222: 'Fu',
|
||
|
221: 'F#u',
|
||
|
220: 'Gu'
|
||
|
},
|
||
|
keyDownCallback,
|
||
|
keyUpCallback;
|
||
|
|
||
|
/**
|
||
|
* Calculate width of white key.
|
||
|
* @return {number} Width of a single white key in pixels.
|
||
|
*/
|
||
|
var getWhiteKeyWidth = function (number_of_white_keys) {
|
||
|
return Math.floor((settings.width - number_of_white_keys) / number_of_white_keys);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge user settings with defaults.
|
||
|
* @param {object} user_settings
|
||
|
*/
|
||
|
var init = function (us) {
|
||
|
var container;
|
||
|
|
||
|
user_settings = us || {};
|
||
|
|
||
|
settings = {
|
||
|
id: user_settings.id || 'keyboard',
|
||
|
octaves: user_settings.octaves || 3,
|
||
|
width: user_settings.width,
|
||
|
height: user_settings.height,
|
||
|
startNote: user_settings.startNote || 'A3',
|
||
|
whiteKeyColour: user_settings.whiteKeyColour || '#fff',
|
||
|
blackKeyColour: user_settings.blackKeyColour || '#000',
|
||
|
activeColour: user_settings.activeColour || 'yellow',
|
||
|
borderColour: user_settings.borderColour || '#000',
|
||
|
keyboardLayout: user_settings.keyboardLayout || 'en'
|
||
|
};
|
||
|
|
||
|
container = document.getElementById(settings.id);
|
||
|
|
||
|
if (typeof settings.width === 'undefined') {
|
||
|
settings.width = container.offsetWidth;
|
||
|
}
|
||
|
|
||
|
if (typeof settings.height === 'undefined') {
|
||
|
settings.height = container.offsetHeight;
|
||
|
}
|
||
|
|
||
|
settings.startOctave = parseInt(settings.startNote.charAt(1), 10);
|
||
|
|
||
|
createKeyboard();
|
||
|
addListeners.call(this, container);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get frequency of a given note.
|
||
|
* @param {string} note Musical note to convert into hertz.
|
||
|
* @return {number} Frequency of note in hertz.
|
||
|
*/
|
||
|
var getFrequencyOfNote = function (note) {
|
||
|
var notes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'],
|
||
|
key_number,
|
||
|
octave;
|
||
|
|
||
|
if (note.length === 3) {
|
||
|
octave = note.charAt(2);
|
||
|
} else {
|
||
|
octave = note.charAt(1);
|
||
|
}
|
||
|
|
||
|
key_number = notes.indexOf(note.slice(0, -1));
|
||
|
|
||
|
if (key_number < 3) {
|
||
|
key_number = key_number + 12 + ((octave - 1) * 12) + 1;
|
||
|
} else {
|
||
|
key_number = key_number + ((octave - 1) * 12) + 1;
|
||
|
}
|
||
|
|
||
|
return 440 * Math.pow(2, (key_number - 49) / 12);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Lighten up man. Change the colour of a key.
|
||
|
* @param {element} el DOM element to change colour of.
|
||
|
*/
|
||
|
var lightenUp = function lightenUp (el) {
|
||
|
if (el !== null || typeof el === undefined) {
|
||
|
el.style.backgroundColor = settings.activeColour;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Revert key to original colour.
|
||
|
* @param {element} el DOM element to change colour of.
|
||
|
*/
|
||
|
var darkenDown = function darkenDown (el) {
|
||
|
if (el !== null) {
|
||
|
if (el.getAttribute('data-note-type') === 'white') {
|
||
|
el.style.backgroundColor = settings.whiteKeyColour;
|
||
|
} else {
|
||
|
el.style.backgroundColor = settings.blackKeyColour;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Order notes into order defined by starting key in settings.
|
||
|
* @param {array} notes_to_order Notes to be ordered.
|
||
|
* @return {array{ ordered_notes Ordered notes.
|
||
|
*/
|
||
|
var orderNotes = function (notes_to_order) {
|
||
|
var i,
|
||
|
keyOffset = 0,
|
||
|
number_of_notes_to_order = notes_to_order.length,
|
||
|
ordered_notes = [];
|
||
|
|
||
|
for (i = 0; i < number_of_notes_to_order; i++) {
|
||
|
if (settings.startNote.charAt(0) === notes_to_order[i]) {
|
||
|
keyOffset = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < number_of_notes_to_order; i++) {
|
||
|
if (i + keyOffset > number_of_notes_to_order - 1) {
|
||
|
ordered_notes[i] = notes_to_order[i + keyOffset - number_of_notes_to_order];
|
||
|
} else {
|
||
|
ordered_notes[i] = notes_to_order[i + keyOffset];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ordered_notes;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add styling to individual white key.
|
||
|
* @param {element} el White key DOM element.
|
||
|
*/
|
||
|
var styleWhiteKey = function (key) {
|
||
|
key.el.style.backgroundColor = settings.whiteKeyColour;
|
||
|
key.el.style.border = '1px solid ' + settings.borderColour;
|
||
|
key.el.style.borderRight = 0;
|
||
|
key.el.style.height = settings.height + 'px';
|
||
|
key.el.style.width = key.width + 'px';
|
||
|
key.el.style.borderRadius = '0 0 5px 5px';
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add styling to individual black key.
|
||
|
* @param {element} el Black key DOM element.
|
||
|
*/
|
||
|
var styleBlackKey = function (key) {
|
||
|
var white_key_width = getWhiteKeyWidth(getTotalWhiteKeys()),
|
||
|
black_key_width = Math.floor(white_key_width / 2);
|
||
|
|
||
|
key.el.style.backgroundColor = settings.blackKeyColour;
|
||
|
key.el.style.border = '1px solid ' + settings.borderColour;
|
||
|
key.el.style.position = 'absolute';
|
||
|
key.el.style.left = Math.floor(((white_key_width + 1) * (key.noteNumber + 1)) - (black_key_width / 2)) + 'px';
|
||
|
key.el.style.width = black_key_width + 'px';
|
||
|
key.el.style.height = (settings.height / 1.5) + 'px';
|
||
|
key.el.style.borderRadius = '0 0 3px 3px';
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add styling to individual key on keyboard.
|
||
|
* @param {object} key Element of key.
|
||
|
*/
|
||
|
var styleKey = function (key) {
|
||
|
key.el.style.display = 'inline-block';
|
||
|
key.el.style['-webkit-user-select'] = 'none';
|
||
|
|
||
|
if (key.colour === 'white') {
|
||
|
styleWhiteKey(key);
|
||
|
} else {
|
||
|
styleBlackKey(key);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Reset styles on keyboard container and list element.
|
||
|
* @param {element} keyboard Keyboard container DOM element.
|
||
|
*/
|
||
|
var styleKeyboard = function (keyboard) {
|
||
|
var styleElement = function (el) {
|
||
|
el.style.cursor = 'default';
|
||
|
el.style.fontSize = '0px';
|
||
|
el.style.height = settings.height + 'px';
|
||
|
el.style.padding = 0;
|
||
|
el.style.position = 'relative';
|
||
|
el.style.listStyle = 'none';
|
||
|
el.style.margin = 0;
|
||
|
el.style.width = settings.width + 'px';
|
||
|
el.style['-webkit-user-select'] = 'none';
|
||
|
};
|
||
|
|
||
|
styleElement(keyboard.container);
|
||
|
styleElement(keyboard.el);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Call user's mouseDown event.
|
||
|
*/
|
||
|
var mouseDown = function (element, callback) {
|
||
|
mouse_is_down = true;
|
||
|
lightenUp(element);
|
||
|
callback(element.title, getFrequencyOfNote(element.title));
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Call user's mouseUp event.
|
||
|
*/
|
||
|
var mouseUp = function (element, callback) {
|
||
|
mouse_is_down = false;
|
||
|
darkenDown(element);
|
||
|
callback(element.title, getFrequencyOfNote(element.title));
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Call user's mouseDown if required.
|
||
|
*/
|
||
|
var mouseOver = function (element, callback) {
|
||
|
if (mouse_is_down) {
|
||
|
lightenUp(element);
|
||
|
callback(element.title, getFrequencyOfNote(element.title));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Call user's mouseUp if required.
|
||
|
*/
|
||
|
var mouseOut = function (element, callback) {
|
||
|
if (mouse_is_down) {
|
||
|
darkenDown(element);
|
||
|
callback(element.title, getFrequencyOfNote(element.title));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Create key DOM element.
|
||
|
* @return {object} Key DOM element.
|
||
|
*/
|
||
|
var createKey = function (key) {
|
||
|
key.el = document.createElement('li');
|
||
|
key.el.id = key.id;
|
||
|
key.el.title = key.id;
|
||
|
key.el.setAttribute('data-note-type', key.colour);
|
||
|
|
||
|
styleKey(key);
|
||
|
|
||
|
return key;
|
||
|
};
|
||
|
|
||
|
var getTotalWhiteKeys = function () {
|
||
|
return settings.octaves * 7;
|
||
|
};
|
||
|
|
||
|
var createKeys = function () {
|
||
|
var that = this,
|
||
|
i,
|
||
|
key,
|
||
|
keys = [],
|
||
|
note_counter = 0,
|
||
|
octave_counter = settings.startOctave,
|
||
|
total_white_keys = getTotalWhiteKeys();
|
||
|
|
||
|
for (i = 0; i < total_white_keys; i++) {
|
||
|
|
||
|
if (i % this.whiteNotes.length === 0) {
|
||
|
note_counter = 0;
|
||
|
}
|
||
|
|
||
|
bizarre_note_counter = this.whiteNotes[note_counter];
|
||
|
|
||
|
if ((bizarre_note_counter === 'C') && (i !== 0)) {
|
||
|
octave_counter++;
|
||
|
}
|
||
|
|
||
|
key = createKey({
|
||
|
colour: 'white',
|
||
|
octave: octave_counter,
|
||
|
width: getWhiteKeyWidth(total_white_keys),
|
||
|
id: this.whiteNotes[note_counter] + octave_counter,
|
||
|
noteNumber: i
|
||
|
});
|
||
|
|
||
|
keys.push(key.el);
|
||
|
|
||
|
if (i !== total_white_keys - 1) {
|
||
|
this.notesWithSharps.forEach(function (note, index) {
|
||
|
if (note === that.whiteNotes[note_counter]) {
|
||
|
key = createKey({
|
||
|
colour: 'black',
|
||
|
octave: octave_counter,
|
||
|
width: getWhiteKeyWidth(total_white_keys) / 2,
|
||
|
id: that.whiteNotes[note_counter] + '#' + octave_counter,
|
||
|
noteNumber: i
|
||
|
});
|
||
|
|
||
|
keys.push(key.el);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
note_counter++;
|
||
|
}
|
||
|
|
||
|
return keys;
|
||
|
};
|
||
|
|
||
|
var addKeysToKeyboard = function (keyboard) {
|
||
|
keyboard.keys.forEach(function (key) {
|
||
|
keyboard.el.appendChild(key);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var setKeyPressOffset = function (sorted_white_notes) {
|
||
|
settings.keyPressOffset = sorted_white_notes[0] === 'C' ? 0 : 1;
|
||
|
};
|
||
|
|
||
|
var createKeyboard = function () {
|
||
|
var keyboard = {
|
||
|
container: document.getElementById(settings.id),
|
||
|
el: document.createElement('ul'),
|
||
|
whiteNotes: orderNotes(['C', 'D', 'E', 'F', 'G', 'A', 'B']),
|
||
|
notesWithSharps: orderNotes(['C', 'D', 'F', 'G', 'A']),
|
||
|
};
|
||
|
|
||
|
keyboard.keys = createKeys.call(keyboard);
|
||
|
|
||
|
setKeyPressOffset(keyboard.whiteNotes);
|
||
|
styleKeyboard(keyboard);
|
||
|
|
||
|
// Add keys to keyboard, and keyboard to container.
|
||
|
addKeysToKeyboard(keyboard);
|
||
|
|
||
|
keyboard.container.appendChild(keyboard.el);
|
||
|
|
||
|
return keyboard;
|
||
|
};
|
||
|
|
||
|
var getKeyPressed = function (keyCode) {
|
||
|
return key_map[keyCode]
|
||
|
.replace('l', parseInt(settings.startOctave, 10) + settings.keyPressOffset)
|
||
|
.replace('u', (parseInt(settings.startOctave, 10) + settings.keyPressOffset + 1)
|
||
|
.toString());
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Handle a keyboard key being pressed.
|
||
|
* @param {object} key The keyboard event of the currently pressed key.
|
||
|
* @param {callback} callback The user's noteDown function.
|
||
|
*/
|
||
|
var keyboardDown = function (key, callback) {
|
||
|
var key_pressed;
|
||
|
|
||
|
if (key.keyCode in keysDown) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
keysDown[key.keyCode] = true;
|
||
|
|
||
|
if (typeof key_map[key.keyCode] !== 'undefined') {
|
||
|
key_pressed = getKeyPressed(key.keyCode);
|
||
|
|
||
|
// Call user's noteDown function.
|
||
|
callback(key_pressed, getFrequencyOfNote(key_pressed));
|
||
|
lightenUp(document.getElementById(key_pressed));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Handle a keyboard key being released.
|
||
|
* @param {element} key The DOM element of the key that was released.
|
||
|
* @param {callback} callback The user's noteDown function.
|
||
|
*/
|
||
|
var keyboardUp = function (key, callback) {
|
||
|
var key_pressed;
|
||
|
|
||
|
delete keysDown[key.keyCode];
|
||
|
|
||
|
if (typeof key_map[key.keyCode] !== 'undefined') {
|
||
|
key_pressed = getKeyPressed(key.keyCode);
|
||
|
// Call user's noteDown function.
|
||
|
callback(key_pressed, getFrequencyOfNote(key_pressed));
|
||
|
darkenDown(document.getElementById(key_pressed));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add event listeners to keyboard.
|
||
|
* @param {element} keyboard_element
|
||
|
*/
|
||
|
var addListeners = function (keyboard_element) {
|
||
|
var that = this;
|
||
|
|
||
|
// Key is pressed down on keyboard.
|
||
|
window.addEventListener('keydown', function (key) {
|
||
|
keyboardDown(key, that.keyDown);
|
||
|
});
|
||
|
|
||
|
// Key is released on keyboard.
|
||
|
window.addEventListener('keyup', function (key) {
|
||
|
keyboardUp(key, that.keyUp);
|
||
|
});
|
||
|
|
||
|
// Mouse is clicked down on keyboard element.
|
||
|
keyboard_element.addEventListener('mousedown', function (event) {
|
||
|
mouseDown(event.target, that.keyDown);
|
||
|
});
|
||
|
|
||
|
// Mouse is released from keyboard element.
|
||
|
keyboard_element.addEventListener('mouseup', function (event) {
|
||
|
mouseUp(event.target, that.keyUp);
|
||
|
});
|
||
|
|
||
|
// Mouse is moved over keyboard element.
|
||
|
keyboard_element.addEventListener('mouseover', function (event) {
|
||
|
mouseOver(event.target, that.keyDown);
|
||
|
});
|
||
|
|
||
|
// Mouse is moved out of keyvoard element.
|
||
|
keyboard_element.addEventListener('mouseout', function (event) {
|
||
|
mouseOut(event.target, that.keyUp);
|
||
|
});
|
||
|
|
||
|
// Device supports touch events.
|
||
|
if ('ontouchstart' in document.documentElement) {
|
||
|
keyboard_element.addEventListener('touchstart', function (event) {
|
||
|
mouseDown(event.target, that.keyDown);
|
||
|
});
|
||
|
|
||
|
keyboard_element.addEventListener('touchend', function (event) {
|
||
|
mouseUp(event.target, that.keyUp);
|
||
|
});
|
||
|
|
||
|
keyboard_element.addEventListener('touchleave', function (event) {
|
||
|
mouseOut(event.target, that.keyUp);
|
||
|
});
|
||
|
|
||
|
keyboard_element.addEventListener('touchcancel', function (event) {
|
||
|
mouseOut(event.target, that.keyUp);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Qwerty Hancock constructor.
|
||
|
* @param {object} settings Optional user settings.
|
||
|
*/
|
||
|
var QwertyHancock = function (settings) {
|
||
|
this.version = version;
|
||
|
|
||
|
this.keyDown = function () {
|
||
|
// Placeholder function.
|
||
|
};
|
||
|
|
||
|
this.keyUp = function () {
|
||
|
// Placeholder function.
|
||
|
};
|
||
|
|
||
|
init.call(this, settings);
|
||
|
};
|
||
|
|
||
|
window.QwertyHancock = QwertyHancock;
|
||
|
})(window);
|