Last active
January 31, 2022 13:16
-
-
Save SBell6hf/a6912f06d20ea4a8577f885e141d10a0 to your computer and use it in GitHub Desktop.
Record and restore MIDI events.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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