Last active
December 22, 2020 15:32
-
-
Save iErcann/081dc27258a25cd5d60a671f5afdfba6 to your computer and use it in GitHub Desktop.
mpplus
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(function () { | |
var noteToNum = {}; | |
var numToNote = {}; | |
let transpose = -1; | |
function getNoteKeyCode(note){ | |
for (let keyCode in key_binding) { | |
const keyNote =key_binding[keyCode].note.note+key_binding[keyCode].note.octave; | |
if (keyNote === note) { | |
return keyCode; | |
} | |
//console.log(String.fromCharCode(bind), bind, temp2[bind]) | |
} | |
} | |
function updateKeyCodes(pianoKeys){ | |
Object.keys(pianoKeys).forEach(function(note, num) { | |
var number = num + 21; | |
noteToNum[note] = number; | |
numToNote[number] = note; | |
}); | |
for (let key in pianoKeys) { | |
console.log(getNoteKeyCode(pianoKeys[key].note)) | |
const octave = pianoKeys[key].octave-transpose_octave-1; | |
const note = pianoKeys[key].baseNote+octave; | |
//text += octave; | |
//text = getNoteKeyCode(keyText[k].key.baseNote+octave); | |
const transposedNote = numToNote[noteToNum[note]-transpose]; | |
pianoKeys[key].keyCode = getNoteKeyCode(transposedNote); | |
pianoKeys[key].keyChar = String.fromCharCode( pianoKeys[key].keyCode); | |
} | |
} | |
var test_mode = | |
window.location.hash && | |
window.location.hash.match(/^(?:#.+)*#test(?:#.+)*$/i); | |
var gSeeOwnCursor = | |
window.location.hash && | |
window.location.hash.match(/^(?:#.+)*#seeowncursor(?:#.+)*$/i); | |
var gMidiVolumeTest = | |
window.location.hash && | |
window.location.hash.match(/^(?:#.+)*#midivolumetest(?:#.+)*$/i); | |
var gMidiOutTest; | |
if (!Array.prototype.indexOf) { | |
Array.prototype.indexOf = function (elt /*, from*/) { | |
var len = this.length >>> 0; | |
var from = Number(arguments[1]) || 0; | |
from = from < 0 ? Math.ceil(from) : Math.floor(from); | |
if (from < 0) from += len; | |
for (; from < len; from++) { | |
if (from in this && this[from] === elt) return from; | |
} | |
return -1; | |
}; | |
} | |
window.requestAnimationFrame = | |
window.requestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function (cb) { | |
setTimeout(cb, 1000 / 30); | |
}; | |
var DEFAULT_VELOCITY = 0.5; | |
var TIMING_TARGET = 1000; | |
// Utility | |
//////////////////////////////////////////////////////////////// | |
var Rect = function (x, y, w, h) { | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
this.x2 = x + w; | |
this.y2 = y + h; | |
}; | |
Rect.prototype.contains = function (x, y) { | |
return x >= this.x && x <= this.x2 && y >= this.y && y <= this.y2; | |
}; | |
// performing translation | |
//////////////////////////////////////////////////////////////// | |
var Translation = (function () { | |
var strings = { | |
"people are playing": { | |
pt: "pessoas estão jogando", | |
es: "personas están jugando", | |
ru: "человек играет", | |
fr: "personnes jouent", | |
ja: "人が遊んでいる", | |
de: "Leute spielen", | |
zh: "人在玩", | |
nl: "mensen spelen", | |
pl: "osób grają", | |
hu: "ember játszik", | |
}, | |
"New Room...": { | |
pt: "Nova Sala ...", | |
es: "Nueva sala de...", | |
ru: "Новый номер...", | |
ja: "新しい部屋", | |
zh: "新房间", | |
nl: "nieuwe Kamer", | |
hu: "új szoba", | |
}, | |
"room name": { | |
pt: "nome da sala", | |
es: "sala de nombre", | |
ru: "название комнаты", | |
fr: "nom de la chambre", | |
ja: "ルーム名", | |
de: "Raumnamen", | |
zh: "房间名称", | |
nl: "kamernaam", | |
pl: "nazwa pokój", | |
hu: "szoba neve", | |
}, | |
"Visible (open to everyone)": { | |
pt: "Visível (aberto a todos)", | |
es: "Visible (abierto a todo el mundo)", | |
ru: "Visible (открытый для всех)", | |
fr: "Visible (ouvert à tous)", | |
ja: "目に見える(誰にでも開いている)", | |
de: "Sichtbar (offen für alle)", | |
zh: "可见(向所有人开放)", | |
nl: "Zichtbaar (open voor iedereen)", | |
pl: "Widoczne (otwarte dla wszystkich)", | |
hu: "Látható (nyitott mindenki számára)", | |
}, | |
"Enable Chat": { | |
pt: "Ativar bate-papo", | |
es: "Habilitar chat", | |
ru: "Включить чат", | |
fr: "Activer discuter", | |
ja: "チャットを有効にする", | |
de: "aktivieren Sie chatten", | |
zh: "启用聊天", | |
nl: "Chat inschakelen", | |
pl: "Włącz czat", | |
hu: "a csevegést", | |
}, | |
"Play Alone": { | |
pt: "Jogar Sozinho", | |
es: "Jugar Solo", | |
ru: "Играть в одиночку", | |
fr: "Jouez Seul", | |
ja: "一人でプレイ", | |
de: "Alleine Spielen", | |
zh: "独自玩耍", | |
nl: "Speel Alleen", | |
pl: "Zagraj sam", | |
hu: "Játssz egyedül", | |
}, | |
// todo: it, tr, th, sv, ar, fi, nb, da, sv, he, cs, ko, ro, vi, id, nb, el, sk, bg, lt, sl, hr | |
// todo: Connecting, Offline mode, input placeholder, Notifications | |
}; | |
var setLanguage = function (lang) { | |
language = lang; | |
}; | |
var getLanguage = function () { | |
if ( | |
window.navigator && | |
navigator.language && | |
navigator.language.length >= 2 | |
) { | |
return navigator.language.substr(0, 2).toLowerCase(); | |
} else { | |
return "en"; | |
} | |
}; | |
var get = function (text, lang) { | |
if (typeof lang === "undefined") lang = language; | |
var row = strings[text]; | |
if (row == undefined) return text; | |
var string = row[lang]; | |
if (string == undefined) return text; | |
return string; | |
}; | |
var perform = function (lang) { | |
if (typeof lang === "undefined") lang = language; | |
$(".translate").each(function (i, ele) { | |
var th = $(this); | |
if (ele.tagName && ele.tagName.toLowerCase() == "input") { | |
if (typeof ele.placeholder != "undefined") { | |
th.attr("placeholder", get(th.attr("placeholder"), lang)); | |
} | |
} else { | |
th.text(get(th.text(), lang)); | |
} | |
}); | |
}; | |
var language = getLanguage(); | |
return { | |
setLanguage: setLanguage, | |
getLanguage: getLanguage, | |
get: get, | |
perform: perform, | |
}; | |
})(); | |
Translation.perform(); | |
// AudioEngine classes | |
//////////////////////////////////////////////////////////////// | |
var AudioEngine = function () {}; | |
AudioEngine.prototype.init = function (cb) { | |
this.volume = 0.6; | |
this.sounds = {}; | |
this.paused = true; | |
return this; | |
}; | |
AudioEngine.prototype.load = function (id, url, cb) {}; | |
AudioEngine.prototype.play = function () {}; | |
AudioEngine.prototype.stop = function () {}; | |
AudioEngine.prototype.setVolume = function (vol) { | |
this.volume = vol; | |
}; | |
AudioEngine.prototype.resume = function () { | |
this.paused = false; | |
}; | |
AudioEngineWeb = function () { | |
this.threshold = 1000; | |
this.worker = new Worker("/workerTimer.js"); | |
var self = this; | |
this.worker.onmessage = function (event) { | |
if (event.data.args) | |
if (event.data.args.action == 0) { | |
self.actualPlay( | |
event.data.args.id, | |
event.data.args.vol, | |
event.data.args.time, | |
event.data.args.part_id | |
); | |
} else { | |
self.actualStop( | |
event.data.args.id, | |
event.data.args.time, | |
event.data.args.part_id | |
); | |
} | |
}; | |
}; | |
AudioEngineWeb.prototype = new AudioEngine(); | |
AudioEngineWeb.prototype.init = function (cb) { | |
AudioEngine.prototype.init.call(this); | |
this.context = new AudioContext({ latencyHint: "interactive" }); | |
this.masterGain = this.context.createGain(); | |
this.masterGain.connect(this.context.destination); | |
this.masterGain.gain.value = this.volume; | |
this.limiterNode = this.context.createDynamicsCompressor(); | |
this.limiterNode.threshold.value = -10; | |
this.limiterNode.knee.value = 0; | |
this.limiterNode.ratio.value = 20; | |
this.limiterNode.attack.value = 0; | |
this.limiterNode.release.value = 0.1; | |
this.limiterNode.connect(this.masterGain); | |
// for synth mix | |
this.pianoGain = this.context.createGain(); | |
this.pianoGain.gain.value = 0.5; | |
this.pianoGain.connect(this.limiterNode); | |
this.synthGain = this.context.createGain(); | |
this.synthGain.gain.value = 0.5; | |
this.synthGain.connect(this.limiterNode); | |
this.playings = {}; | |
if (cb) setTimeout(cb, 0); | |
return this; | |
}; | |
AudioEngineWeb.prototype.load = function (id, url, cb) { | |
var audio = this; | |
var req = new XMLHttpRequest(); | |
req.open("GET", url); | |
req.responseType = "arraybuffer"; | |
req.addEventListener("readystatechange", function (evt) { | |
if (req.readyState !== 4) return; | |
try { | |
audio.context.decodeAudioData(req.response, function (buffer) { | |
audio.sounds[id] = buffer; | |
if (cb) cb(); | |
}); | |
} catch (e) { | |
/*throw new Error(e.message | |
+ " / id: " + id | |
+ " / url: " + url | |
+ " / status: " + req.status | |
+ " / ArrayBuffer: " + (req.response instanceof ArrayBuffer) | |
+ " / byteLength: " + (req.response && req.response.byteLength ? req.response.byteLength : "undefined"));*/ | |
new Notification({ | |
id: "audio-download-error", | |
title: "Problem", | |
text: | |
"For some reason, an audio download failed with a status of " + | |
req.status + | |
". ", | |
target: "#piano", | |
duration: 10000, | |
}); | |
} | |
}); | |
req.send(); | |
}; | |
AudioEngineWeb.prototype.actualPlay = function (id, vol, time, part_id) { | |
//the old play(), but with time insted of delay_ms. | |
if (this.paused) return; | |
if (!this.sounds.hasOwnProperty(id)) return; | |
var source = this.context.createBufferSource(); | |
source.buffer = this.sounds[id]; | |
var gain = this.context.createGain(); | |
gain.gain.value = vol; | |
source.connect(gain); | |
gain.connect(this.pianoGain); | |
source.start(time); | |
// Patch from ste-art remedies stuttering under heavy load | |
if (this.playings[id]) { | |
var playing = this.playings[id]; | |
playing.gain.gain.setValueAtTime(playing.gain.gain.value, time); | |
playing.gain.gain.linearRampToValueAtTime(0.0, time + 0.2); | |
playing.source.stop(time + 0.21); | |
if (enableSynth && playing.voice) { | |
playing.voice.stop(time); | |
} | |
} | |
this.playings[id] = { source: source, gain: gain, part_id: part_id }; | |
if (enableSynth) { | |
this.playings[id].voice = new synthVoice(id, time); | |
} | |
}; | |
AudioEngineWeb.prototype.play = function (id, vol, delay_ms, part_id) { | |
if (!this.sounds.hasOwnProperty(id)) return; | |
var time = this.context.currentTime + delay_ms / 1000; //calculate time on note receive. | |
var delay = delay_ms - this.threshold; | |
if (delay <= 0) this.actualPlay(id, vol, time, part_id); | |
else { | |
this.worker.postMessage({ | |
delay: delay, | |
args: { | |
action: 0 /*play*/, | |
id: id, | |
vol: vol, | |
time: time, | |
part_id: part_id, | |
}, | |
}); // but start scheduling right before play. | |
} | |
}; | |
AudioEngineWeb.prototype.actualStop = function (id, time, part_id) { | |
if ( | |
this.playings.hasOwnProperty(id) && | |
this.playings[id] && | |
this.playings[id].part_id === part_id | |
) { | |
var gain = this.playings[id].gain.gain; | |
gain.setValueAtTime(gain.value, time); | |
gain.linearRampToValueAtTime(gain.value * 0.1, time + 0.16); | |
gain.linearRampToValueAtTime(0.0, time + 0.4); | |
this.playings[id].source.stop(time + 0.41); | |
if (this.playings[id].voice) { | |
this.playings[id].voice.stop(time); | |
} | |
this.playings[id] = null; | |
} | |
}; | |
AudioEngineWeb.prototype.stop = function (id, delay_ms, part_id) { | |
var time = this.context.currentTime + delay_ms / 1000; | |
var delay = delay_ms - this.threshold; | |
if (delay <= 0) this.actualStop(id, time, part_id); | |
else { | |
this.worker.postMessage({ | |
delay: delay, | |
args: { action: 1 /*stop*/, id: id, time: time, part_id: part_id }, | |
}); | |
} | |
}; | |
AudioEngineWeb.prototype.setVolume = function (vol) { | |
AudioEngine.prototype.setVolume.call(this, vol); | |
this.masterGain.gain.value = this.volume; | |
}; | |
AudioEngineWeb.prototype.resume = function () { | |
this.paused = false; | |
this.context.resume(); | |
}; | |
// Renderer classes | |
//////////////////////////////////////////////////////////////// | |
var Renderer = function () {}; | |
Renderer.prototype.init = function (piano) { | |
this.piano = piano; | |
this.resize(); | |
return this; | |
}; | |
Renderer.prototype.resize = function (width, height) { | |
if (typeof width == "undefined") width = $(this.piano.rootElement).width(); | |
if (typeof height == "undefined") height = Math.floor(width * 0.2); | |
$(this.piano.rootElement).css({ | |
height: height + "px", | |
marginTop: Math.floor($(window).height() / 2 - height / 2) + "px", | |
}); | |
this.width = width * window.devicePixelRatio; | |
this.height = height * window.devicePixelRatio; | |
}; | |
Renderer.prototype.visualize = function (key, color) {}; | |
var CanvasRenderer = function () { | |
Renderer.call(this); | |
}; | |
CanvasRenderer.prototype = new Renderer(); | |
CanvasRenderer.prototype.init = function (piano) { | |
this.canvas = document.createElement("canvas"); | |
this.ctx = this.canvas.getContext("2d"); | |
piano.rootElement.appendChild(this.canvas); | |
Renderer.prototype.init.call(this, piano); // calls resize() | |
// create render loop | |
var self = this; | |
var render = function () { | |
self.redraw(); | |
requestAnimationFrame(render); | |
}; | |
requestAnimationFrame(render); | |
// add event listeners | |
var mouse_down = false; | |
var last_key = null; | |
$(piano.rootElement).mousedown(function (event) { | |
mouse_down = true; | |
//event.stopPropagation(); | |
event.preventDefault(); | |
var pos = CanvasRenderer.translateMouseEvent(event); | |
var hit = self.getHit(pos.x, pos.y); | |
if (hit) { | |
press(hit.key.note, hit.v); | |
last_key = hit.key; | |
} | |
}); | |
piano.rootElement.addEventListener( | |
"touchstart", | |
function (event) { | |
mouse_down = true; | |
//event.stopPropagation(); | |
event.preventDefault(); | |
for (var i in event.changedTouches) { | |
var pos = CanvasRenderer.translateMouseEvent(event.changedTouches[i]); | |
var hit = self.getHit(pos.x, pos.y); | |
if (hit) { | |
press(hit.key.note, hit.v); | |
last_key = hit.key; | |
} | |
} | |
}, | |
false | |
); | |
$(window).mouseup(function (event) { | |
if (last_key) { | |
release(last_key.note); | |
} | |
mouse_down = false; | |
last_key = null; | |
}); | |
/*$(piano.rootElement).mousemove(function(event) { | |
if(!mouse_down) return; | |
var pos = CanvasRenderer.translateMouseEvent(event); | |
var hit = self.getHit(pos.x, pos.y); | |
if(hit && hit.key != last_key) { | |
press(hit.key.note, hit.v); | |
last_key = hit.key; | |
} | |
});*/ | |
return this; | |
}; | |
CanvasRenderer.prototype.resize = function (width, height) { | |
updateKeyCodes(this.piano.keys); | |
Renderer.prototype.resize.call(this, width, height); | |
if (this.width < 52 * 2) this.width = 52 * 2; | |
if (this.height < this.width * 0.2) | |
this.height = Math.floor(this.width * 0.2); | |
this.canvas.width = this.width; | |
this.canvas.height = this.height; | |
this.canvas.style.width = this.width / window.devicePixelRatio + "px"; | |
this.canvas.style.height = this.height / window.devicePixelRatio + "px"; | |
// calculate key sizes | |
this.whiteKeyWidth = Math.floor(this.width / 52); | |
this.whiteKeyHeight = Math.floor(this.height * 0.9); | |
this.blackKeyWidth = Math.floor(this.whiteKeyWidth * 0.75); | |
this.blackKeyHeight = Math.floor(this.height * 0.5); | |
this.blackKeyOffset = Math.floor( | |
this.whiteKeyWidth - this.blackKeyWidth / 2 | |
); | |
this.keyMovement = Math.floor(this.whiteKeyHeight * 0.015); | |
this.whiteBlipWidth = Math.floor(this.whiteKeyWidth * 0.7); | |
this.whiteBlipHeight = Math.floor(this.whiteBlipWidth * 0.8); | |
this.whiteBlipX = Math.floor( | |
(this.whiteKeyWidth - this.whiteBlipWidth) / 2 | |
); | |
this.whiteBlipY = Math.floor( | |
this.whiteKeyHeight - this.whiteBlipHeight * 1.2 | |
); | |
this.blackBlipWidth = Math.floor(this.blackKeyWidth * 0.7); | |
this.blackBlipHeight = Math.floor(this.blackBlipWidth * 0.8); | |
this.blackBlipY = Math.floor( | |
this.blackKeyHeight - this.blackBlipHeight * 1.2 | |
); | |
this.blackBlipX = Math.floor( | |
(this.blackKeyWidth - this.blackBlipWidth) / 2 | |
); | |
// prerender white key | |
this.whiteKeyRender = document.createElement("canvas"); | |
this.whiteKeyRender.width = this.whiteKeyWidth; | |
this.whiteKeyRender.height = this.height + 10; | |
var ctx = this.whiteKeyRender.getContext("2d"); | |
if (ctx.createLinearGradient) { | |
var gradient = ctx.createLinearGradient(0, 0, 0, this.whiteKeyHeight); | |
gradient.addColorStop(0, "#eee"); | |
gradient.addColorStop(0.75, "#fff"); | |
gradient.addColorStop(1, "#dad4d4"); | |
ctx.fillStyle = gradient; | |
} else { | |
ctx.fillStyle = "#fff"; | |
} | |
ctx.strokeStyle = "#000"; | |
ctx.lineJoin = "round"; | |
ctx.lineCap = "round"; | |
ctx.lineWidth = 10; | |
ctx.strokeRect( | |
ctx.lineWidth / 2, | |
ctx.lineWidth / 2, | |
this.whiteKeyWidth - ctx.lineWidth, | |
this.whiteKeyHeight - ctx.lineWidth | |
); | |
ctx.lineWidth = 4; | |
ctx.fillRect( | |
ctx.lineWidth / 2, | |
ctx.lineWidth / 2, | |
this.whiteKeyWidth - ctx.lineWidth, | |
this.whiteKeyHeight - ctx.lineWidth | |
); | |
// prerender black key | |
this.blackKeyRender = document.createElement("canvas"); | |
this.blackKeyRender.width = this.blackKeyWidth + 10; | |
this.blackKeyRender.height = this.blackKeyHeight + 10; | |
var ctx = this.blackKeyRender.getContext("2d"); | |
if (ctx.createLinearGradient) { | |
var gradient = ctx.createLinearGradient(0, 0, 0, this.blackKeyHeight); | |
gradient.addColorStop(0, "#000"); | |
gradient.addColorStop(1, "#444"); | |
ctx.fillStyle = gradient; | |
} else { | |
ctx.fillStyle = "#000"; | |
} | |
ctx.strokeStyle = "#222"; | |
ctx.lineJoin = "round"; | |
ctx.lineCap = "round"; | |
ctx.lineWidth = 8; | |
ctx.strokeRect( | |
ctx.lineWidth / 2, | |
ctx.lineWidth / 2, | |
this.blackKeyWidth - ctx.lineWidth, | |
this.blackKeyHeight - ctx.lineWidth | |
); | |
ctx.lineWidth = 4; | |
ctx.fillRect( | |
ctx.lineWidth / 2, | |
ctx.lineWidth / 2, | |
this.blackKeyWidth - ctx.lineWidth, | |
this.blackKeyHeight - ctx.lineWidth | |
); | |
// prerender shadows | |
this.shadowRender = []; | |
var y = -this.canvas.height * 2; | |
for (var j = 0; j < 2; j++) { | |
var canvas = document.createElement("canvas"); | |
this.shadowRender[j] = canvas; | |
canvas.width = this.canvas.width; | |
canvas.height = this.canvas.height; | |
var ctx = canvas.getContext("2d"); | |
var sharp = j ? true : false; | |
ctx.lineJoin = "round"; | |
ctx.lineCap = "round"; | |
ctx.lineWidth = 1; | |
ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; | |
ctx.shadowBlur = this.keyMovement * 3; | |
ctx.shadowOffsetY = -y + this.keyMovement; | |
if (sharp) { | |
ctx.shadowOffsetX = this.keyMovement; | |
} else { | |
ctx.shadowOffsetX = 0; | |
ctx.shadowOffsetY = -y + this.keyMovement; | |
} | |
for (var i in this.piano.keys) { | |
if (!this.piano.keys.hasOwnProperty(i)) continue; | |
var key = this.piano.keys[i]; | |
if (key.sharp != sharp) continue; | |
if (key.sharp) { | |
ctx.fillRect( | |
this.blackKeyOffset + | |
this.whiteKeyWidth * key.spatial + | |
ctx.lineWidth / 2, | |
y + ctx.lineWidth / 2, | |
this.blackKeyWidth - ctx.lineWidth, | |
this.blackKeyHeight - ctx.lineWidth | |
); | |
} else { | |
ctx.fillRect( | |
this.whiteKeyWidth * key.spatial + ctx.lineWidth / 2, | |
y + ctx.lineWidth / 2, | |
this.whiteKeyWidth - ctx.lineWidth, | |
this.whiteKeyHeight - ctx.lineWidth | |
); | |
} | |
} | |
} | |
// update key rects | |
for (var i in this.piano.keys) { | |
if (!this.piano.keys.hasOwnProperty(i)) continue; | |
var key = this.piano.keys[i]; | |
if (key.sharp) { | |
key.rect = new Rect( | |
this.blackKeyOffset + this.whiteKeyWidth * key.spatial, | |
0, | |
this.blackKeyWidth, | |
this.blackKeyHeight | |
); | |
} else { | |
key.rect = new Rect( | |
this.whiteKeyWidth * key.spatial, | |
0, | |
this.whiteKeyWidth, | |
this.whiteKeyHeight | |
); | |
} | |
} | |
}; | |
CanvasRenderer.prototype.visualize = function (key, color) { | |
key.timePlayed = Date.now(); | |
key.blips.push({ time: key.timePlayed, color: color }); | |
}; | |
CanvasRenderer.prototype.redraw = function () { | |
var now = Date.now(); | |
var timeLoadedEnd = now - 1000; | |
var timePlayedEnd = now - 100; | |
var timeBlipEnd = now - 1000; | |
this.ctx.save(); | |
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
let keyText = []; | |
// draw all keys | |
let keyTextNumber = 0; | |
for (var j = 0; j < 2; j++) { | |
this.ctx.globalAlpha = 1.0; | |
this.ctx.drawImage(this.shadowRender[j], 0, 0); | |
var sharp = j ? true : false; | |
for (var i in this.piano.keys) { | |
if (!this.piano.keys.hasOwnProperty(i)) continue; | |
var key = this.piano.keys[i]; | |
if (key.sharp != sharp) continue; | |
if (!key.loaded) { | |
this.ctx.globalAlpha = 0.2; | |
} else if (key.timeLoaded > timeLoadedEnd) { | |
this.ctx.globalAlpha = ((now - key.timeLoaded) / 1000) * 0.8 + 0.2; | |
} else { | |
this.ctx.globalAlpha = 1.0; | |
} | |
var y = 0; | |
if (key.timePlayed > timePlayedEnd) { | |
y = Math.floor( | |
this.keyMovement - ((now - key.timePlayed) / 100) * this.keyMovement | |
); | |
} | |
var x = Math.floor( | |
key.sharp | |
? this.blackKeyOffset + this.whiteKeyWidth * key.spatial | |
: this.whiteKeyWidth * key.spatial | |
); | |
var image = key.sharp ? this.blackKeyRender : this.whiteKeyRender; | |
this.ctx.drawImage(image, x, y); | |
let keyTextY = key.sharp ? this.blackKeyOffset + this.whiteKeyWidth: this.whiteKeyWidth*8; | |
keyTextY+= ((keyTextNumber++)%2)==0?20:0; | |
keyText.push({ | |
x: !key.sharp?x+this.whiteKeyWidth/7:x, | |
y: keyTextY , | |
key: key, | |
color: "cyan", | |
}) | |
// render blips | |
if (key.blips.length) { | |
var alpha = this.ctx.globalAlpha; | |
var w, h; | |
if (key.sharp) { | |
x += this.blackBlipX; | |
y = this.blackBlipY; | |
w = this.blackBlipWidth; | |
h = this.blackBlipHeight; | |
} else { | |
x += this.whiteBlipX; | |
y = this.whiteBlipY; | |
w = this.whiteBlipWidth; | |
h = this.whiteBlipHeight; | |
} | |
for (var b = 0; b < key.blips.length; b++) { | |
var blip = key.blips[b]; | |
if (blip.time > timeBlipEnd) { | |
this.ctx.fillStyle = blip.color; | |
this.ctx.globalAlpha = alpha - (now - blip.time) / 1000; | |
this.ctx.fillRect(x, y, w, h); | |
keyText.push({ | |
x: x, | |
y: y, | |
key: key, | |
color: "yellow" | |
}) | |
} else { | |
key.blips.splice(b, 1); | |
--b; | |
} | |
y -= Math.floor(h * 1.1); | |
} | |
} | |
} | |
} | |
this.ctx.fillStyle = "yellow" | |
this.ctx.font = "20px Arial"; | |
// console.log(note.octave, octave-transpose_octave-1) | |
for (let k = 0; k < keyText.length; k++) { | |
const octave = keyText[k].key.octave-transpose_octave-1; | |
if (keyText[k].key.keyCode != undefined) { | |
const x = keyText[k].x; | |
const y = keyText[k].y; | |
let text = keyText[k].key.keyChar; | |
const octave = keyText[k].key.octave-transpose_octave-1; | |
this.ctx.fillStyle = keyText[k].color; | |
this.ctx.font = '22px Sans-serif'; | |
this.ctx.strokeStyle = 'black'; | |
this.ctx.lineWidth = 4; | |
this.ctx.strokeText(text, x, y); | |
this.ctx.fillText(text, x, y); | |
} | |
//this.ctx.fillText(keyText[k].key.note, keyText[k].x, keyText[k].y+20); | |
} | |
this.ctx.restore(); | |
}; | |
CanvasRenderer.prototype.renderNoteLyrics = function () { | |
// render lyric | |
for (var part_id in this.noteLyrics) { | |
if (!this.noteLyrics.hasOwnProperty(i)) continue; | |
var lyric = this.noteLyrics[part_id]; | |
var lyric_x = x; | |
var lyric_y = this.whiteKeyHeight + 1; | |
this.ctx.fillStyle = key.lyric.color; | |
var alpha = this.ctx.globalAlpha; | |
this.ctx.globalAlpha = alpha - (now - key.lyric.time) / 1000; | |
this.ctx.fillRect(x, y, 10, 10); | |
} | |
}; | |
CanvasRenderer.prototype.getHit = function (x, y) { | |
for (var j = 0; j < 2; j++) { | |
var sharp = j ? false : true; // black keys first | |
for (var i in this.piano.keys) { | |
if (!this.piano.keys.hasOwnProperty(i)) continue; | |
var key = this.piano.keys[i]; | |
if (key.sharp != sharp) continue; | |
if (key.rect.contains(x, y)) { | |
var v = y / (key.sharp ? this.blackKeyHeight : this.whiteKeyHeight); | |
v += 0.25; | |
v *= DEFAULT_VELOCITY; | |
if (v > 1.0) v = 1.0; | |
return { key: key, v: v }; | |
} | |
} | |
} | |
return null; | |
}; | |
CanvasRenderer.isSupported = function () { | |
var canvas = document.createElement("canvas"); | |
return !!(canvas.getContext && canvas.getContext("2d")); | |
}; | |
CanvasRenderer.translateMouseEvent = function (evt) { | |
var element = evt.target; | |
var offx = 0; | |
var offy = 0; | |
do { | |
if (!element) break; // wtf, wtf? | |
offx += element.offsetLeft; | |
offy += element.offsetTop; | |
} while ((element = element.offsetParent)); | |
return { | |
x: (evt.pageX - offx) * window.devicePixelRatio, | |
y: (evt.pageY - offy) * window.devicePixelRatio, | |
}; | |
}; | |
// Soundpack Stuff by electrashave ♥ | |
//////////////////////////////////////////////////////////////// | |
function SoundSelector(piano) { | |
this.initialized = false; | |
this.keys = piano.keys; | |
this.loading = {}; | |
this.notification; | |
this.packs = []; | |
this.piano = piano; | |
this.soundSelection = localStorage.soundSelection | |
? localStorage.soundSelection | |
: "MPP Classic"; | |
this.addPack({ | |
name: "MPP Classic", | |
keys: Object.keys(this.piano.keys), | |
ext: ".mp3", | |
url: "/sounds/mppclassic/", | |
}); | |
} | |
SoundSelector.prototype.addPack = function (pack, load) { | |
var self = this; | |
self.loading[pack.url || pack] = true; | |
function add(obj) { | |
var added = false; | |
for (var i = 0; self.packs.length > i; i++) { | |
if (obj.name == self.packs[i].name) { | |
added = true; | |
break; | |
} | |
} | |
if (added) return console.warn("Sounds already added!!"); //no adding soundpacks twice D:< | |
if (obj.url.substr(obj.url.length - 1) != "/") obj.url = obj.url + "/"; | |
var html = document.createElement("li"); | |
html.classList = "pack"; | |
html.innerText = obj.name + " (" + obj.keys.length + " keys)"; | |
html.onclick = function () { | |
self.loadPack(obj.name); | |
self.notification.close(); | |
}; | |
obj.html = html; | |
self.packs.push(obj); | |
self.packs.sort(function (a, b) { | |
if (a.name < b.name) return -1; | |
if (a.name > b.name) return 1; | |
return 0; | |
}); | |
if (load) self.loadPack(obj.name); | |
delete self.loading[obj.url]; | |
} | |
if (typeof pack == "string") { | |
$.getJSON(pack + "/info.json").done(function (json) { | |
json.url = pack; | |
add(json); | |
}); | |
} else add(pack); //validate packs?? | |
}; | |
SoundSelector.prototype.addPacks = function (packs) { | |
for (var i = 0; packs.length > i; i++) this.addPack(packs[i]); | |
}; | |
SoundSelector.prototype.init = function () { | |
var self = this; | |
if (self.initialized) | |
return console.warn("Sound selector already initialized!"); | |
if (!!Object.keys(self.loading).length) | |
return setTimeout(function () { | |
self.init(); | |
}, 250); | |
$("#sound-btn").on("click", function () { | |
if (document.getElementById("Notification-Sound-Selector") != null) | |
return self.notification.close(); | |
var html = document.createElement("ul"); | |
//$(html).append("<p>Current Sound: " + self.soundSelection + "</p>"); | |
for (var i = 0; self.packs.length > i; i++) { | |
var pack = self.packs[i]; | |
if (pack.name == self.soundSelection) | |
pack.html.classList = "pack enabled"; | |
else pack.html.classList = "pack"; | |
html.appendChild(pack.html); | |
} | |
self.notification = new Notification({ | |
title: "Sound Selector", | |
html: html, | |
id: "Sound-Selector", | |
duration: -1, | |
target: "#sound-btn", | |
}); | |
}); | |
self.initialized = true; | |
self.loadPack(self.soundSelection, true); | |
}; | |
SoundSelector.prototype.loadPack = function (pack, f) { | |
for (var i = 0; this.packs.length > i; i++) { | |
var p = this.packs[i]; | |
if (p.name == pack) { | |
pack = p; | |
break; | |
} | |
} | |
if (typeof pack == "string") { | |
console.warn("Sound pack does not exist! Loading default pack..."); | |
return this.loadPack("MPP Classic"); | |
} | |
if (pack.name == this.soundSelection && !f) return; | |
if (pack.keys.length != Object.keys(this.piano.keys).length) { | |
this.piano.keys = {}; | |
for (var i = 0; pack.keys.length > i; i++) | |
this.piano.keys[pack.keys[i]] = this.keys[pack.keys[i]]; | |
this.piano.renderer.resize(); | |
} | |
var self = this; | |
for (var i in this.piano.keys) { | |
if (!this.piano.keys.hasOwnProperty(i)) continue; | |
(function () { | |
var key = self.piano.keys[i]; | |
key.loaded = false; | |
self.piano.audio.load( | |
key.note, | |
pack.url + key.note + pack.ext, | |
function () { | |
key.loaded = true; | |
key.timeLoaded = Date.now(); | |
} | |
); | |
})(); | |
} | |
if (localStorage) localStorage.soundSelection = pack.name; | |
this.soundSelection = pack.name; | |
}; | |
SoundSelector.prototype.removePack = function (name) { | |
var found = false; | |
for (var i = 0; this.packs.length > i; i++) { | |
var pack = this.packs[i]; | |
if (pack.name == name) { | |
this.packs.splice(i, 1); | |
if (pack.name == this.soundSelection) this.loadPack(this.packs[0].name); //add mpp default if none? | |
break; | |
} | |
} | |
if (!found) console.warn("Sound pack not found!"); | |
}; | |
// Pianoctor | |
//////////////////////////////////////////////////////////////// | |
var PianoKey = function (note, octave) { | |
this.note = note + octave; | |
this.baseNote = note; | |
this.octave = octave; | |
this.sharp = note.indexOf("s") != -1; | |
this.loaded = false; | |
this.timeLoaded = 0; | |
this.domElement = null; | |
this.timePlayed = 0; | |
this.blips = []; | |
}; | |
var Piano = function (rootElement) { | |
var piano = this; | |
piano.rootElement = rootElement; | |
piano.keys = {}; | |
var white_spatial = 0; | |
var black_spatial = 0; | |
var black_it = 0; | |
var black_lut = [2, 1, 2, 1, 1]; | |
var addKey = function (note, octave) { | |
var key = new PianoKey(note, octave); | |
piano.keys[key.note] = key; | |
if (key.sharp) { | |
key.spatial = black_spatial; | |
black_spatial += black_lut[black_it % 5]; | |
++black_it; | |
} else { | |
key.spatial = white_spatial; | |
++white_spatial; | |
} | |
}; | |
if (test_mode) { | |
addKey("c", 2); | |
} else { | |
addKey("a", -1); | |
addKey("as", -1); | |
addKey("b", -1); | |
var notes = "c cs d ds e f fs g gs a as b".split(" "); | |
for (var oct = 0; oct < 7; oct++) { | |
for (var i in notes) { | |
addKey(notes[i], oct); | |
} | |
} | |
addKey("c", 7); | |
} | |
this.renderer = new CanvasRenderer().init(this); | |
window.addEventListener("resize", function () { | |
piano.renderer.resize(); | |
}); | |
window.AudioContext = | |
window.AudioContext || window.webkitAudioContext || undefined; | |
var audio_engine = AudioEngineWeb; | |
this.audio = new audio_engine().init(); | |
}; | |
Piano.prototype.play = function (note, vol, participant, delay_ms, lyric) { | |
if (!this.keys.hasOwnProperty(note) || !participant) return; | |
var key = this.keys[note]; | |
if (key.loaded) this.audio.play(key.note, vol, delay_ms, participant.id); | |
if (gMidiOutTest) gMidiOutTest(key.note, vol * 100, delay_ms); | |
var self = this; | |
setTimeout(function () { | |
self.renderer.visualize(key, participant.color); | |
if (lyric) { | |
} | |
var jq_namediv = $(participant.nameDiv); | |
jq_namediv.addClass("play"); | |
setTimeout(function () { | |
jq_namediv.removeClass("play"); | |
}, 30); | |
}, delay_ms || 0); | |
}; | |
Piano.prototype.stop = function (note, participant, delay_ms) { | |
if (!this.keys.hasOwnProperty(note)) return; | |
var key = this.keys[note]; | |
if (key.loaded) this.audio.stop(key.note, delay_ms, participant.id); | |
if (gMidiOutTest) gMidiOutTest(key.note, 0, delay_ms); | |
}; | |
var gPiano = new Piano(document.getElementById("piano")); | |
var gSoundSelector = new SoundSelector(gPiano); | |
gSoundSelector.addPacks([ | |
"/sounds/Emotional_2.0/", | |
"/sounds/Harp/", | |
"/sounds/Music_Box/", | |
"/sounds/Vintage_Upright/", | |
"/sounds/Steinway_Grand/", | |
"/sounds/Emotional/", | |
"/sounds/Untitled/", | |
]); | |
gSoundSelector.init(); | |
var gAutoSustain = false; | |
var gSustain = false; | |
var gHeldNotes = {}; | |
var gSustainedNotes = {}; | |
function press(id, vol) { | |
if (!gClient.preventsPlaying() && gNoteQuota.spend(1)) { | |
console.log(numToNote); | |
console.log(noteToNum); | |
id = numToNote[noteToNum[id] + transpose]; | |
gHeldNotes[id] = true; | |
gSustainedNotes[id] = true; | |
gPiano.play( | |
id, | |
vol !== undefined ? vol : DEFAULT_VELOCITY, | |
gClient.getOwnParticipant(), | |
0 | |
); | |
gClient.startNote(id, vol); | |
} | |
} | |
function release(id) { | |
if (gHeldNotes[id]) { | |
gHeldNotes[id] = false; | |
if ((gAutoSustain || gSustain) && !enableSynth) { | |
gSustainedNotes[id] = true; | |
} else { | |
if (gNoteQuota.spend(1)) { | |
gPiano.stop(id, gClient.getOwnParticipant(), 0); | |
gClient.stopNote(id); | |
gSustainedNotes[id] = false; | |
} | |
} | |
} | |
} | |
function pressSustain() { | |
gSustain = true; | |
} | |
function releaseSustain() { | |
gSustain = false; | |
if (!gAutoSustain) { | |
for (var id in gSustainedNotes) { | |
if ( | |
gSustainedNotes.hasOwnProperty(id) && | |
gSustainedNotes[id] && | |
!gHeldNotes[id] | |
) { | |
gSustainedNotes[id] = false; | |
if (gNoteQuota.spend(1)) { | |
gPiano.stop(id, gClient.getOwnParticipant(), 0); | |
gClient.stopNote(id); | |
} | |
} | |
} | |
} | |
} | |
// internet science | |
//////////////////////////////////////////////////////////////// | |
var channel_id = decodeURIComponent(window.location.pathname); | |
if (channel_id.substr(0, 1) == "/") channel_id = channel_id.substr(1); | |
if (channel_id == "") channel_id = "lobby"; | |
var wssport = | |
window.location.hostname == "www.multiplayerpiano.com" ? 443 : 8443; | |
var gClient = new Client("wss://" + window.location.hostname + ":" + wssport); | |
gClient.setChannel(channel_id); | |
gClient.start(); | |
gClient.on("disconnect", function (evt) { | |
console.log(evt); | |
}); | |
// Setting status | |
(function () { | |
gClient.on("status", function (status) { | |
$("#status").text(status); | |
}); | |
gClient.on("count", function (count) { | |
if (count > 0) { | |
$("#status").html( | |
'<span class="number">' + | |
count + | |
"</span> " + | |
(count == 1 ? "person is" : "people are") + | |
" playing" | |
); | |
document.title = "Piano (" + count + ")"; | |
} else { | |
document.title = "Multiplayer Piano"; | |
} | |
}); | |
})(); | |
// Handle changes to participants | |
(function () { | |
gClient.on("participant added", function (part) { | |
part.displayX = 150; | |
part.displayY = 50; | |
// add nameDiv | |
var div = document.createElement("div"); | |
div.className = "name"; | |
div.participantId = part.id; | |
div.textContent = part.name || ""; | |
div.style.backgroundColor = part.color || "#777"; | |
if (gClient.participantId === part.id) { | |
$(div).addClass("me"); | |
} | |
if ( | |
gClient.channel && | |
gClient.channel.crown && | |
gClient.channel.crown.participantId === part.id | |
) { | |
$(div).addClass("owner"); | |
} | |
if (gPianoMutes.indexOf(part._id) !== -1) { | |
$(part.nameDiv).addClass("muted-notes"); | |
} | |
if (gChatMutes.indexOf(part._id) !== -1) { | |
$(part.nameDiv).addClass("muted-chat"); | |
} | |
div.style.display = "none"; | |
part.nameDiv = $("#names")[0].appendChild(div); | |
$(part.nameDiv).fadeIn(2000); | |
// sort names | |
var arr = $("#names .name"); | |
arr.sort(function (a, b) { | |
a = a.style.backgroundColor; // todo: sort based on user id instead | |
b = b.style.backgroundColor; | |
if (a > b) return 1; | |
else if (a < b) return -1; | |
else return 0; | |
}); | |
$("#names").html(arr); | |
// add cursorDiv | |
if (gClient.participantId !== part.id || gSeeOwnCursor) { | |
var div = document.createElement("div"); | |
div.className = "cursor"; | |
div.style.display = "none"; | |
part.cursorDiv = $("#cursors")[0].appendChild(div); | |
$(part.cursorDiv).fadeIn(2000); | |
var div = document.createElement("div"); | |
div.className = "name"; | |
div.style.backgroundColor = part.color || "#777"; | |
div.textContent = part.name || ""; | |
part.cursorDiv.appendChild(div); | |
} else { | |
part.cursorDiv = undefined; | |
} | |
}); | |
gClient.on("participant removed", function (part) { | |
// remove nameDiv | |
var nd = $(part.nameDiv); | |
var cd = $(part.cursorDiv); | |
cd.fadeOut(2000); | |
nd.fadeOut(2000, function () { | |
nd.remove(); | |
cd.remove(); | |
part.nameDiv = undefined; | |
part.cursorDiv = undefined; | |
}); | |
}); | |
gClient.on("participant update", function (part) { | |
var name = part.name || ""; | |
var color = part.color || "#777"; | |
part.nameDiv.style.backgroundColor = color; | |
part.nameDiv.textContent = name; | |
$(part.cursorDiv).find(".name").text(name).css("background-color", color); | |
}); | |
gClient.on("ch", function (msg) { | |
for (var id in gClient.ppl) { | |
if (gClient.ppl.hasOwnProperty(id)) { | |
var part = gClient.ppl[id]; | |
if (part.id === gClient.participantId) { | |
$(part.nameDiv).addClass("me"); | |
} else { | |
$(part.nameDiv).removeClass("me"); | |
} | |
if (msg.ch.crown && msg.ch.crown.participantId === part.id) { | |
$(part.nameDiv).addClass("owner"); | |
$(part.cursorDiv).addClass("owner"); | |
} else { | |
$(part.nameDiv).removeClass("owner"); | |
$(part.cursorDiv).removeClass("owner"); | |
} | |
if (gPianoMutes.indexOf(part._id) !== -1) { | |
$(part.nameDiv).addClass("muted-notes"); | |
} else { | |
$(part.nameDiv).removeClass("muted-notes"); | |
} | |
if (gChatMutes.indexOf(part._id) !== -1) { | |
$(part.nameDiv).addClass("muted-chat"); | |
} else { | |
$(part.nameDiv).removeClass("muted-chat"); | |
} | |
} | |
} | |
}); | |
function updateCursor(msg) { | |
const part = gClient.ppl[msg.id]; | |
if (part && part.cursorDiv) { | |
part.cursorDiv.style.left = msg.x + "%"; | |
part.cursorDiv.style.top = msg.y + "%"; | |
} | |
} | |
gClient.on("m", updateCursor); | |
gClient.on("participant added", updateCursor); | |
})(); | |
// Handle changes to crown | |
(function () { | |
var jqcrown = $('<div id="crown"></div>').appendTo(document.body).hide(); | |
var jqcountdown = $("<span></span>").appendTo(jqcrown); | |
var countdown_interval; | |
jqcrown.click(function () { | |
gClient.sendArray([{ m: "chown", id: gClient.participantId }]); | |
}); | |
gClient.on("ch", function (msg) { | |
if (msg.ch.crown) { | |
var crown = msg.ch.crown; | |
if (!crown.participantId || !gClient.ppl[crown.participantId]) { | |
var land_time = crown.time + 2000 - gClient.serverTimeOffset; | |
var avail_time = crown.time + 15000 - gClient.serverTimeOffset; | |
jqcountdown.text(""); | |
jqcrown.show(); | |
if (land_time - Date.now() <= 0) { | |
jqcrown.css({ | |
left: crown.endPos.x + "%", | |
top: crown.endPos.y + "%", | |
}); | |
} else { | |
jqcrown.css({ | |
left: crown.startPos.x + "%", | |
top: crown.startPos.y + "%", | |
}); | |
jqcrown.addClass("spin"); | |
jqcrown.animate( | |
{ left: crown.endPos.x + "%", top: crown.endPos.y + "%" }, | |
2000, | |
"linear", | |
function () { | |
jqcrown.removeClass("spin"); | |
} | |
); | |
} | |
clearInterval(countdown_interval); | |
countdown_interval = setInterval(function () { | |
var time = Date.now(); | |
if (time >= land_time) { | |
var ms = avail_time - time; | |
if (ms > 0) { | |
jqcountdown.text(Math.ceil(ms / 1000) + "s"); | |
} else { | |
jqcountdown.text(""); | |
clearInterval(countdown_interval); | |
} | |
} | |
}, 1000); | |
} else { | |
jqcrown.hide(); | |
} | |
} else { | |
jqcrown.hide(); | |
} | |
}); | |
gClient.on("disconnect", function () { | |
jqcrown.fadeOut(2000); | |
}); | |
})(); | |
// Playing notes | |
gClient.on("n", function (msg) { | |
var t = msg.t - gClient.serverTimeOffset + TIMING_TARGET - Date.now(); | |
var participant = gClient.findParticipantById(msg.p); | |
if (gPianoMutes.indexOf(participant._id) !== -1) return; | |
for (var i = 0; i < msg.n.length; i++) { | |
var note = msg.n[i]; | |
var ms = t + (note.d || 0); | |
if (ms < 0) { | |
ms = 0; | |
} else if (ms > 10000) continue; | |
if (note.s) { | |
gPiano.stop(note.n, participant, ms); | |
} else { | |
var vel = | |
typeof note.v !== "undefined" ? parseFloat(note.v) : DEFAULT_VELOCITY; | |
if (!vel) vel = 0; | |
else if (vel < 0) vel = 0; | |
else if (vel > 1) vel = 1; | |
gPiano.play(note.n, vel, participant, ms); | |
if (enableSynth) { | |
gPiano.stop(note.n, participant, ms + 1000); | |
} | |
} | |
} | |
}); | |
// Send cursor updates | |
var mx = 0, | |
last_mx = -10, | |
my = 0, | |
last_my = -10; | |
setInterval(function () { | |
if (Math.abs(mx - last_mx) > 0.1 || Math.abs(my - last_my) > 0.1) { | |
last_mx = mx; | |
last_my = my; | |
gClient.sendArray([{ m: "m", x: mx, y: my }]); | |
if (gSeeOwnCursor) { | |
gClient.emit("m", { m: "m", id: gClient.participantId, x: mx, y: my }); | |
} | |
var part = gClient.getOwnParticipant(); | |
if (part) { | |
part.x = mx; | |
part.y = my; | |
} | |
} | |
}, 50); | |
$(document).mousemove(function (event) { | |
mx = ((event.pageX / $(window).width()) * 100).toFixed(2); | |
my = ((event.pageY / $(window).height()) * 100).toFixed(2); | |
}); | |
// Room settings button | |
(function () { | |
gClient.on("ch", function (msg) { | |
if (gClient.isOwner()) { | |
$("#room-settings-btn").show(); | |
} else { | |
$("#room-settings-btn").hide(); | |
} | |
}); | |
$("#room-settings-btn").click(function (evt) { | |
if (gClient.channel && gClient.isOwner()) { | |
var settings = gClient.channel.settings; | |
openModal("#room-settings"); | |
setTimeout(function () { | |
$("#room-settings .checkbox[name=visible]").prop( | |
"checked", | |
settings.visible | |
); | |
$("#room-settings .checkbox[name=chat]").prop( | |
"checked", | |
settings.chat | |
); | |
$("#room-settings .checkbox[name=crownsolo]").prop( | |
"checked", | |
settings.crownsolo | |
); | |
$("#room-settings input[name=color]").val(settings.color); | |
}, 100); | |
} | |
}); | |
$("#room-settings .submit").click(function () { | |
var settings = { | |
visible: $("#room-settings .checkbox[name=visible]").is(":checked"), | |
chat: $("#room-settings .checkbox[name=chat]").is(":checked"), | |
crownsolo: $("#room-settings .checkbox[name=crownsolo]").is(":checked"), | |
color: $("#room-settings input[name=color]").val(), | |
}; | |
gClient.setChannelSettings(settings); | |
closeModal(); | |
}); | |
$("#room-settings .drop-crown").click(function () { | |
closeModal(); | |
if (confirm("This will drop the crown...!")) | |
gClient.sendArray([{ m: "chown" }]); | |
}); | |
})(); | |
// Handle notifications | |
gClient.on("notification", function (msg) { | |
new Notification(msg); | |
}); | |
// Don't foget spin | |
gClient.on("ch", function (msg) { | |
var chidlo = msg.ch._id.toLowerCase(); | |
if (chidlo === "spin" || chidlo.substr(-5) === "/spin") { | |
$("#piano").addClass("spin"); | |
} else { | |
$("#piano").removeClass("spin"); | |
} | |
}); | |
/*function eb() { | |
if(gClient.channel && gClient.channel._id.toLowerCase() === "test/fishing") { | |
ebsprite.start(gClient); | |
} else { | |
ebsprite.stop(); | |
} | |
} | |
if(ebsprite) { | |
gClient.on("ch", eb); | |
eb(); | |
}*/ | |
// Crownsolo notice | |
gClient.on("ch", function (msg) { | |
let notice = ""; | |
let has_notice = false; | |
if (msg.ch.settings.crownsolo) { | |
has_notice = true; | |
notice += '<p>This room is set to "only the owner can play."</p>'; | |
} | |
if (msg.ch.settings["no cussing"]) { | |
has_notice = true; | |
notice += '<p>This room is set to "no cussing."</p>'; | |
} | |
let notice_div = $("#room-notice"); | |
if (has_notice) { | |
notice_div.html(notice); | |
if (notice_div.is(":hidden")) notice_div.fadeIn(1000); | |
} else { | |
if (notice_div.is(":visible")) notice_div.fadeOut(1000); | |
} | |
}); | |
gClient.on("disconnect", function () { | |
$("#room-notice").fadeOut(1000); | |
}); | |
// Background color | |
(function () { | |
var old_color1 = new Color("#000000"); | |
var old_color2 = new Color("#000000"); | |
function setColor(hex, hex2) { | |
var color1 = new Color(hex); | |
var color2 = new Color(hex2 || hex); | |
if (!hex2) color2.add(-0x40, -0x40, -0x40); | |
var bottom = document.getElementById("bottom"); | |
var duration = 500; | |
var step = 0; | |
var steps = 30; | |
var step_ms = duration / steps; | |
var difference = new Color(color1.r, color1.g, color1.b); | |
difference.r -= old_color1.r; | |
difference.g -= old_color1.g; | |
difference.b -= old_color1.b; | |
var inc1 = new Color( | |
difference.r / steps, | |
difference.g / steps, | |
difference.b / steps | |
); | |
difference = new Color(color2.r, color2.g, color2.b); | |
difference.r -= old_color2.r; | |
difference.g -= old_color2.g; | |
difference.b -= old_color2.b; | |
var inc2 = new Color( | |
difference.r / steps, | |
difference.g / steps, | |
difference.b / steps | |
); | |
var iv; | |
iv = setInterval(function () { | |
old_color1.add(inc1.r, inc1.g, inc1.b); | |
old_color2.add(inc2.r, inc2.g, inc2.b); | |
document.body.style.background = | |
"radial-gradient(ellipse at center, " + | |
old_color1.toHexa() + | |
" 0%," + | |
old_color2.toHexa() + | |
" 100%)"; | |
bottom.style.background = old_color2.toHexa(); | |
if (++step >= steps) { | |
clearInterval(iv); | |
old_color1 = color1; | |
old_color2 = color2; | |
document.body.style.background = | |
"radial-gradient(ellipse at center, " + | |
color1.toHexa() + | |
" 0%," + | |
color2.toHexa() + | |
" 100%)"; | |
bottom.style.background = color2.toHexa(); | |
} | |
}, step_ms); | |
} | |
function setColorToDefault() { | |
setColor("#000000", "#000000"); | |
} | |
setColorToDefault(); | |
gClient.on("ch", function (ch) { | |
if (ch.ch.settings) { | |
if (ch.ch.settings.color) { | |
setColor(ch.ch.settings.color, ch.ch.settings.color2); | |
} else { | |
setColorToDefault(); | |
} | |
} | |
}); | |
})(); | |
var gPianoMutes = (localStorage.pianoMutes ? localStorage.pianoMutes : "") | |
.split(",") | |
.filter((v) => v); | |
var gChatMutes = (localStorage.pianoMutes ? localStorage.pianoMutes : "") | |
.split(",") | |
.filter((v) => v); | |
var volume_slider = document.getElementById("volume-slider"); | |
volume_slider.value = gPiano.audio.volume; | |
$("#volume-label").text( | |
"Volume: " + Math.floor(gPiano.audio.volume * 100) + "%" | |
); | |
volume_slider.addEventListener("input", function (evt) { | |
var v = +volume_slider.value; | |
gPiano.audio.setVolume(v); | |
if (window.localStorage) localStorage.volume = v; | |
$("#volume-label").text("Volume: " + Math.floor(v * 100) + "%"); | |
}); | |
var Note = function (note, octave) { | |
this.note = note; | |
this.octave = octave || 0; | |
}; | |
var n = function (a, b) { | |
return { note: new Note(a, b), held: false }; | |
}; | |
var key_binding = { | |
65: n("gs"), | |
90: n("a"), | |
83: n("as"), | |
88: n("b"), | |
67: n("c", 1), | |
70: n("cs", 1), | |
86: n("d", 1), | |
71: n("ds", 1), | |
66: n("e", 1), | |
78: n("f", 1), | |
74: n("fs", 1), | |
77: n("g", 1), | |
75: n("gs", 1), | |
188: n("a", 1), | |
76: n("as", 1), | |
190: n("b", 1), | |
191: n("c", 2), | |
222: n("cs", 2), | |
49: n("gs", 1), | |
81: n("a", 1), | |
50: n("as", 1), | |
87: n("b", 1), | |
69: n("c", 2), | |
52: n("cs", 2), | |
82: n("d", 2), | |
53: n("ds", 2), | |
84: n("e", 2), | |
89: n("f", 2), | |
55: n("fs", 2), | |
85: n("g", 2), | |
56: n("gs", 2), | |
73: n("a", 2), | |
57: n("as", 2), | |
79: n("b", 2), | |
80: n("c", 3), | |
189: n("cs", 3), | |
173: n("cs", 3), // firefox why | |
219: n("d", 3), | |
187: n("ds", 3), | |
61: n("ds", 3), // firefox why | |
221: n("e", 3) | |
}; | |
var capsLockKey = false; | |
console.log(key_binding) | |
var transpose_octave = 0; | |
function handleKeyDown(evt) { | |
//console.log(evt); | |
var code = parseInt(evt.keyCode); | |
if (key_binding[code] !== undefined) { | |
var binding = key_binding[code]; | |
if (!binding.held) { | |
binding.held = true; | |
var note = binding.note; | |
var octave = 1 + note.octave + transpose_octave; | |
console.log(note.octave, octave-transpose_octave-1) | |
if (evt.shiftKey) ++octave; | |
else if (capsLockKey || evt.ctrlKey) --octave; | |
note = note.note + octave; | |
var vol = velocityFromMouseY(); | |
press(note, vol); | |
} | |
if (++gKeyboardSeq == 3) { | |
gKnowsYouCanUseKeyboard = true; | |
if (window.gKnowsYouCanUseKeyboardTimeout) | |
clearTimeout(gKnowsYouCanUseKeyboardTimeout); | |
if (localStorage) localStorage.knowsYouCanUseKeyboard = true; | |
if (window.gKnowsYouCanUseKeyboardNotification) | |
gKnowsYouCanUseKeyboardNotification.close(); | |
} | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
return false; | |
} else if (code == 20) { | |
// Caps Lock | |
capsLockKey = true; | |
evt.preventDefault(); | |
} else if (code === 0x20) { | |
// Space Bar | |
pressSustain(); | |
evt.preventDefault(); | |
} else if ((code === 38 || code === 39) && transpose_octave < 3) { | |
++transpose_octave; | |
updateKeyCodes(gPiano.keys); | |
} else if ((code === 40 || code === 37) && transpose_octave > -2) { | |
--transpose_octave; | |
updateKeyCodes(gPiano.keys); | |
} else if (code == 9) { | |
// Tab (don't tab away from the piano) | |
evt.preventDefault(); | |
} else if (code == 8) { | |
// Backspace (don't navigate Back) | |
gAutoSustain = !gAutoSustain; | |
evt.preventDefault(); | |
} else if (code == 33) { | |
transpose++; | |
updateKeyCodes(gPiano.keys); | |
} else if (code == 34) { | |
transpose--; | |
updateKeyCodes(gPiano.keys); | |
} | |
} | |
function handleKeyUp(evt) { | |
var code = parseInt(evt.keyCode); | |
if (key_binding[code] !== undefined) { | |
var binding = key_binding[code]; | |
if (binding.held) { | |
binding.held = false; | |
var note = binding.note; | |
var octave = 1 + note.octave + transpose_octave; | |
if (evt.shiftKey) ++octave; | |
else if (capsLockKey || evt.ctrlKey) --octave; | |
note = note.note + octave; | |
release(note); | |
} | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
return false; | |
} else if (code == 20) { | |
// Caps Lock | |
capsLockKey = false; | |
evt.preventDefault(); | |
} else if (code === 0x20) { | |
// Space Bar | |
releaseSustain(); | |
evt.preventDefault(); | |
} | |
} | |
function handleKeyPress(evt) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
if (evt.keyCode == 27 || evt.keyCode == 13) { | |
//$("#chat input").focus(); | |
} | |
return false; | |
} | |
var recapListener = function (evt) { | |
captureKeyboard(); | |
}; | |
function captureKeyboard() { | |
$("#piano").off("mousedown", recapListener); | |
$("#piano").off("touchstart", recapListener); | |
$(document).on("keydown", handleKeyDown); | |
$(document).on("keyup", handleKeyUp); | |
$(window).on("keypress", handleKeyPress); | |
} | |
function releaseKeyboard() { | |
$(document).off("keydown", handleKeyDown); | |
$(document).off("keyup", handleKeyUp); | |
$(window).off("keypress", handleKeyPress); | |
$("#piano").on("mousedown", recapListener); | |
$("#piano").on("touchstart", recapListener); | |
} | |
captureKeyboard(); | |
var velocityFromMouseY = function () { | |
return 0.1 + (my / 100) * 0.6; | |
}; | |
// NoteQuota | |
var gNoteQuota = (function () { | |
var last_rat = 0; | |
var nqjq = $("#quota .value"); | |
setInterval(function () { | |
gNoteQuota.tick(); | |
}, 2000); | |
return new NoteQuota(function (points) { | |
// update UI | |
var rat = (points / this.max) * 100; | |
if (rat <= last_rat) | |
nqjq.stop(true, true).css("width", rat.toFixed(0) + "%"); | |
else | |
nqjq | |
.stop(true, true) | |
.animate({ width: rat.toFixed(0) + "%" }, 2000, "linear"); | |
last_rat = rat; | |
}); | |
})(); | |
gClient.on("nq", function (nq_params) { | |
gNoteQuota.setParams(nq_params); | |
}); | |
gClient.on("disconnect", function () { | |
gNoteQuota.setParams(NoteQuota.PARAMS_OFFLINE); | |
}); | |
// click participant names | |
(function () { | |
var ele = document.getElementById("names"); | |
var touchhandler = function (e) { | |
var target_jq = $(e.target); | |
if (target_jq.hasClass("name")) { | |
target_jq.addClass("play"); | |
if (e.target.participantId == gClient.participantId) { | |
openModal("#rename", "input[name=name]"); | |
setTimeout(function () { | |
$("#rename input[name=name]").val( | |
gClient.ppl[gClient.participantId].name | |
); | |
$("#rename input[name=color]").val( | |
gClient.ppl[gClient.participantId].color | |
); | |
}, 100); | |
} else if (e.target.participantId) { | |
var id = e.target.participantId; | |
var part = gClient.ppl[id] || null; | |
if (part) { | |
participantMenu(part); | |
e.stopPropagation(); | |
} | |
} | |
} | |
}; | |
ele.addEventListener("mousedown", touchhandler); | |
ele.addEventListener("touchstart", touchhandler); | |
var releasehandler = function (e) { | |
$("#names .name").removeClass("play"); | |
}; | |
document.body.addEventListener("mouseup", releasehandler); | |
document.body.addEventListener("touchend", releasehandler); | |
var removeParticipantMenus = function () { | |
$(".participant-menu").remove(); | |
$(".participantSpotlight").hide(); | |
document.removeEventListener("mousedown", removeParticipantMenus); | |
document.removeEventListener("touchstart", removeParticipantMenus); | |
}; | |
var participantMenu = function (part) { | |
if (!part) return; | |
removeParticipantMenus(); | |
document.addEventListener("mousedown", removeParticipantMenus); | |
document.addEventListener("touchstart", removeParticipantMenus); | |
$("#" + part.id) | |
.find(".enemySpotlight") | |
.show(); | |
var menu = $('<div class="participant-menu"></div>'); | |
$("body").append(menu); | |
// move menu to name position | |
var jq_nd = $(part.nameDiv); | |
var pos = jq_nd.position(); | |
menu.css({ | |
top: pos.top + jq_nd.height() + 15, | |
left: pos.left + 6, | |
background: part.color || "black", | |
}); | |
menu.on("mousedown touchstart", function (evt) { | |
evt.stopPropagation(); | |
var target = $(evt.target); | |
if (target.hasClass("menu-item")) { | |
target.addClass("clicked"); | |
menu.fadeOut(200, function () { | |
removeParticipantMenus(); | |
}); | |
} | |
}); | |
// this spaces stuff out but also can be used for informational | |
$('<div class="info"></div>').appendTo(menu).text(part._id); | |
// add menu items | |
if (gPianoMutes.indexOf(part._id) == -1) { | |
$('<div class="menu-item">Mute Notes</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
gPianoMutes.push(part._id); | |
if (localStorage) localStorage.pianoMutes = gPianoMutes.join(","); | |
$(part.nameDiv).addClass("muted-notes"); | |
}); | |
} else { | |
$('<div class="menu-item">Unmute Notes</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
var i; | |
while ((i = gPianoMutes.indexOf(part._id)) != -1) | |
gPianoMutes.splice(i, 1); | |
if (localStorage) localStorage.pianoMutes = gPianoMutes.join(","); | |
$(part.nameDiv).removeClass("muted-notes"); | |
}); | |
} | |
if (gChatMutes.indexOf(part._id) == -1) { | |
$('<div class="menu-item">Mute Chat</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
gChatMutes.push(part._id); | |
if (localStorage) localStorage.chatMutes = gChatMutes.join(","); | |
$(part.nameDiv).addClass("muted-chat"); | |
}); | |
} else { | |
$('<div class="menu-item">Unmute Chat</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
var i; | |
while ((i = gChatMutes.indexOf(part._id)) != -1) | |
gChatMutes.splice(i, 1); | |
if (localStorage) localStorage.chatMutes = gChatMutes.join(","); | |
$(part.nameDiv).removeClass("muted-chat"); | |
}); | |
} | |
if ( | |
!(gPianoMutes.indexOf(part._id) >= 0) || | |
!(gChatMutes.indexOf(part._id) >= 0) | |
) { | |
$('<div class="menu-item">Mute Completely</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
gPianoMutes.push(part._id); | |
if (localStorage) localStorage.pianoMutes = gPianoMutes.join(","); | |
gChatMutes.push(part._id); | |
if (localStorage) localStorage.chatMutes = gChatMutes.join(","); | |
$(part.nameDiv).addClass("muted-notes"); | |
$(part.nameDiv).addClass("muted-chat"); | |
}); | |
} | |
if ( | |
gPianoMutes.indexOf(part._id) >= 0 || | |
gChatMutes.indexOf(part._id) >= 0 | |
) { | |
$('<div class="menu-item">Unmute Completely</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
var i; | |
while ((i = gPianoMutes.indexOf(part._id)) != -1) | |
gPianoMutes.splice(i, 1); | |
while ((i = gChatMutes.indexOf(part._id)) != -1) | |
gChatMutes.splice(i, 1); | |
if (localStorage) localStorage.pianoMutes = gPianoMutes.join(","); | |
if (localStorage) localStorage.chatMutes = gChatMutes.join(","); | |
$(part.nameDiv).removeClass("muted-notes"); | |
$(part.nameDiv).removeClass("muted-chat"); | |
}); | |
} | |
if (gClient.isOwner()) { | |
$('<div class="menu-item give-crown">Give Crown</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
if (confirm("Give room ownership to " + part.name + "?")) | |
gClient.sendArray([{ m: "chown", id: part.id }]); | |
}); | |
$('<div class="menu-item kickban">Kickban</div>') | |
.appendTo(menu) | |
.on("mousedown touchstart", function (evt) { | |
var minutes = prompt("How many minutes? (0-60)", "30"); | |
if (minutes === null) return; | |
minutes = parseFloat(minutes) || 0; | |
var ms = minutes * 60 * 1000; | |
gClient.sendArray([{ m: "kickban", _id: part._id, ms: ms }]); | |
}); | |
} | |
menu.fadeIn(100); | |
}; | |
})(); | |
// Notification class | |
//////////////////////////////////////////////////////////////// | |
var Notification = function (par) { | |
if (this instanceof Notification === false) throw "yeet"; | |
EventEmitter.call(this); | |
var par = par || {}; | |
this.id = "Notification-" + (par.id || Math.random()); | |
this.title = par.title || ""; | |
this.text = par.text || ""; | |
this.html = par.html || ""; | |
this.target = $(par.target || "#piano"); | |
this.duration = par.duration || 30000; | |
this["class"] = par["class"] || "classic"; | |
var self = this; | |
var eles = $("#" + this.id); | |
if (eles.length > 0) { | |
eles.remove(); | |
} | |
this.domElement = $( | |
'<div class="notification"><div class="notification-body"><div class="title"></div>' + | |
'<div class="text"></div></div><div class="x">Ⓧ</div></div>' | |
); | |
this.domElement[0].id = this.id; | |
this.domElement.addClass(this["class"]); | |
this.domElement.find(".title").text(this.title); | |
if (this.text.length > 0) { | |
this.domElement.find(".text").text(this.text); | |
} else if (this.html instanceof HTMLElement) { | |
this.domElement.find(".text")[0].appendChild(this.html); | |
} else if (this.html.length > 0) { | |
this.domElement.find(".text").html(this.html); | |
} | |
document.body.appendChild(this.domElement.get(0)); | |
this.position(); | |
this.onresize = function () { | |
self.position(); | |
}; | |
window.addEventListener("resize", this.onresize); | |
this.domElement.find(".x").click(function () { | |
self.close(); | |
}); | |
if (this.duration > 0) { | |
setTimeout(function () { | |
self.close(); | |
}, this.duration); | |
} | |
return this; | |
}; | |
mixin(Notification.prototype, EventEmitter.prototype); | |
Notification.prototype.constructor = Notification; | |
Notification.prototype.position = function () { | |
var pos = this.target.offset(); | |
var x = pos.left - this.domElement.width() / 2 + this.target.width() / 4; | |
var y = pos.top - this.domElement.height() - 8; | |
var width = this.domElement.width(); | |
if (x + width > $("body").width()) { | |
x -= x + width - $("body").width(); | |
} | |
if (x < 0) x = 0; | |
this.domElement.offset({ left: x, top: y }); | |
}; | |
Notification.prototype.close = function () { | |
var self = this; | |
window.removeEventListener("resize", this.onresize); | |
this.domElement.fadeOut(500, function () { | |
self.domElement.remove(); | |
self.emit("close"); | |
}); | |
}; | |
// set variables from settings or set settings | |
//////////////////////////////////////////////////////////////// | |
var gKeyboardSeq = 0; | |
var gKnowsYouCanUseKeyboard = false; | |
if (localStorage && localStorage.knowsYouCanUseKeyboard) | |
gKnowsYouCanUseKeyboard = true; | |
if (!gKnowsYouCanUseKeyboard) { | |
window.gKnowsYouCanUseKeyboardTimeout = setTimeout(function () { | |
window.gKnowsYouCanUseKeyboardNotification = new Notification({ | |
title: "Did you know!?!", | |
text: "You can play the piano with your keyboard, too. Try it!", | |
target: "#piano", | |
duration: 10000, | |
}); | |
}, 30000); | |
} | |
if (window.localStorage) { | |
if (localStorage.volume) { | |
volume_slider.value = localStorage.volume; | |
gPiano.audio.setVolume(localStorage.volume); | |
$("#volume-label").text( | |
"Volume: " + Math.floor(gPiano.audio.volume * 100) + "%" | |
); | |
} else localStorage.volume = gPiano.audio.volume; | |
window.gHasBeenHereBefore = localStorage.gHasBeenHereBefore || false; | |
if (gHasBeenHereBefore) { | |
} | |
localStorage.gHasBeenHereBefore = true; | |
} | |
// warn user about loud noises before starting sound (no autoplay) | |
openModal("#sound-warning"); | |
var user_interact = function (evt) { | |
document.removeEventListener("click", user_interact); | |
closeModal(); | |
MPP.piano.audio.resume(); | |
}; | |
document.addEventListener("click", user_interact); | |
// New room, change room | |
//////////////////////////////////////////////////////////////// | |
$("#room > .info").text("--"); | |
gClient.on("ch", function (msg) { | |
var channel = msg.ch; | |
var info = $("#room > .info"); | |
info.text(channel._id); | |
if (channel.settings.lobby) info.addClass("lobby"); | |
else info.removeClass("lobby"); | |
if (!channel.settings.chat) info.addClass("no-chat"); | |
else info.removeClass("no-chat"); | |
if (channel.settings.crownsolo) info.addClass("crownsolo"); | |
else info.removeClass("crownsolo"); | |
if (channel.settings["no cussing"]) info.addClass("no-cussing"); | |
else info.removeClass("no-cussing"); | |
if (!channel.settings.visible) info.addClass("not-visible"); | |
else info.removeClass("not-visible"); | |
}); | |
gClient.on("ls", function (ls) { | |
for (var i in ls.u) { | |
if (!ls.u.hasOwnProperty(i)) continue; | |
var room = ls.u[i]; | |
var info = $( | |
'#room .info[roomname="' + | |
(room._id + "").replace(/[\\"']/g, "\\$&").replace(/\u0000/g, "\\0") + | |
'"]' | |
); | |
if (info.length == 0) { | |
info = $('<div class="info"></div>'); | |
info.attr("roomname", room._id); | |
$("#room .more").append(info); | |
} | |
info.text(room._id + " (" + room.count + ")"); | |
if (room.settings.lobby) info.addClass("lobby"); | |
else info.removeClass("lobby"); | |
if (!room.settings.chat) info.addClass("no-chat"); | |
else info.removeClass("no-chat"); | |
if (room.settings.crownsolo) info.addClass("crownsolo"); | |
else info.removeClass("crownsolo"); | |
if (room.settings["no cussing"]) info.addClass("no-cussing"); | |
else info.removeClass("no-cussing"); | |
if (!room.settings.visible) info.addClass("not-visible"); | |
else info.removeClass("not-visible"); | |
if (room.banned) info.addClass("banned"); | |
else info.removeClass("banned"); | |
} | |
}); | |
$("#room").on("click", function (evt) { | |
evt.stopPropagation(); | |
// clicks on a new room | |
if ( | |
$(evt.target).hasClass("info") && | |
$(evt.target).parents(".more").length | |
) { | |
$("#room .more").fadeOut(250); | |
var selected_name = $(evt.target).attr("roomname"); | |
if (typeof selected_name != "undefined") { | |
changeRoom(selected_name, "right"); | |
} | |
return false; | |
} | |
// clicks on "New Room..." | |
else if ($(evt.target).hasClass("new")) { | |
openModal("#new-room", "input[name=name]"); | |
} | |
// all other clicks | |
var doc_click = function (evt) { | |
if ($(evt.target).is("#room .more")) return; | |
$(document).off("mousedown", doc_click); | |
$("#room .more").fadeOut(250); | |
gClient.sendArray([{ m: "-ls" }]); | |
}; | |
$(document).on("mousedown", doc_click); | |
$("#room .more .info").remove(); | |
$("#room .more").show(); | |
gClient.sendArray([{ m: "+ls" }]); | |
}); | |
$("#new-room-btn").on("click", function (evt) { | |
evt.stopPropagation(); | |
openModal("#new-room", "input[name=name]"); | |
}); | |
$("#play-alone-btn").on("click", function (evt) { | |
evt.stopPropagation(); | |
var room_name = "Room" + Math.floor(Math.random() * 1000000000000); | |
changeRoom(room_name, "right", { visible: false }); | |
setTimeout(function () { | |
new Notification({ | |
id: "share", | |
title: "Playing alone", | |
html: | |
"You are playing alone in a room by yourself, but you can always invite \ | |
friends by sending them the link.<br/><br/>\ | |
<a href=\"#\" onclick=\"window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;\">Share on Facebook</a><br/><br/>\ | |
<a href=\"http://twitter.com/home?status=" + | |
encodeURIComponent(location.href) + | |
'" target="_blank">Tweet</a>', | |
duration: 25000, | |
}); | |
}, 1000); | |
}); | |
var gModal; | |
function modalHandleEsc(evt) { | |
if (evt.keyCode == 27) { | |
closeModal(); | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
} | |
} | |
function openModal(selector, focus) { | |
if (chat) chat.blur(); | |
releaseKeyboard(); | |
$(document).on("keydown", modalHandleEsc); | |
$("#modal #modals > *").hide(); | |
$("#modal").fadeIn(250); | |
$(selector).show(); | |
setTimeout(function () { | |
$(selector).find(focus).focus(); | |
}, 100); | |
gModal = selector; | |
} | |
function closeModal() { | |
$(document).off("keydown", modalHandleEsc); | |
$("#modal").fadeOut(100); | |
$("#modal #modals > *").hide(); | |
captureKeyboard(); | |
gModal = null; | |
} | |
var modal_bg = $("#modal .bg")[0]; | |
$(modal_bg).on("click", function (evt) { | |
if (evt.target != modal_bg) return; | |
closeModal(); | |
}); | |
(function () { | |
function submit() { | |
var name = $("#new-room .text[name=name]").val(); | |
var settings = { | |
visible: $("#new-room .checkbox[name=visible]").is(":checked"), | |
chat: true, | |
}; | |
$("#new-room .text[name=name]").val(""); | |
closeModal(); | |
changeRoom(name, "right", settings); | |
setTimeout(function () { | |
new Notification({ | |
id: "share", | |
title: "Created a Room", | |
html: | |
"You can invite friends to your room by sending them the link.<br/><br/>\ | |
<a href=\"#\" onclick=\"window.open('https://www.facebook.com/sharer/sharer.php?u='+encodeURIComponent(location.href),'facebook-share-dialog','width=626,height=436');return false;\">Share on Facebook</a><br/><br/>\ | |
<a href=\"http://twitter.com/home?status=" + | |
encodeURIComponent(location.href) + | |
'" target="_blank">Tweet</a>', | |
duration: 25000, | |
}); | |
}, 1000); | |
} | |
$("#new-room .submit").click(function (evt) { | |
submit(); | |
}); | |
$("#new-room .text[name=name]").keypress(function (evt) { | |
if (evt.keyCode == 13) { | |
submit(); | |
} else if (evt.keyCode == 27) { | |
closeModal(); | |
} else { | |
return; | |
} | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
return false; | |
}); | |
})(); | |
function changeRoom(name, direction, settings, push) { | |
if (!settings) settings = {}; | |
if (!direction) direction = "right"; | |
if (typeof push == "undefined") push = true; | |
var opposite = direction == "left" ? "right" : "left"; | |
if (name == "") name = "lobby"; | |
if (gClient.channel && gClient.channel._id === name) return; | |
if (push) { | |
var url = "/" + encodeURIComponent(name).replace("'", "%27"); | |
if (window.history && history.pushState) { | |
history.pushState( | |
{ depth: (gHistoryDepth += 1), name: name }, | |
"Piano > " + name, | |
url | |
); | |
} else { | |
window.location = url; | |
return; | |
} | |
} | |
gClient.setChannel(name, settings); | |
var t = 0, | |
d = 100; | |
$("#piano") | |
.addClass("ease-out") | |
.addClass("slide-" + opposite); | |
setTimeout(function () { | |
$("#piano") | |
.removeClass("ease-out") | |
.removeClass("slide-" + opposite) | |
.addClass("slide-" + direction); | |
}, (t += d)); | |
setTimeout(function () { | |
$("#piano") | |
.addClass("ease-in") | |
.removeClass("slide-" + direction); | |
}, (t += d)); | |
setTimeout(function () { | |
$("#piano").removeClass("ease-in"); | |
}, (t += d)); | |
} | |
var gHistoryDepth = 0; | |
$(window).on("popstate", function (evt) { | |
var depth = evt.state ? evt.state.depth : 0; | |
if (depth == gHistoryDepth) return; // <-- forgot why I did that though... | |
var direction = depth <= gHistoryDepth ? "left" : "right"; | |
gHistoryDepth = depth; | |
var name = decodeURIComponent(window.location.pathname); | |
if (name.substr(0, 1) == "/") name = name.substr(1); | |
changeRoom(name, direction, null, false); | |
}); | |
// Rename | |
//////////////////////////////////////////////////////////////// | |
(function () { | |
function submit() { | |
var set = { | |
name: $("#rename input[name=name]").val(), | |
color: $("#rename input[name=color]").val(), | |
}; | |
//$("#rename .text[name=name]").val(""); | |
closeModal(); | |
gClient.sendArray([{ m: "userset", set: set }]); | |
} | |
$("#rename .submit").click(function (evt) { | |
submit(); | |
}); | |
$("#rename .text[name=name]").keypress(function (evt) { | |
if (evt.keyCode == 13) { | |
submit(); | |
} else if (evt.keyCode == 27) { | |
closeModal(); | |
} else { | |
return; | |
} | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
return false; | |
}); | |
})(); | |
// chatctor | |
//////////////////////////////////////////////////////////////// | |
var chat = (function () { | |
gClient.on("ch", function (msg) { | |
if (msg.ch.settings.chat) { | |
chat.show(); | |
} else { | |
chat.hide(); | |
} | |
}); | |
gClient.on("disconnect", function (msg) {}); | |
gClient.on("c", function (msg) { | |
chat.clear(); | |
if (msg.c) { | |
for (var i = 0; i < msg.c.length; i++) { | |
chat.receive(msg.c[i]); | |
} | |
} | |
}); | |
gClient.on("a", function (msg) { | |
chat.receive(msg); | |
}); | |
$("#chat input").on("focus", function (evt) { | |
releaseKeyboard(); | |
$("#chat").addClass("chatting"); | |
chat.scrollToBottom(); | |
}); | |
/*$("#chat input").on("blur", function(evt) { | |
captureKeyboard(); | |
$("#chat").removeClass("chatting"); | |
chat.scrollToBottom(); | |
});*/ | |
$(document).mousedown(function (evt) { | |
if (!$("#chat").has(evt.target).length > 0) { | |
chat.blur(); | |
} | |
}); | |
document.addEventListener("touchstart", function (event) { | |
for (var i in event.changedTouches) { | |
var touch = event.changedTouches[i]; | |
if (!$("#chat").has(touch.target).length > 0) { | |
chat.blur(); | |
} | |
} | |
}); | |
$(document).on("keydown", function (evt) { | |
if ($("#chat").hasClass("chatting")) { | |
if (evt.keyCode == 27) { | |
chat.blur(); | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
} else if (evt.keyCode == 13) { | |
$("#chat input").focus(); | |
} | |
} else if (!gModal && (evt.keyCode == 27 || evt.keyCode == 13)) { | |
$("#chat input").focus(); | |
} | |
}); | |
$("#chat input").on("keydown", function (evt) { | |
if (evt.keyCode == 13) { | |
if (MPP.client.isConnected()) { | |
var message = $(this).val(); | |
if (message.length == 0) { | |
setTimeout(function () { | |
chat.blur(); | |
}, 100); | |
} else if (message.length <= 512) { | |
chat.send(message); | |
$(this).val(""); | |
setTimeout(function () { | |
chat.blur(); | |
}, 100); | |
} | |
} | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
} else if (evt.keyCode == 27) { | |
chat.blur(); | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
} else if (evt.keyCode == 9) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
} | |
}); | |
return { | |
show: function () { | |
$("#chat").fadeIn(); | |
}, | |
hide: function () { | |
$("#chat").fadeOut(); | |
}, | |
clear: function () { | |
$("#chat li").remove(); | |
}, | |
scrollToBottom: function () { | |
var ele = $("#chat ul").get(0); | |
ele.scrollTop = ele.scrollHeight - ele.clientHeight; | |
}, | |
blur: function () { | |
if ($("#chat").hasClass("chatting")) { | |
$("#chat input").get(0).blur(); | |
$("#chat").removeClass("chatting"); | |
chat.scrollToBottom(); | |
captureKeyboard(); | |
} | |
}, | |
send: function (message) { | |
gClient.sendArray([{ m: "a", message: message }]); | |
}, | |
receive: function (msg) { | |
if (gChatMutes.indexOf(msg.p._id) != -1) return; | |
var li = $('<li><span class="name"/><span class="message"/>'); | |
li.find(".name").text(msg.p.name + ":"); | |
li.find(".message").text(msg.a); | |
li.css("color", msg.p.color || "white"); | |
$("#chat ul").append(li); | |
var eles = $("#chat ul li").get(); | |
for (var i = 1; i <= 50 && i <= eles.length; i++) { | |
eles[eles.length - i].style.opacity = 1.0 - i * 0.03; | |
} | |
if (eles.length > 50) { | |
eles[0].style.display = "none"; | |
} | |
if (eles.length > 256) { | |
$(eles[0]).remove(); | |
} | |
// scroll to bottom if not "chatting" or if not scrolled up | |
if (!$("#chat").hasClass("chatting")) { | |
chat.scrollToBottom(); | |
} else { | |
var ele = $("#chat ul").get(0); | |
if (ele.scrollTop > ele.scrollHeight - ele.offsetHeight - 50) | |
chat.scrollToBottom(); | |
} | |
}, | |
}; | |
})(); | |
// MIDI | |
//////////////////////////////////////////////////////////////// | |
var MIDI_TRANSPOSE = -12; | |
var MIDI_KEY_NAMES = ["a-1", "as-1", "b-1"]; | |
var bare_notes = "c cs d ds e f fs g gs a as b".split(" "); | |
for (var oct = 0; oct < 7; oct++) { | |
for (var i in bare_notes) { | |
MIDI_KEY_NAMES.push(bare_notes[i] + oct); | |
} | |
} | |
MIDI_KEY_NAMES.push("c7"); | |
var devices_json = "[]"; | |
function sendDevices() { | |
gClient.sendArray([{ m: "devices", list: JSON.parse(devices_json) }]); | |
} | |
gClient.on("connect", sendDevices); | |
(function () { | |
if (navigator.requestMIDIAccess) { | |
navigator.requestMIDIAccess().then( | |
function (midi) { | |
console.log(midi); | |
function midimessagehandler(evt) { | |
if (!evt.target.enabled) return; | |
//console.log(evt); | |
var channel = evt.data[0] & 0xf; | |
var cmd = evt.data[0] >> 4; | |
var note_number = evt.data[1]; | |
var vel = evt.data[2]; | |
//console.log(channel, cmd, note_number, vel); | |
if (cmd == 8 || (cmd == 9 && vel == 0)) { | |
// NOTE_OFF | |
release(MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE]); | |
} else if (cmd == 9) { | |
// NOTE_ON | |
if (evt.target.volume !== undefined) vel *= evt.target.volume; | |
press( | |
MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE], | |
vel / 100 | |
); | |
} else if (cmd == 11) { | |
// CONTROL_CHANGE | |
if (!gAutoSustain) { | |
if (note_number == 64) { | |
if (vel > 0) { | |
pressSustain(); | |
} else { | |
releaseSustain(); | |
} | |
} | |
} | |
} | |
} | |
function deviceInfo(dev) { | |
return { | |
type: dev.type, | |
//id: dev.id, | |
manufacturer: dev.manufacturer, | |
name: dev.name, | |
version: dev.version, | |
//connection: dev.connection, | |
//state: dev.state, | |
enabled: dev.enabled, | |
volume: dev.volume, | |
}; | |
} | |
function updateDevices() { | |
var list = []; | |
if (midi.inputs.size > 0) { | |
var inputs = midi.inputs.values(); | |
for ( | |
var input_it = inputs.next(); | |
input_it && !input_it.done; | |
input_it = inputs.next() | |
) { | |
var input = input_it.value; | |
list.push(deviceInfo(input)); | |
} | |
} | |
if (midi.outputs.size > 0) { | |
var outputs = midi.outputs.values(); | |
for ( | |
var output_it = outputs.next(); | |
output_it && !output_it.done; | |
output_it = outputs.next() | |
) { | |
var output = output_it.value; | |
list.push(deviceInfo(output)); | |
} | |
} | |
var new_json = JSON.stringify(list); | |
if (new_json !== devices_json) { | |
devices_json = new_json; | |
sendDevices(); | |
} | |
} | |
function plug() { | |
if (midi.inputs.size > 0) { | |
var inputs = midi.inputs.values(); | |
for ( | |
var input_it = inputs.next(); | |
input_it && !input_it.done; | |
input_it = inputs.next() | |
) { | |
var input = input_it.value; | |
//input.removeEventListener("midimessage", midimessagehandler); | |
//input.addEventListener("midimessage", midimessagehandler); | |
input.onmidimessage = midimessagehandler; | |
if (input.enabled !== false) { | |
input.enabled = true; | |
} | |
if (typeof input.volume === "undefined") { | |
input.volume = 1.0; | |
} | |
console.log("input", input); | |
} | |
} | |
if (midi.outputs.size > 0) { | |
var outputs = midi.outputs.values(); | |
for ( | |
var output_it = outputs.next(); | |
output_it && !output_it.done; | |
output_it = outputs.next() | |
) { | |
var output = output_it.value; | |
//output.enabled = false; // edit: don't touch | |
if (typeof output.volume === "undefined") { | |
output.volume = 1.0; | |
} | |
console.log("output", output); | |
} | |
gMidiOutTest = function (note_name, vel, delay_ms) { | |
var note_number = MIDI_KEY_NAMES.indexOf(note_name); | |
if (note_number == -1) return; | |
note_number = note_number + 9 - MIDI_TRANSPOSE; | |
var outputs = midi.outputs.values(); | |
for ( | |
var output_it = outputs.next(); | |
output_it && !output_it.done; | |
output_it = outputs.next() | |
) { | |
var output = output_it.value; | |
if (output.enabled) { | |
var v = vel; | |
if (output.volume !== undefined) v *= output.volume; | |
output.send( | |
[0x90, note_number, v], | |
window.performance.now() + delay_ms | |
); | |
} | |
} | |
}; | |
} | |
showConnections(false); | |
updateDevices(); | |
} | |
midi.addEventListener("statechange", function (evt) { | |
if (evt instanceof MIDIConnectionEvent) { | |
plug(); | |
} | |
}); | |
plug(); | |
var connectionsNotification; | |
function showConnections(sticky) { | |
//if(document.getElementById("Notification-MIDI-Connections")) | |
//sticky = 1; // todo: instead, | |
var inputs_ul = document.createElement("ul"); | |
if (midi.inputs.size > 0) { | |
var inputs = midi.inputs.values(); | |
for ( | |
var input_it = inputs.next(); | |
input_it && !input_it.done; | |
input_it = inputs.next() | |
) { | |
var input = input_it.value; | |
var li = document.createElement("li"); | |
li.connectionId = input.id; | |
li.classList.add("connection"); | |
if (input.enabled) li.classList.add("enabled"); | |
li.textContent = input.name; | |
li.addEventListener("click", function (evt) { | |
var inputs = midi.inputs.values(); | |
for ( | |
var input_it = inputs.next(); | |
input_it && !input_it.done; | |
input_it = inputs.next() | |
) { | |
var input = input_it.value; | |
if (input.id === evt.target.connectionId) { | |
input.enabled = !input.enabled; | |
evt.target.classList.toggle("enabled"); | |
console.log("click", input); | |
updateDevices(); | |
return; | |
} | |
} | |
}); | |
if (gMidiVolumeTest) { | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 16 * window.devicePixelRatio, | |
height: 16 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
li.appendChild(knob); | |
knob = new Knob(knob, 0, 2, 0.01, input.volume, "volume"); | |
knob.canvas.style.width = "16px"; | |
knob.canvas.style.height = "16px"; | |
knob.canvas.style.float = "right"; | |
knob.on("change", function (k) { | |
input.volume = k.value; | |
}); | |
knob.emit("change", knob); | |
} | |
inputs_ul.appendChild(li); | |
} | |
} else { | |
inputs_ul.textContent = "(none)"; | |
} | |
var outputs_ul = document.createElement("ul"); | |
if (midi.outputs.size > 0) { | |
var outputs = midi.outputs.values(); | |
for ( | |
var output_it = outputs.next(); | |
output_it && !output_it.done; | |
output_it = outputs.next() | |
) { | |
var output = output_it.value; | |
var li = document.createElement("li"); | |
li.connectionId = output.id; | |
li.classList.add("connection"); | |
if (output.enabled) li.classList.add("enabled"); | |
li.textContent = output.name; | |
li.addEventListener("click", function (evt) { | |
var outputs = midi.outputs.values(); | |
for ( | |
var output_it = outputs.next(); | |
output_it && !output_it.done; | |
output_it = outputs.next() | |
) { | |
var output = output_it.value; | |
if (output.id === evt.target.connectionId) { | |
output.enabled = !output.enabled; | |
evt.target.classList.toggle("enabled"); | |
console.log("click", output); | |
updateDevices(); | |
return; | |
} | |
} | |
}); | |
if (gMidiVolumeTest) { | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 16 * window.devicePixelRatio, | |
height: 16 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
li.appendChild(knob); | |
knob = new Knob(knob, 0, 2, 0.01, output.volume, "volume"); | |
knob.canvas.style.width = "16px"; | |
knob.canvas.style.height = "16px"; | |
knob.canvas.style.float = "right"; | |
knob.on("change", function (k) { | |
output.volume = k.value; | |
}); | |
knob.emit("change", knob); | |
} | |
outputs_ul.appendChild(li); | |
} | |
} else { | |
outputs_ul.textContent = "(none)"; | |
} | |
var div = document.createElement("div"); | |
var h1 = document.createElement("h1"); | |
h1.textContent = "Inputs"; | |
div.appendChild(h1); | |
div.appendChild(inputs_ul); | |
h1 = document.createElement("h1"); | |
h1.textContent = "Outputs"; | |
div.appendChild(h1); | |
div.appendChild(outputs_ul); | |
connectionsNotification = new Notification({ | |
id: "MIDI-Connections", | |
title: "MIDI Connections", | |
duration: sticky ? "-1" : "4500", | |
html: div, | |
target: "#midi-btn", | |
}); | |
} | |
document | |
.getElementById("midi-btn") | |
.addEventListener("click", function (evt) { | |
if (!document.getElementById("Notification-MIDI-Connections")) | |
showConnections(true); | |
else { | |
connectionsNotification.close(); | |
} | |
}); | |
}, | |
function (err) { | |
console.log(err); | |
} | |
); | |
} | |
})(); | |
// bug supply | |
//////////////////////////////////////////////////////////////// | |
window.onerror = function (message, url, line) { | |
var url = url || "(no url)"; | |
var line = line || "(no line)"; | |
// errors in socket.io | |
if (url.indexOf("socket.io.js") !== -1) { | |
if (message.indexOf("INVALID_STATE_ERR") !== -1) return; | |
if (message.indexOf("InvalidStateError") !== -1) return; | |
if (message.indexOf("DOM Exception 11") !== -1) return; | |
if ( | |
message.indexOf("Property 'open' of object #<c> is not a function") !== | |
-1 | |
) | |
return; | |
if (message.indexOf("Cannot call method 'close' of undefined") !== -1) | |
return; | |
if (message.indexOf("Cannot call method 'close' of null") !== -1) return; | |
if (message.indexOf("Cannot call method 'onClose' of null") !== -1) | |
return; | |
if (message.indexOf("Cannot call method 'payload' of null") !== -1) | |
return; | |
if (message.indexOf("Unable to get value of the property 'close'") !== -1) | |
return; | |
if (message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) return; | |
if ( | |
message.indexOf( | |
"Unable to get property 'close' of undefined or null reference" | |
) !== -1 | |
) | |
return; | |
if ( | |
message.indexOf( | |
"Unable to get value of the property 'close': object is null or undefined" | |
) !== -1 | |
) | |
return; | |
if (message.indexOf("this.transport is null") !== -1) return; | |
} | |
// errors in soundmanager2 | |
if (url.indexOf("soundmanager2.js") !== -1) { | |
// operation disabled in safe mode? | |
if ( | |
message.indexOf( | |
"Could not complete the operation due to error c00d36ef" | |
) !== -1 | |
) | |
return; | |
if (message.indexOf("_s.o._setVolume is not a function") !== -1) return; | |
} | |
// errors in midibridge | |
if (url.indexOf("midibridge") !== -1) { | |
if (message.indexOf("Error calling method on NPObject") !== -1) return; | |
} | |
// too many failing extensions injected in my html | |
if (url.indexOf(".js") !== url.length - 3) return; | |
// extensions inject cross-domain embeds too | |
if (url.toLowerCase().indexOf("multiplayerpiano.com") == -1) return; | |
// errors in my code | |
if (url.indexOf("script.js") !== -1) { | |
if (message.indexOf("Object [object Object] has no method 'on'") !== -1) | |
return; | |
if (message.indexOf("Object [object Object] has no method 'off'") !== -1) | |
return; | |
if ( | |
message.indexOf( | |
"Property '$' of object [object Object] is not a function" | |
) !== -1 | |
) | |
return; | |
} | |
var enc = | |
"/bugreport/" + | |
(message ? encodeURIComponent(message) : "") + | |
"/" + | |
(url ? encodeURIComponent(url) : "") + | |
"/" + | |
(line ? encodeURIComponent(line) : ""); | |
var img = new Image(); | |
img.src = enc; | |
}; | |
// API | |
window.MPP = { | |
press: press, | |
release: release, | |
pressSustain: pressSustain, | |
releaseSustain: releaseSustain, | |
piano: gPiano, | |
client: gClient, | |
chat: chat, | |
noteQuota: gNoteQuota, | |
soundSelector: gSoundSelector, | |
Notification: Notification, | |
}; | |
// record mp3 | |
(function () { | |
var button = document.querySelector("#record-btn"); | |
var audio = MPP.piano.audio; | |
var context = audio.context; | |
var encoder_sample_rate = 44100; | |
var encoder_kbps = 128; | |
var encoder = null; | |
var scriptProcessorNode = context.createScriptProcessor(4096, 2, 2); | |
var recording = false; | |
var recording_start_time = 0; | |
var mp3_buffer = []; | |
button.addEventListener("click", function (evt) { | |
if (!recording) { | |
// start recording | |
mp3_buffer = []; | |
encoder = new lamejs.Mp3Encoder(2, encoder_sample_rate, encoder_kbps); | |
scriptProcessorNode.onaudioprocess = onAudioProcess; | |
audio.masterGain.connect(scriptProcessorNode); | |
scriptProcessorNode.connect(context.destination); | |
recording_start_time = Date.now(); | |
recording = true; | |
button.textContent = "Stop Recording"; | |
button.classList.add("stuck"); | |
new Notification({ | |
id: "mp3", | |
title: "Recording MP3...", | |
html: | |
'It\'s recording now. This could make things slow, maybe. Maybe give it a moment to settle before playing.<br><br>This feature is experimental.<br>Send complaints to <a href="mailto:[email protected]">[email protected]</a>.', | |
duration: 10000, | |
}); | |
} else { | |
// stop recording | |
var mp3buf = encoder.flush(); | |
mp3_buffer.push(mp3buf); | |
var blob = new Blob(mp3_buffer, { type: "audio/mp3" }); | |
var url = URL.createObjectURL(blob); | |
scriptProcessorNode.onaudioprocess = null; | |
audio.masterGain.disconnect(scriptProcessorNode); | |
scriptProcessorNode.disconnect(context.destination); | |
recording = false; | |
button.textContent = "Record MP3"; | |
button.classList.remove("stuck"); | |
new Notification({ | |
id: "mp3", | |
title: "MP3 recording finished", | |
html: | |
'<a href="' + | |
url + | |
'" target="blank">And here it is!</a> (open or save as)<br><br>This feature is experimental.<br>Send complaints to <a href="mailto:[email protected]">[email protected]</a>.', | |
duration: 0, | |
}); | |
} | |
}); | |
function onAudioProcess(evt) { | |
var inputL = evt.inputBuffer.getChannelData(0); | |
var inputR = evt.inputBuffer.getChannelData(1); | |
var mp3buf = encoder.encodeBuffer(convert16(inputL), convert16(inputR)); | |
mp3_buffer.push(mp3buf); | |
} | |
function convert16(samples) { | |
var len = samples.length; | |
var result = new Int16Array(len); | |
for (var i = 0; i < len; i++) { | |
result[i] = 0x8000 * samples[i]; | |
} | |
return result; | |
} | |
})(); | |
// synth | |
var enableSynth = false; | |
var audio = gPiano.audio; | |
var context = gPiano.audio.context; | |
var synth_gain = context.createGain(); | |
synth_gain.gain.value = 0.05; | |
synth_gain.connect(audio.synthGain); | |
var osc_types = ["sine", "square", "sawtooth", "triangle"]; | |
var osc_type_index = 1; | |
var osc1_type = "square"; | |
var osc1_attack = 0; | |
var osc1_decay = 0.2; | |
var osc1_sustain = 0.5; | |
var osc1_release = 2.0; | |
function synthVoice(note_name, time) { | |
var note_number = MIDI_KEY_NAMES.indexOf(note_name); | |
note_number = note_number + 9 - MIDI_TRANSPOSE; | |
var freq = Math.pow(2, (note_number - 69) / 12) * 440.0; | |
this.osc = context.createOscillator(); | |
this.osc.type = osc1_type; | |
this.osc.frequency.value = freq; | |
this.gain = context.createGain(); | |
this.gain.gain.value = 0; | |
this.osc.connect(this.gain); | |
this.gain.connect(synth_gain); | |
this.osc.start(time); | |
this.gain.gain.setValueAtTime(0, time); | |
this.gain.gain.linearRampToValueAtTime(1, time + osc1_attack); | |
this.gain.gain.linearRampToValueAtTime( | |
osc1_sustain, | |
time + osc1_attack + osc1_decay | |
); | |
} | |
synthVoice.prototype.stop = function (time) { | |
//this.gain.gain.setValueAtTime(osc1_sustain, time); | |
this.gain.gain.linearRampToValueAtTime(0, time + osc1_release); | |
this.osc.stop(time + osc1_release); | |
}; | |
(function () { | |
var button = document.getElementById("synth-btn"); | |
var notification; | |
button.addEventListener("click", function () { | |
if (notification) { | |
notification.close(); | |
} else { | |
showSynth(); | |
} | |
}); | |
function showSynth() { | |
var html = document.createElement("div"); | |
// on/off button | |
(function () { | |
var button = document.createElement("input"); | |
mixin(button, { | |
type: "button", | |
value: "ON/OFF", | |
className: enableSynth ? "switched-on" : "switched-off", | |
}); | |
button.addEventListener("click", function (evt) { | |
enableSynth = !enableSynth; | |
button.className = enableSynth ? "switched-on" : "switched-off"; | |
if (!enableSynth) { | |
// stop all | |
for (var i in audio.playings) { | |
if (!audio.playings.hasOwnProperty(i)) continue; | |
var playing = audio.playings[i]; | |
if (playing && playing.voice) { | |
playing.voice.osc.stop(); | |
playing.voice = undefined; | |
} | |
} | |
} | |
}); | |
html.appendChild(button); | |
})(); | |
// mix | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 32 * window.devicePixelRatio, | |
height: 32 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
html.appendChild(knob); | |
knob = new Knob(knob, 0, 100, 0.1, 50, "mix", "%"); | |
knob.canvas.style.width = "32px"; | |
knob.canvas.style.height = "32px"; | |
knob.on("change", function (k) { | |
var mix = k.value / 100; | |
audio.pianoGain.gain.value = 1 - mix; | |
audio.synthGain.gain.value = mix; | |
}); | |
knob.emit("change", knob); | |
// osc1 type | |
(function () { | |
osc1_type = osc_types[osc_type_index]; | |
var button = document.createElement("input"); | |
mixin(button, { type: "button", value: osc_types[osc_type_index] }); | |
button.addEventListener("click", function (evt) { | |
if (++osc_type_index >= osc_types.length) osc_type_index = 0; | |
osc1_type = osc_types[osc_type_index]; | |
button.value = osc1_type; | |
}); | |
html.appendChild(button); | |
})(); | |
// osc1 attack | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 32 * window.devicePixelRatio, | |
height: 32 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
html.appendChild(knob); | |
knob = new Knob(knob, 0, 1, 0.001, osc1_attack, "osc1 attack", "s"); | |
knob.canvas.style.width = "32px"; | |
knob.canvas.style.height = "32px"; | |
knob.on("change", function (k) { | |
osc1_attack = k.value; | |
}); | |
knob.emit("change", knob); | |
// osc1 decay | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 32 * window.devicePixelRatio, | |
height: 32 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
html.appendChild(knob); | |
knob = new Knob(knob, 0, 2, 0.001, osc1_decay, "osc1 decay", "s"); | |
knob.canvas.style.width = "32px"; | |
knob.canvas.style.height = "32px"; | |
knob.on("change", function (k) { | |
osc1_decay = k.value; | |
}); | |
knob.emit("change", knob); | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 32 * window.devicePixelRatio, | |
height: 32 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
html.appendChild(knob); | |
knob = new Knob(knob, 0, 1, 0.001, osc1_sustain, "osc1 sustain", "x"); | |
knob.canvas.style.width = "32px"; | |
knob.canvas.style.height = "32px"; | |
knob.on("change", function (k) { | |
osc1_sustain = k.value; | |
}); | |
knob.emit("change", knob); | |
// osc1 release | |
var knob = document.createElement("canvas"); | |
mixin(knob, { | |
width: 32 * window.devicePixelRatio, | |
height: 32 * window.devicePixelRatio, | |
className: "knob", | |
}); | |
html.appendChild(knob); | |
knob = new Knob(knob, 0, 2, 0.001, osc1_release, "osc1 release", "s"); | |
knob.canvas.style.width = "32px"; | |
knob.canvas.style.height = "32px"; | |
knob.on("change", function (k) { | |
osc1_release = k.value; | |
}); | |
knob.emit("change", knob); | |
var div = document.createElement("div"); | |
div.innerHTML = | |
"<br><br><br><br><center>this space intentionally left blank</center><br><br><br><br>"; | |
html.appendChild(div); | |
// notification | |
notification = new Notification({ | |
title: "Synthesize", | |
html: html, | |
duration: -1, | |
target: "#synth-btn", | |
}); | |
notification.on("close", function () { | |
var tip = document.getElementById("tooltip"); | |
if (tip) tip.parentNode.removeChild(tip); | |
notification = null; | |
}); | |
} | |
// MPPLUS | |
function createModal(id){ | |
const parent = document.getElementById("modals") | |
const div = document.createElement("div"); | |
div.classList.add("dialog"); | |
div.setAttribute("id", id); | |
div.style.display = "none"; | |
parent.appendChild(div); | |
} | |
createModal("mpplus-settings") | |
console.log(key_binding) | |
var button = document.createElement("button"); | |
button.innerHTML = "Settings"; | |
button.style.position = "absolute"; | |
button.style.left = "660px"; | |
button.style.top = "32px"; | |
button.style.height = "24px"; | |
button.style.width = "110px"; | |
button.classList.add("ugly-button"); | |
button.classList.add("translate"); | |
// 2. Append somewhere | |
var body = document.getElementsByClassName("relative")[0]; | |
body.appendChild(button); | |
// 3. Add event handler | |
button.addEventListener ("click", function() { | |
openModal("#mpplus-settings"); | |
}); | |
})(); | |
}); | |
// misc | |
//////////////////////////////////////////////////////////////// | |
// analytics | |
window.google_analytics_uacct = "UA-882009-7"; | |
var _gaq = _gaq || []; | |
_gaq.push(["_setAccount", "UA-882009-7"]); | |
_gaq.push(["_trackPageview"]); | |
_gaq.push(["_setAllowAnchor", true]); | |
(function () { | |
var ga = document.createElement("script"); | |
ga.type = "text/javascript"; | |
ga.async = true; | |
ga.src = | |
("https:" == document.location.protocol ? "https://ssl" : "http://www") + | |
".google-analytics.com/ga.js"; | |
var s = document.getElementsByTagName("script")[0]; | |
s.parentNode.insertBefore(ga, s); | |
})(); | |
!(function (d, s, id) { | |
var js, | |
fjs = d.getElementsByTagName(s)[0]; | |
if (!d.getElementById(id)) { | |
js = d.createElement(s); | |
js.id = id; | |
js.src = "//platform.twitter.com/widgets.js"; | |
fjs.parentNode.insertBefore(js, fjs); | |
} | |
})(document, "script", "twitter-wjs"); | |
// fb | |
(function (d, s, id) { | |
var js, | |
fjs = d.getElementsByTagName(s)[0]; | |
if (d.getElementById(id)) return; | |
js = d.createElement(s); | |
js.id = id; | |
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8"; | |
fjs.parentNode.insertBefore(js, fjs); | |
})(document, "script", "facebook-jssdk"); | |
// non-ad-free experience | |
/*(function() { | |
function adsOn() { | |
if(window.localStorage) { | |
var div = document.querySelector("#inclinations"); | |
div.innerHTML = "Ads:<br>ON / <a id=\"adsoff\" href=\"#\">OFF</a>"; | |
div.querySelector("#adsoff").addEventListener("click", adsOff); | |
localStorage.ads = true; | |
} | |
// adsterra | |
var script = document.createElement("script"); | |
script.src = "//pl132070.puhtml.com/68/7a/97/687a978dd26d579c788cb41e352f5a41.js"; | |
document.head.appendChild(script); | |
} | |
function adsOff() { | |
if(window.localStorage) localStorage.ads = false; | |
document.location.reload(true); | |
} | |
function noAds() { | |
var div = document.querySelector("#inclinations"); | |
div.innerHTML = "Ads:<br><a id=\"adson\" href=\"#\">ON</a> / OFF"; | |
div.querySelector("#adson").addEventListener("click", adsOn); | |
} | |
if(window.localStorage) { | |
if(localStorage.ads === undefined || localStorage.ads === "true") | |
adsOn(); | |
else | |
noAds(); | |
} else { | |
adsOn(); | |
} | |
})();*/ | |
// thanks to this transpose hack : https://pastebin.com/Ffd2s47Y | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment