var gClient = new (require('./Client.js'))('ws://www.multiplayerpiano.com:443'); var channel = process.argv.slice(2).join(' '); gClient.setChannel(channel); gClient.start(); var Midi = require('./jsmidgen.js'); function convertMillisecondsToTicks(ms) {return ms * 0.256} // 0.256 ticks per millisecond, based on 128 ticks per beat and 120 beats per minute function convertMppKeyToMidiNumber(note_name) { var MIDI_KEY_NAMES = ["a-1","as-1","b-1","c0","cs0","d0","ds0","e0","f0","fs0","g0","gs0","a0","as0","b0","c1","cs1","d1","ds1","e1","f1","fs1","g1","gs1","a1","as1","b1","c2","cs2","d2","ds2","e2","f2","fs2","g2","gs2","a2","as2","b2","c3","cs3","d3","ds3","e3","f3","fs3","g3","gs3","a3","as3","b3","c4","cs4","d4","ds4","e4","f4","fs4","g4","gs4","a4","as4","b4","c5","cs5","d5","ds5","e5","f5","fs5","g5","gs5","a5","as5","b5","c6","cs6","d6","ds6","e6","f6","fs6","g6","gs6","a6","as6","b6","c7"]; var MIDI_TRANSPOSE = -12; var note_number = MIDI_KEY_NAMES.indexOf(note_name); if (note_number == -1) return; note_number = note_number + 9 - MIDI_TRANSPOSE; return note_number } var max_note_ms = 3000; var midiFile = new Midi.File(); var startTime = Date.now(); var players = {}; function addPlayer(participant) { players[participant._id] = { track: midiFile.addTrack(), lastNoteTime: startTime, keys: {} } var track_name = `${participant.name} ${participant._id}`; players[participant._id].track.addEvent(new Midi.MetaEvent({type: Midi.MetaEvent.TRACK_NAME, data: track_name })); } function addNote(note_name, vel, participant, isStopNote) { if (!players.hasOwnProperty([participant._id])) addPlayer(participant); var player = players[participant._id]; if (!player.keys.hasOwnProperty(note_name)) player.keys[note_name] = {}; var playerkey = player.keys[note_name]; var note_number = convertMppKeyToMidiNumber(note_name); if (!note_number) return; var time_ms = Date.now() - player.lastNoteTime; var time_ticks = convertMillisecondsToTicks(time_ms); var midi_vel = vel * 127; //player.track[isStopNote ? 'addNoteOff' : 'addNoteOn'](0, note_number, time_ticks, midi_vel); // easy way // but we need to maintain proper on/off order and limit note lengths for it to (dis)play properly in all midi players etc if (isStopNote) { if (!playerkey.isPressed) return; player.track.addNoteOff(0, note_number, time_ticks, midi_vel); playerkey.isPressed = false; clearTimeout(playerkey.timeout); } else { if (playerkey.isPressed) { player.track.addNoteOff(0, note_number, time_ticks, midi_vel); player.track.addNoteOn(0, note_number, 0, midi_vel); clearTimeout(playerkey.timeout); } else { player.track.addNoteOn(0, note_number, time_ticks, midi_vel); } playerkey.isPressed = true; playerkey.timeout = setTimeout(addNote, max_note_ms, note_name, vel, participant, true); } player.lastNoteTime = Date.now(); } function end() { gClient.stop(); var filename = `${channel} ${new Date(startTime).toISOString()}-${new Date().toISOString()}.mid`; require('fs').writeFileSync('./save/'+filename, midiFile.toBytes(), 'binary'); console.log('saved'); process.exit(); } gClient.on("n", function (msg) { var DEFAULT_VELOCITY = 0.5; var TIMING_TARGET = 1000; var t = msg.t - gClient.serverTimeOffset + TIMING_TARGET - Date.now(); var participant = gClient.findParticipantById(msg.p); msg.n.forEach(note => { var ms = t + (note.d || 0); if (ms < 0) ms = 0; else if (ms > 10000) return; if (note.s) { setTimeout(addNote, ms, note.n, undefined, participant, true); } else { var vel = parseFloat(note.v) || DEFAULT_VELOCITY; if (vel < 0) vel = 0; else if (vel > 1) vel = 1; setTimeout(addNote, ms, note.n, vel, participant, false); } }); }); var stdin = process.openStdin(); stdin.addListener("data", function(data) { data = data.toString().trim(); if (['stop','save','s'].includes(data)) end(); });