106 lines
4.1 KiB
JavaScript
106 lines
4.1 KiB
JavaScript
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();
|
|
}); |