ltgcgo/octavia

View on GitHub
src/state/index.mjs

Summary

Maintainability
Test Coverage
// 2022-2025 (C) Lightingale Community
// Licensed under GNU LGPL v3.0 license.
 
"use strict";
 
import {BinaryMatch} from "../../libs/lightfelt@ltgcgo/ext/binMatch.js";
import {CustomEventSource} from "../../libs/lightfelt@ltgcgo/ext/customEvents.js";
import {VoiceBank} from "./bankReader.js";
import {bankDecoder} from "./bankDecoder.js";
import {
xgEffType,
xgPartMode,
xgSgVocals,
xgDelOffset,
xgNormFreq,
xgLfoFreq,
xfEncLabels,
xfSongParts,
xfVocalists,
getSgKana,
getXgRevTime,
getXgDelayOffset,
getVlCtrlSrc
} from "./xgValues.js";
import {
gsRevType,
gsChoType,
gsDelType,
gsParts,
getGsEfx,
getGsEfxDesc,
mt32DefProg
} from "./gsValues.js";
import {
toDecibel,
gsChecksum,
korgFilter,
korgUnpack,
korgPack,
halfByteFilter,
halfByteUnpack,
x5dSendLevel,
getDebugState
} from "./utils.js";
import { Uint8 } from "../../libs/midi-parser@colxi/main.min.js";
 
const modeIdx = [
"?",
"gm", "gs", "sc", "xg", "g2",
"mt32", "doc", "qy10", "qy20",
"ns5r", "x5d", "05rw",
"k11", "sg", "sd", "pa",
"krs", "s90es", "motif", "cs6x", "trin",
"an1x", "cs1x"
],
modeAdapt = {
"gm2": "g2",
"mt-32": "mt32",
"c/m": "mt32",
"ag10": "05rw",
"ag-10": "05rw",
"05r/w": "05rw",
"x5": "05rw",
"x5dr": "x5d",
"gmega": "k11",
"kross 2": "krs",
"motif es": "motif",
"s90 es": "s90es",
"trinity": "trin",
"tr-rack": "trin"
},
voiceIdx = [
"melodic",
"drum",
"menu"
];
let modeDetailsData = { // subMsb, subLsb, drumMsb, defaultMsb, defaultLsb
"?": [0, 0, 120, 0, 0],
"gm": [0, 0, 120, 0, 0],
"gs": [0, 0, 128, 0, 0],
"sc": [0, 0, 128, 0, 0],
"xg": [0, 0, 127, 0, 0],
"g2": [0, 0, 120, 0, 0],
"mt32": [0, 127, 127, 0, 127],
"doc": [57, 112, 127, 57, 112],
"qy10": [57, 113, 127, 57, 113],
"qy20": [57, 114, 127, 57, 114],
"ns5r": [0, 0, 61, 0, 0],
"x5d": [82, 0, 62, 56, 0],
"05rw": [81, 0, 62, 56, 0],
"k11": [0, 0, 122, 0, 0],
"sg": [0, 0, 122, 0, 0],
"sd": [97, 0, 105, 0, 0],
"pa": [0, 0, 120, 121, 0],
"krs": [121, 0, 120, 0, 0],
"s90es": [0, 0, 127, 0, 0],
"motif": [0, 0, 127, 0, 0],
"cs6x": [0, 0, 127, 0, 0],
"trin": [0, 0, 61, 0, 0],
"an1x": [36, 3, 127, 0, 0],
"cs1x": [0, 0, 127, 63, 0],
"cs2x": [0, 0, 127, 63, 0]
};
const drumChannels = [9, 25, 41, 57, 73, 89, 105, 121];
const passedMeta = [0, 3, 81, 84, 88]; // What is meta event 32?
const eventTypes = {
8: "Off",
9: "On",
10: "Note aftertouch",
11: "cc",
12: "pc",
13: "Channel aftertouch",
14: "Pitch"
};
 
const useRpnMap = {
0: 0,
1: 1,
2: 3,
5: 4
},
rpnOptions = {
0: 0,
1: 1,
2: 2,
5: 3
},
rpnCap = [
[0, 24],
[0, 127],
[0, 127],
[40, 88],
[0, 127],
[0, 127]
],
useNormNrpn = [
36, // HPF cutoff freq
37, // Not sure where this came from
48, 49, 52, 53 // MU1000 XG EQ NRPN, has no effect when set to drums
],
useDrumNrpn = [
20, // LPF cutoff freq
21, // LPF resonance
22, // attack rate
23, // decay rate
24, // course tune
25, // fine tune
26, // level (will introduce conflict to the existing system)
28, // panpot
29, // reverb
30, // chorus
31, // variation
36, // HPF cutoff freq
37, // still not sure where this came from
48, // EQ bass gain
49, // EQ treble gain
52, // EQ bass freq
53, // EQ treble freq
64, // not sure where this came from
65 // same as above
],
defDrumNrpn = {
26: 127,
29: 0,
30: 0,
31: 0,
52: 12,
53: 54
},
ccAccepted = [
0, 1, 2, 4, 5, 6, 7, 8, 10, 11,
12, 13, // General-purpose effect controllers
14, 15, // For some reason, used by PLG-VL
16, 17, 18, 19, // General-purpose sound controllers
20, 21, // For some reason, used by PLG-VL
22, // S90 ES and Motif ES
26, 28, // For some reason, used by PLG-VL
32, 38,
40, 41, 42, 43, // PLG-AN AC 1~4 & AN1x AC 1~4
44, 45, 46, 47, // AN1x AC 5~8
64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78,
80, 81, 82, 83, // Used by KORG KROSS 2
84,
91, 92, 93, 94, 95,
98, 99, 100, 101,
128, // Dry level (internal register for Octavia)
129, // PLG-VL part breath strength
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, // PLG-VL part controls
142, 143, 144, 145, 146, 147, 148, 149, // PLG-DX carrier level
150, 151, 152, 153, 154, 155, 156, 157 // PLG-DX modulator level
], // 96, 97, 120 to 127 all have special functions
aceCandidates = [
2, 4,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
40, 41, 42, 43, 44, 45, 46, 47,
80, 81, 83,
136, 130, 131, 132, 133, 134, 135, 137, 138, 139,
142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157
],
nrpnCcMap = [33, 102, 99, 32, 100, 8, 9, 10]; // cc71 to cc78
 
const korgDrums = [0, 16, 25, 40, 32, 64, 24, 48];
 
let modeMap = {};
modeIdx.forEach((e, i) => {
modeMap[e] = i;
});
let ccToPos = [];
ccAccepted.forEach((e, i) => {
ccToPos[e] = i;
});
let dnToPos = {
length: useDrumNrpn.length
};
useDrumNrpn.forEach((e, i) => {
dnToPos[e] = i;
});
let voiceType = {};
voiceIdx.forEach((e, i) => {
voiceType[e] = i;
});
 
let sysExSplitter = function (seq) {
let seqArr = [];
let seqStart = 0;
seq?.forEach(function (e, i) {
if (e === 247) {
// End of SysEx
seqArr.push(seq.subarray(seqStart, i));
} else if (e === 240) {
seqStart = i + 1;
} else {
//seqArr[seqArr.length - 1].push(e);
};
});
if (!seqArr.length) {
seqArr.push(seq.subarray(0));
};
if (getDebugState()) {
console.debug(seqArr);
};
return seqArr;
};
let showTrue = function (data, prefix = "", suffix = "", length = 2) {
return data ? `${prefix}${data.toString().padStart(length, "0")}${suffix}` : "";
};
 
let allocatedPorts = 8;
const allocated = {
port: allocatedPorts,
ch: allocatedPorts << 4, // channels
chShift: Math.ceil(Math.log2(allocatedPorts)) + 4,
cc: ccAccepted.length, // control changes
nn: 128, // notes per channel
pl: 512, // polyphony
tr: 256, // tracks
cmt: 14, // C/M timbre storage size
rpn: 6,
rpnt: 4, // the real size of RPN registers
nrpn: useNormNrpn.length,
ace: 8, // active custom effect
drm: 8, // Drum setup slots
dpn: useDrumNrpn.length, // Drum setup params
dnc: 128, // drum note 0 to 127
ext: 3, // extensions
efx: 7,
cvn: 12, // custom voice names
redir: 32,
vxPrim: 3,
invalidCh: 255
};
allocated.chcc = allocated.cc * allocated.ch;
const overrides = {
bank0: 255
};
 
// Decoders
const decoderL9 = new TextDecoder("l9");
 
// Lookup tables
const ccOffTable = new Uint16Array(allocated.ch);
const rpnOffTable = new Uint16Array(allocated.ch);
const nrpnOffTable = new Uint16Array(allocated.ch);
const aceOffTable = new Uint16Array(allocated.ch);
const extOffTable = new Uint16Array(allocated.ch);
const cvnOffTable = new Uint16Array(allocated.ch);
const dNrpnOffTable = new Array(allocated.drm);
for (let i = 0; i < allocated.ch; i ++) {
ccOffTable[i] = i * allocated.cc;
rpnOffTable[i] = i * allocated.rpn;
nrpnOffTable[i] = i * allocated.nrpn;
//dNrpnOffTable[i] = i * allocated.dpn;
aceOffTable[i] = i * allocated.ace;
extOffTable[i] = i * allocated.ext;
cvnOffTable[i] = i * allocated.cvn;
};
for (let i0 = 0; i0 < allocated.drm; i0 ++) {
dNrpnOffTable[i0] = new Uint16Array(allocated.dpn);
let drumOff0 = i0 * allocated.dpn * allocated.dnc;
for (let i1 = 0; i1 < allocated.dpn; i1 ++) {
dNrpnOffTable[i0][i1] = drumOff0 + i1 * allocated.dnc;
};
};
 
/*
Extensions:
0: part extension mode (EXT_*)
1: VL breath mode (VLBC_*)
2: ACMP toggle
*/
 
