Skip to content

Instantly share code, notes, and snippets.

@SBell6hf
Last active January 31, 2022 13:16
Show Gist options
  • Save SBell6hf/a6912f06d20ea4a8577f885e141d10a0 to your computer and use it in GitHub Desktop.
Save SBell6hf/a6912f06d20ea4a8577f885e141d10a0 to your computer and use it in GitHub Desktop.
Record and restore MIDI events.
// License: The Unlicense
// args: midiPort:Number
let midiPort = Number(process.argv[2 + 0]);
let binaryMode = false;
let midi = require("midi");
let input = new midi.Input();
if (binaryMode) {
input.on("message", function (deltaTime, message) {
let time = performance.now();
let binMsg = Buffer.alloc(1 + 8 + 8 + b.length);
binMsg[0] = binMsg.length - 1 - 8 - 8;
binMsg.writeDoubleBE(time, 1);
binMsg.writeDoubleBE(deltaTime, 9);
for (let i = 0; i < message.length; i++) {
binMsg[17 + i] = message[i];
}
process.stdout.write(binMsg);
});
} else {
input.on("message", function (deltaTime, message) {
console.log(JSON.stringify([performance.now(), deltaTime, message]));
});
}
input.ignoreTypes(false, true, false);
input.openPort(midiPort);
// args: midiPort:Number
let midiPort = Number(process.argv[2 + 0]);
let midi = require("midi");
let event2Promise = (ee, en) => {
return new Promise((resolve, reject) => {
for (let i of en) {
ee.on(i, function () {
resolve([i, arguments]);
});
}
ee.on("error", function () {
reject(arguments)
});
});
};
let streamGetLine = async (stream, lineTerminator, endErr = "streamGetLine/streamEnded", readLength = 512) => {
if (stream._streamGetLineBuffer === undefined) {
stream._streamGetLineBuffer = Buffer.alloc(0);
}
let ret = Buffer.alloc(0), emptyBuffer = Buffer.alloc(0);
do {
let i;
stream._streamGetLineBuffer = Buffer.concat([stream._streamGetLineBuffer, stream.read(readLength) || emptyBuffer]);
for (i = 0; i < stream._streamGetLineBuffer.length; i++) {
if (stream._streamGetLineBuffer[i] === lineTerminator) {
ret = Buffer.concat([ret, stream._streamGetLineBuffer.slice(0, i)]);
stream._streamGetLineBuffer = stream._streamGetLineBuffer.slice(i + 1);
i = -1;
break;
}
}
if (i === -1) {
break;
}
ret = Buffer.concat([ret, stream._streamGetLineBuffer]);
stream._streamGetLineBuffer = Buffer.alloc(0);
if (!stream.readable) {
if (ret.length) {
break;
}
let err = new Error("streamGetLine: Stream ended");
err.name = endErr;
throw err;
}
await event2Promise(stream, ["readable", "end"]);
} while (true);
return ret;
};
let eventRestore = (getNextEvent, eventHandler, options = {}) => {
options = {
minQNum: 128,
minQTime: 1000,
maxQNum: 256,
maxQTime: 3000,
maxAnd: false,
minAnd: false,
timeShift: 0,
timeShiftBase: null,
...options
};
let queuedNum = 0, lastQMsgTime = -1, end = false, ret, pushQueueRunning = false, onQueueDrain = async () => {
if (pushQueueRunning) {
return;
}
pushQueueRunning = true;
while ( options.maxAnd
? (queuedNum < options.maxQNum && lastQMsgTime - performance.now() < options.maxQTime)
: (queuedNum < options.maxQNum || lastQMsgTime - performance.now() < options.maxQTime)
) {
let event = await getNextEvent();
if (event === "end") {
end = true;
onQueueDrain = () => {
if (queuedNum === 0) {
ret.status = "end";
eventHandler("end");
}
};
break;
}
lastQMsgTime = event.time + options.timeShiftBase + options.timeShift;
++ queuedNum;
setTimeout(() => {
eventHandler(event);
-- queuedNum;
if ( options.minAnd
? (queuedNum < options.minQNum && lastQMsgTime - performance.now() < options.minQTime)
: (queuedNum < options.minQNum || lastQMsgTime - performance.now() < options.minQTime)
) {
onQueueDrain();
}
}, lastQMsgTime - performance.now());
}
if (queuedNum === 0 && end) {
ret.status = "end";
eventHandler("end");
}
pushQueueRunning = false;
};
return ret = {
run: () => {
if (options.timeShiftBase === null) {
options.timeShiftBase = performance.now();
}
onQueueDrain();
ret.status = "running";
},
options: options,
status: "waiting"
}
};
(async () => {
let output = new midi.Output();
output.openPort(midiPort);
eventRestore(async () => {
let message;
try {
message = JSON.parse((await streamGetLine(process.stdin, "\n".charCodeAt(0))).toString());
} catch (err) {
if (err.name !== "streamGetLine/streamEnded") {
throw err;
}
return "end";
}
return {
time: message[0],
body: message[2]
};
}, async (event) => {
if (event === "end") {
output.closePort();
} else {
output.send(event.body);
}
}).run();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment