piano/frontend/main.js

1162 lines
27 KiB
JavaScript

/*
TODO:
*/
const audioCtx = new (AudioContext || webkitAudioContext)();
function mod(n, m) {
return ((n % m) + m) % m;
}
const WHITE = 0;
const BLACK = 1;
let pianoLayout = [
WHITE,
BLACK,
WHITE,
BLACK,
WHITE,
WHITE,
BLACK,
WHITE,
BLACK,
WHITE,
BLACK,
WHITE
];
let blackKeyOffset = [1, 3, 0, 6, 8, 10, 0];
let whiteKeyOffset = [0, 2, 4, 5, 7, 9, 11];
let keybindValues = [0, 1, 2, 3, 4, -1, 5, 6, 7, 8, 9, 10, 11, -1];
let keyMap = {
"IntlBackslash": -16,
"KeyA": -15,
"KeyZ": -14,
"KeyS": -13,
"KeyX": -12,
"KeyD": -11,
"KeyC": -10,
"KeyF": -9,
"KeyV": -8,
"KeyG": -7,
"KeyB": -6,
"KeyH": -5,
"KeyN": -4,
"KeyJ": -3,
"KeyM": -2,
"KeyK": -1,
"Comma": 0,
"KeyL": 1,
"Period": 2,
"Semicolon": 3,
"Slash": 4,
"Quote": 5,
"Digit1": -1,
"KeyQ": 0,
"Digit2": 1,
"KeyW": 2,
"Digit3": 3,
"KeyE": 4,
"Digit4": 5,
"KeyR": 6,
"Digit5": 7,
"KeyT": 8,
"Digit6": 9,
"KeyY": 10,
"Digit7": 11,
"KeyU": 12,
"Digit8": 13,
"KeyI": 14,
"Digit9": 15,
"KeyO": 16,
"Digit0": 17,
"KeyP": 18,
"Minus": 19,
"BracketLeft": 20,
"Equal": 21,
"BracketRight": 22
};
let keysDown = new Set();
let piano;
let audioEngine;
let net;
let chat;
let focus = true;
let userMenuUser;
let soundBank = [];
let loading = 0;
function loadMppBank(url, format) {
for (let i=21; i<109; i++) {
piano.keys[i - 21].loaded = false;
let keyName = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"][i % 12] + (Math.floor(i / 12) - 2);
let xhttp = new XMLHttpRequest();
xhttp.responseType = "arraybuffer";
xhttp.addEventListener("load", (function(index) {
return function() {
audioCtx.decodeAudioData(xhttp.response, function(buffer) {
soundBank[index] = buffer;
loading++;
piano.keys[i - 21].loaded = true;
});
}
})(i));
xhttp.open("GET", url + keyName + "." + format);
xhttp.send();
}
}
class Key {
constructor(index) {
this.index = index;
this.value = index + 21;
this.type = pianoLayout[this.value % 12];
this.pressed = false;
this.presser = null;
this.loaded = false;
this.y = 1;
this.color = null;
}
animate() {
if (this.pressed) {
this.y = 0;
if (!(this.presser in net.users)) {
this.pressed = false;
return;
}
if (Date.now() - this.pressed > 5000) {
this.pressed = false;
return;
}
this.color = net.users[this.presser].color;
} else {
this.y = 1;
this.color = null;
}
}
}
class Piano {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.keys = [];
for (let i=0; i<88; i++) {
this.keys.push(new Key(i));
}
this.sustain = false;
this.performanceMode = false;
this.margin = 8;
this.scale = 1;
this.setScale(3);
}
// Get key at position
hit(x, y) {
let whiteWidth = (this.canvas.width - 155 - this.margin * 2) / 52 + 2;
let blackWidth = Math.ceil(whiteWidth * 0.5 / 2) * 2 + 1;
let blackHeight = Math.floor((this.canvas.height - this.margin * 2) / 2);
// Offset to octave 0
x += (whiteWidth + 1) * 5 - this.margin;
y -= this.margin;
if (y <= blackHeight) {
let blackX = x - (blackWidth - 1) / 2;
let blackKey = Math.floor(blackX / (whiteWidth + 1));
if (!(blackKey % 7 === 2 || blackKey % 7 === 6 || blackKey == 4 || blackKey == 56)) {
blackX = blackX % (whiteWidth + 1);
if (blackX >= whiteWidth - (blackWidth - 1)) {
let index = Math.floor(blackKey / 7) * 12 + blackKeyOffset[blackKey % 7];
return this.keys[index - 9];
}
}
}
if (x % (whiteWidth + 1) == whiteWidth) {
return null;
}
let whiteKey = Math.floor(x / (whiteWidth + 1));
let index = Math.floor(whiteKey / 7) * 12 + whiteKeyOffset[whiteKey % 7];
return this.keys[index - 9];
}
setScale(scale) {
this.scale = scale;
this.canvas.style.transform = "scale(" + scale + ")";
this.resize();
}
resize() {
// border + margin pixels: 155
let size = Math.floor((window.innerWidth / this.scale - 155) / 52);
this.canvas.width = size * 52 + 155 + this.margin * 2;
this.canvas.height = Math.floor(this.canvas.width * 0.18);
}
animateKeys() {
this.keys.forEach(function(key) {
key.animate();
});
}
render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Render casing
this.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Render keys
let keyRegionWidth = this.canvas.width - this.margin * 2;
let keyRegionHeight = this.canvas.height - this.margin * 2;
let whiteWidth = (keyRegionWidth - 155) / 52 + 2;
let blackWidth = Math.ceil(whiteWidth * 0.5 / 2) * 2 + 1;
let blackHeight = Math.floor(keyRegionHeight / 2);
let keyDepth = Math.ceil(keyRegionHeight / 20);
this.ctx.save();
this.ctx.translate(this.margin, this.margin);
// Draw white keys
this.ctx.strokeStyle = "#ccc";
for (let i=0, x=0; i<this.keys.length; i++) {
let key = this.keys[i];
if (key.type === BLACK) continue;
if (!key.loaded) {
x += whiteWidth + 1;
continue;
}
let y = Math.floor(keyDepth * (1 - key.y));
let color = [0xff, 0xff, 0xff];
if (key.color) {
color = key.color;
}
this.ctx.fillStyle = `rgb(${color[0] * 0.8}, ${color[1] * 0.8}, ${color[2] * 0.8})`;
this.ctx.fillRect(x, keyDepth + y, whiteWidth, keyRegionHeight - y - keyDepth);
this.ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
this.ctx.fillRect(x + 1, keyDepth + y + 1, whiteWidth - 2, keyRegionHeight - keyDepth * 2 - 2);
x += whiteWidth + 1;
}
this.ctx.strokeStyle = "#222";
for (let i=0, x=-((blackWidth + 1) / 2); i<this.keys.length; i++) {
let key = this.keys[i];
if (key.type === WHITE) {
x += whiteWidth + 1;
continue;
}
if (!key.loaded) continue;
let y = Math.floor(keyDepth * (1 - key.y));
let color = [0x44, 0x44, 0x44];
if (key.color) {
color = key.color;
}
this.ctx.fillStyle = `rgb(${color[0] * 0.6}, ${color[1] * 0.6}, ${color[2] * 0.6})`;
this.ctx.fillRect(x, y, blackWidth, blackHeight - y);
this.ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
this.ctx.fillRect(x + 1, y + 1, blackWidth - 2, blackHeight - (keyDepth + 2));
}
this.ctx.restore();
}
localKeyDown(key, velocity) {
net.keyDown(key, velocity);
this.keyDown(key, velocity, net.id);
}
localKeyUp(key) {
net.keyUp(key, this.sustain);
this.keyUp(key, this.sustain, net.id);
}
localReleaseSustain() {
let should = false;
for (let i=0; i<audioEngine.playing.length; i++) {
if (audioEngine.playing[i].user == net.id) {
should = true;
break;
}
}
if (!should) return;
net.releaseSustain();
this.releaseSustain(net.id);
}
keyDown(key, velocity, user) {
if (!((key - 21) in this.keys)) return;
if (net.users[user].muteNotes) return;
this.keys[key - 21].pressed = Date.now();
this.keys[key - 21].presser = user;
audioEngine.noteOn(key, velocity, user);
}
keyUp(key, sustain, user) {
if (!((key - 21) in this.keys)) return;
if (net.users[user].muteNotes) return;
this.keys[key - 21].pressed = false;
if (!sustain) {
audioEngine.noteOff(key, user);
}
}
releaseSustain(user) {
audioEngine.releaseSustain(user);
}
}
class NoteSound {
constructor(key, velocity, user) {
this.key = key;
let sound = soundBank[key];
this.source = audioCtx.createBufferSource();
this.source.buffer = sound;
this.ended = () => {
this.remove();
};
this.source.addEventListener("ended", this.ended);
this.gain = audioCtx.createGain();
this.gain.gain.value = velocity;
this.source.connect(this.gain);
this.gain.connect(audioEngine.destination);
this.source.start();
this.user = user;
audioEngine.playing.push(this);
}
stop() {
let fade = 0.15;
this.gain.gain.setTargetAtTime(0, audioCtx.currentTime, fade / 3);
//this.gain.gain.linearRampToValueAtTime(this.gain.gain.value * 0.1, time + 0.2);
//this.gain.gain.linearRampToValueAtTime(0, time + 0.5);
this.source.removeEventListener("ended", this.ended);
this.source.stop(audioCtx.currentTime + fade);
this.remove();
}
purge() {
this.source.removeEventListener("ended", this.ended);
this.source.stop();
this.gain.disconnect(audioEngine.destination);
this.remove();
}
remove() {
let index = audioEngine.playing.indexOf(this);
if (index === -1) {
// This should never happen, but shit will fuck up if it happens.
return;
}
audioEngine.playing.splice(index, 1);
}
}
class AudioEngine {
constructor() {
this.active = false; // autoplay policy
this.playing = [];
this.masterGain = audioCtx.createGain();
this.masterGain.gain.value = 0.5;
this.masterGain.connect(audioCtx.destination);
this.compressor = audioCtx.createDynamicsCompressor();
// These should probably be fine tuned
this.compressor.threshold.value = -10;
this.compressor.knee.value = 0;
this.compressor.ratio.value = 20;
this.compressor.attack.value = 0;
this.compressor.release.value = 0.1;
this.compressor.connect(this.masterGain);
this.destination = this.compressor;
}
get volume() {
return this.masterGain.gain.value;
}
set volume(value) {
this.masterGain.gain.value = value;
}
noteOn(key, velocity, user) {
if (!this.active) return;
/*if (this.playing.length >= 500) {
// Prevent lag
return;
}*/
for (let i=0; i<this.playing.length; i++) {
if (this.playing[i].key == key && this.playing[i].user == user) {
if (piano.performanceMode) {
this.playing[i].purge();
} else {
this.playing[i].stop();
}
break;
}
}
new NoteSound(key, velocity, user);
}
noteOff(key, user) {
for (let i=this.playing.length - 1; i>=0; i--) {
if (this.playing[i].key == key && this.playing[i].user == user) {
this.playing[i].stop();
}
}
}
releaseSustain(user) {
for (let i=this.playing.length - 1; i>=0; i--) {
if (this.playing[i].user == user) {
this.playing[i].stop();
}
}
}
}
function render() {
piano.animateKeys();
piano.render();
window.requestAnimationFrame(render);
}
function setVolume(value, modifySlider) {
audioEngine.volume = value;
if (value === 0) {
document.getElementById("volume-indicator").className = "mute";
} else if (value <= 0.5) {
document.getElementById("volume-indicator").className = "low";
} else {
document.getElementById("volume-indicator").className = "";
}
if (modifySlider) {
document.getElementById("volume").value = value;
}
}
function openModal(element, dimmer, callback) {
if (dimmer) {
document.getElementById("dimmer").classList.add("active");
}
element.classList.add("active");
function clickListener(event) {
if (!element.contains(event.target)) {
element.classList.remove("active");
document.getElementById("dimmer").classList.remove("active");
if (callback) {
callback();
}
document.removeEventListener("click", clickListener, true);
}
}
document.addEventListener("click", clickListener, true);
}
class User {
constructor(id, uid, nick, color) {
this.id = id;
this.uid = uid;
this.nick = nick;
this.color = color;
this.local = this.id === net.id;
this.muteNotes = false;
this.element = document.createElement("li");
if (this.local) {
this.element.classList.add("local");
}
this.element.addEventListener("click", () => {
this.updateUserMenu();
openModal(document.getElementById("user-menu"), false, () => {
if (!this.local) return;
let nick = document.getElementById("user-nick").value;
if (nick != this.nick) {
net.setNick(nick);
}
});
event.stopImmediatePropagation();
});
this.element.innerText = this.nick ? this.nick : this.id;
this.element.style.backgroundColor = this.getColor();
document.getElementById("users").appendChild(this.element);
}
getColor() {
if (this.muteNotes) {
return "rgb(140, 140, 140)";
} else {
return `rgb(${this.color[0]}, ${this.color[1]}, ${this.color[2]})`;
}
}
updateUserMenu() {
userMenuUser = this.id;
let userMenu = document.getElementById("user-menu");
if (this.id == net.id) {
userMenu.classList.add("local");
} else {
userMenu.classList.remove("local");
}
userMenu.style.backgroundColor = this.getColor();
document.getElementById("user-uid").textContent = this.uid;
document.getElementById("user-nick").value = this.nick;
document.getElementById("user-mute-notes").checked = this.muteNotes;
let box = this.element.getBoundingClientRect();
userMenu.style.top = (box.bottom + 8) + "px";
userMenu.style.left = Math.max(box.left, 8) + "px";
}
update() {
this.element.style.backgroundColor = this.getColor();
}
setMuteNotes(value) {
this.muteNotes = value;
if (this.muteNotes) {
piano.releaseSustain(this.id);
}
}
changeNick(nick) {
this.nick = nick;
this.element.innerText = this.nick;
}
leave() {
document.getElementById("users").removeChild(this.element);
delete net.users[this.id];
}
}
class Networker {
constructor(url = location.origin.replace("http", "ws")) {
if (localStorage.nick) url += "?nick=" + encodeURIComponent(localStorage.nick);
this.ws = new WebSocket(url);
this.ws.binaryType = "arraybuffer";
this.ws.addEventListener("open", function() {
console.log("open");
});
this.ws.addEventListener("message", (message) => {
this.message(message.data);
});
this.ws.addEventListener("close", function() {
console.log("close");
chat.receive({
type: "disconnected"
});
});
this.users = {};
}
message(message) {
if (typeof message == "string") {
message = JSON.parse(message);
console.log(message);
switch(message.type) {
case "load":
this.id = message.id;
this.users = {};
for (let i=0; i<message.users.length; i++) {
let user = message.users[i];
this.users[user.id] = new User(user.id, user.uid, user.nick, user.color);
}
chat.chatLog(message.chatlog);
if (localStorage.adminlogin) {
net.chat("/adminlogin " + localStorage.adminlogin);
}
break;
case "join":
this.users[message.id] = new User(message.id, message.uid, message.nick, message.color);
break;
case "leave":
this.users[message.id].leave();
break;
case "chat":
chat.receive(message.message);
break;
case "nick":
this.users[message.id].changeNick(message.nick);
if (message.id == this.id) {
localStorage.nick = message.nick;
}
break;
case "rank":
let rank = message.rank;
chat.receive({
type: "rank",
rank: rank
});
if (rank === 0) {
document.body.className = "";
} else if (rank === 1) {
document.body.className = "owner";
} else if (rank === 2) {
document.body.className = "admin";
}
break;
}
return;
}
let dv = new DataView(message);
switch(dv.getUint8(0)) {
case 0: { // Key down
let key = dv.getUint8(1);
let velocity = dv.getUint8(2) / 255;
let id = dv.getUint8(3);
piano.keyDown(key, velocity, id);
break;
}
case 1: { // Key up
let key = dv.getUint8(1);
let sustain = dv.getUint8(2);
let id = dv.getUint8(3);
piano.keyUp(key, sustain, id);
break;
}
case 2: { // Sustain release
let id = dv.getUint8(1);
piano.releaseSustain(id);
}
}
}
keyDown(key, velocity) {
let buffer = new ArrayBuffer(3);
let dv = new DataView(buffer);
dv.setUint8(0, 0);
dv.setUint8(1, key);
dv.setUint8(2, Math.floor(velocity * 255));
this.ws.send(buffer);
}
keyUp(key, sustain) {
let buffer = new ArrayBuffer(3);
let dv = new DataView(buffer);
dv.setUint8(0, 1);
dv.setUint8(1, key);
dv.setUint8(2, sustain);
this.ws.send(buffer);
}
releaseSustain() {
let buffer = new ArrayBuffer(1);
let dv = new DataView(buffer);
dv.setUint8(0, 2);
this.ws.send(buffer);
}
chat(message) {
this.ws.send(JSON.stringify({
type: "chat",
message: message
}));
}
setNick(nick) {
this.ws.send(JSON.stringify({
type: "nick",
nick: nick
}));
}
}
class Chat {
constructor(chatElement, messagesElement, inputElement) {
this.chatElement = chatElement;
this.messagesElement = messagesElement;
this.inputElement = inputElement;
this.prevNotif = null;
this.notifTimeout = null;
this.addListeners();
}
addListeners() {
window.addEventListener("keydown", event => {
if (event.code == "Enter") {
if (document.activeElement == this.inputElement) {
let message = this.inputElement.value;
message = message.trim();
if (message) {
this.send(message);
}
this.inputElement.value = "";
this.inputElement.blur();
} else {
this.inputElement.focus();
}
}
});
this.inputElement.addEventListener("focus", () => {
this.chatElement.className = "focus";
});
this.inputElement.addEventListener("blur", () => {
this.chatElement.className = "";
});
}
chatLog(chatLog) {
for (let i=0; i<chatLog.length; i++) {
this.receive(chatLog[i], true);
}
}
notification(nick, message) {
if (focus) return;
if (!("Notification" in window)) return false;
if (Notification.permission == "denied") return false;
if (Notification.permission == "granted") {
if (this.prevNotif) {
console.log("close notif");
this.prevNotif.close();
}
let notif = new Notification(nick, {
body: message
});
this.prevNotif = notif;
notif.addEventListener("click", function() {
window.focus();
});
// Close notification after 2 seconds
clearInterval(this.notifTimeout);
this.notifTimeout = setTimeout(() => {
if (this.prevNotif) {
this.prevNotif.close();
}
}, 2000);
return true;
}
Notification.requestPermission().then(function(permission) {
if (permission === "granted") {
new Notification("Notifications enabled!");
}
});
}
receive(message, chatLog) {
let element = document.createElement("li");
switch(message.type) {
case "message":
element.className = "message";
let user = message.user;
let nickSpan = document.createElement("span");
nickSpan.className = "nick";
nickSpan.style.color = `rgb(${user.color[0]}, ${user.color[1]}, ${user.color[2]})`;
nickSpan.textContent = user.nick;
element.appendChild(nickSpan);
let text = document.createElement("span");
text.className = "content";
text.textContent = message.content;
element.appendChild(text);
if (!chatLog) {
this.notification(user.nick, message.content);
}
break;
case "join":
element.className = "join";
element.textContent = message.nick + " joined";
break;
case "leave":
element.className = "leave";
element.textContent = message.nick + " left";
break;
case "nick":
element.className = "nick";
element.textContent = message.id + " changed nick to " + message.nick;
break;
case "disconnected":
element.className = "disconnected";
element.textContent = "You were disconnected from the server!";
break;
}
let shouldScroll = false;
if (this.messagesElement.scrollTop >= this.messagesElement.scrollHeight - this.messagesElement.clientHeight) {
shouldScroll = true;
}
this.messagesElement.appendChild(element);
if (shouldScroll) {
this.messagesElement.scrollTop = this.messagesElement.scrollHeight - this.messagesElement.clientHeight;
}
}
send(message) {
net.chat(message);
}
}
window.addEventListener("load", function() {
piano = new Piano(document.getElementById("piano"));
audioEngine = new AudioEngine();
//loadMppBank("https://ledlamp.github.io/piano-sounds/Emotional/", "mp3");
loadMppBank("https://ledlamp.github.io/piano-sounds/Untitled/", "mp3");
window.addEventListener("resize", function() {
piano.resize();
});
function activate() {
audioEngine.active = true;
document.getElementById("autoplay-notice").classList.add("hidden");
window.removeEventListener("click", activate);
}
window.addEventListener("click", activate);
chat = new Chat(
document.getElementById("chat"),
document.getElementById("chat-messages"),
document.getElementById("chat-input")
);
// User menu
document.getElementById("user-mute-notes").addEventListener("input", function() {
net.users[userMenuUser].setMuteNotes(this.checked);
net.users[userMenuUser].updateUserMenu();
net.users[userMenuUser].update();
});
// Focus
window.addEventListener("focus", function() {
focus = true;
});
window.addEventListener("blur", function() {
focus = false;
});
// Volume slider
document.getElementById("volume").addEventListener("input", function(event) {
let value = parseFloat(event.target.value);
setVolume(value);
});
document.getElementById("volume").addEventListener("focus", function(event) {
this.blur();
});
// Footer menus
document.getElementById("midi-player-button").addEventListener("click", function(event) {
openModal(document.getElementById("midi-player-menu"), false);
event.stopImmediatePropagation();
});
document.getElementById("settings-button").addEventListener("click", function(event) {
openModal(document.getElementById("settings-menu"), false);
event.stopImmediatePropagation();
});
// Midi player
document.getElementById("midi-upload").addEventListener("change", function() {
let reader = new FileReader();
reader.addEventListener("load", function() {
let midiFile = new MidiFile(reader.result);
midiPlayer.loadMidi(midiFile);
console.log(midiFile);
});
reader.readAsArrayBuffer(this.files[0]);
});
document.getElementById("midi-play").addEventListener("click", function() {
midiPlayer.play();
});
document.getElementById("midi-pause").addEventListener("click", function() {
midiPlayer.pause();
});
// Settings menu
let tabs = ["general", "midi", "input", "audio"];
tabs.forEach(function(tab) {
document.getElementById("tab-" + tab + "-button").addEventListener("click", function() {
for (let i=0; i<tabs.length; i++) {
document.getElementById("tab-" + tabs[i]).classList.remove("active");
document.getElementById("tab-" + tabs[i] + "-button").classList.remove("active");
}
document.getElementById("tab-" + tab).classList.add("active");
document.getElementById("tab-" + tab + "-button").classList.add("active");
});
});
// Scale option
for (let i=1; i<=6; i++) {
let option = document.createElement("option");
option.textContent = i + "x";
option.value = i;
document.getElementById("setting-scale").appendChild(option);
}
document.getElementById("setting-scale").value = piano.scale;
document.getElementById("setting-scale").addEventListener("input", function() {
let scale = parseInt(this.value);
piano.setScale(scale);
});
// Performance mode
document.getElementById("setting-performance").addEventListener("input", function() {
piano.performanceMode = this.checked;
});
// Sounds
let sounds = [
["Emotional", "https://ledlamp.github.io/piano-sounds/Emotional/", "mp3"],
["Emotional_2.0", "https://ledlamp.github.io/piano-sounds/Emotional_2.0/", "mp3"],
["GreatAndSoftPiano", "https://ledlamp.github.io/piano-sounds/GreatAndSoftPiano/", "mp3"],
["HardAndToughPiano", "https://ledlamp.github.io/piano-sounds/HardAndToughPiano/", "mp3"],
["HardPiano", "https://ledlamp.github.io/piano-sounds/HardPiano/", "mp3"],
["Harp", "https://ledlamp.github.io/piano-sounds/Harp/", "mp3"],
["Harpsicord", "https://ledlamp.github.io/piano-sounds/Harpsicord/", "mp3"],
["LoudAndProudPiano", "https://ledlamp.github.io/piano-sounds/LoudAndProudPiano/", "mp3"],
["MLG", "https://ledlamp.github.io/piano-sounds/MLG/", "wav"],
["Music_Box", "https://ledlamp.github.io/piano-sounds/Music_Box/", "mp3"],
["NewPiano", "https://ledlamp.github.io/piano-sounds/NewPiano/", "mp3"],
["Orchestra", "https://ledlamp.github.io/piano-sounds/Orchestra/", "wav"],
["Piano2", "https://ledlamp.github.io/piano-sounds/Piano2/", "mp3"],
["PianoSounds", "https://ledlamp.github.io/piano-sounds/PianoSounds/", "mp3"],
["Rhodes_MK1", "https://ledlamp.github.io/piano-sounds/Rhodes_MK1/", "mp3"],
["SoftPiano", "https://ledlamp.github.io/piano-sounds/SoftPiano/", "mp3"],
["Steinway_Grand", "https://ledlamp.github.io/piano-sounds/Steinway_Grand/", "mp3"],
["Untitled", "https://ledlamp.github.io/piano-sounds/Untitled/", "mp3"],
["Vintage_Upright", "https://ledlamp.github.io/piano-sounds/Vintage_Upright/", "mp3"],
["Vintage_Upright_Soft", "https://ledlamp.github.io/piano-sounds/Vintage_Upright_Soft/", "mp3"]
];
sounds.forEach(function(sound, index) {
function getOption() {
let element = document.createElement("option");
//element.value = index;
element.textContent = sound[0];
return element;
}
document.getElementById("setting-sounds").appendChild(getOption());
});
document.getElementById("setting-sounds").addEventListener("input", function() {
let sound = this.value;
let index = sounds.findIndex(function(s) {
return s[0] == sound;
});
loadMppBank(sounds[index][1], sounds[index][2]);
});
// Mouse Input
let keyDown = null;
piano.canvas.addEventListener("mousedown", (event) => {
if (keyDown) {
piano.localKeyUp(keyDown);
keyDown = null;
}
let key = piano.hit(event.offsetX, event.offsetY);
if (key) {
piano.localKeyDown(key.value, 1);
keyDown = key.value;
}
});
window.addEventListener("mouseup", (event) => {
if (keyDown) {
piano.localKeyUp(keyDown);
keyDown = null;
}
});
// Keyboard Input
window.addEventListener("keydown", (event) => {
if (document.activeElement != document.body) return;
let key = event.code;
if (key == "Backspace") {
piano.sustain = !piano.sustain;
if (!piano.sustain) {
piano.localReleaseSustain();
}
return;
}
if (keysDown.has(key)) return;
keysDown.add(key);
if (!(key in keyMap)) return;
let rawValue = keyMap[key];
if (keybindValues[mod(rawValue, 14)] === -1) return;
let value = Math.floor(rawValue / 14) * 12 + keybindValues[mod(rawValue, 14)] + 60;
piano.localKeyDown(value, 1);
});
window.addEventListener("keyup", (event) => {
if (document.activeElement != document.body) return;
let key = event.code;
keysDown.delete(key);
let rawValue = keyMap[key];
if (!(key in keyMap)) return;
if (keybindValues[mod(rawValue, 14)] === -1) return;
let value = Math.floor(rawValue / 14) * 12 + keybindValues[mod(rawValue, 14)] + 60;
piano.localKeyUp(value);
});
// MIDI Input
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(function(midi) {
console.log(midi);
function messageHandler(event) {
let cmd = event.data[0] >> 4;
let channel = event.data[0] & 0xF;
let note = event.data[1];
let vel = event.data[2];
if (cmd == 8 || (cmd == 9 && vel === 0)) {
// Note off
piano.localKeyUp(note);
} else if (cmd == 9) {
// Note on
piano.localKeyDown(note, vel / 127);
} else if (cmd == 11) {
// Control change
if (note == 64) {
if (vel >= 64) {
// Sustain
piano.sustain = true;
} else {
// Sustain off
piano.sustain = false;
piano.localReleaseSustain();
}
} else if (note == 7) {
// Volume
let volume = vel / 127;
setVolume(volume, true);
}
}
}
function port() {
midi.inputs.forEach(function(input) {
input.addEventListener("midimessage", messageHandler);
});
}
port();
// Get lists of available MIDI controllers
const inputs = midi.inputs.values();
const outputs = midi.outputs.values();
midi.addEventListener("statechange", function(e) {
// Print information about the (dis)connected MIDI controller
console.log(e.port.name, e.port.manufacturer, e.port.state);
});
});
}
// Net
net = new Networker();
window.requestAnimationFrame(render);
});