mpp2midi/mpprecorder-cli-orig.js

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();
});