midi-auto-note-off
Version:
Solves the problem with certain e-drums not sending MIDI NOTE OFF command by playing a proxy and adding the missing MIDI NOTE off command.
323 lines (278 loc) • 11.2 kB
JavaScript
/**
@author Superfusion Mobile - superfusion.mobile@googlemail.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
⭑ ⭑ ⭑ 🥁 Auto MIDI NOTE OFF 🥁 ⭑ ⭑ ⭑
This programm solves a common problem with certain e-drum sets
which do not send any MIDI *NOTE OFF* command what
make a note last for ever.
Every note that is played will consume the total available polyphony and
the DAW will not play any note from there on.
I have been confronted with this issue in Logic Pro X
with the e-drum set Millenium HD-120
This programm adds to every MIDI *NOTE ON* command
received from the target input device
an additional MIDI *NOTE OFF* command
🥁 🥁 🥁
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Usage :
node ${scriptFile} "<Input-Device-Name>" "<Output-Device-Name> (optional)"
*/
const os = require('os')
const isWin = os.platform().toLowerCase() === 'win32' || os.platform().toLowerCase() === 'win64'
// when device argument is missing it will use the first midi device that has been found
const autoUseFirstDeviceFound = false
const easymidi = require('easymidi')
const { resolve } = require('path')
const outputDeviceNameDefault = 'Virtual MIDI output device - Auto NOTE OFF'
/**
*
* @param {*} inputDeviceName The name of the MIDI input device. Must not be `null`.
* @param {*} outputDeviceName The name of the output MIDI device. If `null` or empty then a virtual device will be created.
* @param {*} verbose Set to `true` to log all received and sent MIDI messages to console
*/
const startAutoNoteOff = (inputDeviceName, outputDeviceName, verbose) => {
const inputDevice = new easymidi.Input(inputDeviceName, false)
const outputVirtual = new easymidi.Output(outputDeviceName || outputDeviceNameDefault, (outputDeviceName ? false : true))
/** auto send note-off on note-on - begin */
const deviceSource = inputDevice
const deviceTarget = outputVirtual
if (verbose) {
console.log('DEBUG: listen on device', deviceSource.name)
}
if (verbose) {
console.log('DEBUG: destination device', deviceTarget.name)
}
deviceSource.on('noteon', function (msg) {
if (verbose) {
console.log('DEBUG: received msg "noteon"', msg)
}
deviceTarget.send('noteon', msg)
if (verbose) {
console.log('DEBUG: sent msg "noteon"', msg)
}
const noteOffMsg = Object.assign(msg)
noteOffMsg.velocity = 0
noteOffMsg._type = 'noteoff'
deviceTarget.send('noteoff', noteOffMsg)
if (verbose) {
console.log('DEBUG: sent msg "noteoff"', noteOffMsg)
}
})
/** auto send note-off on note-on - end */
}
/** main */
const path = require('path')
const scriptFile = path.parse(path.basename(process.argv[1])).name
// when started from a npm link script then no file extension is present ;-)
const startProgrammName = process.argv[1].endsWith('.js') ? 'node ' : ''
const printUsage = () => {
const usage = `
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
⭑ ⭑ ⭑ 🥁 Auto MIDI NOTE OFF 🥁 ⭑ ⭑ ⭑
This programm solves a common problem with certain e-drum sets
which do not send any MIDI *NOTE OFF* command what
make a note last for ever.
Every note that is played will consume the total available polyphony and
the DAW will not play any note from there on.
I have been confronted with this issue in Logic Pro X
with the e-drum set Millenium HD-120
This programm adds to every MIDI *NOTE ON* command
received from the target input device
an additional MIDI *NOTE OFF* command
🥁 🥁 🥁
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Usage :
${startProgrammName}${scriptFile} "<Input-Device-Name>" "<Output-Device-Name> (optional)"
. . . . . . . . . . . . .
Below is a list of recognized devices:
Input devices (Target)
\t${easymidi.getInputs().length ? easymidi.getInputs().join('\n\t') : '<no device connected>'}
Output devices
\t${easymidi.getOutputs().length ? easymidi.getOutputs().join('\n\t') : '<no device connected>'}
Example :
${startProgrammName}${scriptFile} "${easymidi.getInputs()[0] || 'TDX-15 MIDI'}"
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
`
console.log(usage)
}
const options = {}
process.argv.slice(2).filter(item => {
if (item === '-h' || item === '--help' || -1 != item.indexOf('help')) {
options.help = true
} else if (item === '-v' || item === '--verbose') {
options.verbose = true
} else {
if (!options.inputDeviceName) {
options.inputDeviceName = item
} else {
options.outputDeviceName = item
}
}
})
if (options.help) {
printUsage()
process.exit(1)
}
if (!easymidi.getInputs().length) {
printUsage()
console.log('⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ')
console.log('⚠ 🎹 ! NO MIDI INPUT DEVICE FOUND ! 🎹 ⚠')
console.log('⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ⚠ ')
process.exit(1)
}
if (autoUseFirstDeviceFound && !options.inputDeviceName && easymidi.getInputs().length === 1) {
options.inputDeviceName = easymidi.getInputs()[0]
}
const main = async () => {
// can create virtual device?
const canCreateVirtualDevice = async () => {
let testVirtualDevice
try {
testVirtualDevice = new easymidi.Output('Test virtual device', true)
} catch (e) {
console.error(e)
testVirtualDevice = null
}
const prom = new Promise((resolve, reject) => {
if (!testVirtualDevice) {
reject(new Error('Virtual device could not be created'))
} else {
try {
testVirtualDevice.send('noteoff', {
note: 1,
velocity: 1,
channel: 1
})
if (!testVirtualDevice.on) {
resolve(true)
}
} catch (e) {
console.error(e)
reject(e)
}
if (testVirtualDevice.on)
try {
testVirtualDevice.on('noteoff', (msg) => {
resolve(true)
})
} catch (e) {
console.error(e)
reject(e)
}
try { testVirtualDevice.close() } catch (e) {
console.error(e)
reject(e)
}
}
})
let retVal = false
try {
retVal = await prom
} catch (e) {
console.error(e)
}
return retVal
}
// promt for device selection
const askForDevice = async (title, devices) => {
const devList = []
devices.filter((item, index, all) => {
devList.push(`${index + 1}) ${item}`)
})
const rlp = require('readline')
const rl = rlp.createInterface({
input: process.stdin,
output: process.stdout
})
const prom = new Promise((resolve, reject) => {
devList.push('')
devList.push('Type key \'Enter\' to exit')
rl.question(
(title) + '\n' +
('\t' + devList.join('\n\t')) +
('\n') +
'Enter number: ',
(input) => {
rl.close()
resolve(input)
}
)
})
let selectedIndex = -2
try {
selectedIndex = await prom
if (selectedIndex === '' || selectedIndex === '0' || selectedIndex === 'Enter') {
// provoke exit
selectedIndex = -2
}
} catch (e) {
// nothing selected
}
if (!selectedIndex) {
selectedIndex = 1
}
return devices[Number.parseInt(selectedIndex) - 1]
}
if (!options.inputDeviceName) {
// ask for device from list
const deviceName = await askForDevice('Select the input MIDI device', easymidi.getInputs())
if (deviceName)
options.inputDeviceName = deviceName
}
if (!options.inputDeviceName) {
printUsage()
console.log()
console.log(' ⚠ ⚠ ⚠ ! Input MIDI device required ! ⚠ ⚠ ⚠')
console.log()
process.exit(1)
}
const requireOutputDevice = isWin || !(await canCreateVirtualDevice())
if (!options.outputDeviceName && requireOutputDevice) {
const printOutputDeviceMSG = () => {
console.log()
console.log('This operating system is not able to create virtual MIDI devices.')
console.log('You must select an existing MIDI device or')
console.log('create a virtual device with 3rd-party software like')
console.log('LoopMIDI from http://www.tobias-erichsen.de/software/loopmidi.html')
console.log()
}
// ask for device from list
printOutputDeviceMSG()
const deviceName = await askForDevice('Select the output MIDI device', easymidi.getOutputs())
if (deviceName)
options.outputDeviceName = deviceName
if (!options.outputDeviceName) {
// printUsage()
console.log()
console.log(' ⚠ ⚠ ⚠ ! Output MIDI device required ! ⚠ ⚠ ⚠')
printOutputDeviceMSG()
process.exit(1)
}
}
const testDeviceName = (deviceName, devices, errorMsg) => {
const deviceFoundByName = devices.filter(item => item === deviceName)[0]
if (!deviceFoundByName) {
console.log()
// console.log('⚠ 🎹 ! No MIDI INPUT DEVICE FOUND with name: "' + deviceName + '" ! 🎹 ⚠')
console.log(errorMsg)
console.log('Run the following command to show a list of devices:')
console.log(`\tnode ${scriptFile}`)
console.log()
process.exit(1)
}
return deviceFoundByName
}
const inputDevice = testDeviceName(options.inputDeviceName, easymidi.getInputs(), '⚠ 🎹 ! No MIDI INPUT DEVICE FOUND with name: "' + options.inputDeviceName + '" ! 🎹 ⚠')
const outputDevice = requireOutputDevice ? testDeviceName(options.outputDeviceName, easymidi.getOutputs(), '⚠ 🎹 ! No MIDI OUTPUT DEVICE FOUND with name: "' + options.outputDeviceName + '" ! 🎹 ⚠') : null
console.log(`- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
⭑ ⭑ ⭑ Auto MIDI NOTE OFF started ⭑ ⭑ ⭑
Listening on device:
"${inputDevice}"
Setup your DAW to use the MIDI output device:
"${requireOutputDevice ? outputDevice : outputDeviceNameDefault}"
🥁 🥁 🥁
`)
startAutoNoteOff(inputDevice, outputDevice, options.verbose)
}
main()