2021-05-24 18:30:16 +02:00
// 钢琴
2023-09-09 04:09:28 +02:00
$ ( function ( ) {
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 ) {
/ * t h r o w n e w E r r o r ( e . m e s s a g e
2021-05-24 18:30:16 +02:00
+ " / id: " + id
+ " / url: " + url
+ " / status: " + req . status
+ " / ArrayBuffer: " + ( req . response instanceof ArrayBuffer )
+ " / byteLength: " + ( req . response && req . response . byteLength ? req . response . byteLength : "undefined" ) ) ; * /
2023-09-09 04:09:28 +02:00
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 ;
} ) ;
/ * $ ( p i a n o . r o o t E l e m e n t ) . m o u s e m o v e ( f u n c t i o n ( e v e n t ) {
2021-05-24 18:30:16 +02:00
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 ;
}
} ) ; * /
2023-09-09 04:09:28 +02:00
return this ;
} ;
CanvasRenderer . prototype . resize = function ( width , height ) {
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 ) ;
// draw all keys
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 ) ;
// 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 ) ;
} else {
key . blips . splice ( b , 1 ) ;
-- b ;
}
y -= Math . floor ( h * 1.1 ) ;
}
}
}
}
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 ) ) {
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 . hash ) ;
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" ) ;
}
} ) ;
/ * f u n c t i o n e b ( ) {
if ( gClient . channel && gClient . channel . _id . toLowerCase ( ) === "test/fishing" ) {
ebsprite . start ( gClient ) ;
2021-05-24 18:30:16 +02:00
} else {
2023-09-09 04:09:28 +02:00
ebsprite . stop ( ) ;
2021-05-24 18:30:16 +02:00
}
}
2023-09-09 04:09:28 +02:00
if ( ebsprite ) {
gClient . on ( "ch" , eb ) ;
eb ( ) ;
} * /
2021-05-24 18:30:16 +02:00
2023-09-09 04:09:28 +02:00
// 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 ;
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 ;
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 ;
} else if ( ( code === 40 || code === 37 ) && transpose _octave > - 2 ) {
-- transpose _octave ;
} 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 ( ) ;
}
}
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 \
2021-05-24 18:30:16 +02:00
friends by sending them the link . < br / > < br / > \
2023-09-09 04:09:28 +02:00
< 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 ( ) ;
} ) ;
/ * $ ( " # c h a t i n p u t " ) . o n ( " b l u r " , f u n c t i o n ( e v t ) {
2021-05-24 18:30:16 +02:00
captureKeyboard ( ) ;
$ ( "#chat" ) . removeClass ( "chatting" ) ;
chat . scrollToBottom ( ) ;
} ) ; * /
2023-09-09 04:09:28 +02:00
$ ( 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:multiplayerpiano.com@gmail.com">multiplayerpiano.com@gmail.com</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:multiplayerpiano.com@gmail.com">multiplayerpiano.com@gmail.com</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 ;
} ) ;
}
} ) ( ) ;
2021-05-24 18:30:16 +02:00
} ) ;
2021-05-31 20:33:42 +02:00
function catSound ( ) {
2023-09-09 04:09:28 +02:00
let sounds = [
"cat-sounds/meow1.mp3" ,
"cat-sounds/meow2.mp3" ,
"cat-sounds/meow3.mp3" ,
"cat-sounds/meow4.mp3" ,
"cat-sounds/meow5.mp3" ,
"cat-sounds/meow6.mp3" ,
"cat-sounds/meow7.mp3" ,
"cat-sounds/meow8.mp3" ,
"cat-sounds/meow9.mp3" ,
"cat-sounds/meow10.mp3"
] ;
let random = sounds [ Math . floor ( Math . random ( ) * sounds . length ) ] ;
2021-05-31 20:33:42 +02:00
const meow = new Audio ( random ) ;
meow . play ( ) ;
}
2021-05-24 18:30:16 +02:00
2023-09-09 04:09:28 +02:00
document . getElementById ( "more-button" ) . onclick = catSound ;
2021-05-24 18:30:16 +02:00
// misc
////////////////////////////////////////////////////////////////
2023-09-09 04:09:28 +02:00
// analytics
2021-05-24 18:30:16 +02:00
window . google _analytics _uacct = "UA-882009-7" ;
var _gaq = _gaq || [ ] ;
2023-09-09 04:09:28 +02:00
_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 ) ;
2021-05-24 18:30:16 +02:00
} ) ( ) ;
// twitter
2023-09-09 04:09:28 +02:00
! ( 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" ) ;
2021-05-24 18:30:16 +02:00
// fb
2023-09-09 04:09:28 +02:00
( 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" ) ;
2021-05-24 18:30:16 +02:00
// non-ad-free experience
/ * ( f u n c t i o n ( ) {
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 ( ) ;
}
} ) ( ) ; * /