let OctaviaDevice = class extends CustomEventSource {
// Constants
NOTE_IDLE = 0;
NOTE_ATTACK = 1;
NOTE_DECAY = 2;
NOTE_SUSTAIN = 3;
NOTE_HELD = 4;
NOTE_RELEASE = 5;
NOTE_SOSTENUTO_ATTACK = 8;
NOTE_SOSTENUTO_DECAY = 9;
NOTE_SOSTENUTO_SUSTAIN = 10;
NOTE_SOSTENUTO_HELD = 11;
NOTE_MUTED_RECOVERABLE = 16;
CH_MELODIC = 0;
CH_DRUMS = 1;
CH_DRUM1 = 2;
CH_DRUM2 = 3;
CH_DRUM3 = 4;
CH_DRUM4 = 5;
CH_DRUM5 = 6;
CH_DRUM6 = 7;
CH_DRUM7 = 8;
CH_DRUM8 = 9;
DUMP_ALL = 0;
DUMP_ONCE = 1;
DUMP_MODE = 2;
EXT_NONE = 0;
EXT_VL = 1;
EXT_DX = 3;
VLBC_SYSTEM = 0; // Follow system
VLBC_BRTHEXPR = 1; // Breath controller or expression
VLBC_VELOINIT = 2; // Initial key velocity
VLBC_VELOALL = 3; // Initial key velocity and aftertouch
CH_INACTIVE = 0;
CH_ACTIVE = 1;
CH_DISABLED = 2;
KARAOKE_NONE = 0;
KARAOKE_TEXT = 1; // Repurposed text events as karaoke lyrics
KARAOKE_XF = 2; // Yamaha XF karaoke lyrics
// Values
#mode = 0;
#bitmapPage = 0;
#bitmapExpire = 0;
#bitmapStore = new Array(11); // 10 pages of bitmaps, 1 KORG bitmap
get #bitmap() {
return this.#bitmapStore[this.#bitmapPage];
};
set #bitmap(value) {
this.#bitmapStore[this.#bitmapPage] = value;
};
#chActive = new Uint8Array(allocated.ch); // Whether the channel is in use
#chReceive = new Uint8Array(allocated.ch); // Determine the receiving channel
#chType = new Uint8Array(allocated.ch); // Types of channels
#cc = new Uint8Array(allocated.chcc << 1); // 64 channels, 128 controllers, all with write state tracks which utilizes the upper half of the space
#ace = new Uint8Array(allocated.ch * allocated.ace); // 4 active custom effects
#prg = new Uint8Array(allocated.ch * allocated.vxPrim); // segmented by channels; (part) for program, (allocated.ch | part) for cc0, (2 * allocated.ch | part) for cc32
#velo = new Uint8Array(allocated.ch * allocated.nn); // 128 channels. 128 velocity registers
#mono = new Uint8Array(allocated.ch); // Mono/poly mode
#poly = new Uint16Array(allocated.pl); // 512 polyphony allowed
#polyState = new Uint8Array(allocated.pl); // State of each active voice.
#pitch = new Int16Array(allocated.ch); // Pitch for channels, from -8192 to 8191
#rawStrength = new Uint8Array(allocated.ch);
#dataCommit = new Uint8Array(allocated.ch); // 0 for RPN, 1 for NRPN
#ext = new Uint8Array(allocated.ch * allocated.ext); // Extension configs
#rpn = new Uint8Array(allocated.ch * allocated.rpn); // RPN registers (0 pitch MSB, 1 fine tune MSB, 2 fine tune LSB, 3 coarse tune MSB, 4 mod sensitivity MSB, 5 mod sensitivity LSB)
#rpnt = new Uint8Array(allocated.ch * allocated.rpnt); // Whether or not an RPN has been written
#nrpn = new Int8Array(allocated.ch * useNormNrpn.length); // Normal section of NRPN registers
#drum = new Uint8Array(allocated.drm * allocated.dpn * allocated.dnc); // Drum setup
#drumFirstWrite = new Uint8Array(allocated.drm * 2); // The default source part of drum sets
#efxBase = new Uint8Array(allocated.efx * 3); // Base register for EFX types
#efxTo = new Uint8Array(allocated.ch); // Define EFX targets for each channel
#ccCapturer = new Uint8Array(allocated.ch * allocated.redir); // Redirect non-internal CCs to internal CCs
#portMode = new Uint8Array(allocated.port); // Per-port mode
#portModeInitial = new Uint8Array(allocated.port); // Which port has received the port mode sets?
#chMode = new Uint8Array(allocated.ch); // Per-part mode
#bnCustom = new Uint8Array(allocated.ch); // Custom name activation
#cvnBuffer = new Uint8Array(allocated.ch * allocated.cvn); // Per-channel custom voice name
#cmTPatch = new Uint8Array(128); // C/M part patch storage
#cmTTimbre = new Uint8Array(allocated.cmt * 8); // C/M part timbre storage
#cmPatch = new Uint8Array(1024); // C/M device patch storage
#cmTimbre = new Uint8Array(allocated.cmt * 64); // C/M device timbre storage (64)
#subDb = {};
modelEx = {
"sc": {
"showBar": true,
"invBar": false,
"invDisp": false,
"peakHold": 1
},
"cs1x": {
"perfCh": 0
}
};
#detect;
get detect() {
return structuredClone(this.#detect);
};
#conf;
#confProxy;
#masterVol = 100;
#metaChannel = 0;
#noteLength = 500;
#sgConvertLastSyllable = 0;
#sgRunningLineLength = 0;
#sgMaxLineLength = 32;
#sgSplittedMask = false;
#letterDisp = "";
#letterExpire = 0;
#letterSet = 0;
#selectPort = 0;
#receiveRS = true; // Receive remote switch
#modeKaraoke = 0;
#vlSysBreathMode = 1; // PLG-VL system breath mode
#receiveTree;
#maskNewLyric = false;
#lastSysExSize = 0;
#ccRedirMap = new Array(allocated.ch);
// Metadata text events
#metaTexts = [];
// GS Track Occupation
#trkRedir = new Uint8Array(allocated.ch);
#trkAsReq = new Uint8Array(allocated.tr); // Track Assignment request
baseBank = new VoiceBank("gm2", "ns5r", "xg", "gs", "sd", "gmega", "plg-vl", "plg-pf", "plg-dx", "plg-an", "plg-dr", "plg-sg", "kross", "s90es", "cs2x", "pa"); // Load all possible voice banks
userBank = new VoiceBank("gm2"); // User-defined bank for MT-32, X5DR and NS5R
//bankProps = new SheetD;
initOnReset = false; // If this is true, Octavia will re-init upon mode switches
aiEfxName = "";
polyIndexShrink = true;
polyIndexLatest = 0;
polyIndexLast = 0;
chRedir(part, track, noConquer) {
let upThis = this;
if (upThis.#trkAsReq[track]) {
// Allow part assigning via meta
let metaChosen = (upThis.#trkAsReq[track] - 1) * 16 + part;
return metaChosen;
} else if (upThis.#mode === modeMap.sc || upThis.#mode === modeMap.sd) {
// Do not conquer channels if requested.
if (noConquer === 1) {
return part;
};
let shift = 0, unmet = true;
while (unmet) {
if (upThis.#trkRedir[part + shift] === 0) {
upThis.#trkRedir[part + shift] = track;
console.debug(`Assign track ${track} to channel ${part + shift + 1}.`);
unmet = false;
} else if (this.#trkRedir[part + shift] === track) {
unmet = false;
} else {
shift += 16;
if (shift >= 128) {
shift = 0;
unmet = false;
};
};
};
return part + shift;
} else {
return part;
};
};
getTrackPort(track) {
return this.chRedir(0, track, true) >> 4;
};
// Exec Pools
forceVoiceRefresh() {
for (let part = 0; part < allocated.ch; part ++) {
if (this.#chActive[part]) {
this.dispatchEvent("voice", {
part
});
};
};
};
// Meta event pool
#metaRun = [];
// Sequencer/Implementation-specific meta pool
#metaSeq;
// Universal actions
#uAction = {
nOff: (part, note) => {
// Note off
let rawNote = part * 128 + note;
let polyIdx = this.#poly.lastIndexOf(rawNote);
if (polyIdx > -1) {
if (this.getChCc(part, 64) >> 6) {
// Held by cc64
this.#polyState[polyIdx] = this.NOTE_HELD;
this.dispatchEvent("note", {
part,
note,
velo: this.#velo[rawNote],
state: this.NOTE_HELD
});
} else if ((this.getChCc(part, 66) >> 6) && this.#polyState[polyIdx] === this.NOTE_SOSTENUTO_SUSTAIN) {
// Held by cc66
this.#polyState[polyIdx] = this.NOTE_SOSTENUTO_HELD;
this.dispatchEvent("note", {
part,
note,
velo: this.#velo[rawNote],
state: this.NOTE_SOSTENUTO_HELD
});
} else {
this.#poly[polyIdx] = 0;
this.#velo[rawNote] = 0;
this.#polyState[polyIdx] = this.NOTE_IDLE;
let breathMode = this.getExt(part)[1];
if (breathMode === this.VLBC_VELOINIT ||
breathMode === this.VLBC_VELOALL) {
this.setChCc(part, 129, 0);
};
this.dispatchEvent("note", {
part,
note,
velo: 0,
state: this.NOTE_IDLE
});
};
};
if (this.#mono[part]) {
for (let slot = this.polyIndexLast; slot >= 0; slot --) {
let rawValue = this.#poly[slot];
let ch = rawValue >> 7;
if (ch === part && this.#polyState[slot] !== 0) {
//console.debug(`CH${ch+1} ${rawValue & 127}: ${this.#polyState[slot]}`);
if (this.#polyState[slot] === this.NOTE_MUTED_RECOVERABLE) {
// Should recover to the previous state here, but there aren't ways to store that for now
this.#polyState[slot] = this.NOTE_SUSTAIN;
//console.debug(`State recovered.`);
}/* else {
console.debug(`State persisted.`);
}*/;
break;
};
};
};
if (this.polyIndexShrink && this.polyIndexLast > 0 && this.#polyState[this.polyIndexLast] === 0) {
this.polyIndexLast --;
};
},
nOn: (part, note, velo) => {
// Note on
let rawNote = part * 128 + note;
let place = 0;
if (this.#mono[part]) {
for (let slot = 0; slot <= this.polyIndexLast; slot ++) {
let rawValue = this.#poly[slot];
let ch = rawValue >> 7;
if (ch === part && this.#polyState[slot] !== 0) {
this.#polyState[slot] = this.NOTE_MUTED_RECOVERABLE;
//console.debug(`CH${ch+1} ${rawValue & 127}: ${this.#polyState[slot]}`);
};
};
//console.debug(`Muted previous notes.`);
};
while (this.#polyState[place] > 0 && this.#poly[place] !== rawNote) {
// If just by judging whether a polyphonic voice is occupied,
// "multi" mode is considered active.
// If "rawNote" is also taken into consideration,
// this will be "single" mode instead.
// 0: idle
// 1: attack
// 2: decay
// 3: sustain (active)
// 4: hold
// 5: release
// 6: sostenuto sustain
// 7: sostenuto hold
place ++;
};
if (place < allocated.pl) {
this.#poly[place] = rawNote;
this.#velo[rawNote] = velo;
this.#polyState[place] = this.NOTE_SUSTAIN;
if (this.#rawStrength[part] < velo) {
this.#rawStrength[part] = velo;
};
let breathMode = this.getExt(part)[1];
if (breathMode === this.VLBC_VELOINIT ||
breathMode === this.VLBC_VELOALL) {
this.setChCc(part, 129, velo);
};
this.dispatchEvent("note", {
part,
note,
velo,
state: this.NOTE_SUSTAIN
});
this.polyIndexLatest = place + 1;
if (place > this.polyIndexLast) {
this.polyIndexLast = place;
// getDebugState() && console.debug(`Highest polyphony register index: ${this.polyIndexLast}`);
};
} else {
console.error("Polyphony exceeded.");
};
},
nAt: (part, note, velo) => {
// Note/polyphonic aftertouch
},
cAt: (part, velo) => {
// Channel aftertouch
},
hoOf: (part) => {
// Scan and turn off all notes held by cc64
this.#polyState.forEach((e, i) => {
if (e === this.NOTE_HELD) {
// Held by cc64
let rawNote = this.#poly[i];
let channel = rawNote >> 7;
if (part === channel) {
this.#polyState[i] = this.NOTE_IDLE;
this.#poly[i] = 0;
this.#velo[rawNote] = 0;
this.dispatchEvent("note", {
part,
note: rawNote & 127,
velo: 0,
state: this.NOTE_IDLE
});
};
};
});
},
soOn: (part) => {
// Scan and convert all unoccupied active notes to be managed by sostenuto
this.#polyState.forEach((e, i) => {
let emitEvent;
switch (e) {
case this.NOTE_ATTACK: {
emitEvent = this.NOTE_SOSTENUTO_ATTACK;
break;
};
case this.NOTE_DECAY: {
emitEvent = this.NOTE_SOSTENUTO_DECAY;
break;
};
case this.NOTE_SUSTAIN: {
emitEvent = this.NOTE_SOSTENUTO_SUSTAIN;
break;
};
};
if (emitEvent) {
this.#polyState[i] = emitEvent;
let rawNote = this.#poly[i];
this.dispatchEvent("note", {
part,
note: rawNote & 127,
velo: this.#velo[rawNote],
state: emitEvent
});
};
});
},
soOf: (part) => {
// Scan and turn off all notes held by cc66
this.#polyState.forEach((e, i) => {
if (e === this.NOTE_SOSTENUTO_HELD) {
// Held by cc66
let rawNote = this.#poly[i];
let channel = rawNote >> 7;
if (part === channel) {
this.#polyState[i] = this.NOTE_IDLE;
this.#poly[i] = 0;
this.#velo[rawNote] = 0;
this.dispatchEvent("note", {
part,
note: rawNote & 127,
velo: 0,
state: this.NOTE_IDLE
});
};
};
});
},
ano: (part) => {
// All notes off
// Current implementation uses the static velocity register
this.#poly.forEach((e) => {
let ch = e >> 7, no = e & 127;
if (e === 0 && this.#velo[0] === 0) {
} else if (ch === part) {
this.#uAction.nOff(ch, no);
};
});
}
};
// Channel event pool
#runChEvent = {
8: function (det) {
let part = det.channel;
// Note off, velocity should be ignored.
let rawNote = det.data[0];
this.#uAction.nOff(part, rawNote);
},
9: function (det) {
let part = det.channel;
let upThis = this;
// Note on, but should be off if velocity is 0.
if (upThis.getChModeId(part) === modeMap.xg) {
if (upThis.#chType[part] > 1) {
let fwData = upThis.getChDrumFirstWrite(part);
if (fwData[0] && fwData[1] !== allocated.invalidCh && !upThis.#chActive[part]) {
upThis.copyChSetup(fwData[1], part);
upThis.dispatchEvent("voice", {
part
});
console.debug(`Part CH${part + 1} copied from CH${fwData[1] + 1}.`);
};
};
};
// Set channel active
upThis.setChActive(part, 1)
let rawNote = det.data[0];
let velocity = det.data[1];
if (velocity > 0) {
upThis.#uAction.nOn(part, rawNote, velocity);
} else {
upThis.#uAction.nOff(part, rawNote);
};
},
10: function (det) {
let part = det.channel;
// Note aftertouch.
// Currently it directly changes velocity to set value.
let rawNote = part * 128 + det.data[0];
let polyIdx = this.#poly.indexOf(rawNote);
if (polyIdx > -1) {
this.#velo[rawNote] = data[1];
let breathMode = this.getExt(part)[1];
if (breathMode === this.VLBC_VELOALL) {
this.setChCc(part, 129, data[1]);
};
this.dispatchEvent("note", {
part,
note: det.data[0],
velo: det.data[1],
state: this.NOTE_SUSTAIN
});
};
},
11: function (det) {
let part = det.channel;
let upThis = this;
// Redirect CC event
let redirMap = upThis.#ccRedirMap[part],
redirTarget = redirMap[det.data[0]];
if (redirTarget) {
//console.debug(`Redirected cc${det.data[0]} on CH${part + 1} to cc${redirTarget}.`);
det.data[0] = redirTarget;
};
// CC event, directly assign values to the register.
if ([0, 32].indexOf(det.data[0]) > -1) {
(() => {
switch(this.#mode) {
case modeMap.s90es:
case modeMap.motif: {
if (det.data[0] === 0) {
([0, 63].indexOf(det.data[1]) > -1) && upThis.setChActive(part, 1);
break;
};
det.data[1] && upThis.setChActive(part, 1);
break;
};
default: {
//this.setChActive(part, 1);
break;
};
};
})();
};
let extOff = part * allocated.ext,
partMode = upThis.getChModeId(part);
// Non-store CC messages
switch (det.data[0]) {
case 96: {
// RPN Data increment
return;
break;
};
case 97: {
// RPN Data decrement
return;
break;
};
case 120: {
// All sound off, but keys stay on
return;
break;
};
case 121: {
// Reset controllers
this.#uAction.ano(part);
this.#pitch[part] = 0;
// Reset to zero
upThis.resetChCc(part, 1, 0); // Modulation
upThis.resetChCc(part, 5, 0); // Portamento Time
upThis.resetChCc(part, 64, 0); // Sustain
upThis.resetChCc(part, 65, 0); // Portamento
upThis.resetChCc(part, 66, 0); // Sostenuto
upThis.resetChCc(part, 67, 0); // Soft Pedal
// Reset to full
upThis.resetChCc(part, 11, 127); // Expression
// RPN/NRPN to null
upThis.resetChCc(part, 101, 127);
upThis.resetChCc(part, 100, 127);
upThis.resetChCc(part, 99, 127);
upThis.resetChCc(part, 98, 127);
return;
break;
};
case 123: {
// All notes off
this.#uAction.ano(part);
return;
break;
};
case 124: {
// Omni off
this.#uAction.ano(part);
return;
break;
};
case 125: {
// Omni on
this.#uAction.ano(part);
return;
break;
};
case 126: {
// Mono mode
this.#mono[part] = 1;
this.#uAction.ano(part);
return;
break;
};
case 127: {
// Poly mode
this.#mono[part] = 0;
this.#uAction.ano(part);
return;
break;
};
};
// Check if control change is accepted
if (ccToPos[det.data[0]] === undefined) {
console.warn(`cc${det.data[0]} is not accepted.`);
} else {
// ACE allocation
if (aceCandidates.indexOf(det.data[0]) > -1) {
this.allocateAce(part, det.data[0]);
};
// Stored CC messages
switch (det.data[0]) {
case 0: {
// Detect mode via bank MSB
if (getDebugState()) {
console.debug(`${modeIdx[this.#mode]}, CH${part + 1}: ${det.data[1]}`);
};
if (this.#mode === 0) {
if (det.data[1] < 48) {
// Do not change drum channel to a melodic
if (this.#chType[part] > 0) {
det.data[1] = this.getChPrimitive(part, 1, false) /*this.getChCc(part, 0)*/;
det.data[1] = modeDetailsData.gs[2];
console.debug(`Forced channel ${part + 1} to stay drums.`);
};
if (det.data[1] > 0) {
console.debug(`Roland GS detected with MSB: ${det.data[1]}`);
this.switchMode("gs");
};
} else if (det.data[1] === 56) {
this.switchMode(this.#detect.x5 === 82 ? "x5d" : "05rw");
} else if (det.data[1] === 62) {
this.switchMode(this.#detect.x5 === 82 ? "x5d" : "05rw");
} else if (det.data[1] === 63) {
this.switchMode(modeIdx[this.#detect.ds]);
} else if (det.data[1] === 64 || det.data[1] === 127) {
this.switchMode("xg");
} else if (det.data[1] === 120 || det.data[1] === 121) {
this.switchMode("g2");
};
} else if (this.#mode === modeMap.gs || this.#mode === modeMap.sc) {
if (det.data[1] < 56) {
// Do not change drum channel to a melodic
if (this.#chType[part] > 0) {
det.data[1] = this.getChPrimitive(part, 1, false) /*this.getChCc(part, 0)*/;
det.data[1] = modeDetailsData.gs[2];
console.debug(`Forced channel ${part + 1} to stay drums.`);
};
};
} else if (this.#mode === modeMap.gm) {
if (det.data[1] < 48) {
// Do not change drum channel to a melodic
if (this.#chType[part] > 0) {
det.data[1] = modeDetailsData.gs[2];
this.switchMode("gs", true);
console.debug(`Forced channel ${part + 1} to stay drums.`);
};
} else if (det.data[1] === 64 || det.data[1] === 127) {
this.switchMode("xg", true);
};
}/* else if (this.#mode === modeMap.x5d) {
if (det.data[1] > 0 && det.data[1] < 8) {
this.switchMode("05rw", true);
};
}*/;
switch (this.#mode) {
case modeMap.xg: {
if ([79, 95, 126, 127].indexOf(det.data[1]) > -1) {
if (this.#chType[part] === 0) {
this.setChType(part, this.CH_DRUM2);
console.debug(`CH${part + 1} set to drums by MSB.`);
};
} else {
if (this.#chType[part] > 0) {
this.setChType(part, this.CH_MELODIC);
console.debug(`CH${part + 1} set to melodic by MSB.`);
};
};
if ([33, 81, 97].indexOf(det.data[1]) > -1) {
this.#ext[extOff] = this.EXT_VL;
} else if ([35, 67, 83, 99].indexOf(det.data[1]) > -1) {
this.#ext[extOff] = this.EXT_DX;
//this.#cc.subarray(chOff + ccToPos[142], chOff + ccToPos[157] + 1).fill(64);
} else {
this.#ext[extOff] = this.EXT_NONE;
};
break;
};
case modeMap["05rw"]:
case modeMap.x5d:
case modeMap.ns5r: {
if ([61, 62, 126, 127].indexOf(det.data[1]) > -1) {
if (this.#chType[part] === this.CH_MELODIC) {
this.setChType(part, this.CH_DRUM2);
console.debug(`CH${part + 1} set to drums by MSB.`);
};
} else if ([80, 81, 82, 83].indexOf(det.data[1]) > -1) {
/*let voiceObject = this.getVoice(
this.getChCc(part, 0),
this.#prg[part],
this.getChCc(part, 32),
modeIdx[this.#mode]
);
console.debug(voiceObject);
if (this.#chType[part] === 0) {
//this.setChType(part, this.CH_MELODIC);
console.debug(`CH${part + 1} set to ${voiceIdx[(voiceObject.type || 0) & 1]} by MSB.`);
};*/
} else {
if (this.#chType[part] !== this.CH_MELODIC) {
this.setChType(part, this.CH_MELODIC);
console.debug(`CH${part + 1} set to melodic by MSB.`);
};
};
break;
};
case modeMap.sd: {
if ([104, 105, 106, 107].indexOf(det.data[1]) > -1) {
if (this.#chType[part] === 0) {
this.setChType(part, this.CH_DRUM2);
console.debug(`CH${part + 1} set to drums by MSB.`);
};
} else {
if (this.#chType[part] > 0) {
this.setChType(part, this.CH_MELODIC);
console.debug(`CH${part + 1} set to melodic by MSB.`);
};
};
break;
};
case modeMap.g2: {
if (det.data[1] === 120) {
if (this.#chType[part] === 0) {
this.setChType(part, this.CH_DRUMS);
console.debug(`CH${part + 1} set to drums by MSB.`);
};
} else {
if (this.#chType[part] > 0) {
this.setChType(part, this.CH_MELODIC);
console.debug(`CH${part + 1} set to melodic by MSB.`);
};
};
break;
};
};
/* this.dispatchEvent("voice", {
part
}); */
break;
};
case 2: {
// Breath for VL and more!
let breathMode = this.getExt(part)[1];
if (breathMode === this.VLBC_BRTHEXPR) {
this.setChCc(part, 129, det.data[1]);
};
break;
};
case 6: {
// Show RPN and NRPN
if (this.#dataCommit[part]) {
// Commit supported NRPN values
if ([modeMap.xg, modeMap.gs, modeMap.sc, modeMap.ns5r].indexOf(this.#mode) < 0) {
console.warn(`NRPN commits are not available under "${modeIdx[this.#mode]}" mode, even when they are supported in Octavia.`);
};
let msb = this.getChCc(part, 99),
lsb = this.getChCc(part, 98);
if (msb === 1) {
let toCc = nrpnCcMap.indexOf(lsb);
if (toCc > -1) {
this.setChCc(part, 71 + toCc, det.data[1]);
getDebugState() && console.debug(`Redirected NRPN 1 ${lsb} to cc${71 + toCc}.`);
this.dispatchEvent("cc", {
part,
cc: 71 + toCc,
data: det.data[1]
});
} else {
let nrpnIdx = useNormNrpn.indexOf(lsb);
if (nrpnIdx > -1) {
this.#nrpn[part * 10 + nrpnIdx] = det.data[1] - 64;
} else {
console.warn(`NRPN 0x01${lsb.toString(16).padStart(2, "0")} is not supported.`);
};
getDebugState() && console.debug(`CH${part + 1} voice NRPN ${lsb} commit`);
};
} else {
let nrpnIdx = useDrumNrpn.indexOf(msb);
if (nrpnIdx < 0) {
let dPref = `NRPN 0x${msb.toString(16).padStart(2, "0")}${lsb.toString(16).padStart(2, "0")} `;
if (msb === 127) {
console.warn(`${dPref}is not necessary. Consider removing it.`);
} else {
console.warn(`${dPref}is not supported.`);
};
} else {
let targetSlot = this.#chType[part] - 2;
if (targetSlot < 0) {
console.warn(`CH${part + 1} cannot accept drum NRPN as type ${xgPartMode[this.#chType[part]]}.`);
} else {
this.#drum[(targetSlot * allocated.dpn + dnToPos[msb]) * allocated.dnc + lsb] = det.data[1];
};
};
getDebugState() && console.debug(`CH${part + 1} (${xgPartMode[this.#chType[part]]}) drum NRPN ${msb} commit`);
};
} else {
// Commit supported RPN values
let rpnIndex = useRpnMap[this.getChCc(part, 100)],
rpnIndex2 = rpnOptions[this.getChCc(part, 100)];
if (this.getChCc(part, 101) === 0 && rpnIndex !== undefined) {
getDebugState() && console.debug(`CH${part + 1} RPN 0 ${this.getChCc(part, 100)} commit: ${det.data[1]}`);
det.data[1] = Math.min(Math.max(det.data[1], rpnCap[rpnIndex][0]), rpnCap[rpnIndex][1]);
this.#rpn[part * allocated.rpn + rpnIndex] = det.data[1];
this.#rpnt[part * allocated.rpnt + rpnIndex2] = 1;
switch (partMode) {
case modeMap.xg:
case modeMap.gs:
case modeMap.sc:
case modeMap.mt32:
case modeMap.s90es:
case modeMap.motif:
case modeMap.cs1x:
case modeMap.cs6x: {
switch (this.getChCc(part, 100)) {
case 1:
case 2: {
this.dispatchEvent("pitch", {
part,
pitch: this.getPitchShift(part)
});
break;
};
};
break;
};
default: {
this.dispatchEvent("pitch", {
part,
pitch: this.getPitchShift(part)
});
};
};
};
};
break;
};
case 32: {
if (getDebugState()) {
console.debug(`${modeIdx[this.#mode]}, CH${part + 1} LSB: ${det.data[1]}`);
};
switch (this.#mode) {
case modeMap.s90es:
case modeMap.motif: {
this.setChType(part, ([32, 40].indexOf(det.data[1]) > -1) ? this.CH_DRUMS : this.CH_MELODIC, this.#mode, true);
break;
};
};
if (this.getExt(part)[0] === this.EXT_DX) {
//this.#cc.subarray(chOff + ccToPos[142], chOff + ccToPos[157] + 1).fill(64);
};
/*this.dispatchEvent("voice", {
part
});*/
break;
};
case 38: {
// Show RPN and NRPN
if (!this.#dataCommit[part]) {
// Commit supported RPN values
let rpnIndex = useRpnMap[this.getChCc(part, 100)],
rpnIndex2 = rpnOptions[this.getChCc(part, 100)];
if (this.getChCc(part, 101) === 0 && rpnIndex !== undefined) {
// This section is potentially unsafe
this.#rpn[part * allocated.rpn + rpnIndex + 1] = det.data[1];
this.#rpnt[part * allocated.rpnt + rpnIndex2] = 1;
};
};
break;
};
case 64: {
// cc64: hold
if (det.data[1] < 64) {
this.#uAction.hoOf(part);
};
break;
};
case 66: {
// cc66: sostenuto
if (det.data[1] >> 6) {
// Sostenuto on
this.#uAction.soOn(part);
} else {
// Sostenuto off
this.#uAction.soOf(part);
};
break;
};
case 98:
case 99: {
this.#dataCommit[part] = 1;
break;
};
case 100:
case 101: {
this.#dataCommit[part] = 0;
break;
};
};
this.setChCc(part, det.data[0], det.data[1]);
this.dispatchEvent("cc", {
part,
cc: det.data[0],
data: det.data[1]
});
};
if (upThis.getChModeId(part) === modeMap.xg) {
if (upThis.#chType[part]) {
upThis.setDrumFirstWrite(part);
};
};
},
12: function (det) {
let part = det.channel;
let upThis = this;
// Program change
switch (upThis.#mode) {
case modeMap.s90es:
case modeMap.motif: {
det.data && upThis.setChActive(part, 1);
break;
};
default: {
upThis.setChActive(part, 1);
};
};
/* if (upThis.getExt(part)[0] === upThis.EXT_DX) {
let chOff = ccOffTable[part];
//this.#cc.subarray(chOff + ccToPos[142], chOff + ccToPos[157] + 1).fill(64);
}; */
let chOff = ccOffTable[part];
switch (upThis.getExt(part)[0]) {
case upThis.EXT_VL: {
// Force reset actual VL breath strength
// Turns out it's not valid
//upThis.setChCc(part, 129, 127);
break;
};
};
upThis.#prg[part] = det.data;
upThis.#bnCustom[part] = 0;
upThis.pushChPrimitives(part);
if (getDebugState()) {
console.debug(`T:${det.track} C:${part} P:${det.data}`);
};
upThis.dispatchEvent("voice", {
part
});
if (upThis.getChModeId(part) === modeMap.xg) {
if (upThis.#chType[part]) {
upThis.setDrumFirstWrite(part);
};
};
},
13: function (det) {
// Channel aftertouch
let upThis = this;
let part = det.channel;
this.#poly.forEach(function (e) {
let realCh = e >> 7;
if (part === realCh) {
upThis.#velo[e] = det.data;
upThis.dispatchEvent("note", {
part,
note: e & 127,
velo: det.data,
state: upThis.NOTE_SUSTAIN
});
};
});
},
14: function (det) {
let part = det.channel;
// Pitch bending
this.#pitch[part] = det.data[1] * 128 + det.data[0] - 8192;
this.dispatchEvent("pitch", {
part,
pitch: this.getPitchShift(part)
});
},
15: function (det) {
// SysEx
sysExSplitter(det.data).forEach((seq) => {
let manId = seq[0],
deviceId = seq[1];
this.#lastSysExSize = seq.length;
(this.#seMan[manId] || function () {
console.debug(`Unknown manufacturer ${manId}.`);
})(deviceId, seq.subarray(2), det.track);
//upThis.#seMain.run(seq, det.track);
});
},
248: function (det) {
// MIDI clock
},
250: function (det) {
// MIDI start
},
251: function (det) {
// MIDI continue
},
252: function (det) {
// MIDI stop
},
254: function (det) {
// Active sense
},
255: function (det) {
// Meta
(this.#metaRun[det.meta] || function (data, track, meta) {}).call(this, det.data, det.track, det.meta);
if (det.meta !== 32) {
this.#metaChannel = 0;
};
let useReply = passedMeta.indexOf(det.meta) > -1;
if (getDebugState()) {
console.debug(det);
};
if (useReply) {
det.reply = "meta";
return det;
};
}
};
// SysEx manufacturer table
#seMan = {
64: (id, msg, track) => {
// Kawai
this.#seKg.run(msg, track, id);
},
65: (id, msg, track) => {
// Roland
// CmdId is usually 18 (DT1)
// D-50: [20, CmdId]
// C/M: [22, CmdId]
// GS: [66, CmdId, HH, MM, LL, ...DD, Checksum]
if (msg[0] < 16) {
if (msg[1] === 72) {
let sentCs = msg[msg.length - 1];
let calcCs = gsChecksum(msg.subarray(3, msg.length - 1));
if (sentCs === calcCs) {
this.#seGs.run(msg.subarray(0, msg.length - 1), track, id);
} else {
console.warn(`Bad SD checksum ${sentCs} - should be ${calcCs}.`);
};
} else {
this.#seGs.run(msg, track, id);
};
//console.warn(`Unknown device SysEx!`);
} else {
let sentCs = msg[msg.length - 1];
let calcCs = gsChecksum(msg.subarray(2, msg.length - 1));
if (sentCs === calcCs) {
this.#seGs.run(msg.subarray(0, msg.length - 1), track, id);
} else {
console.warn(`Bad GS checksum ${sentCs} - should be ${calcCs}.`);
};
};
},
66: (id, msg, track) => {
// Korg
this.#seAi.run(msg, track, id);
},
67: (id, msg, track) => {
// Yamaha
// XG: [76, HH, MM, LL, ...DD]
switch (id >> 4) {
case 0: {
// bulk dumps
let bulkOffset = 0;
while (msg[bulkOffset] === 127 && bulkOffset < 4) {
bulkOffset ++;
};
//console.debug(`Bulk dump length read offset: ${bulkOffset}`);
let targetLength = (msg[1 + bulkOffset] << 7) | msg[2 + bulkOffset];
//console.debug(`Yamaha: bulk dump (${targetLength})`);
if (targetLength + 7 + bulkOffset !== msg.length) {
console.warn(`Yamaha bulk dump length mismatch! Expected ${msg.length - 7 - bulkOffset}, received ${targetLength}.`);
console.debug(msg);
break;
};
let expectedChecksum = gsChecksum(msg.subarray(1 + bulkOffset, msg.length - 1));
let receivedChecksum = msg[msg.length - 1];
if (msg[msg.length - 1] >> 7) {
console.warn(`Yamaha bulk dump checksum invalid! Expected ${expectedChecksum}, received ${receivedChecksum}:\n`);
console.debug(msg);
} else if (expectedChecksum !== receivedChecksum) {
console.warn(`Yamaha bulk dump checksum mismatch! Expected ${expectedChecksum}, received ${receivedChecksum}:\n`);
console.debug(msg);
} else {
let msgPs = msg.slice(2, msg.length - 1);
for (let i = 0; i <= bulkOffset; i ++) {
msgPs[i] = msg[i];
};
this.#seXg.run(msgPs, track, id & 15);
};
break;
};
case 1: {
// parameter sets
this.#seXg.run(msg, track, id & 15);
break;
};
case 2: {
console.warn(`Octavia doesn't yet support Yamaha bulk dump requests.`);
break;
};
case 3: {
console.warn(`Octavia doesn't yet support Yamaha parameter requests.`);
break;
};
case 7: {
// special-use
switch (id & 15) {
case 14: {
// style control
let newBuffer = new Uint8Array(msg.length + 1);
newBuffer[0] = 126;
newBuffer.set(msg, 1);
this.#seXg.run(newBuffer, track, 0);
break;
};
default: {
console.warn(`Unknown Yamaha special SysEx type: ${id}.`);
};
};
break;
};
default: {
console.warn(`Unknown Yamaha SysEx type: ${id >> 4}.`);
};
};
},
68: (id, msg, track) => {
// Casio
this.#seCs.run(msg, track, id);
},
71: (id, msg, track) => {
// Akai
this.#seSg.run(msg, track, id);
},
126: (id, msg, track) => {
// Universal non-realtime
this.#seUnr.run(msg, track, id);
},
127: (id, msg, track) => {
// Universal realtime
this.switchMode("gm");
this.#seUr.run(msg, track, id);
}
};
#seUnr; // Universal non-realtime
#seUr; // Universal realtime
#seXg; // YAMAHA
#seGs; // Roland
#seAi; // KORG
#seKg; // Kawai
#seSg; // Akai
#seCs; // Casio
buildRchTree() {
// Build a receiving tree from currently set receive channels
// Now builds from the ground up each time
// Can be optimized to move elements instead
let tree = [];
this.#chReceive.forEach((e, i) => {
if (e < allocated.ch) {
if (!tree[e]?.constructor) {
tree[e] = [];
};
// Remove disabled channels
tree[e].push(i);
};
});
this.#receiveTree = tree;
//console.debug(tree);
};
buildRccMap() {
// Build a receiving tree from the defined CCs
let upThis = this;
// Builds from the ground up each time
for (let ch = 0; ch < allocated.ch; ch ++) {
upThis.#ccRedirMap[ch] = {};
};
upThis.#ccCapturer.forEach((e, i) => {
if (e) {
upThis.#ccRedirMap[Math.floor(i / allocated.redir)][e] = (i % allocated.redir) | 128;
};
});
getDebugState() && console.debug(upThis.#ccRedirMap);
};
invokeSysExIndicator() {
this.dispatchEvent("mupromptex", this.#lastSysExSize);
};
getActive() {
let result = this.#chActive;
//if (this.#mode === modeMap.mt32) {
//result[0] = 0;
//};
return result;
};
getChActive(part) {
return this.#chActive[part];
};
getCc(part) {
// Return channel CC registers
// Potential bug exists here
if (typeof part !== "number" || part < 0 || part >= allocated.ch) {
throw(new RangeError(`Invalid part number: CH${part + 1}`));
return;
};
let upThis = this;
let start = ccOffTable[part];
let arr = upThis.#cc.subarray(start, start + allocated.cc);
/* arr[ccToPos[0]] = arr[ccToPos[0]] || upThis.#subDb[upThis.getChModeId(channel)][0];
arr[ccToPos[32]] = arr[ccToPos[32]] || upThis.#subDb[upThis.getChModeId(channel)][1];
if (arr[ccToPos[0]] === overrides.bank0) {
arr[ccToPos[0]] = 0;
}; */
return arr;
};
getChCc(part, cc) {
let upThis = this;
if (ccAccepted.indexOf(cc) < 0) {
throw(new Error("CC number not accepted"));
};
let result = upThis.#cc[ccOffTable[part] + ccToPos[cc]];
/* switch (cc) {
case 0: {
result = result || upThis.#subDb[upThis.getChModeId(channel)][0];
if (result === overrides.bank0) {
result = 0;
};
break;
};
case 32: {
result = result || upThis.#subDb[upThis.getChModeId(channel)][1];
break;
};
}; */
return result;
};
getChCcWritten(part, cc) {
let upThis = this;
if (ccAccepted.indexOf(cc) < 0) {
throw(new Error("CC number not accepted"));
};
let result = upThis.#cc[allocated.chcc + ccOffTable[part] + ccToPos[cc]];
return result;
};
setChCc(part = 0, cc, value) {
let upThis = this;
if (ccAccepted.indexOf(cc) < 0) {
throw(new Error("CC number not accepted"));
};
if (value?.constructor !== Number) {
throw(new TypeError("Expected numbers for value"));
};
let data = value & 255;
let posCache = ccOffTable[part] + ccToPos[cc];
upThis.#cc[posCache] = data;
upThis.#cc[posCache + allocated.chcc] = 1;
upThis.dispatchEvent("cc", {
part,
cc,
data
});
/* if (part === 9 && cc === 0) {
console.info(new Error("WTF"));
}; */
};
getCcAll() {
// Return all CC registers
let upThis = this;
let arr = upThis.#cc.slice();
/* for (let c = 0; c < allocated.ch; c ++) {
let chOff = c * allocated.cc;
arr[chOff + ccToPos[0]] = arr[chOff + ccToPos[0]] || upThis.#subDb[upThis.getChModeId(c)][0];
arr[chOff + ccToPos[32]] = arr[chOff + ccToPos[32]] || upThis.#subDb[upThis.getChModeId(c)][1];
if (arr[ccToPos[0]] === overrides.bank0) {
arr[ccToPos[0]] = 0;
};
}; */
return arr;
};
resetCc(part) {
let upThis = this;
let start = ccOffTable[part] + allocated.chcc;
upThis.#cc.fill(0, start, start + allocated.cc);
};
resetChCc(part, cc, value) {
let upThis = this;
if (value?.constructor) {
upThis.setChCc(part, cc, value);
};
upThis.#cc[ccOffTable[part] + ccToPos[cc] + allocated.chcc] = 0;
};
resetCcAll() {
// Placeholder until CC write state is ready
this.#cc.fill(0, allocated.chcc, allocated.chcc << 1);
};
isChCcWritten(part, cc) {
if (ccAccepted.indexOf(cc) < 0) {
throw(new Error("CC number not accepted"));
};
return upThis.#cc[ccOffTable[part] + ccToPos[cc] + allocated.chcc];
};
getChSource() {
return this.#chReceive;
};
getChType() {
return this.#chType;
};
setChType(part, type, mode = 0, disableMsbSet = false) {
type &= 15;
let upThis = this;
mode = mode || upThis.getChModeId(part);
upThis.#chType[part] = type;
if (type > 0 && !disableMsbSet) {
upThis.setChCc(part, 0, upThis.#subDb[mode][2]);
upThis.pushChPrimitives(part);
};
};
setChActive(part, active = 0) {
if (this.#chActive[part] !== active) {
this.dispatchEvent("channeltoggle", {
part,
active
});
};
this.#chActive[part] = active;
};
getExt(part) {
let start = allocated.ext * part;
let view = this.#ext.subarray(start, start + allocated.ext);
let copy = new Uint8Array(view.length);
copy.set(view);
copy[1] = copy[1] || this.#vlSysBreathMode;
return copy;
};
getPitch() {
return this.#pitch;
};
getProgram() {
return this.#prg;
};
getTexts() {
return this.#metaTexts.slice();
};
getVel(channel) {
// Return all pressed keys with velocity in a channel
let notes = new Map();
let upThis = this;
upThis.#poly.forEach(function (e, i) {
let realCh = e >> 7,
realNote = e & 127;
if (channel === realCh && upThis.#velo[e] > 0) {
notes.set(realNote, {
v: upThis.#velo[e], // Short for velocity
s: upThis.#polyState[i] // Short for state
});
};
});
return notes;
};
getBitmap() {
return {
bitmap: this.#bitmap,
expire: this.#bitmapExpire
};
};
getLetter() {
return {
text: this.#letterDisp,
set: this.#letterSet,
expire: this.#letterExpire
};
};
getMode() {
return modeIdx[this.#mode];
};
getMaster() {
return {
volume: this.#masterVol
};
};
getRawStrength() {
// 0 to 127
let upThis = this;
this.#poly.forEach(function (e) {
let channel = e >> 7;
if (upThis.#velo[e] > upThis.#rawStrength[channel]) {
upThis.#rawStrength[channel] = upThis.#velo[e];
};
});
return this.#rawStrength;
};
getStrength() {
// 0 to 255
// Should later become 0 to 65535
let str = [], upThis = this;
this.getRawStrength().forEach(function (e, i) {
str[i] = Math.floor(e * upThis.getChCc(i, 7) * upThis.getChCc(i, 11) * upThis.#masterVol / 803288);
});
return str;
};
getRpn() {
return this.#rpn;
};
getNrpn() {
return this.#nrpn;
};
getSubDb() {
return self?.structuredClone(this.#subDb);
};
getDetect() {
return self?.structuredClone(this.#detect);
};
getVoice(msbO, prgO, lsbO, mode = "?") {
let upThis = this;
if (!modeMap[mode]?.constructor) {
mode = "?";
};
let modeId = modeMap[mode];
let msb = msbO || upThis.#subDb[modeId][0],
prg = prgO,
lsb = lsbO || upThis.#subDb[modeId][1];
if (msb === overrides.bank0) {
msb = 0;
};
if (lsb === overrides.bank0) {
lsb = 0;
};
if (mode === "ns5r") {
if (msb > 0 && msb < 56) {
lsb = 3; // Use SC-88 Pro map
};
};
let bank = upThis.userBank.get(msb, prg, lsb, mode);
if (mode === "mt32") {
// Reload MT-32 user bank transparently
if (bank.name.indexOf("MT-m:") === 0) {
// Device patch
let patch = parseInt(bank.name.slice(5)),
timbreOff = patch * allocated.cmt,
userBank = "";
upThis.#cmTimbre.subarray(timbreOff, timbreOff + 10).forEach((e) => {
if (e > 31) {
userBank += String.fromCharCode(e);
};
});
let loadTsv = `MSB\tLSB\tPRG\tNME\n49\t127\t${prg}\t${userBank}`;
//console.debug(loadTsv);
upThis.userBank.load(loadTsv, true);
bank.name = userBank;
bank.ending = " ";
};
};
if (bank.ending !== " " || !bank.name.length) {
bank = upThis.baseBank.get(msb, prg, lsb, mode);
};
return bank;
};
getChPrimitive(part, component, useSubDb) {
/*
Valid component values:
0: Program number
1: cc0/Bank MSB
2: cc32/Bank LSB
*/
if (component >= allocated.vxPrim || component?.constructor !== Number) {
throw(new RangeError(`Invalid voice primitive component "${component}"`));
return;
};
if (typeof part !== "number" || part < 0 || part >= allocated.ch) {
throw(new RangeError(`Invalid part number: CH${part + 1}`));
return;
};
let upThis = this;
let result = upThis.#prg[(component << allocated.chShift) | part];
switch (component) {
case 1:
case 2: {
if (!upThis.getChCcWritten(part, [255, 0, 32][component])) {
result = result || upThis.#subDb[upThis.getChModeId(part)][component + 2];
};
if (useSubDb) {
result = result || upThis.#subDb[upThis.getChModeId(part)][component - 1];
};
if (result === overrides.bank0) {
result = 0;
};
break;
};
};
return result;
};
getChPrimitives(part, useSubDb) {
/*
Unlike OctaviaDevice.getChPrimitive(),
this method lays out the primitives in the order listed below:
cc0/Bank MSB, program number, cc32/Bank LSB
*/
let upThis = this;
let primBuf = new Uint8Array(3);
primBuf[1] = upThis.#prg[part];
primBuf[0] = upThis.getChPrimitive(part, 1, useSubDb);
primBuf[2] = upThis.getChPrimitive(part, 2, useSubDb);
return primBuf;
};
pushChPrimitives(part) {
let upThis = this;
upThis.#prg[(1 << allocated.chShift) | part] = upThis.getChCc(part, 0);
upThis.#prg[(2 << allocated.chShift) | part] = upThis.getChCc(part, 32);
upThis.dispatchEvent("voice", {
part
});
};
getChCvnBuffer(part, maxBufferLength = allocated.cvn) {
// A buffer that can be written directly
if (maxBufferLength > allocated.cvn || maxBufferLength < 1) {
throw(new RangeError("Invalid custom voice name buffer length"));
};
let pointer = cvnOffTable[part];
return this.#cvnBuffer.subarray(pointer, pointer + maxBufferLength);
};
getChCvnRegister(part, regIdx) {
if (regIdx >= allocated.cvn || regIdx < 0) {
throw(new RangeError("Invalid custom voice name register"));
};
return this.#cvnBuffer[cvnOffTable[part] + regIdx];
};
setChCvnRegister(part, regIdx, value = 32) {
if (regIdx >= allocated.cvn || regIdx < 0) {
throw(new RangeError("Invalid custom voice name register"));
};
this.#cvnBuffer[cvnOffTable[part] + regIdx] = Math.max(32, value & 255);
};
getChCvnString(part, preserveEnd) {
let result = decoderL9.decode(this.getChCvnBuffer(part));
if (!preserveEnd) {
result = result.trimEnd();
};
return result;
};
getChVoice(part) {
let upThis = this;
let voice = upThis.getVoice(...upThis.getChPrimitives(part), upThis.getChMode(part));
if (upThis.#bnCustom[part]) {
let name = "";
switch (upThis.#mode) {
case modeMap.mt32: {
upThis.#cmTTimbre.subarray(allocated.cmt * (part - 1), allocated.cmt * (part - 1) + 10).forEach((e) => {
name += String.fromCharCode(Math.max(e, 32));
});
name = name.trimEnd();
break;
};
default: {
name = upThis.getChCvnString(part);
};
};
if (name.length) {
voice.ending = "~";
voice.name = name;
};
};
return voice;
};
getRawPitch() {
return this.#pitch;
};
getPitchShift(part) {
let upThis = this;
let rpnOff = part * allocated.rpn;
let pitchBendRange = upThis.#rpn[rpnOff];
if (!upThis.#rpnt[part * allocated.rpnt]) {
// Override unwritten RPN values according to the current modeIdx
if (upThis.#mode === modeMap.mt32) {
pitchBendRange = 12;
};
} else {
if (pitchBendRange >> 7) {
pitchBendRange -= 256;
};
};
return upThis.#pitch[part] / 8192 * pitchBendRange + (upThis.#rpn[rpnOff + 3] - 64) + ((upThis.#rpn[rpnOff + 1] << 7) + upThis.#rpn[rpnOff + 2] - 8192) / 8192;
};
getEffectType(slot = 0) {
let index = 3 * slot + 1;
return this.#efxBase.subarray(index, index + 2);
};
getPolyState(slot = 0) {
return this.#polyState[slot];
};
setEffectTypeRaw(slot = 0, isLsb, value) {
let efxbOff = 3 * slot;
this.#efxBase[efxbOff] = 1;
this.#efxBase[efxbOff + 1 + +isLsb] = value;
};
setEffectType(slot = 0, msb, lsb) {
this.setEffectTypeRaw(slot, false, msb);
this.setEffectTypeRaw(slot, true, lsb);
};
getEffectSink() {
return this.#efxTo;
};
setLetterDisplay(data, source, offset = 0, delay = 3200) {
let upThis = this,
invalidCp;
upThis.#letterDisp = " ".repeat(offset);
data.forEach((e) => {
upThis.#letterDisp += String.fromCharCode(e > 31 ? e : 32);
if (e < 32) {
invalidCp = invalidCp || new Set();
invalidCp.add(e);
};
});
upThis.#letterSet = Date.now();
upThis.#letterExpire = Date.now() + delay;
upThis.dispatchEvent("letter");
//upThis.#letterDisp = upThis.#letterDisp.padEnd(16, " ");
if (invalidCp) {
invalidCp = Array.from(invalidCp);
invalidCp.forEach((e, i, a) => {
a[i] = e.toString(16).padStart(2, "0");
});
console.warn(`${source}${source ? " " : ""}invalid code point${invalidCp.length > 1 ? "s" : ""}: 0x${invalidCp.join(", 0x")}`);
};
};
setDetectionTargets(mode = "?", port = 0) {
let upThis = this,
validId = -1;
mode.replaceAll(", ", ",").split(",").forEach((e) => {
e = e.toLowerCase();
let modeId = modeIdx.indexOf(modeAdapt[e] || e);
getDebugState() && console.debug(`Mapped mode "${e}" to ID "${modeId}".`);
if (modeId > -1) {
validId = modeId;
};
});
getDebugState() && console.debug(`Set detection target to ID "${validId}".`);
if (validId > 0) {
upThis.#detect.x5 = 82; // Reset to X5DR
upThis.#detect.ds = modeMap.krs; // Reset to KORG KROSS 2
upThis.#detect.gm = modeMap.gm;
upThis.#detect.g2 = modeMap.g2;
};
switch (validId) {
case modeMap["05rw"]: {
upThis.#detect.x5 = 81;
break;
};
case modeMap.s90es: {
upThis.#detect.ds = modeMap.s90es;
upThis.#detect.smotif = modeMap.s90es;
break;
};
case modeMap.motif: {
upThis.#detect.ds = modeMap.motif;
upThis.#detect.smotif = modeMap.motif;
break;
};
case modeMap.sd: {
upThis.#detect.g2 = modeMap.sd;
break;
};
case modeMap.pa: {
upThis.#detect.g2 = modeMap.pa;
break;
};
};
};
setGsTargets(useSc = false, gsLevel = 4) {
if (!gsLevel) {
gsLevel = useSc ? 3 : 4;
} else if (gsLevel > 4 || gsLevel < 0) {
throw(new Error(`Invalid GS level ${gsLevel}`));
return;
} else if (useSc && (gsLevel >> 1) !== 1) {
throw(new Error(`Invalid SC level ${gsLevel}`));
};
let upThis = this;
upThis.#detect[useSc ? "sc" : "gs"] = gsLevel;
upThis.#subDb[modeMap[useSc ? "sc" : "gs"]][1] = gsLevel;
upThis.forceVoiceRefresh();
};
getConfigs() {
return this.#confProxy;
};
setDumpLimit(limit) {
if (limit > 2 || limit < 0) {
throw(new RangeError("Invalid dump limit."));
};
this.#conf.dumpLimit = limit;
};
allocateAce(part = 0, cc) {
// Allocate active custom effect
// Off, cc1~cc95, CAT, velo, PB
let upThis = this;
if (!cc || (cc < 128 && cc > 95)) {
console.warn(`cc${cc} cannot be allocated as an active custom effect.`);
return;
};
let continueScan = true, pointer = 0, aceOff = allocated.ace * part;
while (continueScan && pointer < allocated.ace) {
if (upThis.#ace[pointer + aceOff] === cc) {
continueScan = false;
} else if (!upThis.#ace[pointer + aceOff]) {
continueScan = false;
upThis.#ace[pointer + aceOff] = cc;
console.debug(`Allocated cc${cc} to ACE slot ${pointer} in CH${part + 1}.`);
};
pointer ++;
};
if (pointer >= allocated.ace) {
console.warn(`ACE slots are full in CH${part + 1}.`);
};
};
allocateAceAll(cc) {
let upThis = this;
if (!cc || (cc < 128 && cc > 95)) {
console.warn(`cc${cc} cannot be allocated as an active custom effect.`);
return;
};
for (let part = 0; part < allocated.ch; part ++) {
let continueScan = true, pointer = 0, aceOff = allocated.ace * part;
while (continueScan && pointer < allocated.ace) {
if (upThis.#ace[pointer + aceOff] === cc) {
continueScan = false;
} else if (!upThis.#ace[pointer + aceOff]) {
continueScan = false;
upThis.#ace[pointer + aceOff] = cc;
//console.info(`Allocated cc${cc} to ACE slot ${pointer}.`);
};
pointer ++;
};
if (pointer >= allocated.ace) {
console.warn(`ACE slots are full in CH${part + 1}.`);
};
};
};
resetAce() {
// Clear all allocated ACE
this.#ace.fill(0);
console.info(`All ACE slots have been reset.`);
};
resetChAceAll(part = 0) {
this.#ace.subarray(aceOffTable[part], aceOffTable[part] + allocated.ace).fill(0);
};
resetChAce(part = 0, cc) {
// Waiting rewrite
let continueScan = true, pointer = aceOffTable[part], maxPointer = pointer + allocated.ace;
while (continueScan && pointer < maxPointer) {
if (this.#ace[pointer] === cc) {
this.#ace[pointer] = 0;
continueScan = false;
};
pointer ++;
};
if (continueScan) {
getDebugState() && console.debug(`No ACE slot was allocated to cc${cc} in CH${part + 1}.`);
};
};
getAce() {
return this.#ace;
};
getChAce(part = 0, aceSlot = 0) {
// Get channel ACE value
if (aceSlot < 0 || aceSlot >= allocated.ace) {
throw(new RangeError(`No such ACE slot`));
};
let cc = this.#ace[allocated.ace * part + aceSlot];
if (!cc) {
return 0;
} else if (ccAccepted.indexOf(cc) >= 0) {
return this.getChCc(part, cc);
} else {
throw(new Error(`Invalid ACE source in CH${part + 1}: ${cc}`));
};
};
initDrums() {
// NRPN drum section reset
let upThis = this;
upThis.#drum.fill(64);
for (let targetSlot = 0; targetSlot < allocated.drm; targetSlot ++) {
let drumOff = targetSlot * allocated.dpn;
for (let key in defDrumNrpn) {
let value = defDrumNrpn[key],
boundary = (drumOff + dnToPos[key]) * allocated.dnc,
slice = upThis.#drum.subarray(boundary, boundary + allocated.dnc);
//console.debug(`Init drum write slot #${targetSlot + 1} param ${key} value ${value}: ${boundary}\n`, slice);
slice.fill(value);
//console.debug(slice);
};
};
//console.debug(`Init drum write registers:\n`, upThis.#drum);
return;
};
init(type = 0) {
// Type 0 is full reset
// Type 1 is almost-full reset
let upThis = this;
// Full reset, except the loaded banks
upThis.#metaChannel = 0;
upThis.#subDb[modeMap.xg][1] = 0;
upThis.dispatchEvent("banklevel", {
"mode": "xg",
"data": 0
});
//upThis.#detect.x5 = 82; // Reset to X5DR
//upThis.#detect.ds = modeMap.krs; // Reset to KROSS 2
upThis.#chActive.fill(0);
upThis.#cc.fill(0);
upThis.#ace.fill(0);
upThis.#prg.fill(0);
upThis.#velo.fill(0);
upThis.#poly.fill(0);
upThis.#polyState.fill(0);
upThis.#mono.fill(0);
upThis.#rawStrength.fill(0);
upThis.#pitch.fill(0);
upThis.#nrpn.fill(0);
upThis.#rpnt.fill(0);
upThis.#ext.fill(0);
upThis.#portMode.fill(0);
upThis.#portModeInitial.fill(0);
upThis.#chMode.fill(0);
upThis.#ccCapturer.fill(0);
upThis.#masterVol = 100;
upThis.#metaTexts = [];
upThis.#noteLength = 500;
upThis.#sgConvertLastSyllable = 0;
upThis.#sgRunningLineLength = 0;
upThis.#sgSplittedMask = false;
upThis.#bitmapExpire = 0;
upThis.#bitmapPage = 0;
for (let i = 0; i < upThis.#bitmapStore.length; i ++) {
upThis.#bitmap.fill(0);
};
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
upThis.#selectPort = 0;
upThis.#receiveRS = true;
upThis.#maskNewLyric = false;
upThis.#lastSysExSize = 0;
upThis.initDrums();
upThis.polyIndexLatest = 0;
upThis.polyIndexLast = 0;
// Reset MIDI receive channel
upThis.#chReceive.forEach(function (e, i, a) {
a[i] = i;
});
// Reset channel redirection
if (type === 0) {
upThis.dispatchEvent("mode", "?");
upThis.#mode = 0;
upThis.#trkRedir.fill(0);
upThis.#trkAsReq.fill(0);
upThis.#letterExpire = 0;
upThis.#letterDisp = "";
};
// Channel 10 to drum set
drumChannels.forEach((e) => {
upThis.setChCc(e, 0, upThis.#subDb[upThis.getChModeId(e)][2]);
});
// Channel types
upThis.#chType.fill(upThis.CH_MELODIC);
upThis.#chType[9] = upThis.CH_DRUM1;
upThis.#chType[25] = upThis.CH_DRUM3;
upThis.#chType[41] = upThis.CH_DRUMS;
upThis.#chType[57] = upThis.CH_DRUMS;
upThis.#chType[73] = upThis.CH_DRUM5;
upThis.#chType[89] = upThis.CH_DRUM7;
upThis.#chType[105] = upThis.CH_DRUMS;
upThis.#chType[121] = upThis.CH_DRUMS;
// Drum source channels
for (let ds = 0; ds < allocated.drm; ds ++) {
upThis.#drumFirstWrite[ds << 1] = 0; // active or not
upThis.#drumFirstWrite[(ds << 1) | 1] = allocated.invalidCh; // set all to invalid
};
// Reset MT-32 user patch and timbre storage
upThis.#cmPatch.fill(0);
upThis.#cmTimbre.fill(0);
upThis.#cmTPatch.fill(0);
upThis.#cmTTimbre.fill(0);
upThis.#bnCustom.fill(0);
// Reset custom voice name buffers
upThis.#cvnBuffer.fill(32);
// Reset EFX base registers
upThis.#efxBase.fill(0);
upThis.#efxTo.fill(0);
// Reset AI EFX display name
upThis.aiEfxName = "";
// Reset MT-32 user bank
upThis.userBank.clearRange({msb: 0, lsb: 127, prg: [0, 127]});
// Reset SC-exclusive params
upThis.modelEx.sc.showBar = true;
upThis.modelEx.sc.invBar = false;
upThis.modelEx.sc.invDisp = false;
upThis.modelEx.sc.peakHold = 1;
// Reset CS1x-exclusive params
upThis.modelEx.cs1x.perfCh = 0;
for (let ch = 0; ch < allocated.ch; ch ++) {
let chOff = ccOffTable[ch];
// Reset to full
upThis.#cc[chOff + ccToPos[7]] = 100; // Volume
upThis.#cc[chOff + ccToPos[11]] = 127; // Expression
upThis.#cc[chOff + ccToPos[2]] = 127; // Breath
// Reset to centre
upThis.#cc[chOff + ccToPos[10]] = 64; // Pan
/*upThis.#cc[chOff + ccToPos[71]] = 64; // Resonance
upThis.#cc[chOff + ccToPos[72]] = 64; // Release Time
upThis.#cc[chOff + ccToPos[73]] = 64; // Attack Time
upThis.#cc[chOff + ccToPos[74]] = 64; // Brightness
upThis.#cc[chOff + ccToPos[75]] = 64; // Decay Time
upThis.#cc[chOff + ccToPos[76]] = 64; // Vibrato Rate
upThis.#cc[chOff + ccToPos[77]] = 64; // Vibrato Depth
upThis.#cc[chOff + ccToPos[78]] = 64; // Vibrato Delay*/
upThis.#cc.subarray(chOff + ccToPos[71], chOff + ccToPos[71] + 8).fill(64);
// Internal reset
upThis.#cc[chOff + ccToPos[128]] = 127; // Set dry level to full
upThis.#cc.subarray(chOff + ccToPos[130], chOff + ccToPos[157] + 1).fill(64);
// Extra default values
upThis.#cc[chOff + ccToPos[91]] = 40; // Reverb
// RPN/NRPN to null
upThis.#cc[chOff + ccToPos[101]] = 127;
upThis.#cc[chOff + ccToPos[100]] = 127;
upThis.#cc[chOff + ccToPos[99]] = 127;
upThis.#cc[chOff + ccToPos[98]] = 127;
// RPN reset
let rpnOff = ch * allocated.rpn;
upThis.#rpn[rpnOff] = 2; // pitch bend range
upThis.#rpn[rpnOff + 1] = 64; // Fine tune MSB
upThis.#rpn[rpnOff + 2] = 0; // Fine tune LSB
upThis.#rpn[rpnOff + 3] = 64; // Coarse tune MSB
upThis.#rpn[rpnOff + 4] = 0; // Mod sensitivity MSB
upThis.#rpn[rpnOff + 5] = 0; // Mod sensitivity LSB
// NRPN normal section reset
// Extension config reset
let extOff = ch * allocated.ext;
upThis.#ext[extOff + 1] = upThis.VLBC_BRTHEXPR;
// CC redirection reset
let redirOff = ch * allocated.redir;
upThis.#ccCapturer[redirOff + 8] = 13;
};
upThis.buildRchTree();
upThis.buildRccMap();
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
upThis.dispatchEvent(`efxreverb`, upThis.getEffectType(0));
upThis.dispatchEvent(`efxchorus`, upThis.getEffectType(1));
upThis.dispatchEvent(`efxdelay`, upThis.getEffectType(2));
upThis.dispatchEvent(`efxinsert0`, upThis.getEffectType(3));
upThis.dispatchEvent(`efxinsert1`, upThis.getEffectType(4));
upThis.dispatchEvent(`efxinsert2`, upThis.getEffectType(5));
upThis.dispatchEvent(`efxinsert3`, upThis.getEffectType(6));
upThis.dispatchEvent("reset");
upThis.switchMode("?");
return;
};
setChMode(part, modeId) {
// Per-channel mode
let upThis = this;
if (typeof part !== "number" || part < 0 || part >= allocated.ch) {
throw(new RangeError(`Invalid part number: CH${part + 1}`));
return;
};
if (part < 0 || modeId >= modeIdx.length) {
throw(new RangeError(`Invalid mode ID ${modeId}`));
return;
};
upThis.#chMode[part] = modeId;
upThis.dispatchEvent("chmode", {
part,
id: modeId,
mode: modeIdx[modeId]
});
upThis.dispatchEvent("voice", {
part
});
};
getChMode(part, noFallback) {
return modeIdx[this.getChModeId(part, noFallback)];
};
getChModeId(part, noFallback) {
if (noFallback) {
return this.#chMode[part] || this.#portMode[part >> 4];
} else {
return this.#chMode[part] || this.#portMode[part >> 4] || this.#mode;
};
};
getPortMode(port, noFallback) {
return modeIdx[this.getPortModeId(port, noFallback)];
};
getPortModeId(port, noFallback) {
if (noFallback) {
return this.#portMode[port];
} else {
return this.#portMode[port] || this.#mode;
};
};
setPortMode(port, range, modeId) {
// Per-channel mode, but in bulk
let upThis = this;
if (port < 0 || port >= (allocated.ch >> 4)) {
throw(new RangeError(`Invalid port ${port + 1}`));
return;
};
if (range < 1) {
throw(new RangeError(`Range must be a positive integer`));
return;
} else if ((port + range) >= (allocated.ch >> 4)) {
throw(new RangeError(`Range must be in bound`));
return;
};
if (port < 0 || modeId >= modeIdx.length) {
throw(new RangeError(`Invalid mode ID ${modeId}`));
return;
};
upThis.#portModeInitial[port] = modeId; // Leaves room for improvement with scenarios needing different devices on different ports, but with same standard. Examples include JayB's Micro Sequencing.
for (let sect = port; sect < port + range; sect ++) {
let initializedModeOnPort = upThis.#portModeInitial[sect];
if (range > 1 && initializedModeOnPort !== 0 && initializedModeOnPort !== modeId) {
break;
};
upThis.#portMode[sect] = modeId;
for (let part = sect << 4; part < ((sect + 1) << 4); part ++) {
upThis.dispatchEvent("chmode", {
part,
id: modeId,
mode: modeIdx[modeId]
});
upThis.dispatchEvent("voice", {
part
});
};
};
};
copyChSetup(sourceCh, targetCh, failWhenActive) {
if (sourceCh === targetCh) {
console.warn(`Failed attempt at overwriting CH${sourceCh + 1} with its own data.`);
return;
};
let upThis = this;
if (failWhenActive && upThis.#chActive[targetCh]) {
console.warn(`Failed attempt at copying setup data from CH${sourceCh + 1} to CH${targetCh + 1}.`);
return;
};
let sourceChOff = ccOffTable[sourceCh],
chOff = ccOffTable[targetCh];
upThis.#prg[targetCh] = upThis.#prg[sourceCh];
upThis.#cc.set(upThis.#cc.subarray(sourceChOff, sourceChOff + allocated.cc), chOff);
upThis.pushChPrimitives(targetCh);
};
setDrumFirstWrite(part, disable) {
let upThis = this;
let chType = upThis.#chType[part];
if (chType < 2) {
return;
};
let ds = chType - 2;
if (upThis.#drumFirstWrite[ds << 1]) {
if (disable) {
upThis.#drumFirstWrite[ds << 1] = 0;
getDebugState() && console.debug(`First write part for drum set ${ds + 1} has been reset.`);
} else {
//getDebugState() && console.debug(`Drum set ${ds + 1} has already been written by CH${upThis.#drumFirstWrite[allocated.drm | ds] + 1}.`);
return;
};
} else {
if (disable) {
/*getDebugState() && */console.warn(`First write part for drum set ${ds + 1} is already blank.`);
} else {
upThis.#drumFirstWrite[ds << 1] = 1;
upThis.#drumFirstWrite[(ds << 1) | 1] = part;
console.debug(`First write part for drum set ${ds + 1} is set to CH${part + 1}.`);
};
};
};
getChDrumFirstWrite(part) {
let upThis = this;
let chType = upThis.#chType[part];
if (chType < 2) {
return;
};
let ds = chType - 2;
return upThis.#drumFirstWrite.subarray(ds << 1, (ds + 1) << 1);
};
getDrumFirstWrite(ds) {
if (ds >= 0 && ds < allocated.drm) {
return this.#drumFirstWrite.subarray(ds << 1, (ds + 1) << 1);
};
};
switchMode(mode, forced = false, setTarget = false) {
// The global fallback mode
let upThis = this;
let idx = modeMap[mode];
if (idx > -1) {
if (upThis.#mode === 0 || forced) {
//let oldMode = upThis.#mode;
if (upThis.initOnReset && forced) {
this.init(1);
//oldMode = modeMap["?"];
};
upThis.#bitmapPage = 0; // Restore page
//console.debug(`Mode ${mode} has drum MSB: ${drumMsb[idx]}`);
// Mode redirection
switch (idx) {
case modeMap.gm: {
idx = upThis.#detect.gm;
break;
};
case modeMap.g2: {
idx = upThis.#detect.g2;
break;
};
};
upThis.#mode = idx;
// Drum initialization
for (let ch = 0; ch < allocated.ch; ch ++) {
let oldMode = upThis.getChModeId(ch, true);
if (upThis.#chType[ch] > 0 && oldMode === modeMap["?"]) {
// Switch drum MSBs.
upThis.setChCc(ch, 0, upThis.#subDb[idx][2]);
getDebugState() && console.debug(`CH${ch + 1} (${upThis.#chType[ch]}) (${upThis.getChMode(ch)}), ${modeIdx[oldMode]} (${upThis.#subDb[oldMode][2]}) -> ${modeIdx[idx]} (${upThis.#subDb[idx][2]})`);
// I'll deal with this later?
upThis.pushChPrimitives(ch);
};
//this.initOnReset && forced && this.#uAction.ano(ch);
};
// Bank defaults
switch (idx) {
case modeMap.mt32: {
mt32DefProg.forEach((e, i) => {
let ch = i + 1;
if (!upThis.#chActive[ch]) {
upThis.#prg[ch] = e;
upThis.setChCc(ch, 91, 127);
upThis.pushChPrimitives(ch);
};
});
for (let part = 1; part < 10; part ++) {
upThis.dispatchEvent("voice", {
part
});
};
break;
};
};
// EFX defaults
let efxDefault;
switch (idx) {
case modeMap["?"]:
case modeMap.g2: {
efxDefault = [52, 4, 52, 18, 0, 255, 0, 255];
break;
};
case modeMap.xg:
case modeMap.cs1x: {
efxDefault = [1, 0, 65, 0, 5, 0, 64, 0];
break;
};
case modeMap.gm:
case modeMap.gs:
case modeMap.sc: {
efxDefault = [40, 4, 40, 18, 40, 32, 32, 0];
break;
};
case modeMap.sd: {
efxDefault = [58, 0, 60, 0, 61, 0, 61, 0];
break;
};
case modeMap["05rw"]:
case modeMap.x5d:
case modeMap.ns5r: {
efxDefault = [44, 1, 44, 19, 0, 255, 0, 255];
break;
};
case modeMap.k11:
case modeMap.sg: {
efxDefault = [24, 0, 0, 255, 0, 255, 0, 255];
break;
};
case modeMap.mt32: {
efxDefault = [40, 4, 0, 255, 0, 255, 0, 255];
break;
};
case modeMap.doc: {
efxDefault = [24, 16, 0, 255, 0, 255, 0, 255];
break;
};
case modeMap.motif:
case modeMap.s90es:
case modeMap.cs6x: {
efxDefault = [129, 0, 133, 0, 130, 0, 0, 0];
break;
};
case modeMap.pa: {
efxDefault = [28, 52, 28, 16, 28, 0, 28, 0];
break;
};
default: {
efxDefault = [0, 0, 0, 0, 0, 0, 0, 0];
};
};
for (let i = 0; i < allocated.efx; i ++) {
if (!upThis.#efxBase[3 * i] && efxDefault[i << 1]?.constructor) {
upThis.#efxBase[3 * i + 1] = efxDefault[2 * i];
upThis.#efxBase[3 * i + 2] = efxDefault[2 * i + 1];
upThis.dispatchEvent(`efx${['reverb', 'chorus', 'delay', 'insert0'][i]}`, upThis.getEffectType(i));
};
};
if (setTarget) {
upThis.setDetectionTargets(mode);
};
upThis.dispatchEvent("mode", modeIdx[idx]);
upThis.forceVoiceRefresh();
/*drumChannels.forEach((e) => {
upThis.dispatchEvent("voice", {
part: e
});
});*/
};
} else {
throw(new Error(`Unknown mode ${mode}`));
};
};
newStrength() {
this.#rawStrength.fill(0);
};
runJson(json) {
// Execute transformed JSON event
if (json.type > 14) {
if (json.type === 15 && json.data.constructor !== Uint8Array) {
json.data = Uint8Array.from(json.data);
};
return this.#runChEvent[json.type].call(this, json);
} else {
// Universal MIDI channel receive support.
let rcvPart = this.chRedir(json.part, json.track),
executed = false;
this.#receiveTree[rcvPart]?.forEach((e) => {
json.channel = e;
executed = true;
this.#runChEvent[json.type].call(this, json);
});
/* this.#chReceive.forEach((e, i) => {
if (e === rcvPart) {
//json.channel = this.chRedir(i, json.track);
json.channel = i;
executed = true;
this.#runChEvent[json.type].call(this, json);
};
}); */
if (!executed) {
console.warn(`${eventTypes[json.type] ? eventTypes[json.type] : json.type}${[11, 12].includes(json.type) ? (json.data[0] !== undefined ? json.data[0] : json.data).toString() : ""} event sent to CH${rcvPart + 1} without any recipient.`);
};
};
if (this.#metaTexts.length > 100) {
this.#metaTexts.splice(100, this.#metaTexts.length - 99);
};
};
runRaw(midiArr) {
// Translate raw byte stream into JSON MIDI event
};
async loadBank(format, blob) {
let upThis = this;
format = format.toLowerCase();
switch (format) {
case "s7e": {
upThis.userBank.clearRange({msb: 63, lsb: [21, 22]});
upThis.userBank.clearRange({msb: 63, lsb: [24, 27]});
break;
};
case "pcg": {
upThis.userBank.clearRange({msb: 63, lsb: [6, 9]});
upThis.userBank.clearRange({msb: 63, lsb: [13, 16]});
break;
};
default: {
throw(new Error(`Unknown bank format ${format}`));
};
};
switch (format) {
case "s7e":
case "pcg": {
bankDecoder.context = this;
await upThis.userBank.load(await bankDecoder.read(format, blob), false);
break;
};
};
upThis.forceVoiceRefresh();
};
constructor() {
super();
let upThis = this;
for (let i = 0; i < 10; i ++) {
upThis.#bitmapStore[i] = new Uint8Array(256);
};
upThis.#bitmapStore[10] = new Uint8Array(512);
upThis.#metaSeq = new BinaryMatch();
upThis.#detect = {
"x5": 82, // X5-related functions
"ds": modeMap.krs, // device-exclusive banks
"smotif": modeMap.s90es, // defaults to S90 ES
"gs": 4, // GS reset
"sc": 3, // GS mode set
"gm": modeMap.gm, // GM flavour (GM, XG, GS, 05R/W)
"g2": modeMap.g2 // GM2 flavour (GM2, XG, SD, PA)
};
upThis.#conf = {
"dumpLimit": upThis.DUMP_MODE
};
upThis.#confProxy = new Proxy(upThis.#conf, {
"set": () => {}
});
for (let mode in modeDetailsData) {
let index = modeMap[mode];
if (index === undefined) {
console.debug(`SubDB build error: "${mode}" does not exist`);
} else {
upThis.#subDb[index] = new Uint8Array(modeDetailsData[mode]);
};
};
/* upThis.#detect = new Proxy(upThis.#detectR, {
get: (real, key) => {
return real[key];
},
set: (real, key, value) => {
console.debug(new Error(`Caught detection target writes: ${key} = ${value}.`));
real[key] = value;
return true;
}
});
console.debug(`Detection target initialization finished.`); */
upThis.userBank.strictMode = true;
// Prevent bank readers from getting stalled
upThis.userBank.load(`MSB\tPRG\tLSB\tNME\n062\t000\t000\t\n122\t000\t000\t\n122\t001\t000\t\n122\t002\t000\t\n122\t003\t000\t\n122\t004\t000\t\n122\t005\t000\t\n122\t006\t000\t`);
upThis.addEventListener("metacommit", function (ev) {
//upThis.dispatchEvent("metacommit", ev.data);
let {data} = ev;
if (upThis.#metaTexts[0]?.type === data.type && upThis.#metaTexts[0]?.amend) {
upThis.#metaTexts[0].amend = data.amend;
upThis.#metaTexts[0].data += data.data;
} else {
upThis.#metaTexts.unshift(data);
};
});
// Metadata events
// Should be moved to somewhere else
upThis.#metaRun[1] = function (data) {
data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
// Normal text
switch (data.substring(0, 2)) {
case "@I": {
upThis.#modeKaraoke = upThis.KARAOKE_TEXT;
upThis.dispatchEvent("metacommit", {
"type": "Kar.Info",
"data": data.substring(2)?.trimStart()
});
break;
};
case "@K": {
upThis.#modeKaraoke = upThis.KARAOKE_TEXT;
let textBuffer = data.substring(2);
if (textBuffer !== "MIDI KARAOKE FILE") {
upThis.dispatchEvent("metacommit", {
"type": "Kar.Mode",
"data": textBuffer?.trimStart()
});
};
console.debug(`Karaoke mode active: ${textBuffer}`);
break;
};
case "@L": {
upThis.#modeKaraoke = upThis.KARAOKE_TEXT;
upThis.dispatchEvent("metacommit", {
"type": "Kar.Lang",
"data": data.substring(2)?.trimStart()
});
break;
};
case "@T": {
upThis.#modeKaraoke = upThis.KARAOKE_TEXT;
upThis.dispatchEvent("metacommit", {
"type": "KarTitle",
"data": data.substring(2)?.trimStart()
});
break;
};
case "@V": {
upThis.#modeKaraoke = upThis.KARAOKE_TEXT;
upThis.dispatchEvent("metacommit", {
"type": "Kar.Ver.",
"data": data.substring(2)?.trimStart()
});
break;
};
case "XF": {
// XG File Data section
let dataArr = data.slice(2).split(":");
switch (dataArr[0]) {
case "hd": {
dataArr.slice(1).forEach((e, i) => {
e.length && upThis.dispatchEvent("metacommit", {
"type": [
"XfSngDte", "XfSngRgn", "XfSngCat", "XfSongBt",
"XfSngIns", "XfSngVoc", "XfSngCmp", "XfSngLrc",
"XfSngArr", "XfSngPer", "XfSngPrg", "XfSngTag"
][i],
"data": e
});
});
break;
};
case "ln": {
dataArr.slice(1).forEach((e, i) => {
e.length && upThis.dispatchEvent("metacommit", {
"type": [
"XfKarLng", "XfKarNme", "XfKarCmp", "XfKarLrc",
"XfKarArr", "XfKarPer", "XfKarPrg"
][i],
"data": e
});
});
break;
};
default: {
upThis.dispatchEvent("metacommit", {
"type": "XfUnData",
"data": data
});
};
};
break;
};
default: {
switch (upThis.#modeKaraoke) {
case upThis.KARAOKE_TEXT: {
switch (data[0]) {
case "\\": {
// New section
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": "",
"amend": false
});
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": data.substring(1),
"amend": true
});
break;
};
case "/": {
// New line
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": "",
"mask": true,
"amend": false
});
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": data.substring(1),
"mask": true,
"amend": true
});
break;
};
default: {
// Normal append
//this.#metaTexts[0] += data;
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": data,
"amend": true
});
};
};
break;
};
default: {
//this.#metaTexts[0] = data;
data.split("\n").forEach((e, i) => {
upThis.dispatchEvent("metacommit", {
"type": "Cmn.Text",
"data": e,
"mask": i !== 0
});
});
};
};
};
};
};
upThis.#metaRun[2] = function (data) {
upThis.dispatchEvent("metacommit", {
"type": "Copyrite",
"data": data
});
};
upThis.#metaRun[3] = function (data, track) {
// Filter overly annoying meta events
if (track < 1 && upThis.#metaChannel < 1) {
upThis.dispatchEvent("metacommit", {
"type": "TrkTitle",
"data": data
});
};
};
upThis.#metaRun[4] = function (data, track) {
//if (track < 1 && this.#metaChannel < 1) {
//this.#metaTexts.unshift(`${showTrue(this.#metaChannel, "", " ")}Instrmnt: ${data}`);
//};
upThis.dispatchEvent("metacommit", {
"type": "Instrmnt",
"data": data
});
};
upThis.#metaRun[5] = function (data) {
switch (upThis.#modeKaraoke) {
case upThis.KARAOKE_XF: {
// Stateful XF lyrics parser
let textBuffer = "";
for (let e of data) {
switch (e) {
case "^": // Space
case "%": { // Secondary line break, treated the same here
textBuffer += " ";
break;
};
case ">": { // Tab, should be styled as 2-4 spaces when fit
textBuffer += "\t";
break;
};
case "<": { // New page, treated as new event in Octavia
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": textBuffer,
"mask": upThis.#maskNewLyric,
"amend": false
});
textBuffer = "";
upThis.#maskNewLyric = false;
break;
};
case "/": { // Line break
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": textBuffer,
"mask": false,
"amend": false
});
textBuffer = "";
upThis.#maskNewLyric = true;
break;
};
default: {
textBuffer += e;
};
};
};
if (textBuffer.length > 0) {
// Normal append
upThis.dispatchEvent("metacommit", {
"type": "KarLyric",
"data": textBuffer,
"mask": upThis.#maskNewLyric,
"amend": true
});
upThis.#maskNewLyric = false;
};
break;
};
default: {
let firstChar = 0, scanChar = true;
let lastChar = data.length - 1;
while (scanChar && firstChar < 8) {
switch (data.charCodeAt(firstChar)) {
case 32: {
firstChar ++;
break;
};
default: {
scanChar = false;
};
};
};
if (firstChar > 0) {
//console.debug(`Preceding spaces.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": data.substring(0, firstChar),
"amend": true,
"mask": upThis.#maskNewLyric,
"untimed": true
});
upThis.#maskNewLyric = false;
};
switch (data.charCodeAt(lastChar)) {
case 10: {
// Line feed
//console.debug(`Line feed.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": data.substring(firstChar, lastChar),
"amend": false,
"mask": upThis.#maskNewLyric
});
upThis.#maskNewLyric = false;
break;
};
case 11:
case 13: {
// Vertical tab and carriage return
//console.debug(`Carriage return.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": data.substring(firstChar, lastChar),
"amend": false,
"mask": upThis.#maskNewLyric
});
upThis.#maskNewLyric = true;
break;
};
case 32: {
// Space
//console.debug(`Before trailing spaces.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": data.substring(firstChar, lastChar),
"amend": true,
"mask": upThis.#maskNewLyric
});
//console.debug(`Trailing spaces.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": " ",
"amend": true,
"mask": false,
"untimed": true
});
upThis.#maskNewLyric = false;
break;
};
default: {
if (data.trim() === "") {
console.info(`Blank line? Shouldn't happen.`);
/*upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": "",
"amend": false,
"mask": upThis.#maskNewLyric
});*/
} else {
//console.debug(`Normal data commit.`);
upThis.dispatchEvent("metacommit", {
"type": "C.Lyrics",
"data": data,
"amend": true,
"mask": upThis.#maskNewLyric
});
};
upThis.#maskNewLyric = false;
};
};
break;
};
};
};
upThis.#metaRun[6] = function (data) {
upThis.dispatchEvent("metacommit", {
"type": "C.Marker",
"data": data
});
};
upThis.#metaRun[7] = function (data) {
switch(data[0]) {
case "$": {
if (data.substring(1, 5) === "Lyrc") {
// XF karaoke lyrics trigger & config
upThis.#modeKaraoke = upThis.KARAOKE_XF;
let xfKarLConf = data.substring(6).split(":");
let xfMelodyCh = xfKarLConf[0].replaceAll(" ", "").split(",");
xfMelodyCh.forEach((e, i, a) => {
a[i] = parseInt(e) - 1;
});
let xfLabel = xfEncLabels[xfKarLConf[2].substring(0, 2).toLowerCase()];
upThis.dispatchEvent("metacommit", {
"type": "XfMeloCh",
"data": `CH${xfKarLConf[0].replaceAll(" ", "").split(",").join(", CH")}`,
"parsed": xfMelodyCh
});
upThis.dispatchEvent("metacommit", {
"type": "XfLyrOff",
"data": xfKarLConf[1],
"parsed": parseInt(xfKarLConf[1])
});
upThis.dispatchEvent("metacommit", {
"type": "XfLyrEnc",
"data": xfLabel[1],
"parsed": xfLabel[0]
});
upThis.#maskNewLyric = false;
} else {
upThis.dispatchEvent("metacommit", {
"type": "CuePoint",
"data": data
});
};
break;
};
case "#": {
// XF scene number cue
upThis.dispatchEvent("metacommit", {
"type": "XfScneNo",
"data": data.substring(1)
});
break;
};
case "&": {
if (data.length === 2) {
// XF song part cue
upThis.dispatchEvent("metacommit", {
"type": "XfSngPrt",
"data": xfSongParts[data[1]] || `Unknown "${data[1]}"`
});
upThis.#maskNewLyric = false;
} else {
upThis.dispatchEvent("metacommit", {
"type": "CuePoint",
"data": data
});
};
break;
};
default: {
upThis.dispatchEvent("metacommit", {
"type": "CuePoint",
"data": data
});
};
};
};
upThis.#metaRun[32] = function (data) {
upThis.#metaChannel = data[0] + 1;
};
upThis.#metaRun[33] = function (data, track) {
getDebugState() && console.debug(`Track ${track} requests getting assigned to port ${data}.`);
upThis.#trkAsReq[track] = data + 1;
};
upThis.#metaRun[81] = function (data, track) {
upThis.#noteLength = data / 1000;
};
upThis.#metaRun[127] = function (data, track) {
//console.debug(`Sequencer specific on track ${track}: `, data);
upThis.#metaSeq.run(data, track);
};
// Sequencer/Implementation-specific meta event
// No refactoring needed.
upThis.#metaSeq.default = function (seq) {
let hexaText = [];
for (let i = 0; i < 8 && i < seq.length; i ++) {
hexaText.push(seq[i].toString(16).padStart(2, "0").toUpperCase());
};
console.warn(`Unrecognized implementation-specific byte sequence: ${hexaText.join(" ")}${seq.length > 8 ? " ..." : ""}\n%o`, seq);
};
upThis.#metaSeq.add([67, 0, 1], (msg, track) => {
// XGworks port assign
getDebugState() && console.debug(`XGworks port assign requests assigning track ${track} to port ${msg[0]}.`);
upThis.#trkAsReq[track] = msg[0] + 1;
}).add([67, 123, 1], (msg, track) => {
// XF chords
let data = [];
for (let i = 0; i < msg.length; i += 2) {
let chordAccidental = msg[i] >> 4;
let chordRoot = msg[i] & 15;
if (chordAccidental < 7 && chordRoot && chordRoot < 8) {
let chordIdNative = new Uint8Array(5);
chordIdNative[0] = chordRoot - 1;
chordIdNative[1] = (chordAccidental - 3) << 2;
chordIdNative[2] = msg[i | 1]; // XF ID
// Write the actual chord type here at 3 and 4
data.push(chordIdNative);
};
};
upThis.dispatchEvent("metacommit", {
type: "ChordCtl",
src: "yxf",
data
});
console.debug(`Yamaha XF chord data: %o`, data);
}).add([67, 123, 2], (msg, track) => {
// XF rehearsal mark
let data = new Uint8Array(2);
data[0] = msg[0] & 15; // Intro, Ending, Fill-in, A, B...
data[1] = msg[0] >> 4;
upThis.dispatchEvent("metacommit", {
type: "RhrslMrk",
src: "yxf",
data
});
console.debug(`Yamaha XF rehearsal mark: ${["Intro", "Ending", "Fill-in"][data[0]] ?? String.fromCharCode(62 + data[0])}${"'".repeat(data[1])} %o`, data);
}).add([67, 123, 96], (msg, track) => {
// Custom XF style indicator
let data = [0, 0];
msg.subarray(1, 3).forEach((e) => {
data[0] = data[0] << 7;
data[0] |= e & 127;
});
msg.subarray(3, 5).forEach((e) => {
data[1] = data[1] << 7;
data[1] |= e & 127;
});
upThis.dispatchEvent("metacommit", {
type: "YStyleId",
data
});
console.debug(`Yamaha style ID: model ${data[0].toString(16)}, style ${data[1] + 1}`,);
});
// Binary match should be avoided in favour of a circular structure
upThis.#seUnr = new BinaryMatch("universal non-realtime");
upThis.#seUr = new BinaryMatch("universal realtime");
upThis.#seXg = new BinaryMatch("Yamaha");
upThis.#seGs = new BinaryMatch("Roland");
upThis.#seAi = new BinaryMatch("Korg");
upThis.#seKg = new BinaryMatch("Kawai");
upThis.#seSg = new BinaryMatch("Akai");
upThis.#seCs = new BinaryMatch("Casio");
let cs6xDump = new BinaryMatch("DX7+ Dump");
// Notifies unrecognized SysEx strings with their vendors
let syxDefaultErr = function (msg) {
let hexaText = [];
for (let i = 0; i < 8 && i < msg.length; i ++) {
hexaText.push(msg[i].toString(16).padStart(2, "0").toUpperCase());
};
console.info(`Unrecognized SysEx in "${this.name}" set: ${hexaText.join(" ")}${msg.length > 8 ? " ..." : ""}\n%o`, msg);
};
upThis.#seUnr.default = syxDefaultErr;
upThis.#seUr.default = syxDefaultErr;
upThis.#seXg.default = syxDefaultErr;
upThis.#seGs.default = syxDefaultErr;
upThis.#seAi.default = syxDefaultErr;
upThis.#seKg.default = syxDefaultErr;
upThis.#seSg.default = syxDefaultErr;
upThis.#seCs.default = syxDefaultErr;
cs6xDump.default = syxDefaultErr;
// The new SysEx engine only defines actions when absolutely needed.
// Mode reset section
upThis.#seUnr.add([9], (msg, track, id) => {
// General MIDI reset.
upThis.switchMode(["gm", "?", "g2"][msg[0] - 1], true);
upThis.setPortMode(upThis.getTrackPort(track), 1, [modeMap.gm, modeMap["?"], upThis.#detect.g2][msg[0] - 1]);
upThis.#modeKaraoke = upThis.#modeKaraoke || upThis.KARAOKE_NONE;
console.info(`MIDI reset: ${["GM", "Init", "GM2"][msg[0] - 1]}`);
if (msg[0] === 2) {
upThis.init();
};
});
// GM SysEx section
upThis.#seUr.add([4, 1], (msg, track, id) => {
// Master volume
upThis.invokeSysExIndicator();
upThis.#masterVol = ((msg[1] << 7) + msg[0]) / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
}).add([4, 3], (msg, track, id) => {
// Master fine tune
return (((msg[1] << 7) + msg[0] - 8192) / 8192);
}).add([4, 4], (msg, track, id) => {
// Master coarse tune
return (msg[1] - 64);
}).add([4, 5], (msg, track, id) => {
// Global parameter change
upThis.invokeSysExIndicator();
let slotLen = msg[0], // Slotpath length, 1 means 2?
paramLen = msg[1], // Parameter length
valueLen = msg[2]; // Value length
let slotStart = 3,
paramStart = slotStart + (slotLen << 1),
valueStart = paramStart + paramLen;
if (slotLen !== 1) {
console.error(`Unsupported GM2 global parameter set: slotpath length too long (${slotLen})!\n`, msg);
return;
};
let slot = 0, param = 0, value = 0;
msg.subarray(slotStart, paramStart).forEach((e) => {
slot = slot << 7;
slot |= e;
});
msg.subarray(paramStart, valueStart).forEach((e, i) => {
param |= e << (i * 7);
});
msg.subarray(valueStart).forEach((e, i) => {
value |= e << (i * 7);
});
getDebugState() && console.debug(`GM2 global parameter: (${msg.subarray(3, paramStart)}; p: ${param}, v: ${value})`);
switch (slot) {
case 129: {
// GM reverb set
if (param === 0) {
upThis.setEffectType(0, 52, value);
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
};
break;
};
case 130: {
// GM chorus set
if (param === 0) {
upThis.setEffectType(1, 52, value | 16);
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
};
break;
};
default: {
getDebugState() && console.debug(`GM2 global paramater slot path unknown.`);
};
};
});
// XG SysEx section
this.#seXg.add([76, 0, 0], (msg, track, id) => {
switch (msg[0]) {
case 125: {
// XG drum reset
upThis.initDrums();
console.info(`XG drum setup reset on drum kit ${msg[1]}.`);
break;
};
case 126: {
// Yamaha XG reset
upThis.switchMode("xg", true);
upThis.setPortMode(upThis.getTrackPort(track), 4, modeMap.xg);
upThis.#modeKaraoke = upThis.#modeKaraoke || upThis.KARAOKE_NONE;
console.info("MIDI reset: XG");
break;
};
default: {
upThis.invokeSysExIndicator();
let mTune = [0, 0, 0, 0];
let writeTune = (e, i) => {
// XG master fine tune
mTune[i] = e;
};
msg.subarray(1).forEach((e, i) => {
let addr = i + msg[0];
([
writeTune, writeTune, writeTune, writeTune,
(e) => {
// XG master volume
this.#masterVol = e * 129 / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
},
(e) => {/* XG master attenuator */},
(e) => {/* XG master coarse tune */}
][addr] || (() => {}))(e, i);
});
if (msg[0] < 4) {
// Commit master tune
let rTune = 0;
mTune.forEach((e) => {
rTune = rTune << 4;
rTune += e;
});
rTune -= 1024;
console.debug(`Master tune: ${rTune}`);
};
};
};
}).add([76, 2, 1], (msg, track, id) => {
// XG reverb, chorus and variation
upThis.invokeSysExIndicator();
let dPref = "XG ";
if (msg[0] < 32) {
// XG reverb
dPref += "reverb ";
msg.subarray(1).forEach((e, i) => {
([(e) => {
upThis.setEffectTypeRaw(0, false, e);
console.info(`${dPref}main type: ${xgEffType[e]}`);
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
}, (e) => {
upThis.setEffectTypeRaw(0, true, e);
console.debug(`${dPref}sub type: ${e + 1}`);
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
}, (e) => {
console.debug(`${dPref}time: ${getXgRevTime(e)}s`);
}, (e) => {
console.debug(`${dPref}diffusion: ${e}`);
}, (e) => {
console.debug(`${dPref}initial delay: ${e}`);
}, (e) => {
console.debug(`${dPref}HPF cutoff: ${xgNormFreq[e]}Hz`);
}, (e) => {
console.debug(`${dPref}LPF cutoff: ${xgNormFreq[e]}Hz`);
}, (e) => {
console.debug(`${dPref}width: ${e}`);
}, (e) => {
console.debug(`${dPref}height: ${e}`);
}, (e) => {
console.debug(`${dPref}depth: ${e}`);
}, (e) => {
console.debug(`${dPref}wall type: ${e}`);
}, (e) => {
console.debug(`${dPref}dry/wet: ${e}`);
}, (e) => {
console.debug(`${dPref}send: ${toDecibel(e)}dB`);
}, (e) => {
console.debug(`${dPref}pan: ${e - 64}`);
}, false, false, (e) => {
console.debug(`${dPref}delay: ${e}`);
}, (e) => {
console.debug(`${dPref}density: ${e}`);
}, (e) => {
console.debug(`${dPref}balance: ${e}`);
}, (e) => {
}, (e) => {
console.debug(`${dPref}feedback: ${e}`);
}, (e) => {
}][msg[0] + i] || function () {
console.warn(`Unknown XG reverb address: ${msg[0]}.`);
})(e);
});
} else if (msg[0] < 64) {
// XG chorus
dPref += "chorus ";
msg.subarray(1).forEach((e, i) => {
([(e) => {
upThis.setEffectTypeRaw(1, false, e);
console.info(`${dPref}main type: ${xgEffType[e]}`);
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
}, (e) => {
upThis.setEffectTypeRaw(1, true, e);
console.debug(`${dPref}sub type: ${e + 1}`);
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
}, (e) => {
console.debug(`${dPref}LFO: ${xgLfoFreq[e]}Hz`);
}, (e) => {
//console.debug(`${dPref}LFO phase: ${e}`);
}, (e) => {
console.debug(`${dPref}feedback: ${e}`);
}, (e) => {
console.debug(`${dPref}delay offset: ${getXgDelayOffset(e)}ms`);
}, (e) => {
}, (e) => {
console.debug(`${dPref}low: ${xgNormFreq[e]}Hz`);
}, (e) => {
console.debug(`${dPref}low: ${e - 64}dB`);
}, (e) => {
console.debug(`${dPref}high: ${xgNormFreq[e]}Hz`);
}, (e) => {
console.debug(`${dPref}high: ${e - 64}dB`);
}, (e) => {
console.debug(`${dPref}dry/wet: ${e}`);
}, (e) => {
console.debug(`${dPref}send: ${toDecibel(e)}dB`);
}, (e) => {
console.debug(`${dPref}pan: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}to reverb: ${toDecibel(e)}dB`);
}, false, (e) => {
}, (e) => {
}, (e) => {
}, (e) => {
console.debug(`${dPref}LFO phase diff: ${(e - 64) * 3}deg`);
}, (e) => {
console.debug(`${dPref}input mode: ${e ? "stereo" : "mono"}`);
}, (e) => {
}][msg[0] - 32 + i] || function () {
console.warn(`Unknown XG chorus address: ${msg[0]}.`);
})(e);
});
} else if (msg[0] < 86) {
// XG variation section 1
dPref += "variation ";
msg.subarray(1).forEach((e, i) => {
([(e) => {
upThis.setEffectTypeRaw(2, false, e);
console.info(`${dPref}main type: ${xgEffType[e]}`);
upThis.dispatchEvent("efxdelay", upThis.getEffectType(2));
}, (e) => {
upThis.setEffectTypeRaw(2, true, e);
console.debug(`${dPref}sub type: ${e + 1}`);
upThis.dispatchEvent("efxdelay", upThis.getEffectType(2));
}][msg[0] - 64 + i] || function () {
//console.warn(`Unknown XG variation address: ${msg[0]}.`);
})(e);
});
} else if (msg[0] < 97) {
// XG variation section 2
dPref += "variation ";
msg.subarray(1).forEach((e, i) => {
([(e) => {
console.debug(`${dPref}send: ${toDecibel(e)}dB`);
}, (e) => {
console.debug(`${dPref}pan: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}to reverb: ${toDecibel(e)}dB`);
}, (e) => {
console.debug(`${dPref}to chorus: ${toDecibel(e)}dB`);
}, (e) => {
console.debug(`${dPref}connection: ${e ? "system" : "insertion"}`);
}, (e) => {
console.debug(`${dPref}channel: CH${e + 1}`);
}, (e) => {
console.debug(`${dPref}mod wheel: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}bend wheel: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}channel after touch: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}AC1: ${e - 64}`);
}, (e) => {
console.debug(`${dPref}AC2: ${e - 64}`);
}][msg[0] - 86 + i])(e);
});
} else if (msg[0] > 111 && msg[0] < 118) {
// XG variation section 3
dPref += "variation ";
} else {
console.warn(`Unknown XG variation address: ${msg[0]}`);
};
}).add([76, 2, 64], (msg, track, id) => {
// XG 5-part EQ
msg.subarray(1).forEach((e, i) => {
let c = i + msg[0];
if (c === 0) {
console.debug(`XG EQ preset: ${["flat", "jazz", "pop", "rock", "classic"][e]}`);
} else {
let band = (c - 1) >> 2,
prop = (c - 1) & 3,
dPref = `XG EQ ${band} ${["gain", "freq", "Q", "shape"][prop]}: `;
[() => {
console.debug(`${dPref}${e - 64}dB`);
}, () => {
console.debug(`${dPref}${e} (raw)`); // HELP WANTED
}, () => {
console.debug(`${dPref}${e / 10}`);
}, () => {
console.debug(`${dPref}${["shelf", "peak"][+!!e]}`);
}][prop]();
};
});
}).add([76, 3], (msg, track, id) => {
// XG insertion effects
upThis.invokeSysExIndicator();
let varSlot = msg[0], offset = msg[1];
let dPref = `XG Insertion ${msg[0] + 1} `;
msg.subarray(2).forEach((e, i) => {
([(e) => {
upThis.setEffectTypeRaw(3 + varSlot, false, e);
console.info(`${dPref}main type: ${xgEffType[e]}`);
upThis.dispatchEvent(`efxinsert${varSlot}`, upThis.getEffectType(3 + varSlot));
}, (e) => {
upThis.setEffectTypeRaw(3 + varSlot, true, e);
console.debug(`${dPref}sub type: ${e + 1}`);
upThis.dispatchEvent(`efxinsert${varSlot}`, upThis.getEffectType(3 + varSlot));
}][offset + i] || function () {
//console.warn(`Unknown XG variation address: ${msg[0]}.`);
})(e);
});
}).add([76, 6, 0], (msg, track, id) => {
// XG Letter Display
let offset = msg[0];
if (offset < 64) {
upThis.setLetterDisplay(msg.subarray(1), "XG letter display", offset);
} else {
// Expire all existing letter display
upThis.#letterExpire = Date.now();
};
}).add([76, 7, 0], (msg, track, id) => {
// XG Bitmap Display
let offset = msg[0];
upThis.#bitmapPage = 0;
upThis.#bitmapExpire = Date.now() + 3200;
//upThis.#bitmap.fill(0); // Init
let workArr = msg.subarray(1);
workArr.forEach(function (e, ir) {
let i = ir + offset;
let ln = i >> 4, co = i & 15;
let pt = (co * 3 + ln) * 7, threshold = 7, bi = 0;
pt -= co * 5;
if (ln === 2) {
threshold = 2;
};
while (bi < threshold) {
upThis.#bitmap[pt + bi] = (e >> (6 - bi)) & 1;
bi ++;
};
});
}).add([76, 8], (msg, track) => {
// XG part setup
upThis.invokeSysExIndicator();
let part = upThis.chRedir(msg[0], track, true),
id = msg[1],
chOff = ccOffTable[part],
dPref = `XG CH${part + 1} `,
errMsg = `Unknown XG part address ${id}.`;
let setupWrite = true;
msg.subarray(2).forEach((e, i) => {
// There is a bug here, but I don't have time right now
setupWrite = true;
if (id < 1) {
console.debug(errMsg);
} else if (id < 41) {
// CC manipulation can be further shrunk
([() => {
upThis.setChCc(part, 0, e); // MSB
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
upThis.setChCc(part, 32, e); // MSB
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
upThis.#prg[part] = e; // program
//upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
setupWrite = false;
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
if (!upThis.#chActive[part]) {
upThis.copyChSetup(ch, part, true);
console.debug(`${dPref}copied from CH${ch + 1}.`);
upThis.setChActive(part, 1);
upThis.dispatchEvent("voice", {
part
});
};
}, () => {
upThis.#mono[part] = +!e; // mono/poly
}, () => {
// same note key on assign?
}, () => {
setupWrite = false;
upThis.setChType(part, e, modeMap.xg);
console.debug(`${dPref}type: ${xgPartMode[e] || e}`);
upThis.dispatchEvent("voice", {
part
});
}, () => {
// coarse tune
upThis.#rpn[allocated.rpn * part + 3] = e;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, false, false, () => {
upThis.setChCc(part, 7, e); // volume
}, false, false, () => {
upThis.setChCc(part, 10, e || 128); // pan
}, false, false, () => {
upThis.setChCc(part, 128, e); // dry level
upThis.allocateAce(part, 128);
}, () => {
upThis.setChCc(part, 93, e); // chorus
}, () => {
upThis.setChCc(part, 91, e); // reverb
}, () => {
upThis.setChCc(part, 94, e); // variation
}, () => {
upThis.setChCc(part, 76, e); // vib rate
}, () => {
upThis.setChCc(part, 77, e); // vib depth
}, () => {
upThis.setChCc(part, 78, e); // vib delay
}, () => {
upThis.setChCc(part, 74, e); // brightness
}, () => {
upThis.setChCc(part, 71, e); // resonance
}, () => {
upThis.setChCc(part, 73, e); // attack
}, () => {
upThis.setChCc(part, 75, e); // decay
}, () => {
upThis.setChCc(part, 72, e); // release
}][id + i - 1] || (() => {}))();
} else if (id < 48) {
console.debug(errMsg);
} else if (id < 111) {
if (id > 102 && id < 105) {
upThis.setChCc(part, [5, 65][id & 1], e); // portamento
};
} else if (id < 114) {
console.debug(errMsg);
} else if (id < 116) {
console.debug(`${dPref}EQ ${["bass", "treble"][id & 1]} gain: ${e - 64}dB`);
} else if (id < 118) {
console.debug(errMsg);
} else if (id < 120) {
console.debug(`${dPref}EQ ${["bass", "treble"][id & 1]} freq: ${e}`);
} else {
console.debug(errMsg);
};
});
if (setupWrite && upThis.#chType[part] > 1) {
upThis.setDrumFirstWrite(part);
};
}).add([76, 9], (msg, track) => {
// PLG-VL Part Setup
let part = upThis.chRedir(msg[0], track, true),
id = msg[1],
chOff = ccOffTable[part];
let dPref = `PLG-VL CH${part + 1} `;
msg.subarray(2).forEach((e, i) => {
let ri = i + id;
switch (ri) {
case 1: {
console.info(`${dPref}breath mode: ${["system", "breath", "velocity", "touch EG"][e]}`);
break;
};
case 0:
case 27:
case 28: {
break;
};
default: {
if (ri < 27) {
let pId = (ri - 3) >> 1,
pType = [
"pressure",
"embouchure",
"tonguing",
"scream",
"breath noise",
"growl",
"throat formant",
"harmonic enhancer",
"damping",
"absorption",
"amplification",
"brightness"
][pId];
if (ri & 1) {
if (ri < 23) {
console.debug(`${dPref}${pType} control source: ${getVlCtrlSrc(e)}`);
if (e && e < 96) {
upThis.allocateAce(part, 130 + pId);
};
upThis.#ccCapturer[allocated.redir * part + pId + 2] = e;
upThis.buildRccMap();
} else {
// These actually belong to 0x57, not 0x4c
console.debug(`${dPref}${pType} scale break point: ${e}`);
};
} else {
console.debug(`${dPref}${pType} depth: ${e - 64}`);
upThis.setChCc(part, 130 + pId, e);
};
};
};
};
});
}).add([76, 10], (msg, track, id) => {
// XG HPF cutoff at 76, 10, nn, 32
// Won't implement for now
}).add([76, 16], (msg, track, id) => {
// XG A/D part, won't implement for now
}).add([76, 17, 0, 0], (msg, track, id) => {
// XG A/D mono/stereo mode, won't implement for now
}).add([76, 112], (msg, track, id) => {
// XG plugin board generic
console.debug(`XG enable PLG-${["VL", "SG", "DX", "AN", "PF", "DR", "PC", "AP"][msg[0]]} for CH${msg[2] + 1}.`);
switch (msg[0]) {
case 0: {
upThis.#ext[allocated.ext * msg[2]] = upThis.EXT_VL;
break;
};
case 2: {
upThis.#ext[allocated.ext * msg[2]] = upThis.EXT_DX;
break;
};
default: {
upThis.#ext[allocated.ext * msg[2]] = upThis.EXT_NONE;
};
};
}).add([73, 0, 0], (msg, track) => {
// MU1000/2000 System
let offset = msg[0];
let dPref = `MU1000 System - `;
msg.subarray(1).forEach((e, i) => {
let ri = offset + i;
if (ri === 8) {
console.debug(`${dPref}LCD contrast: ${e}.`);
} else if (ri === 18) {
upThis.#subDb[modeMap.xg][1] = e ? 126 : 0;
console.debug(`${dPref}default bank: ${e ? "MU100 Native" : "MU Basic"}.`);
upThis.dispatchEvent("banklevel", {
"mode": "xg",
"data": +!!e
});
upThis.forceVoiceRefresh();
} else if (ri >= 64 && ri < 69) {
// Octavia custom SysEx, starts from 64 (10 before)
// Deprecated, do not use! Will be removed in 0.6
[() => {
upThis.dispatchEvent("channelactive", e);
}, () => {
if (e < 8) {
upThis.dispatchEvent("channelmin", (e << 4));
console.debug(`Octavia System: Minimum CH${(e << 4) + 1}`);
} else {
upThis.dispatchEvent("channelreset");
console.info(`Octavia System: Clear channel ranges`);
};
}, () => {
if (e < 8) {
upThis.dispatchEvent("channelmax", (e << 4) + 15);
console.debug(`Octavia System: Maximum CH${(e << 4) + 16}`);
} else {
upThis.dispatchEvent("channelreset");
console.info(`Octavia System: Clear channel ranges`);
};
}, () => {
upThis.dispatchEvent("channelreset");
console.info(`Octavia System: Clear channel ranges`);
}, () => {
upThis.#receiveRS = !!e;
console.info(`Octavia System: RS receiving ${["dis", "en"][e]}abled.`);
}][ri - 64]();
};
});
}).add([73, 10, 0], (msg, track) => {
// MU1000 remote switch
// But in practice... They are channel switching commands.
let cmd = msg[0];
let dPref = `MU1000 RS${upThis.#receiveRS ? "" : " (ignored)"}: `;
if (cmd < 16) {
switch (cmd) {
case 2: {
// Show all 64 channels
let e = upThis.chRedir(0, track, true);
if (upThis.#receiveRS) {
upThis.dispatchEvent("portrange", 4);
upThis.dispatchEvent("portstart", e);
};
console.info(`${dPref}Show CH${e + 1}~CH${e + 64}`);
break;
};
case 3: {
// Show 32 channels
let e = upThis.chRedir(msg[1] << 5, track, true);
if (upThis.#receiveRS) {
upThis.dispatchEvent("portrange", 2);
upThis.dispatchEvent("portstart", e);
};
console.info(`${dPref}Show CH${e + 1}~CH${e + 32}`);
break;
};
default: {
console.debug(`${dPref}unknown switch ${cmd} invoked.`);
};
};
} else if (cmd < 32) {
if (upThis.#receiveRS) {
let e = upThis.chRedir(cmd - 16 + (upThis.#selectPort << 4), track, true);
upThis.dispatchEvent("channelactive", e);
};
} else if (cmd < 36) {
let e = upThis.chRedir((cmd - 32) << 4, track, true);
if (upThis.#receiveRS) {
upThis.dispatchEvent("portrange", 1);
upThis.dispatchEvent("portstart", e >> 4);
upThis.#selectPort = cmd - 32;
};
console.info(`${dPref}Show CH${e + 1}~CH${e + 16}`);
};
}).add([73, 11, 0], (msg, track) => {
// MU1000/2000 native channel switch
let dPref = `MU1000 System - channel `,
dPref2 = `Octavia System - channel `;
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = offset + i;
([() => {
// Current channel
upThis.setChActive(e, 1);
upThis.dispatchEvent("channelactive", e);
getDebugState() && console.debug(`${dPref}current part: CH${e + 1}`);
}, () => {
// Port range
// set to 255 to reset to auto mode
if (e < 5) {
upThis.dispatchEvent("portrange", 1 << e);
console.debug(`${dPref}port range: ${1 << e} port(s)`);
} else {
upThis.dispatchEvent("portrange", 0);
console.debug(`${dPref}port range: reset`);
};
}, () => {
// Start port (custom extension)
// set to 255 to reset to auto mode
if (e < 16) {
upThis.dispatchEvent("portstart", e);
console.debug(`${dPref2}start port: ${"ABCDEFGHIJKLMNOP"[e]}`);
} else {
upThis.dispatchEvent("portstart", 255);
console.debug(`${dPref2}start port: reset`);
};
}][ri] || (() => {
console.debug(`${dPref}unknown address: ${ri}`);
}))();
});
/*let part = upThis.chRedir(msg[0], track, true);
let port = part >> 4;
upThis.#chActive[part] = 1;
upThis.dispatchEvent("channelactive", part);
upThis.dispatchEvent("channelmin", port << 4);
upThis.dispatchEvent("channelmax", (port << 4) | 15);
getDebugState() && console.debug(`MU1000 native channel switch: `, msg);*/
}).add([93, 3], (msg, track) => {
// PLG-SG singing voice
let part = upThis.chRedir(msg[0], track, true),
dPref = `PLG-SG CH${part + 1} `,
timeNow = Date.now();
if (msg[1] === 0) {
// Vocal information
let vocal = "",
length = 0;
msg.subarray(2).forEach((e, i) => {
if (i % 2 === 0) {
vocal += xgSgVocals[e] || e.toString().padStart("0");
} else {
length += e * 13; // 7.5ms
};
});
if (
timeNow >= upThis.#sgConvertLastSyllable ||
upThis.#sgRunningLineLength >= upThis.#sgMaxLineLength
) {
upThis.dispatchEvent("metacommit", {
"type": "SGLyrics",
"data": "",
"amend": false
});
//console.debug(`Splitted at length: ${upThis.#sgRunningLineLength}`);
upThis.#sgSplittedMask = timeNow < upThis.#sgConvertLastSyllable;
upThis.#sgRunningLineLength = 0;
};
upThis.dispatchEvent("metacommit", {
"type": "SGLyrics",
"data": `${getSgKana(vocal)}`,
"amend": true,
"mask": upThis.#sgSplittedMask
});
upThis.#sgRunningLineLength ++;
upThis.#sgSplittedMask = false;
//console.debug(`Running length: ${upThis.#sgRunningLineLength}`);
upThis.#sgConvertLastSyllable = timeNow + Math.ceil(length / 2) + upThis.#noteLength;
if (getDebugState()) {
console.debug(`${dPref}vocals: ${vocal}`);
};
} else {
console.warn(`Unknown PLG-SG data: ${msg}`);
};
}).add([98, 96], (msg, track, id, opt) => {
// PLG-DX multi-part param set
let part = upThis.chRedir(msg[0], track, true);
let chOff = ccOffTable[part];
let offset = msg[1];
msg.subarray(2).forEach((e, i) => {
let ri = offset + i;
if (ri < 10) {
// Unknown section
} else if (ri === 10) {
// Only dumps will attempt to write here
upThis.resetAce();
} else if (ri < 27) {
// 11~26 to 142~157
let targetCc = ri + 131;
upThis.setChCc(part, targetCc, e);
if (!opt?.noAce && e > 0 && e !== 64) {
upThis.allocateAce(part, targetCc);
};
upThis.dispatchEvent("cc", {
part,
cc: targetCc,
data: e
});
} else {
// Unknown section
};
});
}).add([1, 20], (msg, track, id) => {
if (id === 115) {
// DOC reset
upThis.switchMode("doc", true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.doc);
console.info("MIDI reset: DOC");
} else {
console.debug(`Unknown Yamaha SysEx: 67, ${id}, ${msg.join(', ')}`);
};
}).add([1, 17, 15, 89], (msg, track, id) => {
if (id === 115) {
// DOC reverb type
upThis.setEffectType(0, 24, msg[0] | 16);
upThis.dispatchEvent(`efxreverb`, upThis.getEffectType(0));
} else {
console.debug(`Unknown Yamaha SysEx: 67, ${id}, ${msg.join(', ')}`);
};
}).add([1, 24], (msg, track, id) => {
if (id === 115) {
// DOC global reverb depth on
for (let ch = 0; ch < 16; ch ++) {
let part = upThis.chRedir(ch, track, true);
upThis.setChCc(part, 91, 127);
};
console.info("DOC Global Reverb: on");
} else {
console.debug(`Unknown Yamaha SysEx: 67, ${id}, ${msg.join(', ')}`);
};
}).add([126, 0], (msg, track, id) => {
// YMCS section control
// Yamaha Music Communications System
switch (msg[1]) {
case 0: {
// Section control off
upThis.dispatchEvent("metacommit", {
type: "YMCSSect",
data: `Disabled`,
raw: "Intro "
});
console.debug(`Yamaha Section Control is off.`);
break;
};
case 127: {
// Section control on
upThis.dispatchEvent("metacommit", {
type: "YMCSSect",
data: ["Intro", "Main A", "Main B", "Fill-in AB", "Fill-in BA", "Ending", "Blank"][msg[0] - 8] ?? `invalid section ${msg[0]}`,
raw: ["Intro ", "Main A", "Main B", "FillAB", "FillBA", "Ending", "Blank "][msg[0] - 8] ?? `ID: ${msg[0]}`
});
console.debug(`Yamaha Section Control switches to "${["intro", "main A", "main B", "fill-in AB", "fill-in BA", "ending", "blank"][msg[0] - 8] ?? `Invalid section ${msg[0]}`}".`);
break;
};
default: {
// Unknown section control
console.debug(`Unknown YMCS section control: ${msg[1]}`);
};
};
}).add([126, 2], (msg, track, id) => {
// YMCS chord control
let data = [];
for (let i = 0; i < msg.length; i += 2) {
let chordAccidental = msg[i] >> 4;
let chordRoot = msg[i] & 15;
if (chordAccidental < 7 && chordRoot && chordRoot < 8) {
let chordIdNative = new Uint8Array(5);
chordIdNative[0] = chordRoot - 1;
chordIdNative[1] = (chordAccidental - 3) << 2;
chordIdNative[2] = msg[i | 1]; // XF ID
// Write the actual chord type here at 3 and 4
data.push(chordIdNative);
};
};
upThis.dispatchEvent("metacommit", {
type: "ChordCtl",
src: "ymcs",
data
});
console.debug(`YMCS chord data: %o`, data);
});
let sysExDrumWrite = function (drumId, note, key, value) {};
let sysExDrumsY = function (drumId, msg) {
// The Yamaha XG-style drum setup
//console.debug(`XG-style drum setup on set ${drumId + 1}:\n`, msg);
upThis.invokeSysExIndicator();
let drumOff = drumId * allocated.dpn;
let note = msg[0], offset = msg[1];
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
let commitSlot = -1;
if (ri < 16) {
([() => {
// coarse tune
commitSlot = 24;
}, () => {
// fine tune
commitSlot = 25;
}, () => {
// level
commitSlot = 26;
}, () => {
// exclusive group
// Postponed until 0.5.1 or 0.6.
}, () => {
// panpot
commitSlot = 28;
}, () => {
// reverb
commitSlot = 29;
}, () => {
// chorus
commitSlot = 30;
}, () => {
// variation
commitSlot = 31;
}, () => {
// assign mode (single/multi)
}, () => {
// Rx note off
// no plans for support yet
}, () => {
// Rx note on
// no plans for support yet
}, () => {
// LPF cutoff
commitSlot = 20;
}, () => {
// LPF resonance
commitSlot = 21;
}, () => {
// attack rate
commitSlot = 22;
}, () => {
// decay rate
commitSlot = 23;
}, () => {
// decay 2 rate
// behaviour not yet documented
}][ri] || (() => {
console.debug(`Unknown XG-style drum param ${ri} on set ${drumId + 1}.`);
}))();
} else if (ri < 32) {
} else if (ri < 40) {
([() => {
// EQ bass gain
commitSlot = 48;
}, () => {
// EQ treble gain
commitSlot = 49;
}, false, false, () => {
// EQ bass freq
commitSlot = 52;
}, () => {
// EQ treble freq
commitSlot = 53;
}][ri - 32] || (() => {
console.debug(`Unknown XG-style drum param ${ri} on set ${drumId + 1}.`);
}))();
} else if (ri < 80) {
} else {
([() => {
// HPF cutoff
commitSlot = 36;
}][ri - 80] || (() => {
console.debug(`Unknown XG-style drum param ${ri} on set ${drumId + 1}.`);
}))();
};
if (commitSlot >= 0) {
getDebugState() && console.debug(drumOff, commitSlot, note, e);
upThis.#drum[(drumOff + dnToPos[commitSlot]) * allocated.dnc + note] = e;
} else {
getDebugState() && console.debug(`XG-style drum param ${ri} has no writes.`);
};
});
};
let sysExDrumsR = function (drumId, param, msg) {
// The Roland GS-style drum setup
//console.debug(`GS-style drum setup on set ${drumId + 1} param ${param}:\n`, msg);
upThis.invokeSysExIndicator();
let drumOff = drumId * allocated.dpn;
let offset = (param << 7) + msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = i + offset,
note = ri & 127,
key = ri >> 7;
let commitSlot = -1;
if (key > 1) {
([() => {
// level
commitSlot = 26;
}, () => {
// exclusive group
}, () => {
// panpot
commitSlot = 28;
}, () => {
// reverb
commitSlot = 29;
}, () => {
// chorus
commitSlot = 30;
}, () => {
// Rx note off
// no plans for support yet
}, () => {
// Rx note on
// no plans for support yet
}, () => {
// variation
commitSlot = 31;
}][key - 2] || (() => {
console.debug(`Unknown GS-style drum param ${key} on set ${drumId + 1}.`);
}))();
};
if (commitSlot > -1) {
getDebugState() && console.debug(drumOff, commitSlot, note, e);
upThis.#drum[(drumOff + dnToPos[commitSlot]) * allocated.dnc + note] = e;
} else {
getDebugState() && console.debug(`GS-style drum param ${key} has no writes.`);
};
});
};
this.#seXg.add([76, 48], (msg, track, id) => {
// XG drum setup 1
sysExDrumsY(0, msg);
}).add([76, 49], (msg, track, id) => {
// XG drum setup 2
sysExDrumsY(1, msg);
}).add([76, 50], (msg, track, id) => {
// XG drum setup 3
sysExDrumsY(2, msg);
}).add([76, 51], (msg, track, id) => {
// XG drum setup 4
sysExDrumsY(3, msg);
}).add([76, 52], (msg, track, id) => {
// XG drum setup 5
sysExDrumsY(4, msg);
}).add([76, 53], (msg, track, id) => {
// XG drum setup 6
sysExDrumsY(5, msg);
}).add([76, 54], (msg, track, id) => {
// XG drum setup 7
sysExDrumsY(6, msg);
}).add([76, 55], (msg, track, id) => {
// XG drum setup 8
sysExDrumsY(7, msg);
});
// MU1000/2000 EPROM write
this.#seXg.add([89, 0], (msg, track, id) => {
// EPROM trail write
if (upThis.eprom) {
let length = msg[0];
let addr = (msg[1] << 14) + (msg[2] << 7) + msg[3] + (upThis.eprom.offset || 0);
getDebugState() && console.debug(`MU1000 EPROM trail to 0x${addr.toString(16).padStart(6, "0")}, ${length} bytes.`);
let target = upThis.eprom.data;
msg.subarray(4).forEach((e, i) => {
// Overlay decoding
let secId = i >> 3, secIdx = i & 7;
if (secIdx === 7) {
for (let bi = 0; bi < 7; bi ++) {
target[addr + 7 * secId + bi] += ((e >> (6 - bi)) & 1) << 7;
};
} else {
target[addr + 7 * secId + secIdx] = e;
};
});
};
}).add([89, 1], (msg, track, id) => {
// EPROM base pointer jump
let addr = (msg[0] << 21) + (msg[1] << 14) + (msg[2] << 7) + msg[3];
getDebugState() && console.debug(`MU1000 EPROM jump to 0x${addr.toString(16).padStart(6, "0")}.`);
if (upThis.eprom) {
upThis.eprom.offset = addr;
};
}).add([89, 2], (msg, track, id) => {
// EPROM bulk write
// The first byte always seem to be zero
if (upThis.eprom) {
let addr = (msg[0] << 21) + (msg[1] << 14) + (msg[2] << 7) + msg[3] + (upThis.eprom.offset || 0);
getDebugState() && console.debug(`MU1000 EPROM write to 0x${addr.toString(16).padStart(6, "0")}.`);
let target = upThis.eprom.data;
msg.subarray(4).forEach((e, i) => {
// Overlay decoding
let secId = i >> 3, secIdx = i & 7;
if (secIdx === 7) {
for (let bi = 0; bi < 7; bi ++) {
target[addr + 7 * secId + bi] += ((e >> (6 - bi)) & 1) << 7;
};
} else {
target[addr + 7 * secId + secIdx] = e;
};
});
};
}).add([89, 3], (msg, track, id) => {
// Unknown instruction
});
// TG300 SysEx section, the parent of XG
this.#seXg.add([39, 48], (msg, track, id) => {
// TG100 pool
}).add([43, 0, 0], (msg, track, id) => {
// TG300 master setup
upThis.invokeSysExIndicator();
let mTune = [0, 0, 0, 0];
let writeTune = (e, i) => {
// GS master fine tune
mTune[i] = e;
};
msg.subarray(1).forEach((e, i) => {
let addr = i + msg[0];
([
writeTune,
writeTune,
writeTune,
writeTune,
() => {
this.#masterVol = e * 129 / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
},
() => {
return e - 64;
},
() => {
return e || 128;
},
() => {
return e;
},
() => {
return e;
},
() => {
console.debug(`TG300 variation on cc${e}.`);
}
] || (() => {}))[addr](e, addr);
});
if (msg[0] < 4) {
// Commit master tune
let rTune = 0;
mTune.forEach((e) => {
rTune = rTune << 4;
rTune += e;
});
rTune -= 1024;
console.debug(`Master tune: ${rTune}`);
};
}).add([43, 1, 0], (msg, track, id) => {
// TG300 effect (R C V) setup
upThis.invokeSysExIndicator();
}).add([43, 2], (msg, track, id) => {
// TG300 part setup
upThis.invokeSysExIndicator();
let part = upThis.chRedir(msg[0], track, true);
let offset = msg[1];
let chOff = ccOffTable[part];
let dPref = `TG300 CH${part + 1} `;
msg.subarray(2).forEach((e, i) => {
if (i < 5) {
([() => {
// element reserve
}, () => {
upThis.setChCc(part, 0, e);
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
upThis.setChCc(part, 32, e);
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
upThis.#prg[part] = e;
//upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}][i + offset] || (() => {}))(e, i + offset);
} else if (i < 21) {} else if (i < 47) {
([() => {
upThis.#mono[part] = +!e;
}, () => {
// same key on assign
}, () => {
// part mode
}, () => {
// coarse tune
upThis.#rpn[allocated.rpn * part + 3] = e;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// absolute detune
}, () => {
upThis.setChCc(part, 7, e);
}, false
, false
, () => {
upThis.setChCc(part, 10, e || 128);
}, false
, false
, () => {
console.debug(`${dPref} AC1 at cc${e}`);
}, () => {
console.debug(`${dPref} AC2 at cc${e}`);
}, () => {
// Dry level
upThis.#cc[chOff + ccToPos[128]] = e;
upThis.allocateAce(part, 128);
}, () => {
upThis.#cc[chOff + ccToPos[93]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[91]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[94]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[76]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[77]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[74]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[71]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[73]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[75]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[72]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[78]] = e;
}][i + offset - 21] || (() => {}))(e, i + offset);
} else if (i < 95) {} else {
([() => {
upThis.#cc[chOff + ccToPos[65]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[5]] = e;
}][i + offset - 95] || (() => {}))(e, i + offset);
};
});
}).add([43, 7, 0], (msg, track, id) => {
// TG300 display letter
// Same as XG letter display
let offset = msg[0];
upThis.setLetterDisplay(msg.subarray(1), "TG300 letter display", offset);
}).add([43, 7, 1], (msg, track, id) => {
// TG300 display bitmap
// Same as XG bitmap display
upThis.#bitmapPage = 0;
upThis.#bitmapExpire = Date.now() + 3200;
//upThis.#bitmap.fill(0); // Init
msg.forEach(function (e, i) {
let ln = i >> 4, co = i & 15;
let pt = (co * 3 + ln) * 7, threshold = 7, bi = 0;
pt -= co * 5;
if (ln === 2) {
threshold = 2;
};
while (bi < threshold) {
upThis.#bitmap[pt + bi] = (e >> (6 - bi)) & 1;
bi ++;
};
});
});
// TG drum setup would also be blank
// GS SysEx section
this.#seGs.add([66, 18, 0, 0, 127], (msg, track, id) => {
// GS mode set
upThis.switchMode("sc", true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap.sc);
// MUST REVISIT AND UPDATE!!!
/*upThis.setChCc(9, 0, 120);
upThis.setChCc(25, 0, 120);
upThis.setChCc(41, 0, 120);
upThis.setChCc(57, 0, 120);*/
upThis.#subDb[modeMap.sc][1] = upThis.#detect.sc;
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
upThis.#trkRedir.fill(0);
console.info(`GS system to ${["single", "dual"][msg[0]]} mode.`);
}).add([66, 18, 64, 0], (msg, track, id) => {
switch (msg[0]) {
case 127: {
// Roland GS reset
upThis.switchMode("gs", true);
upThis.setPortMode(upThis.getTrackPort(track), 4, modeMap.gs);
upThis.#subDb[modeMap.gs][1] = upThis.#detect.gs;
/*upThis.setChCc(9, 0, 120);
upThis.setChCc(25, 0, 120);
upThis.setChCc(41, 0, 120);
upThis.setChCc(57, 0, 120);*/
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
upThis.#trkRedir.fill(0);
console.info("MIDI reset: GS");
break;
};
default: {
upThis.invokeSysExIndicator();
let mTune = [0, 0, 0, 0];
let writeTune = (e, i) => {
// GS master fine tune
mTune[i] = e;
};
msg.subarray(1).forEach((e, i) => {
let addr = i + msg[0];
[
writeTune, writeTune, writeTune, writeTune,
(e) => {
// XG master volume
this.#masterVol = e * 129 / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
},
(e) => {/* XG master coarse tune */},
(e) => {/* XG master pan */}
][addr](e, i);
});
if (msg[0] < 4) {
// Commit master tune
let rTune = 0;
mTune.forEach((e) => {
rTune = rTune << 4;
rTune += e;
});
rTune -= 1024;
console.debug(`Master tune: ${rTune}`);
};
};
};
}).add([66, 18, 64, 1], (msg, track, id) => {
upThis.invokeSysExIndicator();
// GS patch params
let offset = msg[0];
if (offset < 16) {
// GS patch name (what for?)
let string = "".padStart(offset, " ");
msg.subarray(1).forEach((e, i) => {
string += String.fromCharCode(Math.max(32, e));
});
string = string.padEnd(16, " ");
console.debug(`GS patch name: ${string}`);
} else if (offset < 48) {
// GS partial reserve
} else if (offset < 65) {
// GS reverb and chorus
msg.subarray(1).forEach((e, i) => {
let dPref = `GS ${(offset + i) > 55 ? "chorus" : "reverb"} `;
([() => {
console.info(`${dPref}type: ${gsRevType[e]}`);
upThis.setEffectType(0, 40, e);
upThis.dispatchEvent(`efxreverb`, upThis.getEffectType(0));
}, () => {// character
}, () => {// pre-LPF
}, () => {// level
}, () => {// time
}, () => {// delay feedback
}, false, () => {
console.debug(`${dPref}predelay: ${e}ms`);
}, () => {
console.info(`${dPref}type: ${gsChoType[e]}`);
upThis.setEffectType(1, 40, 16 + e);
upThis.dispatchEvent(`efxchorus`, upThis.getEffectType(1));
}, () => {// pre-LPF
}, () => {// level
}, () => {// feedback
}, () => {// delay
}, () => {// rate
}, () => {// depth
}, () => {
console.debug(`${dPref}to reverb: ${toDecibel(e)}`);
}, () => {
console.debug(`${dPref}to delay: ${toDecibel(e)}`);
}][offset + i - 48] || (() => {}))();
});
} else if (offset < 80) {
console.debug(`Unknown GS patch address: ${offset}`);
} else if (offset < 91) {
// GS delay
msg.subarray(1).forEach((e, i) => {
let dPref = `GS delay `;
([() => {
console.info(`${dPref}type: ${gsDelType[e]}`);
upThis.setEffectType(2, 40, 32 + e);
upThis.dispatchEvent(`efxdelay`, upThis.getEffectType(2));
}, () => {// pre-LPF
}, () => {// time C
}, () => {// time L
}, () => {// time R
}, () => {// level C
}, () => {// level L
}, () => {// level R
}, () => {// level
}, () => {// feedback
}, () => {
console.debug(`${dPref}to reverb: ${toDecibel(e)}`);
}][offset + i - 80] || (() => {}))();
});
} else {
console.debug(`Unknown GS patch address: ${offset}`);
};
}).add([66, 18, 64, 2], (msg, track, id) => {
// GS EQ
let dPref = `GS EQ `;
msg.subarray(1).forEach((e, i) => {
([() => {
console.debug(`${dPref}low freq: ${[200, 400][e]}Hz`);
}, () => {
console.debug(`${dPref}low gain: ${e - 64}dB`);
}, () => {
console.debug(`${dPref}high freq: ${[3E3, 6E3][e]}Hz`);
}, () => {
console.debug(`${dPref}high gain: ${e - 64}dB`);
}][msg[0] + i] || function () {
console.warn(`Unknown GS EQ address: ${msg[0] + i}`);
})();
});
}).add([66, 18, 64, 3], (msg, track, id) => {
// GS EFX
upThis.invokeSysExIndicator();
let dPref = `GS EFX `;
let prefDesc = function (e, i) {
let desc = getGsEfxDesc(upThis.#efxBase.subarray(10, 12), i, e);
if (desc) {
console.debug(`${dPref}${getGsEfx(upThis.#efxBase.subarray(10, 12))} ${desc}`);
};
};
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(3, false, 32 + e);
upThis.dispatchEvent(`efxinsert0`, upThis.getEffectType(3));
}, () => {
upThis.setEffectTypeRaw(3, true, e);
console.info(`${dPref}type: ${getGsEfx(upThis.#efxBase.subarray(10, 12))}`);
upThis.dispatchEvent(`efxinsert0`, upThis.getEffectType(3));
}, false,
prefDesc, prefDesc, prefDesc, prefDesc, prefDesc,
prefDesc, prefDesc, prefDesc, prefDesc, prefDesc,
prefDesc, prefDesc, prefDesc, prefDesc, prefDesc,
prefDesc, prefDesc, prefDesc, prefDesc, prefDesc,
() => {
console.debug(`${dPref}to reverb: ${toDecibel(e)}dB`);
}, () => {
console.debug(`${dPref}to chorus: ${toDecibel(e)}dB`);
}, () => {
console.debug(`${dPref}to delay: ${toDecibel(e)}dB`);
}, false, () => {
console.debug(`${dPref}1 source: ${e}`);
if (e && e < 96) {
upThis.allocateAceAll(e);
};
}, () => {
console.debug(`${dPref}1 depth: ${e - 64}`);
}, () => {
console.debug(`${dPref}2 source: ${e}`);
if (e && e < 96) {
upThis.allocateAceAll(e);
};
}, () => {
console.debug(`${dPref}2 depth: ${e - 64}`);
}, () => {
console.debug(`${dPref}to EQ: ${e ? "ON" : "OFF"}`);
}][msg[0] + i] || function (e, i) {
console.warn(`Unknown GS EFX address: ${i}`);
})(e, msg[0] + i);
});
}).add([66, 18, 65], (msg, track, id) => {
// GS drum setup
sysExDrumsR(((msg[0] >> 4) + 1) << 1, msg[0] & 15, msg.subarray(1));
}).add([66, 18, 72], (msg, track, id) => {
// GS system/patch A dump
let offset = ((msg[0] & 127) << 7) | (msg[1] & 127);
console.debug(offset, halfByteUnpack(msg.subarray(2)));
}).add([69, 18, 16], (msg, track, id) => {
// GS display section
switch (msg[0]) {
case 0: {
// GS display letter
let offset = msg[1];
upThis.setLetterDisplay(msg.subarray(2), "GS display text", offset);
//getDebugState() && console.debug(`GS display text "${upThis.#letterDisp}".`);
break;
};
case 8: {
let offset = msg[1];
let scConf = upThis.modelEx.sc;
//console.debug("HORNY DEE");
msg.subarray(2).forEach((e, i) => {
([() => {
scConf.showBar = !(e & 1);
scConf.invBar = (e >> 1) & 1;
scConf.invDisp = (e >> 2) & 1;
}, () => {
scConf.peakHold = e < 4 ? e : 1;
}][i + offset] || (() => {
console.debug(`Unsupported GS display type SysEx.`);
}))();
});
break;
};
case 32: {
upThis.#bitmapExpire = Date.now() + 3200;
if (msg[1] === 0) {
// GS display page
if (msg[2]) {
upThis.#bitmapPage = Math.max(Math.min(msg[2] - 1, 9), 0);
getDebugState() && console.debug(`GS switch display page ${msg[2] - 1}.`);
} else {
upThis.#bitmapPage = 0;
upThis.#bitmapExpire = Date.now();
getDebugState() && console.debug(`GS disable display page.`);
};
};
break;
};
default: {
if (msg[0] < 6) {
// GS bitmap display or frame draw
if (upThis.#bitmapPage > 9) {
upThis.#bitmapPage = 0;
};
let realPage = ((msg[0] - 1) << 1) | (msg[1]) >> 6;
if (upThis.#bitmapPage === realPage) {
upThis.#bitmapExpire = Date.now() + 3200;
};
if (!upThis.#bitmapStore[realPage]?.length) {
upThis.#bitmapStore[realPage] = new Uint8Array(256);
};
let target = upThis.#bitmapStore[realPage];
getDebugState() && console.debug(`GS frame draw page ${realPage}.`);
let offset = msg[1] & 63;
//target.fill(0); // Init
let workArr = msg.subarray(2);
workArr.forEach(function (e, ii) {
let i = ii + offset;
let ln = i >> 4, co = i & 15;
let pt = (co * 4 + ln) * 5, threshold = 5, bi = 0;
pt -= co * 4;
if (ln === 3) {
threshold = 1;
};
while (bi < threshold) {
target[pt + bi] = (e >> (4 - bi)) & 1;
bi ++;
};
});
} else {
console.warn(`Unknown GS display section: ${msg[0]}`);
};
};
};
}).add([69, 18, 32], (msg, track, id) => {
// SC-8850 screen dump (partial)
let bundleId = msg[0], boundOff = msg[1], workArr = msg.subarray(2);
if (bundleId >> 4) {
console.warn(`SC-8850 partial screen dump out of bounds: invalid bundle.\n`, msg);
return;
};
let desiredByteTail = boundOff + workArr.length,
desiredLengthHead = boundOff * 6 - ((boundOff * 2428 >> 16) << 1),
desiredLengthTail = desiredByteTail * 6 - ((desiredByteTail * 2428 >> 16) << 1); // That's an integer division of 27, then a multiple of 2.
if (desiredLengthHead > 640) {
console.warn(`SC-8850 partial screen dump out of bounds: invalid buffer range.\n`, msg);
return;
};
if (desiredLengthTail > 640) {
console.info(`SC-8850 partial screen dump out of bounds: invalid buffer end, automatically restricted.\n`, msg);
desiredLengthTail = 640;
};
let screenBuffer = new Uint8Array(desiredLengthTail - desiredLengthHead);
workArr.forEach((e, i) => {
let ri = i + boundOff;
let isLineEnd = ((ri * 2428 & 65535) * 27 >> 16) === 26;
let pi = ri * 6 - ((ri * 2428 >> 16) << 1);
let bi = isLineEnd ? 2 : 0;
while (bi < 6) {
screenBuffer[pi + (5 - bi)] = (e >> bi) & 1;
bi ++;
};
});
let byteOffset = bundleId * 108 + boundOff,
offset = byteOffset * 6 - ((byteOffset * 2428 >> 16) << 1); // Used for emitting events than for internal processing.
upThis.dispatchEvent("screen", {type: "sc8850", offset, data: screenBuffer});
getDebugState() && console.debug(`SC-8850 screen dump: bundle ${bundleId + 1}, range ${desiredLengthHead}~${desiredLengthTail}`);
});
// GS Part setup
// I wanted this to also be written in a circular structure
// But clearly Roland hates me
let gsPartSec = function (msg, part, track) {
upThis.invokeSysExIndicator();
let offset = msg[0],
chOff = ccOffTable[part],
rpnOff = rpnOffTable[part],
dPref = `GS CH${part + 1} `;
if (offset < 3) {
// Program, MSB and receive channel
msg.subarray(1).forEach((e, i) => {
[() => {
upThis.#cc[chOff + ccToPos[0]] = e; // MSB
upThis.pushChPrimitives(part);
}, () => {
upThis.#prg[part] = e; // program
//upThis.pushChPrimitives(part);
}, () => {
let ch = 0;
if (e < 16) {
ch = upThis.chRedir(e, track, true);
} else {
ch = allocated.ch; // Effectively disabling event receives
};
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}][offset + i]();
});
upThis.dispatchEvent("voice", {
part
});
} else if (offset < 19) {} else if (offset < 44) {
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.#mono[part] = +!e; // mono/poly
}, false // assign mode
, () => {
// drum map
upThis.setChType(part, e << 1, modeMap.gs);
console.debug(`${dPref}type: ${e ? "drum " : "melodic"}${e ? e : ""}`);
}, () => {
// coarse tune
upThis.#rpn[rpnOff + 3] = e;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, false // pitch offset
, () => {
// volume
upThis.#cc[chOff + ccToPos[7]] = e;
}, false // velocity sense depth
, false // velocity sense offset
, () => {
// pan
upThis.#cc[chOff + ccToPos[10]] = e || 128;
}, false // note upperbound
, false // note lowerbound
, () => {
// general-purpose CC source A
console.debug(`${dPref}CC 1: cc${e}`);
}, () => {
// general-purpose CC source B
console.debug(`${dPref}CC 2: cc${e}`);
}, () => {
// chorus
upThis.#cc[chOff + ccToPos[93]] = e;
}, () => {
// reverb
upThis.#cc[chOff + ccToPos[91]] = e;
}, false // Rx bank select MSB
, false // Rx bank select LSB
, () => {
// fine tune MSB
upThis.#rpn[rpnOff + 1] = e;
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// fine tune LSB
upThis.#rpn[rpnOff + 2] = e;
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// delay (variation in XG)
upThis.#cc[chOff + ccToPos[94]] = e;
}][offset + i - 19] || (() => {}))();
});
} else if (offset < 76) {} else {
console.debug(`Unknown GS part address: ${offset}`);
};
},
gsMiscSec = function (msg, part) {
let offset = msg[0],
dPref = `GS CH${part + 1} `;
if (offset < 2) {
msg.subarray(1).forEach((e, i) => {
[() => {
// GS part LSB
upThis.setChCc(part, 32, e);
}, () => {// GS part fallback LSB
}][offset + i]();
});
} else if (offset < 32) {
console.warn(`Unknown GS misc address: ${offset}`);
} else if (offset < 35) {
msg.subarray(1).forEach((e, i) => {
[() => {
// GS part EQ toggle
console.debug(`${dPref}EQ: o${["ff", "n"][e]}`);
}, () => {// GS part output
}, () => {
// GS part EFX toggle
console.debug(`${dPref}EFX: o${["ff", "n"][e]}`);
upThis.#efxTo[part] = e;
upThis.dispatchEvent("partefxtoggle", {
part,
active: e
});
}][offset + i - 32]();
});
} else {
console.warn(`Unknown GS misc address: ${offset}`);
};
};
this.#seGs.add([66, 18, 64, 16], (msg, track) => {
gsPartSec(msg, upThis.chRedir(9, track, true), track);
}).add([66, 18, 64, 17], (msg, track) => {
gsPartSec(msg, upThis.chRedir(0, track, true), track);
}).add([66, 18, 64, 18], (msg, track) => {
gsPartSec(msg, upThis.chRedir(1, track, true), track);
}).add([66, 18, 64, 19], (msg, track) => {
gsPartSec(msg, upThis.chRedir(2, track, true), track);
}).add([66, 18, 64, 20], (msg, track) => {
gsPartSec(msg, upThis.chRedir(3, track, true), track);
}).add([66, 18, 64, 21], (msg, track) => {
gsPartSec(msg, upThis.chRedir(4, track, true), track);
}).add([66, 18, 64, 22], (msg, track) => {
gsPartSec(msg, upThis.chRedir(5, track, true), track);
}).add([66, 18, 64, 23], (msg, track) => {
gsPartSec(msg, upThis.chRedir(6, track, true), track);
}).add([66, 18, 64, 24], (msg, track) => {
gsPartSec(msg, upThis.chRedir(7, track, true), track);
}).add([66, 18, 64, 25], (msg, track) => {
gsPartSec(msg, upThis.chRedir(8, track, true), track);
}).add([66, 18, 64, 26], (msg, track) => {
gsPartSec(msg, upThis.chRedir(10, track, true), track);
}).add([66, 18, 64, 27], (msg, track) => {
gsPartSec(msg, upThis.chRedir(11, track, true), track);
}).add([66, 18, 64, 28], (msg, track) => {
gsPartSec(msg, upThis.chRedir(12, track, true), track);
}).add([66, 18, 64, 29], (msg, track) => {
gsPartSec(msg, upThis.chRedir(13, track, true), track);
}).add([66, 18, 64, 30], (msg, track) => {
gsPartSec(msg, upThis.chRedir(14, track, true), track);
}).add([66, 18, 64, 31], (msg, track) => {
gsPartSec(msg, upThis.chRedir(15, track, true), track);
}).add([66, 18, 64, 64], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(9, track, true));
}).add([66, 18, 64, 65], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(0, track, true));
}).add([66, 18, 64, 66], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(1, track, true));
}).add([66, 18, 64, 67], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(2, track, true));
}).add([66, 18, 64, 68], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(3, track, true));
}).add([66, 18, 64, 69], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(4, track, true));
}).add([66, 18, 64, 70], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(5, track, true));
}).add([66, 18, 64, 71], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(6, track, true));
}).add([66, 18, 64, 72], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(7, track, true));
}).add([66, 18, 64, 73], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(8, track, true));
}).add([66, 18, 64, 74], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(10, track, true));
}).add([66, 18, 64, 75], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(11, track, true));
}).add([66, 18, 64, 76], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(12, track, true));
}).add([66, 18, 64, 77], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(13, track, true));
}).add([66, 18, 64, 78], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(14, track, true));
}).add([66, 18, 64, 79], (msg, track) => {
gsMiscSec(msg, upThis.chRedir(15, track, true));
});
// KORG X5DR SysEx section
this.#seAi.add([54, 65], (msg, track) => {
// X5D multi parameters (part setup)
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target, true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap[x5Target]);
let checksum = msg[msg.length - 1],
msgData = msg.subarray(0, msg.length - 1),
expected = gsChecksum(msgData);
if (expected !== checksum) {
console.info(`X5D multi parameters checksum mismatch! Expected ${expected}, got ${checksum}.`);
console.debug(msg);
};
let key = (msg[1] << 7) + msg[0],
e = (msg[3] << 7) + msg[2],
part = upThis.chRedir(key & 15, track, true),
chOff = ccOffTable[part];
[() => {
// Program change
if (e < 1) {
} else if (e < 101) {
upThis.setChType(part, upThis.CH_MELODIC, modeMap.x5d);
upThis.#prg[part] = e - 1;
upThis.#cc[chOff + ccToPos[0]] = upThis.#detect.x5;
} else if (e < 229) {
upThis.setChType(part, upThis.CH_MELODIC, modeMap.x5d);
upThis.#prg[part] = e - 101;
upThis.#cc[chOff + ccToPos[0]] = 56;
} else {
upThis.setChType(part, upThis.CH_DRUMS, modeMap.x5d);
upThis.#prg[part] = korgDrums[e - 229] || 0;
upThis.#cc[chOff + ccToPos[0]] = 62;
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
// Volume
upThis.#cc[chOff + ccToPos[7]] = e;
}, () => {
// Panpot
if (e < 31) {
upThis.#cc[chOff + ccToPos[10]] = Math.round((e - 15) * 4.2 + 64);
};
}, () => {
// Chorus
upThis.#cc[chOff + ccToPos[93]] = x5dSendLevel(e);
}, () => {
// Reverb
upThis.#cc[chOff + ccToPos[91]] = x5dSendLevel(e);
}, () => {
// Coarse tune
upThis.#rpn[part * allocated.rpn + 3] = (e > 8191 ? e - 16320 : 64 + e);
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// Fine tune
upThis.#rpn[part * allocated.rpn + 1] = (e > 8191 ? e - 16320 : 64 + e);
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// PB range
if (e > 0) {
upThis.#rpn[part * allocated.rpn] = e;
upThis.#rpnt[allocated.rpnt * part] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
};
}, () => {
// program change filter
}][key >> 4]();
}).add([54, 76, 0], (msg, track) => {
// X5D program dump
upThis.invokeSysExIndicator();
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap[x5Target]);
let name = "", msb = upThis.#detect.x5, prg = 0, lsb = 0;
let voiceMap = "MSB\tPRG\tLSB\tNME";
korgFilter(msg, function (e, i) {
if (i < 16400) {
let p = i % 164;
switch (true) {
case (p < 10): {
if (e > 31) {
name += String.fromCharCode(e);
};
break;
};
case (p === 10): {
// 0: singular oscillator
// 1: dual oscillator
// 2: drum kit
//console.debug(`${name}: ${e}`);
break;
};
case (p === 11): {
voiceMap += `\n${msb}\t${prg}\t${lsb}\t${name.trim().replace("Init Voice", "")}`;
prg ++;
name = "";
break;
};
};
if (prg > 99) {
msb = 90;
prg = 0;
};
};
});
upThis.userBank.clearRange({
msb: upThis.#detect.x5,
prg: [0, 99],
lsb: 0
});
upThis.userBank.load(voiceMap);
getDebugState() && console.debug(voiceMap);
upThis.forceVoiceRefresh();
}).add([54, 77, 0], (msg, track) => {
// X5D combi dump
upThis.invokeSysExIndicator();
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap[x5Target]);
let name = "", msb = 90, prg = 0, lsb = 0;// CmbB then CmbA
let voiceMap = "MSB\tPRG\tLSB\tNME";
korgFilter(msg, function (e, i) {
if (i < 13600) {
let p = i % 136;
switch (true) {
case (p < 10): {
if (e > 31) {
name += String.fromCharCode(e);
};
break;
};
case (p === 11): {
voiceMap += `\n${msb}\t${prg}\t${lsb}\t${name.trim().replace("Init Combi", "")}`;
prg ++;
name = "";
break;
};
};
};
});
upThis.userBank.clearRange({
msb: 90,
prg: [0, 99],
lsb: 0
});
upThis.userBank.load(voiceMap);
getDebugState() && console.debug(voiceMap);
upThis.forceVoiceRefresh();
}).add([54, 78], (msg, track) => {
// X5D mode switch
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap[x5Target]);
console.debug(`X5D mode switch requested: ${["combi", "combi edit", "prog", "prog edit", "multi", "global"][msg[0]]} mode.`);
}).add([54, 85], (msg, track) => {
// X5D effect dump
upThis.invokeSysExIndicator();
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap[x5Target]);
korgFilter(msg, (e, i) => {
if (i > 0 && i < 3) {
upThis.setEffectType(i - 1, 44, e);
upThis.dispatchEvent(`efx${['reverb', 'chorus'][i - 1]}`, upThis.getEffectType(i - 1));
};
});
}).add([54, 104], (msg, track) => {
// X5D extended multi setup dump
upThis.invokeSysExIndicator();
let x5Target = upThis.#detect.x5 === "81" ? "05rw" : "x5d";
upThis.switchMode(x5Target, true);
let port = upThis.getTrackPort(track);
if (upThis.#portMode[port] && upThis.#portMode[port] !== modeMap[x5Target]) {
if (upThis.#conf.dumpLimit === upThis.DUMP_MODE) {
console.warn(`Dump cancelled for track ${track}. Port ${String.fromCharCode(65 + port)} mode "${modeIdx[upThis.#portMode[port]]}" mismatch, should be "${x5Target}".`);
return;
} else {
console.info(`Track ${track} is trying to perform a mismached mode dump on port ${String.fromCharCode(65 + port)}.`);
};
};
upThis.setPortMode(port, 1, modeMap[x5Target]);
korgFilter(msg, function (e, i, a, ri) {
if (i < 192) {
let part = upThis.chRedir(Math.floor(i / 12), track, true),
chOff = ccOffTable[part];
switch (i % 12) {
case 0: {
// Program change
if (e < 128) {
upThis.setChType(part, upThis.CH_MELODIC, modeMap.x5d);
upThis.#cc[chOff + ccToPos[0]] = upThis.#detect.x5;
upThis.#prg[part] = e;
} else {
upThis.setChType(part, upThis.CH_DRUMS, modeMap.x5d);
upThis.#cc[chOff + ccToPos[0]] = 62;
upThis.#prg[part] = korgDrums[e - 128];
};
if (e > 0) {
upThis.setChActive(part, 1);
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
break;
};
case 1: {
// Volume
upThis.#cc[chOff + ccToPos[7]] = e;
break;
};
case 2: {
// Coarse tune
upThis.#rpn[part * allocated.rpn + 3] = (e > 127 ? e - 192 : 64 + e);
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
break;
};
case 3: {
// Fine tune
upThis.#rpn[part * allocated.rpn + 1] = (e > 127 ? e - 192 : 64 + e);
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
break;
};
case 4: {
// Pan
if (e < 31) {
upThis.#cc[chOff + ccToPos[10]] = Math.round((e - 15) * 4.2 + 64);
};
break;
};
case 5: {
// Reverb + Chorus
let choSend = e >> 4,
revSend = e & 15;
upThis.#cc[chOff + ccToPos[91]] = x5dSendLevel(revSend);
upThis.#cc[chOff + ccToPos[93]] = x5dSendLevel(choSend);
break;
};
case 10: {
// Control filter
//upThis.#cc[chOff] = (e & 3) ? 82 : 56;
if (!upThis.#chType[part]) {
upThis.#cc[chOff + ccToPos[0]] = (e >> 6) ? 56 : upThis.#detect.x5;
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
};
break;
};
case 11: {
// MIDI Rc Ch + Track Switch
let midiCh = upThis.chRedir(e & 15, track, true),
trkSw = e >> 4;
upThis.#chReceive[part] = e;
if (midiCh !== part || trkSw) {
console.info(`X5D Part CH${part + 1} receives from CH${midiCh + 1}.`);
};
};
};
} else {
let part = upThis.chRedir(i - 192, track, true);
// What the heck is pitch bend range 0xF4(-12) to 0x0C(12)?
};
});
upThis.buildRchTree();
});
// Roland MT-32 or C/M SysEx section
this.#seGs.add([22, 18, 127], (msg, track, id) => {
// MT-32 reset all params
upThis.switchMode("mt32", true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.mt32);
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
upThis.userBank.clearRange({msb: 0, lsb: 127, prg: [0, 127]});
console.info("MIDI reset: MT-32");
}).add([22, 18, 0], (msg, track, id) => {
// MT-32 Part Patch Setup (temp)
upThis.switchMode("mt32");
let part = upThis.chRedir(id, track, true);
let offset = msg[1];
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
upThis.#cmTPatch[ri + (part - 1) * 16] = e;
([false
, () => {
let timbreGroup = upThis.#cmTPatch[(part - 1) << 4];
if (timbreGroup < 3) {
upThis.#bnCustom[part] = 1;
if (timbreGroup === 2) {
// Copy name from timbre memory
for (let c = 0; c < name.length; c ++) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + c] = upThis.#cmTimbre[e * allocated.cmt + c];
};
} else {
// Copy name from bank
let name = upThis.baseBank.get(0, e + (timbreGroup << 6), 127, "mt32").name;
for (let c = 0; c < name.length; c ++) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + c] = name.charCodeAt(c);
};
};
upThis.dispatchEvent("voice", {
part
});
};
}, () => {
upThis.#rpn[part * allocated.rpn + 3] = e + 40;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
}, () => {
upThis.#rpn[part * allocated.rpn + 1] = e + 14;
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
}, () => {
upThis.#rpn[part * allocated.rpn] = e;
upThis.#rpnt[allocated.rpnt * part] = 1;
}, false
, () => {
upThis.setChCc(part, 91, e ? 127 : 0);
}, false
, () => {
upThis.setChCc(part, 7, e);
}, () => {
upThis.setChCc(part, 10, Math.ceil(e * 9.05));
}][ri] || (() => {}))();
});
//console.debug(`MT-32 CH${part + 1} Patch: ${msg}`);
}).add([22, 18, 1], (msg, track, id) => {
// MT-32 Part Drum/Rhythm Setup (temp)
//this.#drum[(targetSlot * allocated.dpn + dnToPos[msb]) * allocated.dnc + lsb] = det.data[1];
upThis.switchMode("mt32");
//let part = upThis.chRedir(/*id*/9, track, true);
let slot = id & 7;
console.debug(`MT-32 slot #${id + 1} Drum: ${msg}`);
let offset = (msg[0] << 7) | msg[1];
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
let note = (ri >> 2) + 24, param = ri & 3, drumOff = slot * allocated.dpn;
getDebugState() && console.debug(`MT-32 temp drum note ${note} param ${param}: ${e}`);
if (note < 24) {
console.warn(`MT-32 dev drum write attempted on an OOB note: ${note}`);
return;
};
[() => {
// timbre (not supported)
}, () => {
// level
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = Math.round(e * 1.27);
}, () => {
// panpot (map 0-14 to 1-127)
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = (e * 9 + 1) & 127;
}, () => {
// reverb switch
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = e ? 127 : 0;
}][param]();
});
}).add([22, 18, 2], (msg, track, id) => {
// MT-32 Part Timbre Setup (temp)
upThis.switchMode("mt32");
let part = upThis.chRedir(id, track, true);
let offset = msg[1] + (msg[0] << 7);
if (offset < 10) {
upThis.#bnCustom[part] = 1;
};
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
if (ri < 14) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + ri] = e;
};
});
upThis.dispatchEvent("voice", {
part
});
}).add([22, 18, 3], (msg, track, id) => {
// MT-32 Part Patch Setup (dev)
upThis.switchMode("mt32");
let slot = id & 7;
if (msg[0]) {
// Rhythm setup
let offset = ((msg[0] - 1) << 7) + msg[1] - 16;
//getDebugState() && console.debug(`MT-32 dev drum setup (${offset}) \n`/*, msg.subarray(0, 2)*/, msg.subarray(2));
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
let note = (ri >> 2) + 24, param = ri & 3, drumOff = slot * allocated.dpn;
getDebugState() && console.debug(`MT-32 dev drum note ${note} param ${param}: ${e}`);
if (note < 24) {
console.warn(`MT-32 dev drum write attempted on an OOB note: ${note}`);
return;
};
[() => {
// timbre (not supported)
}, () => {
// level
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = Math.round(e * 1.27);
}, () => {
// panpot (map 0-14 to 1-127)
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = (e * 9 + 1) & 127;
}, () => {
// reverb switch
upThis.#drum[(drumOff + dnToPos[26]) * allocated.dnc + note] = e ? 127 : 0;
}][param]();
});
} else {
// Part setup
let offset = msg[1];
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
upThis.#cmTPatch[ri] = e;
let part = upThis.chRedir(1 + (ri >> 4), track, true),
ptr = ri & 15;
([false
, () => {
let timbreGroup = upThis.#cmTPatch[(part - 1) << 4];
if (timbreGroup < 3) {
upThis.#bnCustom[part] = 1;
if (timbreGroup === 2) {
// Copy name from timbre memory
for (let c = 0; c < name.length; c ++) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + c] = upThis.#cmTimbre[e * allocated.cmt + c];
};
} else {
// Copy name from bank
let name = upThis.baseBank.get(0, e + (timbreGroup << 6), 127, "mt32").name;
for (let c = 0; c < name.length; c ++) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + c] = name.charCodeAt(c);
};
};
};
upThis.dispatchEvent("voice", {
part
});
}, () => {
upThis.#rpn[part * allocated.rpn + 3] = e + 40;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
}, () => {
upThis.#rpn[part * allocated.rpn + 1] = e + 14;
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
}, () => {
upThis.#rpn[part * allocated.rpn] = e;
upThis.#rpnt[allocated.rpnt * part] = 1;
}, false
, () => {
upThis.setChCc(part, 91, e ? 127 : 0);
}, false
, () => {
upThis.setChCc(part, 7, e);
}, () => {
upThis.setChCc(part, 10, Math.ceil(e * 9.05));
}][ptr] || (() => {}))();
});
};
//console.debug(`MT-32 Part Patch: ${msg}`);
}).add([22, 18, 4], (msg, track, id) => {
// MT-32 Part Timbre Setup (dev)
upThis.switchMode("mt32");
let offsetTotal = msg[1] + (msg[0] << 7);
let parts = [];
msg.subarray(2).forEach((e, i) => {
let ri = i + offsetTotal;
let part = upThis.chRedir(Math.floor(ri / 246 + 1), track, true),
offset = ri % 246;
if (offset < 14) {
upThis.#cmTTimbre[(part - 1) * allocated.cmt + offset] = e;
};
if (offset < 10) {
upThis.#bnCustom[part] = 1;
};
if (parts.indexOf(part) < 0) {
parts.push(part);
};
});
parts.forEach((part) => {
upThis.dispatchEvent("voice", {
part
});
});
}).add([22, 18, 5], (msg, track, id) => {
// MT-32 Patch Memory Write
upThis.switchMode("mt32");
let offset = (msg[0] << 7) + msg[1];
msg.subarray(2).forEach((e, i) => {
let realIndex = (offset + i);
let patch = Math.floor(realIndex / 8), slot = (realIndex & 7);
let patchOff = patch * 8;
upThis.#cmPatch[realIndex] = e;
([false, () => {
let timbreGroup = upThis.#cmPatch[patchOff];
if (timbreGroup < 3) {
// Write for bank A, B and M
let name = "";
if (timbreGroup === 2) {
let timbreOff = allocated.cmt * patch;
name = `MT-m:${e.toString().padStart(3, "0")}`;
} else {
name = upThis.baseBank.get(0, e + (timbreGroup << 6), 127, "mt32").name;
};
upThis.userBank.clearRange({msb: 0, lsb: 127, prg: patch});
let loadTsv = `MSB\tLSB\tPRG\tNME\n49\t127\t${patch}\t${name}`;
//console.debug(loadTsv);
upThis.userBank.load(loadTsv, true);
};
}][slot] || (() => {}))();
});
upThis.forceVoiceRefresh();
}).add([22, 18, 8], (msg, track, id) => {
// MT-32 Timbre Memory Write
upThis.switchMode("mt32");
let offset = ((msg[0] & 1) << 7) + msg[1];
let patch = msg[0] >> 1, wroteName = false;
msg.subarray(2).forEach((e, i) => {
let ri = offset + i;
if (ri < allocated.cmt) {
//console.debug(`MT-32 timbre written to slot ${msg[0] >> 1}.`);
upThis.#cmTimbre[patch * allocated.cmt + ri] = e;
wroteName = true;
};
});
if (wroteName) {
upThis.userBank.clearRange({msb: 0, lsb: 127, prg: patch});
let loadTsv = `MSB\tLSB\tPRG\tNME\n49\t127\t${patch}\tMT-m:${patch}`;
upThis.userBank.load(loadTsv, true);
};
upThis.forceVoiceRefresh();
}).add([22, 18, 16], (msg, track, id) => {
// MT-32 System Setup
upThis.switchMode("mt32");
let offset = msg[1];
let updateRch = false;
let setMidiRch = function (e, i) {
upThis.#chReceive[i - 12] = e;
updateRch = true;
};
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
([false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
setMidiRch,
() => {
upThis.#masterVol = e;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
}][ri] || (() => {}))(e, i);
});
if (updateRch) {
upThis.buildRchTree();
};
}).add([22, 18, 32], (msg, track, id) => {
// MT-32 Text Display
upThis.switchMode("mt32");
let offset = msg[1];
let text = " ".repeat(offset);
msg.subarray(2).forEach((e) => {
if (e > 31) {
text += String.fromCharCode(e);
} else {
text += " ";
};
});
upThis.#letterDisp = text.padStart(20, " ");
upThis.#letterExpire = Date.now() + 3200;
//console.info(msg);
}).add([22, 18, 82], (msg, track) => {
// MT-32 alt reset?
let partBase = upThis.chRedir(0, track, true);
for (let part = 0; part < 16; part ++) {
upThis.#uAction.ano(partBase + part);
if (part && part < 10) {
upThis.#prg[partBase + part] = mt32DefProg[part - 1];
};
};
console.info(`MT-32 alt reset complete.`);
});
// KORG NS5R SysEx section
this.#seAi.add([66, 0], (msg, track) => {
// Mode switch
upThis.switchMode("ns5r", true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap.ns5r);
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
console.debug(`NS5R mode switch requested: ${["global", "multi", "prog edit", "comb edit", "drum edit", "effect edit"][msg[0]]} mode.`);
}).add([66, 1], (msg, track) => {
// Map switch
let n5Target = ["ns5r", "05rw"][msg[0]];
upThis.switchMode(n5Target, true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap[n5Target]);
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
}).add([66, 18, 0, 0], (msg, track) => {
// Master setup
let offset = msg[0];
switch (offset) {
case 124: // all param reset
case 126: // XG reset for NS5R
case 127: { // GS reset for NS5R
upThis.switchMode("ns5r", true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap.ns5r);
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
break;
};
case 125: {// drum reset
upThis.initDrums();
console.info(`NS5R drum setup reset on drum kit ${msg[1]}.`);
break;
};
default: {
if (offset < 10) {
let mTune = [0, 0, 0, 0];
let writeTune = (e, i) => {
// NS5R master fine tune
mTune[i] = e;
};
msg.subarray(1).forEach((e, i) => {
[writeTune, writeTune, writeTune, writeTune,
() => {
upThis.#masterVol = e * 129 / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
}, () => {
return (e - 64);
}, () => {
return (e - 64);
}, () => { // EFX MSB
}, () => { // EFX LSB
}, () => { // EFX PRG
}][offset + i]();
});
if (msg[0] < 4) {
// Commit master tune
let rTune = 0;
mTune.forEach((e) => {
rTune = rTune << 4;
rTune += e;
});
rTune -= 1024;
console.debug(`Master tune: ${rTune}`);
};
};
};
};
}).add([66, 18, 0, 1], (msg, track) => {
// Channel out port setup, trap for now
}).add([66, 18, 0, 2], (msg, track) => {
// Program out port setup, trap for now
}).add([66, 18, 1], (msg, track) => {
// Part setup
upThis.invokeSysExIndicator();
let part = upThis.chRedir(msg[0], track, true),
chOff = ccOffTable[part];
let offset = msg[1];
let dPref = `NS5R CH${part + 1} `;
msg.subarray(2).forEach((e, i) => {
let c = offset + i;
if (c < 3) {
// MSB, LSB, PRG
[() => {
upThis.#cc[chOff + ccToPos[0]] = e || overrides.bank0;
upThis.pushChPrimitives(part);
}, () => {
upThis.#cc[chOff + ccToPos[32]] = e;
upThis.pushChPrimitives(part);
}, () => {
upThis.#prg[part] = e;
//upThis.pushChPrimitives(part);
}][c]();
upThis.dispatchEvent("voice", {
part
});
} else if (c < 8) {
// Trap for junk data
} else if (c < 14) {
[() => {
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}, () => {
upThis.#mono[part] = +!e;
}, () => {
upThis.setChType(part, e, modeMap.ns5r);
console.debug(`${dPref}type: ${xgPartMode[e]}`);
}, () => {
upThis.#rpn[allocated.rpn * part + 3] = e;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
}, () => {
}][c - 8]();
} else if (c < 16) {
// Trap for junk data
} else if (c < 33) {
[() => {
upThis.#cc[chOff + ccToPos[7]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[11]] = e;
}, () => {
}, () => {
}, () => {
upThis.#cc[chOff + ccToPos[10]] = e || 128;
}, () => {
}, () => {
}, () => {
upThis.#cc[chOff + ccToPos[93]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[91]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[76]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[77]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[78]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[74]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[71]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[73]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[75]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[72]] = e;
}][c - 16]();
} else if (c < 112) {
// Trap for data not supported
} else if (c < 114) {
[() => {
upThis.#cc[chOff + ccToPos[5]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[65]] = e;
}][c - 112]();
};
});
}).add([66, 18, 8, 0], (msg, track) => {
// Display (letter and bitmap)
let offset = msg[0];
if (offset < 32) {
// Letter display
upThis.setLetterDisplay(msg.subarray(1, 33), "NS5R letter display");
} else {
// Bitmap display
let bitOffset = offset - 32;
upThis.#bitmapExpire = Date.now() + 3200;
upThis.#bitmapPage = 10; // Use bitmap 11 that holds 512 pixels
//upThis.#bitmap.fill(0); // Init
let workArr = msg.subarray(1);
let lastCol = 4;
workArr.forEach(function (e, i) {
let ri = i + bitOffset;
let tx = ri >> 4, ty = ri & 15;
if (ri < 80) {
let dummy = tx < lastCol ? e : e >> 3, shifted = 0, perspective = tx < lastCol ? 6 : 3;
while (dummy > 0) {
upThis.#bitmap[ty * 32 + tx * 7 + (perspective - shifted)] = dummy & 1;
dummy = dummy >> 1;
shifted ++;
};
};
});
};
}).add([66, 18, 48], (msg, track, id) => {
// NS5R drum setup 1
sysExDrumsY(0, msg);
}).add([66, 18, 49], (msg, track, id) => {
// NS5R drum setup 2
sysExDrumsY(1, msg);
}).add([66, 18, 50], (msg, track, id) => {
// NS5R drum setup 3
sysExDrumsY(2, msg);
}).add([66, 18, 51], (msg, track, id) => {
// NS5R drum setup 4
sysExDrumsY(3, msg);
}).add([66, 18, 52], (msg, track, id) => {
// NS5R drum setup 5
sysExDrumsY(4, msg);
}).add([66, 18, 53], (msg, track, id) => {
// NS5R drum setup 6
sysExDrumsY(5, msg);
}).add([66, 18, 54], (msg, track, id) => {
// NS5R drum setup 7
sysExDrumsY(6, msg);
}).add([66, 18, 55], (msg, track, id) => {
// NS5R drum setup 8
sysExDrumsY(7, msg);
}).add([66, 52], (msg, track) => {
// Currect effect dump
upThis.switchMode("ns5r");
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
//console.debug(`Dumped raw data: ${korgUnpack(msg).join(", ")}`);
let efxName = "";
korgFilter(msg, (e, i) => {
if (i < 8) {
if (e > 31) {
efxName += String.fromCharCode(e);
};
if (i === 7) {
upThis.aiEfxName = efxName;
};
} else if (i < 10) {
// AI effect ID
upThis.setEffectType(i - 8, 44, e);
upThis.dispatchEvent(`efx${['reverb', 'chorus'][i - 8]}`, upThis.getEffectType(i - 8));
};
});
}).add([66, 53], (msg, track) => {
// Current multi dump
upThis.invokeSysExIndicator();
upThis.switchMode("ns5r");
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
let efxName = "";
let checksum = msg[msg.length - 1],
msgData = msg.subarray(0, msg.length - 1),
expected = gsChecksum(msgData);
if (expected !== checksum) {
console.info(`NS5R current multi dump checksum mismatch! Expected ${expected}, got ${checksum}.`);
console.debug(msg);
};
let port = upThis.getTrackPort(track);
if (upThis.#portMode[port] && upThis.#portMode[port] !== modeMap.ns5r) {
if (upThis.#conf.dumpLimit === upThis.DUMP_MODE) {
console.warn(`Dump cancelled for track ${track}. Port ${String.fromCharCode(65 + port)} mode "${modeIdx[upThis.#portMode[port]]}" mismatch, should be "ns5r".`);
return;
} else {
console.info(`Track ${track} is trying to perform a mismached mode dump on port ${String.fromCharCode(65 + port)}.`);
};
};
upThis.setPortMode(port, 2, modeMap.ns5r);
// I'm lazy I just ported the old code here don't judge meee
korgFilter(msgData, function (e, i) {
switch (true) {
case i < 2944: {
// 32 part setup params, 2944 bytes
let part = upThis.chRedir(Math.floor(i / 92), track, true),
chOff = ccOffTable[part];
switch (i % 92) {
case 0: {
// MSB Bank
upThis.#cc[chOff + ccToPos[0]] = e;
upThis.pushChPrimitives(part);
// Needs an MSB = 125 for XG fallback
upThis.dispatchEvent("voice", {
part
});
break;
};
case 1: {
// LSB Bank
upThis.#cc[chOff + ccToPos[32]] = e;
if (!e && !upThis.#cc[chOff + ccToPos[0]]) {
upThis.#cc[chOff + ccToPos[0]] = overrides.bank0;
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
break;
};
case 2: {
// Program
upThis.#prg[part] = e;
if (e > 0) {
upThis.setChActive(part, 1);
};
//upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
break;
};
case 3: {
// Receive MIDI channel
let ch = upThis.chRedir(e, track, true);
upThis.#chReceive[part] = ch;
if (part !== ch) {
console.info(`NS5R CH${part + 1} receives from CH${ch + 1}.`);
};
break;
};
case 7: {
// 0 for melodic, 1 for drum, 2~5 for mod drums 1~4
// KORG has multiple MSBs for drums, well...
upThis.#chType[part] = e;
upThis.dispatchEvent("voice", {
part
});
break;
};
case 8: {
// Coarse Tune
upThis.#rpn[part * allocated.rpn + 3] = (e < 40 || e > 88) ? e + (e > 63 ? -192 : 64) : e;
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
break;
};
case 9: {
// Fine Tune
// This is trying to use absolute values.
};
case 10: {
// Volume
upThis.#cc[chOff + ccToPos[7]] = e;
break;
};
case 11: {
// Expression
upThis.#cc[chOff + ccToPos[11]] = e;
break;
};
case 14: {
// Pan
upThis.#cc[chOff + ccToPos[10]] = e || 128;
break;
};
case 19: {
// Chorus
upThis.#cc[chOff + ccToPos[93]] = e;
break;
};
case 20: {
// Reverb
upThis.#cc[chOff + ccToPos[91]] = e;
break;
};
case 84: {
// Portamento Switch
upThis.#cc[chOff + ccToPos[65]] = e;
break;
};
case 85: {
// Portamento Time
upThis.#cc[chOff + ccToPos[5]] = e;
break;
};
};
break;
};
case i < 3096: {
// part common params, 152 bytes
break;
};
case i < 3134: {
// current effect params, 38 bytes
let ri = i - 3096;
if (ri < 8) {
if (e > 31) {
efxName += String.fromCharCode(e);
};
if (ri === 7) {
upThis.aiEfxName = efxName;
};
} else if (ri < 10) {
// AI effect ID
upThis.setEffectType(ri - 8, 44, e);
upThis.dispatchEvent(`efx${['reverb', 'chorus'][ri - 8]}`, upThis.getEffectType(ri - 8));
};
//console.debug(e);
break;
};
case i < 8566: {
// 4 mod drum params, 5432 bytes
// This HAS TO BE finished before 0.6!
break;
};
};
});
upThis.buildRchTree();
}).add([66, 54], (msg, track) => {
// All program dump
// Yup this one is also ported from old code
upThis.invokeSysExIndicator();
upThis.switchMode("ns5r");
/*let checksum = msg[msg.length - 1],
msgData = msg.subarray(0, msg.length - 1),
expected = gsChecksum(msgData);
if (expected !== checksum) {
console.info(`NS5R all program dump checksum mismatch! Expected ${expected}, got ${checksum}.`);
console.debug(msg);
};*/
let name = "", msb = 80, prg = 0, lsb = 0;
let voiceMap = "MSB\tPRG\tLSB\tNME";
korgFilter(msg, function (e, i) {
let p = i % 158;
switch (true) {
case (p < 10): {
if (e > 31) {
name += String.fromCharCode(e);
};
break;
};
case (p === 10): {
// 0: singular oscillator
// 1: dual oscillator
// 2: drum kit
//console.debug(`${name}: ${e}`);
break;
};
case (p === 11): {
msb = e & 127;
break;
};
case (p === 12): {
lsb = e & 127;
break;
};
case (p === 13): {
voiceMap += `\n${msb}\t${prg}\t${lsb}\t${name.trim().replace("Init Voice", "")}`;
prg ++;
name = "";
break;
};
};
});
upThis.userBank.clearRange({
msb: 80,
lsb: 0
});
//console.debug(voiceMap);
upThis.userBank.load(voiceMap);
getDebugState() && console.debug(voiceMap);
upThis.forceVoiceRefresh();
}).add([66, 55], (msg, track) => {
// All combination dump
// Just modified from above
upThis.invokeSysExIndicator();
upThis.switchMode("ns5r");
let name = "", msb = 88, prg = 0, lsb = 0;
let voiceMap = "MSB\tPRG\tLSB\tNME";
korgFilter(msg, function (e, i) {
let p = i % 126;
switch (true) {
case (p < 10): {
if (e > 31) {
name += String.fromCharCode(e);
};
break;
};
case (p === 11): {
//msb = e;
break;
};
case (p === 12): {
//lsb = e;
break;
};
case (p === 13): {
voiceMap += `\n${msb}\t${prg}\t${lsb}\t${name.trim().replace("Init Combi", "")}`;
prg ++;
name = "";
break;
};
};
});
upThis.userBank.clearRange({
msb: 88,
lsb: 0
});
upThis.userBank.load(voiceMap);
getDebugState() && console.debug(voiceMap);
upThis.forceVoiceRefresh();
}).add([66, 125], (msg, track, id) => {
// Backlight
upThis.dispatchEvent("backlight", ["green", "orange", "red", false, "yellow", "blue", "purple"][msg[0]] || "white");
}).add([66, 127], (msg, track, id) => {
// NS5R screen dump
let checksum = msg[msg.length - 1],
msgData = msg.subarray(0, msg.length - 1),
expected = gsChecksum(msgData);
if (expected !== checksum) {
console.info(`NS5R screen dump checksum mismatch! Expected ${expected}, got ${checksum}.`);
console.debug(msg);
};
let screenBuffer = new Uint8Array(5760);
korgFilter(msg, (e, i, a) => {
if (i < 720) {
for (let bi = 0; bi < 8; bi ++) {
screenBuffer[i * 8 + bi] = (e >> (7 - bi)) & 1;
};
};
});
upThis.dispatchEvent("screen", {type: "ns5r", data: screenBuffer});
}).add([76], (msg, track, id) => {
// N1R to NS5R redirector
upThis.#seAi.run([66, ...msg], track, id);
});
// Kawai GMega
upThis.#seKg.add([16, 0, 8, 0], (msg, track, id) => {
// GMega system section
let e = (msg[2] << 4) + msg[3];
let dPref = "K11 ";
([() => {
// GMega bank set
upThis.switchMode("k11", true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap.k11);
upThis.#modeKaraoke = upThis.KARAOKE_NONE;
upThis.#subDb[modeMap.k11][1] = e ? 4 : 0;
console.info("MIDI reset: GMega/K11");
}, () => {
upThis.setEffectType(0, 24, e);
console.debug(`${dPref}reverb type: ${e}`);
upThis.dispatchEvent(`efxreverb`, upThis.getEffectType(0));
}, () => {
console.debug(`${dPref}reverb time: ${e}`);
}, () => {
console.debug(`${dPref}reverb time: ${e}`);
}, () => {
console.debug(`${dPref}reverb predelay: ${e}`);
}, () => {
console.debug(`${dPref}reverb predelay: ${e}`);
}, () => {
console.debug(`${dPref}depth high: ${e}`);
}, () => {
console.debug(`${dPref}depth high: ${e}`);
}, () => {
console.debug(`${dPref}depth low: ${e}`);
}, () => {
console.debug(`${dPref}depth low: ${e}`);
}][msg[0]] || (() => {}))();
}).add([16, 0, 8, 1], (msg, track, id) => {
// GMega part setup
upThis.invokeSysExIndicator();
let part = upThis.chRedir(msg[1], track, true),
chOff = ccOffTable[part],
rpnOff = rpnOffTable[part],
e = (msg[3] << 4) + msg[4];
let dPref = `GMega CH${part + 1} `;
([() => {
if (e < 128) {
// Melodic voice
upThis.setChType(part, upThis.CH_MELODIC, modeMap.k11);
upThis.#cc[chOff + ccToPos[0]] = 0;
upThis.#prg[part] = e;
} else {
// Drum kit
upThis.setChType(part, upThis.CH_DRUMS, modeMap.k11);
upThis.#prg[part] = e - 128;
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}, () => {
upThis.#cc[chOff + ccToPos[7]] = e; // volume
}, () => {
uupThis.setChActive(part, e); // toggle channel
}, () => {
upThis.#cc[chOff + ccToPos[10]] = e; // pan
}, () => {
upThis.#rpn[rpnOff + 3] = e + 40; // coarse tune
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
upThis.#rpn[rpnOff + 1] = e >> 1; // fine tune
upThis.#rpn[rpnOff + 2] = e & 1;
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
upThis.#cc[chOff + ccToPos[91]] = e ? 127 : 0; // reverb
}, () => {
// What is a negative bend depth/sensitivity?
}, () => {
upThis.#cc[chOff + ccToPos[74]] = e; // brightness
}, () => {
upThis.#cc[chOff + ccToPos[73]] = e; // attack
}, () => {
upThis.#cc[chOff + ccToPos[72]] = e; // release
}][msg[0]] || (() => {}))();
}).add([16, 0, 9, 0], (msg, track, id) => {
// GMega LX system section
upThis.invokeSysExIndicator();
let e = (msg[2] << 4) + msg[3];
let dPref = "GMLX ";
([() => {
console.debug(`${dPref}reverb type: ${e}`);
}, () => {
console.debug(`${dPref}reverb time: ${e}`);
}, () => {
console.debug(`${dPref}reverb predelay: ${e}`);
}, () => {
console.debug(`${dPref}depth high: ${e}`);
}, () => {
console.debug(`${dPref}depth low: ${e}`);
}][msg[0]] || (() => {}))();
}).add([16, 0, 9, 3], (msg, track, id) => {
// GMega LX part setup 1
upThis.invokeSysExIndicator();
let e = (msg[2] << 4) + msg[3];
let part = upThis.chRedir(msg[1], track, true),
chOff = ccOffTable[part];
let dPref = `GMLX CH${part + 1} `;
[() => {
if (e < 128) {
// Melodic voice
upThis.setChType(part, upThis.CH_MELODIC, modeMap.k11);
upThis.#cc[chOff + ccToPos[0]] = 0;
upThis.#cc[chOff + ccToPos[32]] = 0;
upThis.#prg[part] = e;
} else if (e < 160) {
// Melodic voice
upThis.setChType(part, upThis.CH_MELODIC, modeMap.k11);
upThis.#cc[chOff + ccToPos[0]] = 0;
upThis.#cc[chOff + ccToPos[32]] = 7;
upThis.#prg[part] = e - 100;
} else {
// Drum kit
upThis.setChType(part, upThis.CH_DRUMS, modeMap.k11);
upThis.#cc[chOff + ccToPos[0]] = 122;
upThis.#cc[chOff + ccToPos[32]] = 0;
upThis.#prg[part] = e - 160;
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}][msg[0]]();
}).add([16, 0, 9, 4], (msg, track, id) => {
// GMega LX part setup 2
upThis.invokeSysExIndicator();
let e = (msg[2] << 4) + msg[3];
let part = upThis.chRedir(msg[1], track, true),
chOff = ccOffTable[part],
rpnOff = rpnOffTable[part];
[() => {
upThis.setChActive(part, e); // toggle channel
}, () => {
upThis.#cc[chOff + ccToPos[7]] = e; // volume
}, () => {
upThis.#cc[chOff + ccToPos[10]] = e; // pan
}, () => {
upThis.#cc[chOff + ccToPos[91]] = e ? 127 : 0; // reverb
}, () => {
upThis.#rpn[rpnOff + 3] = e + 40; // coarse tune
upThis.#rpnt[allocated.rpnt * part + 2] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
upThis.#rpn[rpnOff + 1] = e; // fine tune
upThis.#rpnt[allocated.rpnt * part + 1] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
upThis.#rpn[rpnOff] = e; // pitch bend range
upThis.#rpnt[allocated.rpnt * part] = 1;
upThis.dispatchEvent("pitch", {
part,
pitch: upThis.getPitchShift(part)
});
}, () => {
// mod depth
}][msg[0]]();
});
// AKAI SG
this.#seSg.add([66, 93, 64], (msg, track, id) => {
let e = msg[2];
switch (msg[0]) {
case 0: {
// SG system section at 0x00
switch (msg[1]) {
case 4: {
// master volume
upThis.invokeSysExIndicator();
upThis.#masterVol = e * 129 / 16383 * 100;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
break;
};
case 5: {
// master key shift, [-24, 24]
upThis.invokeSysExIndicator();
(e - 64);
break;
};
case 6: {
// global reverb toggle
upThis.invokeSysExIndicator();
console.debug(`SG global reverb: ${e ? "on" : "off"}`);
break;
};
case 127: {
// SG reset
upThis.switchMode("sg", true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.sg);
break;
};
};
break;
};
case 1: {
switch (msg[1]) {
case 48: {
// SG reverb macro
upThis.invokeSysExIndicator();
console.debug(`SG reverb type: ${gsRevType[e]}`);
break;
};
};
break;
};
default: {
if ((msg[0] >> 4) === 1) {
// SG part setup
upThis.invokeSysExIndicator();
let part = upThis.chRedir(msg[0] & 15, track, true);
if (msg[1] === 2) {
// SG receive channel
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`SG CH${part + 1} receives from CH${ch + 1}`);
};
} else if (msg[1] === 19) {
// SG part level
upThis.setChCc(part, 7, e);
};
} else {
console.warn(`Unknown AKAI SG SysEx: ${msg}`);
};
};
};
});
this.#seCs.add([9], (msg, track, id) => {
// CASIO GZ-50M cc91 effect type set
upThis.invokeSysExIndicator();
console.debug(`GZ set effect: ${["stage reverb", "hall reverb", "room reverb", "chorus", "tremolo", "phaser", "rotary speaker", "enhancer", "flanger", "EQ"][msg[0]] || "off"}`);
});
// Yamaha S90 ES or Motif ES
this.#seXg.add([127, 0], (msg, track, id) => {
// Motif ES to S90 ES redirector
upThis.switchMode("motif");
let newMsg = new Uint8Array([127, 1, ...msg]);
upThis.#seXg.run(newMsg, track, id);
}).add([127, 1, 0, 0], (msg, track, id) => {
// S90 ES System
upThis.switchMode("s90es");
let dPref = "S90/Motif ES system ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.#masterVol = e * 12900 / 16383;
upThis.dispatchEvent("mastervolume", upThis.#masterVol);
}][offset + i] || (() => {
console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
}).add([127, 1, 14], (msg, track, id) => {
// S90 ES bulk dump header
upThis.switchMode("s90es");
let dPref = "S90/Motif ES bulk header ";
let addrSet = [];
addrSet[95] = (msg, track, id) => {
console.debug(`${dPref}multi edit buffer: ${msg[1]}`);
};
(addrSet[msg[0]] || (() => {
console.info(`Unrecognized ${dPref}ID: ${msg[0]}.`);
}))(msg.subarray(1));
}).add([127, 1, 15], (msg, track, id) => {
// S90 ES bulk dump footer
upThis.switchMode("s90es");
let dPref = "S90/Motif ES bulk footer ";
let addrSet = [];
addrSet[95] = (msg, track, id) => {
console.debug(`${dPref}multi edit buffer: ${msg[1]}`);
};
(addrSet[msg[0]] || (() => {
console.info(`Unrecognized ${dPref}ID: ${msg[0]}.`);
}))(msg.subarray(1));
}).add([127, 1, 54, 1], (msg, track, id) => {
// S90 ES Reverb
upThis.switchMode("s90es");
let dPref = "S90/Motif ES reverb ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(0, false, (e & 15) | 128);
}, () => {
upThis.setEffectTypeRaw(0, true, e);
}][offset + i] || (() => {
//console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
}).add([127, 1, 54, 2], (msg, track, id) => {
// S90 ES Chorus
upThis.switchMode("s90es");
let dPref = "S90/Motif ES chorus ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(1, false, (e & 15) | 128);
}, () => {
upThis.setEffectTypeRaw(1, true, e);
}][offset + i] || (() => {
//console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
}).add([127, 1, 54, 3], (msg, track, id) => {
// S90 ES Ins1
upThis.switchMode("s90es");
let dPref = "S90/Motif ES insert 1 ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(3, false, (e & 15) | 128);
}, () => {
upThis.setEffectTypeRaw(3, true, e);
}][offset + i] || (() => {
//console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
upThis.dispatchEvent("efxinsert0", upThis.getEffectType(3));
}).add([127, 1, 54, 4], (msg, track, id) => {
// S90 ES Ins2
upThis.switchMode("s90es");
let dPref = "S90/Motif ES insert 2 ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(4, false, (e & 15) | 128);
}, () => {
upThis.setEffectTypeRaw(4, true, e);
}][offset + i] || (() => {
//console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
upThis.dispatchEvent("efxinsert1", upThis.getEffectType(4));
}).add([127, 1, 54, 16], (msg, track, id) => {
// S90 ES EQ config
upThis.switchMode("s90es");
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let eqPart = i >> 2;
let dPref = `S90/Motif ES EQ${eqPart + 1} `;
([() => {
let eqGain = e - 64;
//console.debug(`${dPref}gain: ${eqGain}dB`);
}, () => {
let eqFreq = xgNormFreq[e];
//console.debug(`${dPref}freq: ${eqFreq}Hz`);
}, () => {
let eqQFac = e / 10;
//console.debug(`${dPref}Q: ${eqQFac}`);
}, () => {
let eqType = e; // shelf, peak
//console.debug(`${dPref}type: ${["shelf", "peak"][eqTypes]}`);
},][(offset + i) & 3] || (() => {}))();
});
}).add([127, 1, 54, 17], (msg, track, id) => {
// S90 ES Var
upThis.switchMode("s90es");
let dPref = "S90/Motif ES variation ",
offset = msg[0];
msg.subarray(1).forEach((e, i) => {
([() => {
upThis.setEffectTypeRaw(2, false, (e & 15) | 128);
}, () => {
upThis.setEffectTypeRaw(2, true, e);
}][offset + i] || (() => {
//console.info(`Unrecognized ${dPref}ID: ${offset + i}`);
}))();
});
upThis.dispatchEvent("efxdelay", upThis.getEffectType(2));
}).add([127, 1, 55], (msg, track, id) => {
// S90 ES bulk part setup dump (?)
upThis.invokeSysExIndicator();
upThis.switchMode("s90es");
let port = upThis.getTrackPort(track);
if (upThis.#portMode[port] && upThis.#portMode[port] !== upThis.#detect.smotif) {
if (upThis.#conf.dumpLimit === upThis.DUMP_MODE) {
console.warn(`Dump cancelled for track ${track}. Port ${String.fromCharCode(65 + port)} mode "${modeIdx[upThis.#portMode[port]]}" mismatch, should be "${modeIdx[upThis.#detect.smotif]}".`);
return;
} else {
console.info(`Track ${track} is trying to perform a mismached mode dump on port ${String.fromCharCode(65 + port)}.`);
};
};
//upThis.setPortMode(port, 2, modeMap.s90es);
let part = upThis.chRedir(msg[0], track, true),
chOff = ccOffTable[part],
offset = msg[1];
if (msg[0] > 15) {
part = msg[0] + 32;
};
let dPref = `Track ${track} S90/Motif ES bulk CH${part < 128 ? part + 1 : "U" + (part - 127)} `;
console.debug(dPref, msg);
if (msg[0] > 15) {
return;
};
msg.subarray(2).forEach((e, i) => {
([() => {
upThis.#cc[chOff + ccToPos[0]] = e;
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
e && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[32]] = e;
if (upThis.getChPrimitive(part, 1) === 63) {
upThis.setChType(part, ([32, 40].indexOf(e) > -1) ? upThis.CH_DRUMS : upThis.CH_MELODIC, upThis.#mode, true);
};
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
e && upThis.setChActive(part, 1);
upThis.#prg[part] = e;
//upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
let ch = upThis.chRedir(e, track, true);
let prevCh = upThis.#chReceive[part];
upThis.#chReceive[part] = ch; // Rx CH
if (part !== ch || ch !== prevCh) {
upThis.buildRchTree();
console.info(`${dPref}receives from CH${ch + 1}`);
};
}, () => {
upThis.#mono[part] = e ? 0 : 1;
}, false, false, false, false, false, false, false, false, () => {
e !== 100 && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[7]] = e;
}, () => {
e !== 64 && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[10]] = e;
}, false, false, false, () => {
//e !== 40 && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[91]] = e;
}, () => {
e && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[93]] = e;
}, () => {
e && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[94]] = e;
}, () => {
upThis.setChCc(part, 128, e);
if (e !== 127) {
upThis.setChActive(part, 1);
upThis.allocateAce(part, 128);
};
}, () => {
// note shift, RPN
}, () => {
e !== 64 && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[74]] = e;
}, () => {
e !== 64 && upThis.setChActive(part, 1);
upThis.#cc[chOff + ccToPos[71]] = e;
}, false, () => {
upThis.#cc[chOff + ccToPos[65]] = e;
}, () => {
upThis.#cc[chOff + ccToPos[5]] = e;
}, () => {
// portamento mode: fingered, fulltime
}][offset + i] || (() => {}))();
});
});
// Yamaha CS6x
upThis.#seXg.add([100, 14], (msg, track, id) => {
upThis.switchMode("cs6x");
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.cs6x);
// CS6x bulk start
let bulkTarget = ["normal voice", "plugin voice PLG", "drums", , "performance", , "phrase clip"][msg[0] >> 4] ?? "invalid";
if (msg[0] & 15 == 15) {
bulkTarget += " edit buffer";
} else if (msg[0] >> 4 == 1) {
bulkTarget += `${(msg[0] & 1) + 1}`;
} else {
bulkTarget += ` ${["INT", "EXT"][msg[0] & 1]}`;
};
console.debug(`CS6x bulk dump for ${bulkTarget} started.`);
}).add([100, 15], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x bulk end, same as before
let bulkTarget = ["normal voice", "plugin voice PLG", "drums", , "performance", , "phrase clip"][msg[0] >> 4] ?? "invalid";
if (msg[0] & 15 == 15) {
bulkTarget += " edit buffer";
} else if (msg[0] >> 4 == 1) {
bulkTarget += `${(msg[0] & 1) + 1}`;
} else {
bulkTarget += ` ${["INT", "EXT"][msg[0] & 1]}`;
};
console.debug(`CS6x bulk dump for ${bulkTarget} ended.`);
}).add([100, 76, 112], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin extra
let part = 0;
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
if (ri < 10) {
upThis.setChCvnRegister(part, i, e & 127);
upThis.#bnCustom[part] = 1;
} else if (ri < 12) {
// Reserved
} else if (ri < 13) {
console.debug(`Plugin voice type: ${e}`);
};
});
upThis.dispatchEvent("voice", {
part
});
}).add([100, 76, 0], (msg, track, id) => {
upThis.switchMode("cs6x");
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.cs6x);
// CS6x voice plugin common
let part = 0;
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
upThis.setChCc(part, 7, e); // volume
e !== 100 && upThis.setChActive(part, 1);
}, , , () => {
upThis.#mono[part] = e == 0; // mono
upThis.setChCc(part, 64, 0);
upThis.#uAction.ano(part);
upThis.resetChAceAll(part);
}, , () => {
// pitch bend range
}, , , () => {
upThis.setChCc(part, 65, e ? 127 : 0); // portamento SW
}, () => {
upThis.setChCc(part, 5, e); // portamento time
}, , () => {
upThis.setChCc(part, 91, e); // reverb
e !== 40 && upThis.setChActive(part, 1);
}, () => {
upThis.setChCc(part, 93, e); // chorus
e && upThis.setChActive(part, 1);
}][ri] || (() => {}))();
});
}).add([100, 76, 1], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin reverb
let part = 0;
let offset = msg[0];
let efxTypeWritten = false;
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
upThis.setEffectTypeRaw(0, false, (e & 15) | 128);
efxTypeWritten = true;
}, () => {
upThis.setEffectTypeRaw(0, true, e);
efxTypeWritten = true;
}][ri] || (() => {}))();
});
if (efxTypeWritten) {
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
};
}).add([100, 76, 2], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin chorus
let part = 0;
let offset = msg[0];
let efxTypeWritten = false;
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
upThis.setEffectTypeRaw(1, false, (e & 15) | 128);
efxTypeWritten = true;
}, () => {
upThis.setEffectTypeRaw(1, true, e);
efxTypeWritten = true;
}][ri] || (() => {}))();
});
if (efxTypeWritten) {
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
};
}).add([100, 76, 3], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin insertion
let part = 0;
let offset = msg[0];
let efxTypeWritten = false;
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
upThis.setEffectTypeRaw(2, false, (e & 15) | 128);
efxTypeWritten = true;
}, () => {
upThis.setEffectTypeRaw(2, true, e);
efxTypeWritten = true;
}][ri] || (() => {}))();
});
if (efxTypeWritten) {
upThis.dispatchEvent("efxdelay", upThis.getEffectType(2));
};
}).add([100, 76, 5], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin control source
// Not supported yet.
}).add([100, 76, 16], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin element
let part = 0;
let offset = msg[0];
let voiceUpdated = false;
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
// voice MSB
upThis.setChCc(part, 0, e);
voiceUpdated = true;
}, () => {
// voice LSB
upThis.setChCc(part, 32, e);
voiceUpdated = true;
e && upThis.setChActive(part, 1);
}, () => {
// voice PC#
upThis.#prg[part] = e;
voiceUpdated = true;
e && upThis.setChActive(part, 1);
}, () => {
// coarse tuning
upThis.#rpnt[rpnOffTable[part] + useRpnMap[2]] = 1;
upThis.#rpn[rpnOffTable[part] + useRpnMap[2]] = e;
e && upThis.setChActive(part, 1);
}][ri] || (() => {}))();
});
if (voiceUpdated) {
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
let voiceObject = upThis.getChVoice(part);
switch (voiceObject.standard) {
case "DX": {
upThis.#ext[part] = upThis.EXT_DX;
break;
};
case "VL": {
upThis.#ext[part] = upThis.EXT_VL;
break;
};
};
upThis.dispatchEvent("metacommit", {
"type": "OSysMeta",
"data": `CH${part + 1} was renamed from "${upThis.getVoice(...upThis.getChPrimitives(part, true), upThis.getChMode(part))?.name}" to "${upThis.getChCvnString(part)}".`
});
};
}).add([100, 76, 32], (msg, track, id) => {
upThis.switchMode("cs6x");
// CS6x voice plugin native
let part = 0;
let offset = msg[0];
let pluginType = upThis.#ext[part];
switch (pluginType) {
case upThis.EXT_DX: {
if (msg.length > 2) {
console.debug(msg);
};
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
if (ri < 6) {
// Carrier level 1~6
let targetCc = ri + 142;
upThis.setChCc(part, targetCc, e);
e !== 64 && upThis.allocateAce(part, targetCc);
} else if (ri < 12) {
// Modulator level 1~6
let targetCc = ri + 144;
upThis.setChCc(part, targetCc, e);
e !== 64 && upThis.allocateAce(part, targetCc);
} else {
([() => {
// feedback level
}, () => {
// portamento mode
}, () => {
// portamento step
}, () => {
// pitch bend step
}][ri] || (() => {}))();
};
});
break;
};
case upThis.EXT_VL: {
console.info(`Support for Modular System Plug-in writes is not yet present for plugin type ${pluginType}.`);
break;
};
default: {
console.warn(`Unsupported plug-in type: ${pluginType}`);
};
};
});
// SD-90 part setup (part)
this.#seGs.add([0, 72, 18, 0, 0, 0, 0], (msg, track, id) => {
// SD-90 Native System On
upThis.switchMode("sd", true);
upThis.setPortMode(upThis.getTrackPort(track), 2, modeMap.sd);
console.info(`MIDI reset: SD`);
})/*.add([0, 72, 18, 1, 0, 0], (msg, track, id) => {
// Master setup
}).add([0, 72, 18, 1, 0, 2], (msg, track, id) => {
// Master EQ
}).add([0, 72, 18, 2, 16, 0], (msg, track, id) => {
// Master volume
})*/.add([0, 72, 18, 16, 0], (msg, track, id) => {
// Part setup (global)
upThis.invokeSysExIndicator();
let type = msg[0] >> 5, channel = msg[0] & 31;
switch (type) {
case 0: {
// Global effects
let slot = msg[0] >> 1, offset = msg[1];
switch (slot) {
case 1: {
// SD chorus
msg.subarray(2).forEach((e, i, a) => {
let ri = i + offset;
//console.debug(`SD MFX Cho: ${ri} - ${e}, %o`, a);
switch (ri) {
case 0: {
if (e) {
upThis.setEffectType(1, 60, e - 1);
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
console.debug(`SD MFX Cho: ${ri} - ${e}`);
};
break;
};
};
});
//console.debug(`SD chorus message:\n%o`, msg);
break;
};
case 2: {
// SD reverb
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
//console.debug(`SD MFX Rev: ${ri} - ${e}`);
switch (ri) {
case 0: {
if (e) {
upThis.setEffectType(0, 55 + e, 0);
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
console.debug(`SD MFX Rev: ${ri} - ${e}`);
};
break;
};
};
});
//console.debug(`SD reverb message:\n%o`, msg);
break;
};
case 3:
case 4:
case 5: {
// SD EFX (MIDI FX)
let efxSink = slot - 1;
msg.subarray(2).forEach((e, i) => {
let ri = i + offset;
console.debug(`SD MFX ${efxSink - 2}: ${ri} - ${e}`);
switch (ri) {
case 0: {
upThis.setEffectTypeRaw(efxSink, 62, e);
upThis.dispatchEvent(`efx${efxSink > 2 ? "delay" : "insert" + (efxSink - 4)}`, upThis.getEffectType(efxSink));
break;
};
};
});
console.debug(`SD MFX message:\n%o`, msg);
break;
};
default: {
console.debug(`Unknown SD-90 global effects message:\n%o`, msg);
};
};
break;
};
case 1: {
// Global part param setup
let part = upThis.chRedir(channel, track, true),
offset = msg[1], chOff = ccOffTable[part];
//console.debug(`Unknown SD-90 CH${part + 1} setup param message:\n%o`, msg);
msg.subarray(2).forEach((e, i) => {
let pointer = offset + i;
if (pointer < 37) {
([() => {
// Receive channel
}, () => {
// Receive switch
}, 0, () => {
// Receive port
}, () => {
// cc0
switch (e) {
case 104:
case 105:
case 106:
case 107:
case 120:
case 128: {
if (!upThis.#chType[part]) {
upThis.setChType(part, upThis.CH_DRUMS);
};
break;
};
default: {
if (upThis.#chType[part]) {
upThis.setChType(part, upThis.CH_MELODIC);
};
};
};
upThis.setChCc(part, 0, e);
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
// cc32
upThis.#cc[chOff + ccToPos[32]] = e;
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
// PC#
upThis.#prg[part] = e;
//upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
}, () => {
// cc7
upThis.#cc[chOff + ccToPos[7]] = e;
}, () => {
// cc10
upThis.#cc[chOff + ccToPos[10]] = e;
}, () => {
// Coarse tune (±48)
}, () => {
// Fine tune
}, () => {
// Mono/poly
if (e < 2) {
upThis.#mono[part] = e;
};
}, () => {
// cc68
if (e < 2) {
upThis.#cc[chOff + ccToPos[68]] = e ? 127 : 0;
};
}, () => {
// pitch bend range
}, () => {
// cc65
if (e < 2) {
upThis.#cc[chOff + ccToPos[65]] = e ? 127 : 0;
};
}, () => {
// cc5 MSB
upThis.#cc[chOff + ccToPos[5]] = (e & 15 << 4) | (upThis.#cc[chOff + ccToPos[5]] & 15);
}, () => {
// cc5 LSB
upThis.#cc[chOff + ccToPos[5]] = (e & 15) | ((upThis.#cc[chOff + ccToPos[5]] & 240) >> 4);
}, () => {
// cc74
upThis.#cc[chOff + ccToPos[74]] = e;
}, () => {
// cc71
upThis.#cc[chOff + ccToPos[71]] = e;
}, () => {
// cc73
upThis.#cc[chOff + ccToPos[73]] = e;
}, () => {
// cc72
upThis.#cc[chOff + ccToPos[72]] = e;
}, 0, 0, 0, 0, 0, 0, 0, () => {
// Dry level
upThis.#cc[chOff + ccToPos[128]] = e;
upThis.allocateAce(128);
}, () => {
// cc93
upThis.#cc[chOff + ccToPos[93]] = e;
}, () => {
// cc91
upThis.#cc[chOff + ccToPos[91]] = e;
}, 0, 0, () => {
// cc75
upThis.#cc[chOff + ccToPos[75]] = e;
}, () => {
// cc76
upThis.#cc[chOff + ccToPos[76]] = e;
}, () => {
// cc77
upThis.#cc[chOff + ccToPos[77]] = e;
}, () => {
// cc78
upThis.#cc[chOff + ccToPos[78]] = e;
}][pointer]||(() => {}))();
} else if (pointer < 63) {
// Keyboard setup
} else if (pointer < 64) {
// GM2 set
if (upThis.#chType[part]) {
// Drums
upThis.#cc[chOff + ccToPos[0]] = 104 | e;
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
} else {
// Melodic
upThis.#cc[chOff + ccToPos[0]] = 96 | e;
upThis.pushChPrimitives(part);
upThis.dispatchEvent("voice", {
part
});
};
upThis.dispatchEvent("voice", {
part
});
} else {
console.debug(`Unknown SD-90 global CH${part + 1} param setup message:\n%o`, msg);
};
});
break;
};
case 2: {
// Global part MIDI setup
let part = upThis.chRedir(channel, track, true), offset = msg[1];
console.debug(`Unknown SD-90 global CH${part + 1} MIDI setup message:\n%o`, msg.subarray(2));
break;
};
default: {
// No one should use this...
console.warn(`Unknown SD-90 global part setup message:\n%o`, msg);
};
};
})/*.add([0, 72, 18, 17], (msg, track, id) => {
// Part setup (part)
})*/;
upThis.#seAi.add([0, 1, 73, 78], (msg, track, id) => {
upThis.switchMode("krs");
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.krs);
console.debug("Might be KORG KROSS 2 mode reset... Not sure.");
}).add([0, 1, 73, 117, 2, 37], (msg, track, id) => {
// KORG KROSS 2 BMT1 bundled multi-track dump
/*let unpacked = korgUnpack(msg);
console.debug(unpacked);*/
upThis.switchMode("krs");
let port = upThis.getTrackPort(track);
if (upThis.#portMode[port] && upThis.#portMode[port] !== modeMap.krs) {
if (upThis.#conf.dumpLimit === upThis.DUMP_MODE) {
console.warn(`Dump cancelled. Port ${String.fromCharCode(65 + port)} mode "${modeIdx[upThis.#portMode[port]]}" mismatch, should be "krs".`);
return;
} else {
console.info(`Track ${track} is trying to perform a mismached mode dump on port ${String.fromCharCode(65 + port)}.`);
};
};
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.krs);
let dPref = `KROSS 2 BMT1 `
let trackName = "";
korgFilter(msg, (e, i) => {
if (i < 24) {
trackName += String.fromCharCode(Math.max(32, e));
} else if (i < 1276) {
// Trap...
} else {
// Part dump
let si = i - 1276;
let part = upThis.chRedir(Math.floor(si / 44), track, true);
let pi = si % 44;
let chOff = ccOffTable[part];
//console.debug(`${i} ${si} ${Math.floor(si / 44)} ${part} ${pi}`);
if (pi < 15) {
([() => {
e && upThis.setChActive(part, 1);
upThis.#prg[part] = e;
//console.debug(`PRG ${part + 1} ${pi}: ${e}`)
upThis.dispatchEvent("voice", {
part
});
}, () => {
e && upThis.setChActive(part, 1);
if (e < 10) {
upThis.#cc[chOff + ccToPos[0]] = 63;
upThis.#cc[chOff + ccToPos[32]] = e;
} else if (e < 20) {
upThis.#cc[chOff + ccToPos[0]] = 121;
upThis.#cc[chOff + ccToPos[32]] = e - 10;
} else if (e < 24) {
upThis.#cc[chOff + ccToPos[0]] = [120, 0, 56, 62][e - 20];
upThis.#cc[chOff + ccToPos[32]] = 0;
};
upThis.pushChPrimitives(part);
//console.debug(`${dPref}CH${part + 1} LSB ${pi}: ${e}`)
upThis.dispatchEvent("voice", {
part
});
}, () => {
// receive CH and status
let ch = upThis.chRedir(e & 15, track, true);
upThis.#chReceive[part] = ch;
console.info(`${dPref}CH${part + 1} receives from CH${ch + 1}`);
/*let enabled = (e >> 5) & 1;
if (!enabled) {
upThis.setChActive(part, 0);
console.debug(`KORG KROSS 2 CH${part + 1} is disabled.`);
};*/
}, false, false, () => {
upThis.#cc[chOff + ccToPos[7]] = e;
}, false, () => {
// coarse
}, () => {
// fine
}, false, false, false, false, false, () => {
//console.debug(`${dPref}CH${part + 1} pan: ${e}`);
upThis.#cc[chOff + ccToPos[10]] = e || 128;
}][pi] || (() => {}))();
} else if (pi < 36) {} else {
([() => {
upThis.#cc[chOff + ccToPos[74]] = ((e + 128) & 255) - 64;
}, () => {
upThis.#cc[chOff + ccToPos[71]] = ((e + 128) & 255) - 64;
}, false, false, () => {
upThis.#cc[chOff + ccToPos[73]] = ((e + 128) & 255) - 64;
}, () => {
upThis.#cc[chOff + ccToPos[75]] = ((e + 128) & 255) - 64;
}, false, () => {
upThis.#cc[chOff + ccToPos[72]] = ((e + 128) & 255) - 64;
}][pi] || (() => {}))();
//console.debug(`${dPref}${pi} ${e}`);
};
};
});
trackName = trackName.trimEnd();
upThis.dispatchEvent("metacommit", {
"type": "EORTitle",
"data": trackName
});
//upThis.buildRchTree();
});
upThis.#seXg.add([92, 0, 0], (msg, track, id) => {
// Yamaha AN1x system
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
if (ri < 2) {
// Master tune
} else {
([false, false, false, false, false, false, () => {
// Rx channel 1
upThis.setChMode(e, modeMap.an1x);
upThis.pushChPrimitives(e);
console.debug(`Yamaha AN1x slot 1 receives from CH${e + 1}`);
}, () => {
// Rx channel 2
upThis.setChMode(e, modeMap.an1x);
upThis.pushChPrimitives(e);
console.debug(`Yamaha AN1x slot 2 receives from CH${e + 1}.`);
}][ri - 2] || (() => {}))();
};
});
});
upThis.#seXg.add([75, 80, 0], (msg, track, id) => {
// Yamaha CS1x system
let offset = msg[0];
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
([() => {
// performance receive channel
upThis.modelEx.cs1x.perfCh = e;
upThis.pushChPrimitives(e);
console.debug(`Yamaha CS1x performance on CH${e + 1}.`);
}, false, false, false, () => {
// device ID
}, false, () => {
// device mode
switch (e) {
case 1: {
upThis.switchMode("xg", true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.xg);
console.debug(`Yamaha CS1x set to XG mode.`);
break;
};
case 3: {
upThis.switchMode("cs1x", true);
upThis.setPortMode(upThis.getTrackPort(track), 1, modeMap.cs1x);
console.debug(`Yamaha CS1x set to performance mode.`);
break;
};
default: {
console.warn(`Unknown Yamaha CS1x mode: ${e}`);
};
};
}, () => {
// keyboard coarse tune
}][ri] || (() => {}))();
});
}).add([75, 96, 0], (msg, track, id) => {
// Yamaha CS1x current performance common
let offset = msg[0];
let cvnWritten = false;
let perfCh = upThis.modelEx.cs1x.perfCh;
msg.subarray(1).forEach((e, i) => {
let ri = i + offset;
if (ri < 8) {
cvnWritten = true;
upThis.#bnCustom[perfCh] = 1;
upThis.setChCvnRegister(perfCh, ri, e);
} else if (ri < 48) {
// CS1x common
([false, () => {
// not master volume
//upThis.#masterVol = e / 127;
//upThis.dispatchEvent("mastervolume", upThis.#masterVol);
upThis.setChCc(perfCh, 7, e);
}][ri] || (() => {}))();
} else if (ri < 80) {
// CS1x effects
([() => {
upThis.setEffectTypeRaw(0, false, e);
}, () => {
upThis.setEffectTypeRaw(0, true, e);
upThis.dispatchEvent("efxreverb", upThis.getEffectType(0));
}, () => {
upThis.setEffectTypeRaw(1, false, e);
}, () => {
upThis.setEffectTypeRaw(1, true, e);
upThis.dispatchEvent("efxchorus", upThis.getEffectType(1));
}, () => {
upThis.setEffectTypeRaw(2, false, e);
}, () => {
upThis.setEffectTypeRaw(2, true, e);
upThis.dispatchEvent("efxdelay", upThis.getEffectType(2));
}][ri - 48] || (() => {}))();
} else {
getDebugState() && console.debug(`Unknown CS1x effect register: ${ri}.`);
};
});
if (cvnWritten) {
upThis.setChActive(perfCh, 1);
upThis.pushChPrimitives(perfCh);
};
});
// KORG PA effects, only with PA50SD/PA80/MicroArranger/PA1X
let setKorgPaEfx = (slot, offset, msg) => {
let efxId = 0;
msg.forEach((e, i) => {
([() => {
efxId = ((e & 1) << 7) | (efxId & 127);
upThis.setEffectTypeRaw(slot, false, 28);
upThis.setEffectTypeRaw(slot, true, efxId);
}, () => {
efxId = (efxId & 128) | (e & 127);
upThis.setEffectTypeRaw(slot, false, 28);
upThis.setEffectTypeRaw(slot, true, efxId);
upThis.dispatchEvent(["efxreverb", "efxchorus", "efxdelay", "efxinsert0"][slot], upThis.getEffectType(slot));
}][i + offset] || (() => {}))();
});
//console.debug(slot, msg);
};
upThis.#seAi.add([96, 0, 1], (msg, track, id) => {
let catId = msg[0];
switch (msg[1]) {
case 0: {
setKorgPaEfx(0, msg[2], msg.subarray(3));
console.debug("Partially parsed KORG PA EFX SysEX:\n", msg);
break;
};
case 1: {
setKorgPaEfx(1, msg[2], msg.subarray(3));
console.debug("Partially parsed KORG PA EFX SysEX:\n", msg);
break;
};
case 2: {
setKorgPaEfx(2, msg[2], msg.subarray(3));
console.debug("Partially parsed KORG PA EFX SysEX:\n", msg);
break;
};
case 3: {
setKorgPaEfx(3, msg[2], msg.subarray(3));
console.debug("Partially parsed KORG PA EFX SysEX:\n", msg);
break;
};
default: {
console.debug("Unparsed KORG PA SysEX:\n", msg);
};
};
});
};
};
 
export {
OctaviaDevice,
allocated,
ccToPos,
dnToPos,
overrides,
getDebugState
};