/* eslint no-console: off */
const readline = require('readline');
const { basename } = require('path');
const MidiFile = require('./file/index');
const pkg = require('../package');
let cli;
let outputPortArgOrEnvVar;
let outputFile;
for (let i=2; i<process.argv.length; i++) {
const arg = process.argv[i];
if (arg === '-p') outputPortArgOrEnvVar = process.argv[++i] || '';
if (arg === '-f') outputFile = process.argv[++i];
}
if (outputPortArgOrEnvVar == null) outputPortArgOrEnvVar = process.env.CHORUS_OUTPUT_PORT;
function usage() {
console.log(`
Usage
File output: node node_modules/chorus/examples/${basename(process.argv[1])} -f [outputFile]
Realtime output: node node_modules/chorus/examples/${basename(process.argv[1])} -p [outputPort]
Or set outputPort with the environment variable CHORUS_OUTPUT_PORT
No MIDI output specified.\n`);
}
function findPortMatches(ports, substrings) {
return ports.filter(port => substrings.find(substring => port.toLowerCase().includes(substring)));
}
function findPort(ports, portSearch) {
return new Promise((resolve, reject) => {
portSearch = portSearch.toLowerCase();
let matches = findPortMatches(ports, [portSearch]);
if (!matches.length) {
matches = findPortMatches(ports, portSearch.split(/\s+/));
}
switch (matches.length) {
case 0:
console.log('No port found:', portSearch);
return reject();
case 1:
return resolve(matches[0]);
default:
console.log('Found multiple ports:');
return reject(matches);
}
});
}
function selectPort(ports) {
return new Promise((resolve, reject) => {
console.log(' ', ports.join('\n '));
cli.question('Which port? ', portSearch => {
findPort(ports, portSearch)
.then(resolve)
.catch(matches => (matches ? resolve(selectPort(matches)) : reject()));
});
});
}
function selectAndOpenOutput(midiOut, portSearch) {
if (!cli) cli = readline.createInterface({ input: process.stdin, output: process.stdout });
const ports = midiOut.ports();
if (!portSearch) console.log('MIDI output ports:');
return (portSearch ? findPort(ports, portSearch) : selectPort(ports))
.then(port => {
cli.close();
cli = null;
midiOut.open(port);
return midiOut
})
.catch(() => selectAndOpenOutput(midiOut));
}
/**
* Provides a MIDI realtime or file output to play/write a Song.
*/
class Output {
/**
* Select the MIDI output interactively, or via a command line argument or environment variable.
* @param constructorOptions options object passed to the [constructor]{@link MidiOut}.
* @returns {Promise} a Promise object that resolves to a MidiOut instance
* @example
* # chorus-script.js
* const { Song } = require('chorus');
* const { MidiOut } = require('chorus/midi');
* const song = new Song(...);
* MidiOut.select().then(midiOut => midiOut.play(song));
*
* ____________________________________________________
* # command line usage
*
* # realtime output with the given port:
* node chorus-script.js -p midiPortName
*
* # or via an environment variable:
* CHORUS_OUTPUT_PORT=midiPortName node chorus-script.js
*
* # file output:
* node chorus-script.js -f midiFileName
*/
static select(midiOutOptions) {
if (outputFile) {
// Provide the same output.play(song) interface from MidiOut
return Promise.resolve({
play(song) {
return new MidiFile(outputFile)
.write(song.toJSON())
.then(() => console.log(`Wrote MIDI file ${outputFile}`))
.catch(err => console.error(`Failed to write MIDI file ${outputFile}`, err));
}
});
}
if (!outputPortArgOrEnvVar) usage();
try {
const midiOut = new (require('./midi-out'))(midiOutOptions); // using a deferred require because MidOut requires the midi module
return selectAndOpenOutput(midiOut, outputPortArgOrEnvVar);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.error('ERROR: Cannot list MIDI ports because the midi npm module is not installed.');
console.error('You must `npm install midi` or use the -f option for file output.');
console.error(`See the README for more info: ${pkg.repository.url}#installation`);
process.exit(1);
} else {
throw err;
}
}
}
}
module.exports = Output;