UNPKG

midiman

Version:

Manager for synthesizer sounds that are transmitted via MIDI sysex

228 lines (203 loc) 6.24 kB
/** * Class Roland * manages communication with a Roland Synthesizer via Sysex Messages. * It is a Multiton. Each instance can be identified by the triple [MidiIn, MidiOut, MidiChannel]. * But the devices are managed by the top level application mm.js, so theInstances are not used at all. */ var Combi = require('./sysex'); var Sysex = Combi.rs; var Patch = require('./rolandpatch'); const Base64 = require('Base64'); var theInstances = []; module.exports = class Roland { constructor(MIn, MOut, MChan) { this.mIn = MIn; this.mOut = MOut; this.mChan = MChan; this._clipboard = new Patch(); } isThis(MIn, MOut, MChan) { return this.mIn.id == MIn.id && this.mOut.id == MOut.id && this.mChan == MChan; } static getInstance(MIn, MOut, MChan) { var nins; theInstances.forEach((ins) => { if (ins.isThis(MIn, MOut, MChan)) nins = ins; }); if (nins) return nins; nins = new Roland(MIn, MOut, MChan); console.log(`new Roland instance for ${MIn.id}, ${MOut.id}, ${MChan}`); theInstances.unshift(nins); // always return the last with getSingle() return nins; } static getSingle() { return theInstances[0]; } get sysexData() { return this.DataSet.raw; } get clipboard() { return this._clipboard; } /* getCurrentPatchName() { var RequestData = new Sysex(); this.DataSet = new Sysex(); var Ret = this.DataSet.listen(this.mIn); RequestData.brand = 0x41; RequestData.channel = this.mChan; RequestData.model = 0x14; RequestData.command = 0x11; RequestData.append(Patch.num2threebyte(384)); RequestData.append(Patch.num2threebyte(18)); RequestData.send(mOut); return Ret; } */ readCurrentPatch() { var curpat = new Patch (); return new Promise((resolve,reject) => { curpat.readFromSynth(this.mIn, this.mOut, this.mChan).then((ign) => { this._clipboard = curpat; resolve(curpat); }).catch ((e) => { reject(e); }); }); } readMemoryFromSynth() { return new Promise((resolve,reject) => { Patch.waitForWSD(this.mIn).then((sx) => { // console.log(`received 0x${sx.command.toString(16)}`); Patch.readMemoryFromSynth(this.mIn, this.mOut, this.mChan).then((pat) => { var Names = []; this.SynthPatches = pat; pat.forEach((pt) => { Names.push(pt.patchname); }); resolve(Names); }).catch((err) => { reject(err); }); }).catch ((err) => { reject(err); }); }); } writeMemoryToSynth() { // Sysex.trace = true; return new Promise((resolve, reject) => { if (this.SynthPatches == undefined) return reject(new Error("No patches loaded")); Patch.waitForRQD(this.mIn).then((sx) => { let Ret = Patch.waitForACK(this.mIn); Patch.anounceAllPatches(this.mOut, this.mChan); return Ret; }).then((sx) => { return Patch.writeMemoryToSynth(this.SynthPatches, this.mIn, this.mOut, this.mChan); }).then(() => { resolve("Ok"); }).catch ((e) => { reject(new Error(e)); }); // must return a Promise here, or the caller will break on 'then' // this is, what the return statement above does. // the last then or the catch resolve or reject the returned promise }); } readMemoryFromDataURL(postdat) { return new Promise((resolve,reject) => { var start = postdat.indexOf('base64,'); if (start == -1) reject("base64 error"); try { var Names = []; // why doen't this work? // var Dat = Uint8Array.from(Base64.atob(postdat.slice(start+7))); var Dat = Base64.atob(postdat.slice(start+7)); var DatArr = new Array(Dat.length); for (var i=0; i< Dat.length; i++) DatArr[i] = Dat.charCodeAt(i); this.FilePatches = Patch.readMemoryFromBlob(DatArr); this.FilePatches.forEach((pt) => { Names.push(pt.patchname); }); resolve(Names); } catch (e) { reject(e); } }); } /** * writeMemoryToData * The current role is MIDI-Server and we create a sysex file for download to the client. * The browser will store it there. */ writeMemoryToData() { return new Promise((resolve,reject) => { if (this.FilePatches == undefined) reject(new Error("No patches loaded")); try { var DatArr = Patch.writeMemoryToBlob(this.FilePatches); resolve(DatArr); } catch (e) { reject(e); } }); } /** * writeMemoryToData * The current role is MIDI-Server and we create a sysex file for download to the client. * The browser will store it there. */ writePatchToData() { return new Promise((resolve,reject) => { if (this._clipboard == undefined) reject(new Error("Clipboard empty")); try { var DatArr = Patch.writePatchToBlob(this._clipboard); resolve(DatArr); } catch (e) { reject(e); } }); } swap() { var tmp = this.FilePatches; this.FilePatches = this.SynthPatches; this.SynthPatches = tmp; } _getOrSetVar(id, value) { var ind; switch (id[0]) { case 'c': if (value != undefined) this._clipboard = value; else return this._clipboard; break; case 's': ind = Number(id.substr(1)); if (value != undefined) this.SynthPatches[ind] = value; else return this.SynthPatches[ind]; break; case 'f': ind = Number(id.substr(1)); if (value != undefined) this.FilePatches[ind] = value; else return this.FilePatches[ind]; break; } } move(from, to) { if ((from[0] == 's' || to[0] == 's') && this.SynthPatches == undefined) return "SynthPatches undefined!"; if ((from[0] == 'f' || to[0] == 'f') && this.FilePatches == undefined) return "FilePatches undefined!"; try { this._getOrSetVar(to, this._getOrSetVar(from)); return {ok:this._getOrSetVar(from).patchname}; } catch (e) { return {error:e.toString()}; } } test(postdat) { if (this._clipboard == undefined) return Promise.reject(new Error("Clipboard empty")); if (!this._clipboard.complete) return Promise.reject(new Error("Clipboard incomplete")); else return this._clipboard.test(this.mIn, this.mOut, this.mChan, postdat); } }