diff --git a/.gitignore b/.gitignore index 6b468b62..ab4abd27 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +out *.class diff --git a/README.md b/README.md index 29a6db27..12c513e5 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Edisyn presently supports: * Alesis D4 and DM5 * ASM Hydrasynth Family (Single mode only) * Audiothingies Micromonsta +* Behringer UB-Xa * Casio CZ Series (CZ101, CZ1000, CZ3000, CZ5000, CZ-1, CZ-230S) * DSI Prophet '08, Tetra, Mopho, Mopho Keyboard, Mopho SE, and Mopho x4 (Single and (for Tetra) Combo modes) * DSI Prophet 12 diff --git a/edisyn/synth/behringerubxa/BehringerUBXa.java b/edisyn/synth/behringerubxa/BehringerUBXa.java new file mode 100644 index 00000000..5bee6e5c --- /dev/null +++ b/edisyn/synth/behringerubxa/BehringerUBXa.java @@ -0,0 +1,858 @@ +package edisyn.synth.behringerubxa; + +import edisyn.Midi; +import edisyn.Patch; +import edisyn.Synth; +import edisyn.gui.*; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; + +import static edisyn.synth.behringerubxa.BehringerUBXaRec.EOF; +import static edisyn.synth.behringerubxa.BehringerUBXaRec.SysExHeader; +import static edisyn.synth.behringerubxa.ParameterList.*; + + +public class BehringerUBXa extends Synth { + + public static final String BITMASK_SEP = "@@"; + + private String lastDialEmitKey = null; + private int lastDialEmitIdx = -1; + + private int lastDialReceiveIdx = -1; + private final java.util.List patchDump = new ArrayList<>(); + + private final java.util.List usedKeys = new ArrayList<>(); + + private byte[] lastDump; + + public static String getSynthName() { + return "Behringer UB-Xa"; + } + + public static String spaceAtCapitalLetter(String input) { + return String.join(" ",splitAtCapitalLetter(input, 10)); + } + + public static String[] splitAtCapitalLetter(String input, int k) { + java.util.List out = new ArrayList<>(); + StringBuilder s = new StringBuilder(input.substring(0, 1)); + for (int i = 1; i < input.length(); i++) { + if (Character.isUpperCase(input.charAt(i)) + && i != input.length() - 1 + && !Character.isUpperCase(input.charAt(i + 1)) + && Character.isLetter(input.charAt(i + 1))) { + out.add(s.toString()); + s = new StringBuilder(input.substring(i, i + 1)); + } else { + s.append(input.charAt(i)); + } + } + out.add(s.toString()); + return out.toArray(new String[0]); + } + + public static String[] splitAtCapitalLetter_(String input, int k) { + // Check if the input length is greater than k + if (input.length() <= k) { + // If not, return the input as a single string in an array + return new String[]{input}; + } + + int midpoint = input.length() / 2; + + // Search backward from the midpoint for a capital letter + for (int i = midpoint; i > 0; i--) { + if (Character.isUpperCase(input.charAt(i))) { + return new String[]{ + input.substring(0, i), + input.substring(i) + }; + } + } + + // If no capital letter is found before midpoint, search forward + for (int i = midpoint; i < input.length(); i++) { + if (Character.isUpperCase(input.charAt(i))) { + return new String[]{ + input.substring(0, i), + input.substring(i) + }; + } + } + + // If no capital letter is found, return the entire string as a single string in the array + return new String[]{input}; + } + + + public static String longestCommonWordPrefix(String str1, String str2) { + String[] words1 = str1.split(" "); + String[] words2 = str2.split(" "); + + int minLength = Math.min(words1.length, words2.length); + + StringBuilder commonPrefix = new StringBuilder(); + + for (int i = 0; i < minLength; i++) { + if (!words1[i].equals(words2[i])) { + break; + } + commonPrefix.append(words1[i]).append(" "); + } + + // Remove the last extra space if any common prefix is found + if (!commonPrefix.isEmpty()) { + commonPrefix.setLength(commonPrefix.length() - 1); + } + + return commonPrefix.toString(); + } + + + private void addDialByKey(JComponent container, String key, String label) { + assert !usedKeys.contains(key); + for (int i = 0; i < dials.length; i += NUM_PARAMS_DIALS) { + if (dials[i].equals(key)) { + int minVal = (int) dials[i + 2]; + int maxVal = (int) dials[i + 3]; + boolean symmetric = (boolean) dials[i + 4]; + + addDial(container, key, label, minVal, maxVal, symmetric); + break; + } + } + + + } + + private JComponent makeEnv(String a, String d, String s, String r, String mods) { + HBox container = new HBox(); + VBox h = new VBox(); + addCheckboxGroupByKey(h, mods); + container.add(h); + + HBox envDials = new HBox(); + addDialByKey(envDials, a, "Attack"); + addDialByKey(envDials, d, "Decay"); + addDialByKey(envDials, s, "Sustain"); + addDialByKey(envDials, r, "Release"); + + EnvelopeDisplay ed = new EnvelopeDisplay( + this, Style.ENVELOPE_COLOR(), + new String[]{null, a, d, null, r}, + new String[]{null, null, s, s, null}, + new double[]{0, 0.25 / 16383, 0.25 / 16383, 0.25, 0.25 / 16383}, + new double[]{0, 1.0, 1.0 / 16383, 1.0 / 16383, 0} + + ); + envDials.add(ed); + container.add(envDials); + + return container; + } + + private JComponent categoryContainer(HBox parent, String title, Color color, boolean addLast) { + VBox v = new VBox(); + if (addLast) { + parent.addLast(v); + } else { + parent.add(v); + } + Category c = new Category(this, title, color); + v.add(c); + + HBox h = new HBox(); + v.add(h); + return h; + } + + public BehringerUBXa() { + assert checkboxGroups.length % NUM_PARAMS_CHECKBOXES == 0; + assert dials.length % NUM_PARAMS_DIALS == 0; + assert selectors.length % NUM_PARAMS_SELECTORS == 0; + + JComponent main = new SynthPanel(this); + + VBox mainVbox = new VBox(); + main.add(mainVbox, BorderLayout.CENTER); + { + HBox row1 = new HBox(); + mainVbox.add(row1); + JComponent controlCat = categoryContainer(row1, "Behringer UB-Xa", Color.WHITE, false); + + addDialByKey(controlCat, "ControlPortamentoAmount", "Portamento\nAmount"); + addDialByKey(controlCat, "ControlUnison", "Unison"); // would be nice with a button + addDialByKey(controlCat, "ControlDetune", "Detune"); + + JComponent arpCat = categoryContainer(row1, "Arpeggiator", Style.COLOR_C(), true); + VBox arpChoosersCol1 = new VBox(); + arpCat.add(arpChoosersCol1); + addChooserByKey(arpChoosersCol1, "ArpeggiatorMode", "Mode"); + addChooserByKey(arpChoosersCol1, "ArpeggiatorTime", "Time"); + VBox arpChoosersCol2 = new VBox(); + arpCat.add(arpChoosersCol2); + addChooserByKey(arpChoosersCol2, "ArpeggiatorSync", "Sync"); + + HBox arpDials = new HBox(); + arpCat.add(arpDials); + addDialByKey(arpDials, "ArpeggiatorEnabled", "Enabled"); //button? + addDialByKey(arpDials, "ArpeggiatorHold", "Hold"); //button? + addDialByKey(arpDials, "ArpeggiatorGatetime", "Gate Time"); + addDialByKey(arpDials, "ArpeggiatorOctave", "Octave"); + addDialByKey(arpDials, "ArpeggiatorSwing", "Swing"); + addDialByKey(arpDials, "ArpeggiatorRepeat", "Repeat"); + + } + { + HBox row2 = new HBox(); + mainVbox.add(row2); + JComponent oscCat = categoryContainer(row2, "Oscillators", Style.COLOR_A(), false); + + VBox osc1v = new VBox(); + oscCat.add(osc1v); + + addChooserByKey(osc1v, "OscillatorsOSC1Shapes", "Osc 1 Shapes"); + addCheckboxGroupByKey(osc1v, "OscillatorsOSC1State",new String[]{"Osc 1 State","Osc 1 VCO LFO Phase","Osc 1 VCO PWM Phase"},false); + + VBox osc2v = new VBox(); + oscCat.add(osc2v); + addChooserByKey(osc2v, "OscillatorsOSC2Shapes", "Osc 2 Shapes"); + addChooserByKey(osc2v, "OscillatorsOSC2State", "Osc 2 State"); + addCheckboxGroupByKey(osc2v, "OscillatorsMode"); + + addDialByKey(oscCat, "OscillatorsOSC1Transpose", "Osc 1\nTranspose"); + addDialByKey(oscCat, "OscillatorsOSC1PWAmount", "Osc 1\nPW Amount"); + addDialByKey(oscCat, "OscillatorsOSC2Transpose", "Osc 2\nTranspose"); + addDialByKey(oscCat, "OscillatorsOSC2PWAmount", "Osc 2\nPW Amount"); + + JComponent filterCat = categoryContainer(row2, "Filter", Style.COLOR_B(), true); + VBox filterV = new VBox(); + filterCat.add(filterV); + + addFilterModes(filterV); + + HBox filterDials = new HBox(); + addDialByKey(filterDials, "FilterFrequency", "Frequency"); + addDialByKey(filterDials, "FilterResonance", "Resonance"); + addDialByKey(filterDials, "FilterModulation", "Modulation"); + addDialByKey(filterDials, "FilterNoise", "Noise"); + filterCat.add(filterDials); + } + + { + HBox row3 = new HBox(); + mainVbox.add(row3); + JComponent loudnessEnvCat = categoryContainer(row3, "Loudness Envelope", Style.COLOR_A(), false); + JComponent loudnessEnv = makeEnv("EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessS", "EnvelopesLoudnessR", "EnvelopesLoudnessMods"); + loudnessEnvCat.add(loudnessEnv); + + JComponent filterEnvCat = categoryContainer(row3, "Filter Envelope", Style.COLOR_B(), true); + JComponent filterEnv = makeEnv("EnvelopesFilterA", "EnvelopesFilterD", "EnvelopesFilterS", "EnvelopesFilterR", "EnvelopesFilterMods"); + filterEnvCat.add(filterEnv); + } +// ModulationLFOMods + { + HBox row4 = new HBox(); + mainVbox.add(row4); + JComponent modCat = categoryContainer(row4, "Modulation", Style.COLOR_C(), false); + + VBox modMisc = new VBox(); + addChooserByKey(modMisc, "ModulationLFOShapes", "Shapes"); + addCheckboxGroupByKey(modMisc, "ModulationLFOMods"); + modCat.add(modMisc); + VBox modDialsAndQuirks = new VBox(); + modCat.add(modDialsAndQuirks); + HBox modDials = new HBox(); + modDialsAndQuirks.add(modDials); + addDialByKey(modDials, "ModulationLFOTrigPoint", "LFO\nTrig Point"); + addDialByKey(modDials, "ModulationLFORate", "LFORate"); + addDialByKey(modDials, "ModulationLFOPhase", "LFOPhase"); + addDialByKey(modDials, "ModulationLFOTrim", "LFOTrim"); + HBox modQuirks = new HBox(); + addCheckboxGroupByKey(modQuirks, "ModulationQuirks"); + modDialsAndQuirks.add(modQuirks); + + JComponent modC1Cat = categoryContainer(row4, "Mod Channel 1", Style.COLOR_C(), false); + VBox modSelsC1 = new VBox(); + modC1Cat.add(modSelsC1); + addCheckboxGroupByKey(modSelsC1, "ModulationChannel1Sends"); + addCheckboxGroupByKey(modSelsC1, "ModulationChannel1Mods"); + addDialByKey(modC1Cat, "ModulationChannel1Amount", "Amount"); + addDialByKey(modC1Cat, "EnvelopesModChannel1A", "Attack"); + addDialByKey(modC1Cat, "EnvelopesModChannel1Delay", "Delay"); + + JComponent modC2Cat = categoryContainer(row4, "Mod Channel 2", Style.COLOR_C(), true); + VBox modSelsC2 = new VBox(); + modC2Cat.add(modSelsC2); + addCheckboxGroupByKey(modSelsC2, "ModulationChannel2Sends"); + addCheckboxGroupByKey(modSelsC2, "ModulationChannel2Mods"); + addDialByKey(modC2Cat, "ModulationChannel2Amount", "Amount"); + addDialByKey(modC2Cat, "EnvelopesModChannel2A", "Attack"); + addDialByKey(modC2Cat, "EnvelopesModChannel2Delay", "Delay"); + + + } + addTab("Main", main); + + for (Object ctrlSuperGroup: ctrlGroups) { + JComponent p = new SynthPanel(this); + VBox vbox = new VBox(); + p.add(vbox, BorderLayout.CENTER); + for(String ctrlGroup: (String[]) ctrlSuperGroup) { + String tabTitle = String.join(" ", splitAtCapitalLetter(ctrlGroup, 10)); + Category c = new Category(this, tabTitle, Style.COLOR_C()); + vbox.add(c); + JComponent box = makeGroupedControls(ctrlGroup); + vbox.add(box); + } + String tabTitle = String.join("/",(String[]) ctrlSuperGroup); + addTab(tabTitle, p); + } + + // Check that we've added all controls at this point + for (int i = 0; i < dials.length; i += NUM_PARAMS_DIALS) { + String key = (String) dials[i]; + assert (usedKeys.contains(key)); + } + + for (int i = 0; i < selectors.length; i += NUM_PARAMS_SELECTORS) { + String key = (String) selectors[i]; + assert (usedKeys.contains(key)); + } + + for (int i = 0; i < checkboxGroups.length; i += NUM_PARAMS_CHECKBOXES) { + String key = (String) checkboxGroups[i]; + assert (usedKeys.contains(key)); + } + } + + private void addFilterModes(JComponent container) { + String[] opts = new String[]{"2 Pole", "4 Pole"}; // order is "switched" + Chooser filterType = new Chooser("Filter Type", this, "FilterModes@@4 Pole~2 Pole", opts, new int[]{0, 1}); + container.add(filterType); + CheckBox filterTracking = new CheckBox("Filter Tracking", this, "FilterModes@@Filter Tracking on~Filter Tracking off"); + container.add(filterTracking); + usedKeys.add("FilterModes"); + } + + private void addChooserByKey(JComponent container, String key, String label) { + boolean found = false; + for (int i = 0; i < selectors.length; i += NUM_PARAMS_SELECTORS) { + if (key.equals(selectors[i])) { + String[] opts = (String[]) selectors[i + 2]; + addChooser(container, key, label, opts); + usedKeys.add(key); + found = true; + } + } + assert found; + } + + private void addCheckboxGroupByKey(JComponent container, String key) { + addCheckboxGroupByKey(container, key, null,true); + } + + private void addCheckboxGroupByKey(JComponent container, String key, String[] chooserLabels,boolean preferCheckbox ) { + for (int i = 0; i < checkboxGroups.length; i += NUM_PARAMS_CHECKBOXES) { + if (key.equals(checkboxGroups[i])) { + String[] labels = (String[]) checkboxGroups[i + 3]; + addCheckboxGroup(container, key, labels, chooserLabels, preferCheckbox); + return; + } + } + assert false; + } + + private void addChooser(JComponent container, String key, String label, String[] opts) { + int[] vals = new int[opts.length]; + for (int j = 0; j < opts.length; j++) { + vals[j] = j; + } + JComponent comp = new Chooser(label, this, key, opts, vals); + + container.add(comp); + usedKeys.add(key); + } + + private void addDial(JComponent container, String key, String lbl, int minVal, int maxVal, boolean symmetric) { + int sub = symmetric ? maxVal / 2 + 1 : 0; + String[] labels; + if (lbl.contains(" ") || lbl.contains("\n")){ + labels = lbl.split("\n"); + } else { + labels = splitAtCapitalLetter(lbl, 10); + } + + LabelledDial comp = new LabelledDial(labels[0], this, key, Style.COLOR_A(), minVal, maxVal, sub) { + public boolean isSymmetric() { + return symmetric; + } + }; + for (int i = 1; i < labels.length; i++) { + comp.addAdditionalLabel(labels[i]); + } + + container.add(comp); + usedKeys.add(key); + + } + + private JComponent makeGroupedControls(String ctrlGrp) { + JComponent vbox = new VBox(); + JComponent hbox = null; + + int j = 0; + for (int i = 0; i < dials.length; i += NUM_PARAMS_DIALS) { + + String key = (String) dials[i]; + if (key.indexOf(ctrlGrp) != 0) continue; + if (usedKeys.contains(key)) continue; + + if (j % 10 == 0) { + hbox = new HBox(); + vbox.add(hbox); + } + j += 1; + int minVal = (int) dials[i + 2]; + int maxVal = (int) dials[i + 3]; + boolean symmetric = (boolean) dials[i + 4]; + + String label = key.substring(ctrlGrp.length()); + + addDial(hbox, key, label, minVal, maxVal, symmetric); + + + } + + hbox = null; + j = 0; + for (int i = 0; i < selectors.length; i += NUM_PARAMS_SELECTORS) { + String key = (String) selectors[i]; + if (key.indexOf(ctrlGrp) != 0) continue; + if (usedKeys.contains(key)) continue; + + if (j % 4 == 0) { + hbox = new HBox(); + vbox.add(hbox); + } + j += 1; + String[] opts = (String[]) selectors[i + 2]; + String label = spaceAtCapitalLetter(key.substring(ctrlGrp.length())); + addChooser(hbox, key, label, opts); + + } + + for (int i = 0; i < checkboxGroups.length; i += NUM_PARAMS_CHECKBOXES) { + String key = (String) checkboxGroups[i]; + if (key.indexOf(ctrlGrp) != 0) continue; + if (usedKeys.contains(key)) continue; + JComponent hbox2 = new HBox(); + String subCatTitle = spaceAtCapitalLetter(key.substring(ctrlGrp.length())); + Category cat = new Category(this, subCatTitle, Color.WHITE); + String[] labels = (String[]) checkboxGroups[i + 3]; + addCheckboxGroup(hbox2, key, labels, null,false); + vbox.add(cat); + vbox.add(hbox2); + } + + return vbox; + + } + + private void addCheckboxGroup(JComponent container, String key, String[] lbls, String[] chooserLabels,boolean preferCheckbox) { + assert chooserLabels == null || chooserLabels.length == lbls.length; + int i = 0; + for (String lbl : lbls) { + JComponent comp; + if (lbl.contains("~")) { + String[] strs = lbl.split("~"); + String prefix = longestCommonWordPrefix(strs[0], strs[1]); + String chooserLabel = chooserLabels!=null ? chooserLabels[i]:prefix; + if (preferCheckbox && strs[0].equals(prefix + " on") + && strs[1].equals(prefix + " off")) { + comp = new CheckBox(chooserLabel, this, key + BITMASK_SEP + lbl); + } else { + String[] opts = new String[]{strs[1], strs[0]}; // order is "switched" + comp = new Chooser(chooserLabel, this, key + BITMASK_SEP + lbl, opts, new int[]{0, 1}); + } + } else { + comp = new CheckBox(lbl, this, key + BITMASK_SEP + lbl); + } + container.add(comp); + i++; + } + usedKeys.add(key); + + } + + private Object[] emitDial(String key, int i) { + int param = (int) dials[i + 1]; + int val = getModel().get(key); + return buildNRPN(getChannelOut(), + param, val); + } + + + private Object[] emitSelector(String key, int i) { + int param = (int) selectors[i + 1]; + int val = getModel().get(key); + return buildNRPN(getChannelOut(), + param, val); + } + + private Object[] + emitCheckboxGroup(String keyPrefix, int i) { + int param = (int) checkboxGroups[i + 1]; + int sum = 0; + int n = 0; + for (String checkbox : (String[]) checkboxGroups[i + 3]) { + String k = (keyPrefix + BITMASK_SEP + checkbox); + sum += getModel().get(k) << n; + n++; + } + return buildNRPN(getChannelOut(), param, sum); + } + + @Override + public Object[] emitAll(String key) { + if (key.equals(lastDialEmitKey)) { + // Caching + return emitDial(key, lastDialEmitIdx); + } + + for (int i = 0; i < dials.length; i += NUM_PARAMS_DIALS) { + String label = (String) dials[i]; + if (label.equals(key)) { + lastDialEmitKey = key; + lastDialEmitIdx = i; + return emitDial(key, i); + } + } + + String keyPrefix = key.split(BITMASK_SEP)[0]; + + for (int i = 0; i < checkboxGroups.length; i += NUM_PARAMS_CHECKBOXES) { + if (checkboxGroups[i].equals(keyPrefix)) { + return emitCheckboxGroup(keyPrefix, i); + } + } + + for (int i = 0; i < selectors.length; i += NUM_PARAMS_SELECTORS) { + if (selectors[i].equals(keyPrefix)) { + return emitSelector(keyPrefix, i); + } + } + + assert false; + return null; + } + + private void setValueForDial(int dialIdx, int value) { + String label = (String) dials[dialIdx]; + getModel().set(label, value); + + } + + private static String toBinaryString(int number) { + // Convert the integer to a binary string + String binaryString = Integer.toBinaryString(number); + // Add the 0b prefix + return "0b" + binaryString; + } + + @Override + public boolean getRequiresNRPNLSB() { + return true; + } + + @Override + public boolean getRequiresNRPNMSB() { + return true; + } + + @Override + public void handleSynthCCOrNRPN(Midi.CCData data) { + + if (dials[lastDialReceiveIdx + 1].equals(data.number)) { + // Caching + setValueForDial(lastDialReceiveIdx, data.value); + return; + } + + for (int i = 0; i < dials.length; i += NUM_PARAMS_DIALS) { + if (dials[i + 1].equals(data.number)) { + lastDialReceiveIdx = i; + setValueForDial(i, data.value); + return; + } + + + } + + + for (int i = 0; i < checkboxGroups.length; i += NUM_PARAMS_CHECKBOXES) { + if (checkboxGroups[i + 1].equals(data.number)) { + String prefix = (String) checkboxGroups[i]; + for (int j = 0; j < (int) checkboxGroups[i + 2]; j++) { + String suffix = ((String[]) checkboxGroups[i + 3])[j]; + String key = prefix + BITMASK_SEP + suffix; + int val = (data.value & (1 << j)) >> j; + getModel().set(key, val); + } + return; + } + } + + for (int i = 0; i < selectors.length; i += NUM_PARAMS_SELECTORS) { + if (selectors[i + 1].equals(data.number)) { + String key = (String) selectors[i]; + getModel().set(key, data.value); + } + } + } + + public static byte[] generatePaddedByteArray(String asciiString, int n) { + // Ensure the string is not longer than n characters + if (asciiString.length() > n) { + asciiString = asciiString.substring(0, n); + } + + // Create a byte array of length n + byte[] byteArray = new byte[n]; + + // Copy the characters of the string into the byte array + for (int i = 0; i < asciiString.length(); i++) { + byteArray[i] = (byte) asciiString.charAt(i); + } + + // Fill the remaining part of the array with spaces (ASCII value 32) + for (int i = asciiString.length(); i < n; i++) { + byteArray[i] = (byte) ' '; + } + + return byteArray; + } + + + public byte[] requestCurrentDump() { + byte[] data = new byte[36]; + + data[10] = (byte) 0x03; // Request + data[11] = (byte) 0x7F; + System.arraycopy(SysExHeader, 0, data, 0, SysExHeader.length); + byte[] ext = generatePaddedByteArray("BIN ", 4); + System.arraycopy(ext, 0, data, 12, ext.length); + byte[] fileName = generatePaddedByteArray("Upper Patch", 16); + System.arraycopy(fileName, 0, data, 16, fileName.length); + data[32] = (byte) 0x00; + data[33] = (byte) 0x03; + data[34] = (byte) 0x75; + data[35] = (byte) 0xF7; + return data; + } + + public static String byteArrayAsHex(byte[] byteArray, int skip) { + StringBuilder hexString = new StringBuilder(); + + for (int i = 0; i < byteArray.length; i++) { + byte b = byteArray[i]; + // Convert each byte to hex and append to the string builder + if (i >= skip) { + hexString.append(String.format("%02d: %02X ", i, b)); + } + } + + // Print the final string, trimming the trailing space + return hexString.toString().trim(); + } + + public static int parsePosData(int main1, int main2, int aux1, int aux2) { + int b0; + int b1; + int b2; + int b3; + int b4; + if (main2 == -1) { + b0 = (main1 >> 0) & 1; + b1 = (main1 >> 1) & 1; + b2 = 0; + b3 = 0; + b4 = 0; + } else { + b0 = (main1 >> 2) & 1; + b1 = (main1 >> 3) & 1; + b2 = (main1 >> 4) & 1; + b3 = (main1 >> 5) & 1; + b4 = (main1 >> 6) & 1; + } + + int b5; + if (aux2 !=-1) { + b5 = (aux2 >> 7) & 1; + } else if(aux1 != -1) { + b5 = (aux1 >> 7) & 1; + } else { + b5 = 0; + } + + int b6; + int b7; + int b8; + int b9; + int b10; + int b11; + int b12; + if (main2 != -1) { + b6 = (main2 >> 0) & 1; + b7 = (main2 >> 1) & 1; + b8 = (main2 >> 2) & 1; + b9 = (main2 >> 3) & 1; + b10 = (main2 >> 4) & 1; + b11 = (main2 >> 5) & 1; + b12 = (main2 >> 6) & 1; + } else { + b6 = 0; + b7 = 0; + b8 = 0; + b9 = 0; + b10 = 0; + b11 = 0; + b12 = 0; + } + + int b13; + + if(aux2 != -1) { + b13 = (aux1 >> 7) & 1; + } else if (main2 != -1){ + b13 = (aux1 >> 7) & 1; + } else { + b13 = 0; + } + + // Reconstruct the 14-bit value from the scattered bits + int value = b0 | (b1 << 1) | (b2 << 2) | (b3 << 3) | (b4 << 4) | (b5 << 5) | + (b6 << 6) | (b7 << 7) | (b8 << 8) | (b9 << 9) | (b10 << 10) | + (b11 << 11) | (b12 << 12) | (b13 << 13); + + return value; + } + + public boolean parsePatch() { + int length = 0; + for (byte[] b : this.patchDump) { + length += b.length; + } + byte[] data = new byte[length]; + + int dstPos = 0; + for (byte[] b : this.patchDump) { + System.arraycopy(b, 0, data, dstPos, b.length); + dstPos += b.length; + } + + for(int i=0; i1?data[pos[1]]:-1, pos.length>2?data[pos[2]]:-1, pos.length>3?data[pos[3]]:-1); + for (int po : pos) { + System.out.println(po + "=" + data[po]); + } + System.out.println(key+": "+val); + getModel().set(key, val); + + } + + if(lastDump != null){ + for (int i = 0; i < lastDump.length; i++) { + if(data[i] != lastDump[i]) { + System.out.println("diff " + i+": "+lastDump[i] + " != " + data[i]); + } + } + } + this.lastDump = data; + for (int i = 0; i < SysExPosToKeyAndSize.length; i += 3) { + int pos = (int) SysExPosToKeyAndSize[i]; + String key = (String) SysExPosToKeyAndSize[i + 1]; + int size = (int) SysExPosToKeyAndSize[i + 2]; + // FIXME + int val = data[pos];// + data[pos+1] << 2; + int i1 = data[pos] + (data[pos + 1] << 1); +// System.out.println(key+": "+ i1 + "(" +data2[pos]+","+data2[pos+1]+")"); +// getModel().set(key, i1); + } + + return true; + } + + + static void verify7bitizedData(byte[] in) { + for (byte b : in) { + assert (b & 0b10000000) == 0b00000000; + } + } + + static byte[] unpack7b(byte[] data) { + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + int offset = 0; + while (offset < data.length) { + int header = data[offset] & 0xFF; + int remaining = data.length - offset - 1; + if (remaining <= 0) break; + int chunkLen = Math.min(7, remaining); + for (int i = 0; i < chunkLen; i++) { + int msb = (header >> (6 - i)) & 0x01; + int payload = data[offset + 1 + i] & 0x7F; // ensure 7-bit payload + int value = payload | (msb << 7); + out.write(value); + } + offset += 8; // advance by full block (header + up to 7 bytes) + } + return out.toByteArray(); + } + + + @Override + public void updateNumberAndBank(Patch patch){ + int i = 42; + } + + @Override + public int parse(byte[] data, boolean fromFile) { + + if (data.length == EOF.length && + BehringerUBXaRec.msgStartsWith(data, BehringerUBXaRec.EOF) + ) return parsePatch() ? PARSE_SUCCEEDED : PARSE_FAILED; + int fileDumpMessageType = data[SysExHeader.length]; + + if (fileDumpMessageType == (byte) 0x01) { + // Clear on header + patchDump.clear(); + } else { + assert fileDumpMessageType == (byte) 0x02; + int packetNum = data[11]; + assert packetNum == patchDump.size(); + + + int packageLength = data[12] + 1; + int offset = 13; // the extra +// if (packetNum == 0) { + + + byte[] encodedData = Arrays.copyOfRange(data, offset, offset + packageLength); + verify7bitizedData(encodedData); + byte[] decodedData = unpack7b(encodedData); + // TODO - how to interpret the decoded data + +// } +// patchDump.add(Arrays.copyOfRange(data, offset, packageLength + offset)); + patchDump.add(decodedData); + } + return PARSE_INCOMPLETE; + } + +} \ No newline at end of file diff --git a/edisyn/synth/behringerubxa/BehringerUBXaRec.java b/edisyn/synth/behringerubxa/BehringerUBXaRec.java new file mode 100644 index 00000000..aeaf3043 --- /dev/null +++ b/edisyn/synth/behringerubxa/BehringerUBXaRec.java @@ -0,0 +1,28 @@ +package edisyn.synth.behringerubxa; + +import edisyn.Recognize; + +import java.util.Arrays; + + +public class BehringerUBXaRec extends Recognize { + + public static final byte[] SysExHeader = { + (byte) 0xF0, (byte) 0x00, (byte) 0x20, (byte) 0x32, + (byte) 0x00, (byte) 0x01, (byte) 0x21, (byte) 0x7F, // TODO - specify device (7f means all) + (byte)0x74,(byte)0x07 + }; + + public static final byte[] EOF = { + (byte) 0xF0, (byte) 0x7E, (byte) 0x00, (byte) 0x7B, (byte) 0x00, (byte) 0xF7 + }; + + public static boolean msgStartsWith(byte[] msg, byte[] o){ + return Arrays.equals(Arrays.copyOfRange(msg, 0, o.length), o); + } + + public static boolean recognize(byte[] data){ + return msgStartsWith(data, SysExHeader) || + (msgStartsWith(data, EOF) && data.length == EOF.length); + } +} diff --git a/edisyn/synth/behringerubxa/ParameterList.java b/edisyn/synth/behringerubxa/ParameterList.java new file mode 100644 index 00000000..3f3d33a6 --- /dev/null +++ b/edisyn/synth/behringerubxa/ParameterList.java @@ -0,0 +1,315 @@ +package edisyn.synth.behringerubxa; + +public class ParameterList { + + + public static final Object[] ctrlGroups = { + new String []{"Control", "Performance","Envelopes"}, + new String[]{"Oscillators","Filter"}, + new String[]{"ModMatrix", "Amplifier", "Panning"}, + new String[]{"Atrophy", "Sequencer"}, + new String[]{"Midi", "Keyboard"}, + new String[]{"Pedal", "Sysex"}, + new String[]{"Voice", "Surface"}, + new String[]{"Program", "UI", "Background"}}; + + public static final int NUM_PARAMS_DIALS = 5; + public static final Object[] dials = { + "ControlPortamentoAmount", 0, 0, 16383, false, + "ControlPortamentoBend", 1, 0, 16383, false, + "ControlDetune", 2, 0, 16383, true, + "ControlVoiceDetune", 3, 0, 16383, false, + "ControlUnison", 4, 0, 1, false, + "ControlPolyphonicVoiceCount", 5, 1, 8, false, + "ControlUnisonVoiceCount", 6, 1, 16, false, + "ControlAftertouchSmoother", 8, 0, 127, false, + "ControlPortamentoSpread", 11, 0, 255, false, + "ControlPortamentoRange", 12, 0, 255, false, + "PerformanceVolume", 128, 0, 16383, false, + "PerformancePitchBendSensitivity", 130, 0, 16383, false, + "PerformanceTranspose", 131, 0, 16383, true, + "PerformanceLFORate", 132, 0, 16383, false, + "PerformanceLFOChannelAmount", 134, 0, 16383, false, + "PerformanceMpePitchLowerSensitivity", 136, 0, 96, false, + "PerformanceMpePitchUpperSensitivity", 137, 0, 96, false, + "PerformanceLFOTrim", 138, 0, 255, false, + "ModulationLFOTrigPoint", 256, 0, 16383, false, + "ModulationLFORate", 257, 0, 16383, false, + "ModulationLFOPhase", 258, 0, 16383, false, + "ModulationChannel1Amount", 259, 0, 16383, false, + "ModulationChannel2Amount", 260, 0, 16383, false, + "ModulationLFOTrim", 267, 0, 255, false, + "EnvelopesFilterA", 384, 0, 16383, false, + "EnvelopesFilterD", 385, 0, 16383, false, + "EnvelopesFilterS", 386, 0, 16383, false, + "EnvelopesFilterR", 387, 0, 16383, false, + "EnvelopesLoudnessA", 388, 0, 16383, false, + "EnvelopesLoudnessD", 389, 0, 16383, false, + "EnvelopesLoudnessS", 390, 0, 16383, false, + "EnvelopesLoudnessR", 391, 0, 16383, false, + "EnvelopesModChannel1A", 392, 0, 16383, false, + "EnvelopesModChannel1Delay", 393, 0, 16383, false, + "EnvelopesModChannel2A", 394, 0, 16383, false, + "EnvelopesModChannel2Delay", 395, 0, 16383, false, + "EnvelopesPedalR", 396, 0, 16383, false, + "OscillatorsOSC1PWAmount", 512, 0, 16383, false, + "OscillatorsOSC1Transpose", 513, 0, 16383, true, + "OscillatorsOSC2PWAmount", 514, 0, 16383, false, + "OscillatorsOSC2Transpose", 515, 0, 16383, true, + "OscillatorsOSC1PWTrimL", 521, 0, 255, false, + "OscillatorsOSC1PWTrimR", 522, 127, 255, false, + "OscillatorsOSC2PWTrimL", 523, 0, 255, false, + "OscillatorsOSC2PWTrimR", 524, 127, 255, false, + "OscillatorsModDepth", 525, 6, 63, false, + "OscillatorsPWMDepth", 526, 0, 255, false, + "OscillatorsPWMOffsetShift", 527, 0, 255, false, + "OscillatorsChaos", 528, 0, 63, false, + "OscillatorsVPOError", 529, 0, 31, false, + "OscillatorsDriftSpeed", 530, 0, 63, false, + "OscillatorsVPOErrorChaos", 531, 0, 63, false, + "OscillatorsOscAutoInitF", 532, 0, 127, false, + "OscillatorsFEnvRange", 533, 1, 63, false, + "OscillatorsModDepth2", 534, 6, 63, false, + "OscillatorsPWMDepth2", 537, 0, 255, false, + "ControlRFU", 538, 0, 127, false, + "FilterFrequency", 640, 0, 16383, false, + "FilterResonance", 641, 0, 16383, false, + "FilterModulation", 642, 0, 16383, false, + "FilterNoise", 643, 0, 16383, false, + "FilterResonanceTrim", 645, 0, 255, false, + "FilterResonanceTrim4Pole", 646, 0, 255, false, + "FilterFrequencyRange", 647, 0, 127, false, + "FilterZero2pOffset", 648, 0, 48, false, + "FilterZero4pOffset", 649, 0, 48, false, + "FilterModulationRange", 650, 0, 127, false, + "FilterEnvChaos", 652, 0, 31, false, + "FilterLFORange", 653, 0, 127, false, + "FilterChaos", 654, 0, 63, false, + "FilterDriftSpeed", 655, 0, 63, false, + "FilterTrackOffset", 656, 0, 63, false, + "FilterPedalRange", 657, 0, 96, false, + "FilterVPOError", 658, 0, 63, false, + "FilterVPOErrorChaos", 659, 0, 63, false, + "FilterInitFreq", 660, 0, 127, false, + "FilterTrackAmount", 664, 0, 64, false, + "FilterEnvAttackLinearity", 665, 0, 31, false, + "FilterEnvDecayLinearity", 666, 0, 31, false, + "ModMatrixBus1Amount", 768, 0, 16383, true, + "ModMatrixBus2Amount", 769, 0, 16383, true, + "ModMatrixBus3Amount", 770, 0, 16383, true, + "ModMatrixBus4Amount", 771, 0, 16383, true, + "ModMatrixBus5Amount", 772, 0, 16383, true, + "ModMatrixBus6Amount", 773, 0, 16383, true, + "ModMatrixBus7Amount", 774, 0, 16383, true, + "ModMatrixBus8Amount", 775, 0, 16383, true, + "UIPatchNameA", 896, 0, 16383, false, + "UIPatchNameB", 897, 0, 16383, false, + "UIPatchNameC", 898, 0, 16383, false, + "UIPatchNameD", 899, 0, 16383, false, + "UIPatchNameE", 900, 0, 16383, false, + "UIPatchNameF", 901, 0, 16383, false, + "UIPatchNameG", 902, 0, 16383, false, + "UIPatchNameH", 903, 0, 16383, false, + "UILastUpperPatchNumber", 905, 0, 127, false, + "UILastUpperPatchBank", 906, 0, 3, false, + "UILastSplitProgramNumber", 907, 0, 35, false, + "UILastDoubleProgramNumber", 908, 0, 35, false, + "UIAtrophyProfileNumber", 909, 0, 7, false, + "ArpeggiatorEnabled", 1024, 0, 1, false, + "ArpeggiatorHold", 1026, 0, 1, false, + "ArpeggiatorGatetime", 1028, 0, 99, false, + "ArpeggiatorOctave", 1030, 1, 6, false, + "ArpeggiatorSwing", 1031, 0, 10, false, + "ArpeggiatorRepeat", 1032, 0, 10, false, + "MidiTranspose", 1152, 0, 127, true, + "MidiPatchTempo", 1153, 40, 240, false, + "MidiTempo", 1154, 40, 240, false, + "MidiGlobalTranspose", 1166, 0, 127, false, + "KeyboardSplitPoint", 1280, 0, 127, false, + "KeyboardVelocityScaling", 1282, 0, 127, false, + "PedalVibratoLower", 1419, 0, 255, false, + "PedalVibratoUpper", 1420, 0, 255, false, + "PedalFilterLower", 1423, 0, 255, false, + "PedalFilterUpper", 1424, 0, 255, false, + "SurfaceBrightness", 1792, 0, 32, false, + "SurfaceContrast", 1793, 0, 32, false, + "AmplifierOffset2P", 2049, 0, 255, false, + "AmplifierOffsetUni", 2050, 0, 255, false, + "AmplifierOffsetMaster", 2051, 0, 255, false, + "AmplifierEnvChaos", 2053, 0, 31, false, + "AmplifierEnvAttackLinearity", 2054, 0, 31, false, + "AmplifierEnvDecayLinearity", 2055, 0, 31, false, + "AmplifierLFORange", 2056, 0, 127, false, + "PanningVoice1", 2176, 0, 255, false, + "PanningVoice2", 2177, 0, 255, false, + "PanningVoice3", 2178, 0, 255, false, + "PanningVoice4", 2179, 0, 255, false, + "PanningVoice5", 2180, 0, 255, false, + "PanningVoice6", 2181, 0, 255, false, + "PanningVoice7", 2182, 0, 255, false, + "PanningVoice8", 2183, 0, 255, false, + "PanningVoice9", 2184, 0, 255, false, + "PanningVoice10", 2185, 0, 255, false, + "PanningVoice11", 2186, 0, 255, false, + "PanningVoice12", 2187, 0, 255, false, + "PanningVoice13", 2188, 0, 255, false, + "PanningVoice14", 2189, 0, 255, false, + "PanningVoice15", 2190, 0, 255, false, + "PanningVoice16", 2191, 0, 255, false, + "AtrophyProfileNameA", 2304, 32, 255, false, + "AtrophyProfileNameB", 2305, 32, 255, false, + "AtrophyProfileNameC", 2306, 32, 255, false, + "AtrophyProfileNameD", 2307, 32, 255, false, + "AtrophyProfileNameE", 2308, 32, 255, false, + "AtrophyProfileNameF", 2309, 32, 255, false, + "SequencerStepCount", 2432, 0, 128, false, + "SequencerGatetime", 2434, 0, 99, false + }; + + public static final int NUM_PARAMS_CHECKBOXES = 4; + public static final Object[] checkboxGroups = { + "ControlPortamentoSettings", 7, 4, new String[]{"Match", "Quantize", "Bend", "Exponential"}, + "PerformanceSettings", 129, 6, new String[]{"Bend Osc2 only~Bend Osc1 & Osc2", "Custom bend~Bend +/- 2", "OSC1", "OSC2", "Upper", "Lower"}, + "PerformanceLFOMods", 135, 4, new String[]{"Track", "Envelope", "Tempo Lock", "LFO Trig"}, + "ModulationLFOMods", 261, 4, new String[]{"LFO Track on~LFO Track off", "LFO Env2 Rate on~LFO Env2 Rate off", "Tempo Lock", "LFO Trig"}, + "ModulationChannel1Sends", 263, 3, new String[]{"C1 OSC1 LFO on~C1 OSC1 LFO off", "C1 OSC2 LFO on~C1 OSC2 LFO off", "C1 Flt LFO on~C1 Flt LFO off"}, + "ModulationChannel1Mods", 264, 2, new String[]{"C1 Quantize on~C1 Quantize off", "LFO Env1 Invert on~LFO Env1 Invert off"}, + "ModulationChannel2Sends", 265, 3, new String[]{"C2 PWM1 LFO on~C2 PWM1 LFO off", "C2 PWM2 LFO on~C2 PWM2 LFO off", "C2 Vol LFO on~C2 Vol LFO off"}, + "ModulationChannel2Mods", 266, 2, new String[]{"C2 Quantize on~C2 Quantize off", "LFO Env2 Invert on~LFO Env2 Invert off"}, + "ModulationQuirks", 268, 2, new String[]{"Flip Square", "Flip VCFMod"}, + "EnvelopesFilterMods", 397, 5, new String[]{"Flip", "Repeat", "Retrig", "Loop", "Legato"}, + "EnvelopesLoudnessMods", 398, 5, new String[]{"Flip", "Repeat", "Retrig", "Loop", "Legato"}, + "OscillatorsMode", 516, 2, new String[]{"Osc 2 Sync on~Osc 2 Sync off", "Osc 2 Filter Envelope on~Osc 2 Filter Envelope off"}, + "OscillatorsOSC1State", 517, 3, new String[]{"Osc 1 Full~Osc 1 Off", "Osc 1 VCO LFO 180°~Osc 1 VCO LFO 0°", "180°~Osc 1 PWM LFO 0°"}, + "FilterModes", 644, 2, new String[]{"Filter Tracking on~Filter Tracking off", "4 Pole~2 Pole"}, + "UILastPatch", 910, 4, new String[]{"Upper", "Lower", "Combo", "Split"}, + "UIDisable", 911, 3, new String[]{"Assign Preset", "LED Segues", "Pulse revert"}, + "MidiSong", 1156, 5, new String[]{"Position In", "Stop on Seek", "Stop all off", "Start En Seq", "Start En Arp"}, + "MidiLocalControl", 1161, 8, new String[]{"Local Ctrl", "Din Rt", "Din Clock", "USB Rt", "USB Clock", "ProgChngeRx", "ProgChngeTx", "NRPNTx"}, + "MidiForwarding", 1162, 8, new String[]{"Din to Usb", "Usb to Din", "Din to Din", "Rt to Usb", "Rt to Din", "Clk to Din", "Clk to Usb", "ArpSeq Sel"}, + "MidiUSBControl", 1163, 4, new String[]{"USB NRPN TX", "USB NRPN RX", "USB CC TX", "USB CC RX"}, + "MidiDinControl", 1164, 4, new String[]{"DIN NRPN TX", "DIN NRPN RX", "DIN CC TX", "DIN CC RX"}, + "VoiceToggle1to8", 1664, 8, new String[]{"Voice 1", "Voice 2", "Voice 3", "Voice 4", "Voice 5", "Voice 6", "Voice 7", "Voice 8"}, + "VoiceToggle9to16", 1665, 8, new String[]{"Voice 9", "Voice 10", "Voice 11", "Voice 12", "Voice 13", "Voice 14", "Voice 15", "Voice 16"}, + "VoiceIgnorePWMCalibration1to8", 1666, 8, new String[]{"Voice 1", "Voice 2", "Voice 3", "Voice 4", "Voice 5", "Voice 6", "Voice 7", "Voice 8"}, + "VoiceIgnorePWMCalibration9to16", 1667, 8, new String[]{"Voice 9", "Voice 10", "Voice 11", "Voice 12", "Voice 13", "Voice 14", "Voice 15", "Voice 16"}, + "VoiceIgnoreVCOCalibration1to8", 1668, 8, new String[]{"Voice 1", "Voice 2", "Voice 3", "Voice 4", "Voice 5", "Voice 6", "Voice 7", "Voice 8"}, + "VoiceIgnoreVCOCalibration9to16", 1669, 8, new String[]{"Voice 9", "Voice 10", "Voice 11", "Voice 12", "Voice 13", "Voice 14", "Voice 15", "Voice 16"}, + "VoiceIgnoreVCFCalibration1to8", 1670, 8, new String[]{"Voice 1", "Voice 2", "Voice 3", "Voice 4", "Voice 5", "Voice 6", "Voice 7", "Voice 8"}, + "VoiceIgnoreVCFCalibration9to16", 1671, 8, new String[]{"Voice 9", "Voice 10", "Voice 11", "Voice 12", "Voice 13", "Voice 14", "Voice 15", "Voice 16"}, + "SurfaceLeverSettings", 1794, 3, new String[]{"Invert Left", "Invert Right", "Swap Levers"}, + "ProgramPerformancePanelOpts", 2560, 3, new String[]{"Save Patch", "Load Transp", "Transp MIDI"} + }; + + + public static final int NUM_PARAMS_SELECTORS = 3; + public static final Object[] selectors = { + "ControlUnisonNotePriority", 9, new String[]{"Below", "Above", "Last"}, + "ControlChordModeNotePriority", 10, new String[]{"Below", "Above", "Last"}, + "PerformanceLFOShapes", 133, new String[]{"Sine", "Saw", "Square", "Inverse Saw", "S/H", "Triangle", "SMP", "Noise"}, + "ModulationLFOShapes", 262, new String[]{"Sine", "Saw", "Square", "Inverse Saw", "S/H", "Triangle", "SMP", "Noise"}, + "OscillatorsOSC1Shapes", 518, new String[]{"Osc 1 Pulse", "Osc 1 Saw", "Osc 1 Tri"}, + "OscillatorsOSC2State", 519, new String[]{"Osc 2 Half", "Osc 2 Full", "Osc 2 Off"}, + "OscillatorsOSC2Shapes", 520, new String[]{"Osc 2 Pulse", "Osc 2 Saw", "Osc 2 Tri"}, + "OscillatorsOSC1Quantization", 535, new String[]{"Octaves", "Semitones", "Free", "+/- Octaves", "+/- Semitones", "+/- Free"}, + "OscillatorsOSC2Quantization", 536, new String[]{"Octaves", "Semitones", "Free", "+/- Octaves", "+/- Semitones", "+/- Free"}, + "ModMatrixBus1Source", 776, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus1Destination", 777, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus2Source", 778, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus2Destination", 779, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus3Source", 780, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus3Destination", 781, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus4Source", 782, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus4Destination", 783, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus5Source", 784, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus5Destination", 785, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus6Source", 786, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus6Destination", 787, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus7Source", 788, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus7Destination", 789, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "ModMatrixBus8Source", 790, new String[]{"Empty", "ChannelPressure", "PolyAftertouch", "BreathControl", "ModChannel1Envelope", "ModChannel2Envelope", "ModChannel1", "ModChannel2", "Expression", "Cuttoff", "FilterEnvelope", "KeyTrack", "KeyTrackPoly", "LoudnessEnvelope", "MainLFO", "SecondaryLFO", "Noise", "ModWheel", "Oscillator1", "Oscillator2", "PulseWidth1", "PulseWidth2", "PitchBendAmount", "Resonance", "VoiceSlewer", "PerformanceChannel", "PanningLeft", "PanningRight", "KeyVelocity", "SpreadFtn", "GeneralPurpose1", "GeneralPurpose2", "GeneralPurpose3", "GeneralPurpose4", "ModWheelUp", "ModWheelDown", "MPETimbre", "MPEPitch", "MPEPressure", "EightLEightR", "FlipFlop", "Spiral", "Osc1Drift", "Osc2Drift", "FilterDrift"}, + "ModMatrixBus8Destination", 791, new String[]{"Empty", "ButterInput", "EnvelopesFilterA", "EnvelopesFilterD", "FilterFrequency", "FilterModulation", "FilterNoise", "EnvelopesFilterR", "FilterResonance", "EnvelopesFilterS", "EnvelopesLoudnessA", "EnvelopesLoudnessD", "EnvelopesLoudnessR", "EnvelopesLoudnessS", "ModulationChannel1Amount", "ModulationChannel2Amount", "ModulationLFOPhase", "ModulationLFORate", "OscillatorsOSC1PWAmount", "OscillatorsOSC1Transpose", "ControlDetune", "OscillatorsOSC2PWAmount", "OscillatorsOSC2Transpose", "ControlPanning", "ControlPortamentoAmount", "PerformanceLFOChannelAmount", "PerformanceLFORate", "ManualMastertune", "ControlVoiceDetune", "PerformanceVolume", "CuttoffLfo", "Oscillator1Modulation", "Oscillator1Fenv", "Oscillator2Modulation", "Oscillator2Fenv", "PulseWidth1Modulation", "PulseWidth2Modulation", "PanningLeftModulation", "PanningRightModulation", "OscillatorsDriftAmount", "OscillatorsDriftSpeed", "FilterDriftAmount", "FilterDriftSpeed"}, + "UIVintageKnobs", 904, new String[]{"Extended", "Purism"}, + "UIAtrophyMode", 912, new String[]{"Read Only", "Edit Advanced"}, + "AtrophyProfile", 913, new String[]{"Atrophy Profile", "Current Patch"}, + "ArpeggiatorMode", 1025, new String[]{"Up", "Down", "Inclusive", "Exclusive", "Random", "Order", "Up x2", "Down x2", "Up x3", "Down x3", "Up x2/x3", "Down x2/ x3"}, + "ArpeggiatorTime", 1027, new String[]{"1/4 notes", "1/8 notes", "1/16 notes", "1/32 notes", "1/4 triplets", "1/8 triplets", "1/16 triplets"}, + "ArpeggiatorSync", 1029, new String[]{"Global", "Retrigger"}, + "MidiSync", 1155, new String[]{"Internal", "USB", "DIN"}, + "MidiChannelUpperRx", 1157, new String[]{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "All", "None"}, + "MidiChannelLowerRx", 1158, new String[]{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "All", "None"}, + "MidiChannelUpperTX", 1159, new String[]{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "RxChannel"}, + "MidiChannelLowerTX", 1160, new String[]{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "RxChannel"}, + "MidiMpeProfile", 1165, new String[]{"Disabled", "Single", "Zones"}, + "KeyboardVelocityCurviness", 1281, new String[]{"Soft", "Medium", "Hard"}, + "KeyboardAftertouchAssignment", 1283, new String[]{"ChannelPressure", "PolyAftertouch"}, + "KeyboardAftertouchCurviness", 1284, new String[]{"Soft", "Medium", "Hard"}, + "PedalSustainAssignment", 1408, new String[]{"Patch Up", "Patch Down", "Program Up", "Program Down", "Sustain", "Hold", "Sostenuto"}, + "PedalSustainPolarity", 1409, new String[]{"Negative", "Positive", "Disabled"}, + "PedalSustainInternalLatch", 1410, new String[]{"Unlatched", "Latched"}, + "PedalProgramAssignment", 1411, new String[]{"Patch Up", "Patch Down", "Program Up", "Program Down", "Sustain", "Hold", "Sostenuto"}, + "PedalProgramPolarity", 1412, new String[]{"Negative", "Positive", "Disabled"}, + "PedalProgramInternalLatch", 1413, new String[]{"Unlatched", "Latched"}, + "PedalHoldAssignment", 1414, new String[]{"Patch Up", "Patch Down", "Program Up", "Program Down", "Sustain", "Hold", "Sostenuto"}, + "PedalHoldPolarity", 1415, new String[]{"Negative", "Positive", "Disabled"}, + "PedalHoldInternalLatch", 1416, new String[]{"Unlatched", "Latched"}, + "PedalVibratoAssignment", 1417, new String[]{"Filter", "Vibrato", "Attack", "Release", "Decay", "General1 cc16", "General2 cc17", "General3 cc18", "General4 cc19"}, + "PedalVibratoPolarity", 1418, new String[]{"Negative", "Positive", "Disabled"}, + "PedalFilterAssignment", 1421, new String[]{"Filter", "Vibrato", "Attack", "Release", "Decay", "General1 cc16", "General2 cc17", "General3 cc18", "General4 cc19"}, + "PedalFilterPolarity", 1422, new String[]{"Negative", "Positive", "Disabled"}, + "SysexDeviceId", 1536, new String[]{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"}, + "SurfaceFan", 1795, new String[]{"Enabled", "TempCtrl", "Disabled"}, + "BackgroundCalibrationSettings", 1920, new String[]{"Enabled", "Only Auto", "Disabled"}, + "SequencerTime", 2433, new String[]{"1/4 notes", "1/8 notes", "1/16 notes", "1/32 notes", "1/4 triplets", "1/8 triplets", "1/16 triplets"}, + "SequencerSync", 2435, new String[]{"Global", "Retrigger"} + }; + + // Manual mapping for now - might be able to generate + public static final Object[] SysExPosToKeyAndSize = { + 20, "EnvelopesFilterA",2, + 22, "EnvelopesFilterD",2, + 24, "EnvelopesFilterS",2, + 26, "EnvelopesFilterR",2, + 28, "EnvelopesLoudnessA",2, + 30, "EnvelopesLoudnessD",2, + 32, "EnvelopesLoudnessS",2, + 34, "EnvelopesLoudnessR",2, + }; + + public static final Object[] KeyToSysExPos = { + "ControlPortamentoAmount", new int[]{0, 1, 5, 6}, + "ControlPortamentoBend", new int[]{2, 3, 4}, + "ControlDetune", new int[]{4, 5, 1, 2}, + "ControlVoiceDetune", new int[]{6, 7, 13, 0}, + "PerformanceVolume", new int[]{8, 9, 11, 12}, + "ModulationLFORate", new int[]{12, 13, 7, 8}, + "ModulationLFOPhase", new int[]{14, 15, 19, 20}, + "ModulationChannel1Amount", new int[]{16, 17, 18}, + "ModulationChannel2Amount", new int[]{18, 19, 15, 16}, + "EnvelopesFilterA", new int[]{20, 21, 27, 14}, + "EnvelopesFilterD", new int[]{22, 23, 25, 26}, + "EnvelopesFilterR", new int[]{26, 27, 21, 22}, + "EnvelopesLoudnessA", new int[]{28, 29, 33, 34}, + "EnvelopesLoudnessD", new int[]{30, 31, 32}, + "EnvelopesLoudnessS", new int[]{32, 33, 29, 30}, + "EnvelopesLoudnessR", new int[]{34, 35, 41, 28}, + "EnvelopesModChannel1A", new int[]{36, 37, 39, 40}, + "EnvelopesModChannel2A", new int[]{40, 41, 35, 36}, + "EnvelopesModChannel2Delay", new int[]{42, 43, 47, 48}, + "EnvelopesPedalR", new int[]{44, 45, 46}, + "OscillatorsOSC1PWAmount", new int[]{46, 47, 43, 44}, + "OscillatorsOSC1Transpose", new int[]{48, 49, 55, 42}, + "OscillatorsOSC2PWAmount", new int[]{50, 51, 53, 54}, + "FilterFrequency", new int[]{54, 55, 49, 50}, + "FilterResonance", new int[]{56, 57, 61, 62}, + "FilterModulation", new int[]{58, 59, 60}, + "FilterNoise", new int[]{60, 61, 57, 58}, + "ModMatrixBus1Amount", new int[]{62, 63, 69, 56}, + "ModMatrixBus2Amount", new int[]{64, 65, 67, 68}, + "ModMatrixBus4Amount", new int[]{68, 69, 63, 64}, + "ModMatrixBus5Amount", new int[]{70, 71, 75, 76}, + "ModMatrixBus6Amount", new int[]{72, 73, 74}, + "ModMatrixBus7Amount", new int[]{74, 75, 71, 72}, + "ModMatrixBus8Amount", new int[]{76, 77, 83, 70}, + }; +} diff --git a/edisyn/synth/synths.txt b/edisyn/synth/synths.txt index 38e8ec74..b5ea7f99 100644 --- a/edisyn/synth/synths.txt +++ b/edisyn/synth/synths.txt @@ -21,6 +21,7 @@ edisyn.synth.alesisd4.AlesisD4 Alesis Alesis D4 / DM5 edisyn.synth.asmhydrasynth.ASMHydrasynth ASM ASM Hydrasynth edisyn.synth.audiothingiesmicromonsta.AudiothingiesMicroMonsta Audiothingies Audiothingies MicroMonsta +edisyn.synth.behringerubxa.BehringerUBXa Behringer Behringer UB-Xa edisyn.synth.casiocz.CasioCZ Casio Casio CZ edisyn.synth.dsiprophet08.DSIProphet08 DSI / Sequential DSI Prophet '08 / Tetra / Mopho edisyn.synth.dsiprophet08.DSITetraCombo DSI / Sequential DSI Tetra [Combo]