midiman
Version:
Manager for synthesizer sounds that are transmitted via MIDI sysex
323 lines (296 loc) • 9.01 kB
JavaScript
"use strict";
/**
* Class Korg
* manages communication with a Korg Triton via Sysex Messages.
* The clipboard reads the single edit buffer from the synth
*/
// for tracing
var Combi = require('./sysex');
var Sysex = Combi.kg;
var KorgPatchModule = require('./korgpatch');
var Patch = KorgPatchModule.base;
var SinglePatch = KorgPatchModule.single;
var MultiPatch = KorgPatchModule.multi;
const Base64 = require('Base64');
var theInstances = [];
module.exports = class Korg {
constructor(MIn, MOut, MChan) {
this.mIn = MIn;
this.mOut = MOut;
this.mChan = MChan;
this._clipboard = new SinglePatch();
}
get sysexData() {
return this.DataSet.raw;
}
get clipboard() {
return this._clipboard;
}
readCurrentPatch() {
return new Promise((resolve,reject) => {
var curpat;
Patch.getDeviceMode(this.mIn, this.mOut, this.mChan)
.then((mode) => {
switch(mode) {
case 0:
case 1:
curpat = new MultiPatch();
break;
case 2:
case 3:
curpat = new SinglePatch();
break;
default:
return Promise.reject("Mode not program or combi");
}
return Promise.resolve("Ok");
})
.then(() => {
return curpat.readFromSynth(this.mIn, this.mOut, this.mChan);
})
.then(() => {
this._clipboard = curpat;
resolve(curpat);
})
.catch ((e) => {
reject(e);
});
});
}
writeCurrentPatch() {
return new Promise((resolve,reject) => {
this._clipboard.writeToSynth(this.mIn, this.mOut, this.mChan).then((ign) => {
resolve(this.clipboard);
}).catch ((e) => {
reject(e);
});
});
}
/**
* writePatchToData
* The current role is MIDI-Server and we create a sysex file for download to the client.
* The browser will store it there.
*/
writePatchToData() {
Sysex.trace = true;
return new Promise((resolve,reject) => {
if (this._clipboard == undefined) reject(new Error("Clipboard empty"));
try {
var DatArr = this._clipboard.writeToBlob();
resolve(DatArr);
} catch (e) {
reject(e);
}
});
}
readMemoryFromSynth(postdat) {
var BankTypeObject;
if (postdat.type == 'S') {
if (postdat.Bank == 0) this.SynthPatches = [];
BankTypeObject = new SinglePatch();
} else {
BankTypeObject = new MultiPatch();
}
return new Promise((resolve,reject) => {
BankTypeObject.readMemoryBankFromSynth(this.mIn, this.mOut, this.mChan, postdat).then((res) => {
let Names = [];
this.SynthPatches.push(res);
res.pat.forEach((bk) => {
Names.push(bk.patchname);
});
resolve({pat:Names, type: res.type});
}).catch ((err) => {
reject(err);
});
});
}
writeMemoryToSynth(postdat) {
var TypedPatch;
if (this.SynthPatches == undefined) {
return Promis.reject("No patches loaded");
}
if (postdat.bnk[0] == 'S') {
TypedPatch = new SinglePatch();
} else {
TypedPatch = new MultiPatch();
}
return TypedPatch.writeMemoryBankToSynth(this.SynthPatches, this.mIn, this.mOut, this.mChan, postdat);
}
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((bank) => {
let bk = [];
bank.pat.forEach((pt) => {
bk.push(pt.patchname);
});
Names.push({pat:bk, type:bank.type});
});
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);
}
});
}
/**
* swap
* swaps the SynthPatches with the FilePatches completely.
*/
swap() {
var tmp = this.FilePatches;
this.FilePatches = this.SynthPatches;
this.SynthPatches = tmp;
}
/**
* _isCompatible(value, bank)
* checks, if the patch value is compatible with the bank (single / multi)
* and returns an error message, if not
*/
_isCompatible(value, btp) {
if (value instanceof SinglePatch) {
if (btp != 'S')
return "Cannot move single patch to combi bank";
} else {
if (btp != 'M')
return "Cannot move combi patch to single bank";
}
return "Ok";
}
_getOrSetVar(id, value) {
var bank;
var ind;
switch (id[0]) {
case 'c':
if (value != undefined) this._clipboard = value;
else return this._clipboard;
break;
case 's':
bank = Patch.bankLetter2Index(id[1], id[2]);
ind = Number(id.substr(3));
if (value != undefined) {
let comp = this._isCompatible(value, id[1]);
if (comp == "Ok")
this.SynthPatches[bank].pat[ind] = value;
else
throw comp;
} else {
return this.SynthPatches[bank].pat[ind];
}
break;
case 'f':
bank = Patch.bankLetter2Index(id[1], id[2]);
ind = Number(id.substr(3));
if (value != undefined) {
let comp = this._isCompatible(value, id[1]);
if (comp == "Ok")
this.FilePatches[bank].pat[ind] = value;
else
throw comp;
} else {
return this.FilePatches[bank].pat[ind];
}
break;
}
}
/**
* move a Patch in the banks
* from and to are server strings from th ui.
* The server string takes the form [sf][SM][A-N]\d{1,3} with the number in the range 0-127.
* A special case is the clipboard, which has only "c" as the server string.
*/
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()};
}
}
changeProg(postdat) {
let to = postdat.to;
if (to[0] == 'f') return Promise.resolve("Cannot change prog in file bank");
if (to[0] == 's' && this.SynthPatches == undefined) return Promise.resolve("SynthPatches undefined!");
return Patch.changeProg(this.mIn, this.mOut, this.mChan, to.substr(1));
}
test(postdat) {
if (this._clipboard == undefined) return Promise.reject(new Error("Clipboard empty"));
else return this._clipboard.test(this.mIn, this.mOut, this.mChan, postdat);
}
/**
* compare
* compares the current patch on the synth patch against a patch from this.SynthPatches.
* This is used to test if the patches are also "corrected" by memory bank transfer
*/
compare(postdat) {
let to = postdat.to;
if (to[0] == 'f') return Promise.reject("Cannot change prog in file bank");
if (to[0] == 's' && this.SynthPatches == undefined) return Promise.reject("SynthPatches undefined!");
return new Promise((resolve,reject) => {
this.readCurrentPatch().then(current => {
let ind = Patch.bankLetter2Index(to[1],to[2]);
let comp = this.SynthPatches[ind].pat[Number(to.substr(3))];
if (current.isA() != comp.isA()) reject(`Current patch is a ${current.isA()} whereas ${comp.patchname} is a ${comp.isA()}`);
console.log(`Comparing current patch from ${to} (${current.patchname}) with SynthPatches[${ind}].pat[${Number(to.substr(3))}] (${comp.patchname})`);
resolve(current.diffTo(comp.__sd));
}).catch(e => {
reject(e);
});
});
}
/**
* comparePatchToFile
* compares the clipboard patch against a file
* this is a korg specialty
*/
comparePatchToFile(postdat) {
return new Promise((resolve,reject) => {
if (this._clipboard == undefined) {
return reject(new Error("Clipboard empty"));
} else {
console.log("Comparing clipboard against selected file");
let cont = postdat.Cont;
let extension = postdat.ext;
let start = cont.indexOf('base64,');
if (start == -1) reject("base64 error");
try {
var Dat = Base64.atob(cont.slice(start+7));
var DatArr = new Array(Dat.length);
for (var i=0; i< Dat.length; i++)
DatArr[i] = Dat.charCodeAt(i);
let ref = Patch.readFromBlob(extension, DatArr);
resolve(this._clipboard.diffTo(ref.__sd));
} catch (e) {
reject(e);
}
}
});
}
}