dxex
Version:
Create and parse sysex for Yamaha DX synthesizers.
356 lines (355 loc) • 16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DX21 = void 0;
const utils_1 = require("./utils");
/**
* Class for interacting with DX21 synthesizers.
*/
class DX21 {
// eslint-disable-next-line require-jsdoc
constructor(voiceType) {
this.params = null;
this.voiceType = voiceType;
}
/**
* Parse DX sysex data.
* @param {number} data
* @return {DX21Voice[] | null}
*/
parse(data) {
if (this.voiceType === 1) {
// 1 voice bulk
return [this._parseVCED(data.slice(6, 100))]; // Parse VCED
}
else if (this.voiceType === 32) {
// 32 voice bulk
return (0, utils_1.chunkify)(data.slice(6), 32).map((value) => {
return this._parseVMEM(value);
});
}
return null;
}
/**
* Resolve sysex data.
* @param {DX21Voice} data The data to resolve.
* @param {DXResolveOptions} options
* @return {number[]}
*/
resolve(data, options) {
const mode = options?.mode || 'bulk32';
const res = [];
if (mode === 'bulk32') {
for (let i = 0; i < 32; i++) {
const resolvedChunk = this._resolveVMEM(data[i]);
res.push(...resolvedChunk, ...Array(55).fill(0));
}
}
else {
}
res.push(this._checksum(res));
res.unshift(0xF0, 0x43, 0x00, 0x04, 0x20, 0x00);
res.push(0xF7);
return res;
}
/**
* Parse a VCED format chunk.
* @param {number[]} chunk
* @return {DX21Voice}
*/
_parseVCED(chunk) {
return {
voiceName: String.fromCharCode(...chunk.slice(77, 87)),
transpose: chunk[62],
algorithm: chunk[52],
feedbackLevel: chunk[53],
op: [
{
envelopeGenerator: {
attackRate: chunk[39],
decay1Rate: chunk[40],
decay2Rate: chunk[41],
releaseRate: chunk[42],
decay1Level: chunk[43],
},
keyboardScalingLevel: chunk[44],
keyboardScalingRate: chunk[45],
egBiasSensitivity: chunk[46],
amplitudeModulationEnable: chunk[47],
keyVelocity: chunk[48],
outputLevel: chunk[49],
oscillatorFrequency: chunk[50],
detune: chunk[51],
},
{
envelopeGenerator: {
attackRate: chunk[13],
decay1Rate: chunk[14],
decay2Rate: chunk[15],
releaseRate: chunk[16],
decay1Level: chunk[17],
},
keyboardScalingLevel: chunk[18],
keyboardScalingRate: chunk[19],
egBiasSensitivity: chunk[20],
amplitudeModulationEnable: chunk[21],
keyVelocity: chunk[22],
outputLevel: chunk[23],
oscillatorFrequency: chunk[24],
detune: chunk[25],
},
{
envelopeGenerator: {
attackRate: chunk[26],
decay1Rate: chunk[27],
decay2Rate: chunk[28],
releaseRate: chunk[29],
decay1Level: chunk[30],
},
keyboardScalingLevel: chunk[31],
keyboardScalingRate: chunk[32],
egBiasSensitivity: chunk[33],
amplitudeModulationEnable: chunk[34],
keyVelocity: chunk[35],
outputLevel: chunk[36],
oscillatorFrequency: chunk[37],
detune: chunk[38],
},
{
envelopeGenerator: {
attackRate: chunk[0],
decay1Rate: chunk[1],
decay2Rate: chunk[2],
releaseRate: chunk[3],
decay1Level: chunk[4],
},
keyboardScalingLevel: chunk[5],
keyboardScalingRate: chunk[6],
egBiasSensitivity: chunk[7],
amplitudeModulationEnable: chunk[8],
keyVelocity: chunk[9],
outputLevel: chunk[10],
oscillatorFrequency: chunk[11],
detune: chunk[12],
},
],
lfo: {
lfoSpeed: chunk[54],
lfoDelay: chunk[55],
pitchModulationDepth: chunk[56],
amplitudeModulationDepth: chunk[57],
lfoSync: chunk[58],
lfoWave: chunk[59],
pitchModulationSensitivity: chunk[60],
amplitudeModulationSensitivity: chunk[61],
},
pitchEnvelopeGenerator: {
pitchEgRate1: chunk[87],
pitchEgRate2: chunk[88],
pitchEgRate3: chunk[89],
pitchEgLevel1: chunk[90],
pitchEgLevel2: chunk[91],
pitchEgLevel3: chunk[92],
},
performance: {
playMode: chunk[63],
pitchBendRange: chunk[64],
portamentoMode: chunk[65],
portamentoTime: chunk[66],
footVolume: chunk[67],
sustainFootSwitch: chunk[68],
portamentoFootSwitch: chunk[69],
chorusSwitch: chunk[70],
modulationWheelPitchModulationRange: chunk[71],
modulationWheelAmplitudeModulationRange: chunk[72],
breathControlPitchModulationRange: chunk[73],
breathControlAmplitudeModulationRange: chunk[74],
breathControlPitchBiasRange: chunk[75],
breathControlEgBiasRange: chunk[76],
},
};
}
/**
* Parse a VMEM format chunk.
* @param {number[]} chunk
* @param {number[]} data
* @return {object}
*/
_parseVMEM(chunk) {
return {
voiceName: String.fromCharCode(...chunk.slice(57, 67)),
transpose: chunk[46],
feedbackLevel: (chunk[40] & 56) >> 3,
algorithm: chunk[40] & 7,
op: [
{
// 1
envelopeGenerator: {
attackRate: chunk[30],
decay1Rate: chunk[31],
decay2Rate: chunk[32],
releaseRate: chunk[33],
decay1Level: chunk[34],
},
keyboardScalingLevel: chunk[35],
amplitudeModulationEnable: (chunk[36] & 64) >> 6,
egBiasSensitivity: (chunk[36] & 56) >> 3,
keyVelocity: chunk[36] & 7,
outputLevel: chunk[37],
oscillatorFrequency: chunk[38],
keyboardScalingRate: (chunk[39] & 56) >> 3,
detune: chunk[39] & 7,
},
{
// 2
envelopeGenerator: {
attackRate: chunk[10],
decay1Rate: chunk[11],
decay2Rate: chunk[12],
releaseRate: chunk[13],
decay1Level: chunk[14],
},
keyboardScalingLevel: chunk[15],
amplitudeModulationEnable: (chunk[16] & 64) >> 6,
egBiasSensitivity: (chunk[16] & 56) >> 3,
keyVelocity: chunk[16] & 7,
outputLevel: chunk[17],
oscillatorFrequency: chunk[18],
keyboardScalingRate: (chunk[19] & 56) >> 3,
detune: chunk[19] & 7,
},
{
// 3
envelopeGenerator: {
attackRate: chunk[20],
decay1Rate: chunk[21],
decay2Rate: chunk[22],
releaseRate: chunk[23],
decay1Level: chunk[24],
},
keyboardScalingLevel: chunk[25],
amplitudeModulationEnable: (chunk[26] & 64) >> 6,
egBiasSensitivity: (chunk[26] & 56) >> 3,
keyVelocity: chunk[26] & 7,
outputLevel: chunk[27],
oscillatorFrequency: chunk[28],
keyboardScalingRate: (chunk[29] & 56) >> 3,
detune: chunk[29] & 7,
},
{
// 4
envelopeGenerator: {
attackRate: chunk[0],
decay1Rate: chunk[1],
decay2Rate: chunk[2],
releaseRate: chunk[3],
decay1Level: chunk[4],
},
keyboardScalingLevel: chunk[5],
amplitudeModulationEnable: (chunk[6] & 64) >> 6,
egBiasSensitivity: (chunk[6] & 56) >> 3,
keyVelocity: chunk[6] & 7,
outputLevel: chunk[7],
oscillatorFrequency: chunk[8],
keyboardScalingRate: (chunk[9] & 56) >> 3,
detune: chunk[9] & 7,
},
],
lfo: {
lfoWave: chunk[45] & 3,
pitchModulationSensitivity: (chunk[45] >> 5) & 7,
amplitudeModulationSensitivity: (chunk[45] >> 2) & 7,
lfoSync: (chunk[40] >> 6) & 64,
lfoSpeed: chunk[41],
lfoDelay: chunk[42],
pitchModulationDepth: chunk[43],
amplitudeModulationDepth: chunk[44],
},
pitchEnvelopeGenerator: {
pitchEgRate1: chunk[67],
pitchEgRate2: chunk[68],
pitchEgRate3: chunk[69],
pitchEgLevel1: chunk[70],
pitchEgLevel2: chunk[71],
pitchEgLevel3: chunk[72],
},
performance: {
pitchBendRange: chunk[47],
chorusSwitch: (chunk[48] & 16) >> 4,
sustainFootSwitch: (chunk[48] & 4) >> 2,
portamentoFootSwitch: (chunk[48] & 2) >> 1,
playMode: (chunk[48] & 8) >> 3,
portamentoMode: (chunk[48] & 1),
portamentoTime: chunk[49],
footVolume: chunk[50],
modulationWheelPitchModulationRange: chunk[51],
modulationWheelAmplitudeModulationRange: chunk[52],
breathControlPitchModulationRange: chunk[53],
breathControlAmplitudeModulationRange: chunk[54],
breathControlPitchBiasRange: chunk[55],
breathControlEgBiasRange: chunk[56],
},
};
}
/**
* Resolve a VCED format chunk.
* @param {DX21Voice} data
* @return {number[]}
*/
_resolveVCED(data) {
const res = [];
for (const i of [3, 1, 2, 0]) {
res.push(data.op[i].envelopeGenerator.attackRate, data.op[i].envelopeGenerator.decay1Rate, data.op[i].envelopeGenerator.decay2Rate, data.op[i].envelopeGenerator.releaseRate, data.op[i].envelopeGenerator.decay1Level, data.op[i].keyboardScalingLevel, data.op[i].keyboardScalingRate, data.op[i].egBiasSensitivity, data.op[i].amplitudeModulationEnable, data.op[i].keyVelocity, data.op[i].outputLevel, data.op[i].oscillatorFrequency, data.op[i].detune);
}
res.push(data.algorithm, data.feedbackLevel, data.lfo.lfoSpeed, data.lfo.lfoDelay, data.lfo.pitchModulationDepth, data.lfo.amplitudeModulationDepth, data.lfo.lfoSync, data.lfo.lfoWave, data.lfo.pitchModulationSensitivity, data.lfo.amplitudeModulationSensitivity, data.transpose, data.performance.playMode, data.performance.pitchBendRange, data.performance.portamentoMode, data.performance.portamentoTime, data.performance.footVolume, data.performance.sustainFootSwitch, data.performance.portamentoFootSwitch, data.performance.chorusSwitch, data.performance.modulationWheelPitchModulationRange, data.performance.modulationWheelAmplitudeModulationRange, data.performance.breathControlPitchModulationRange, data.performance.breathControlAmplitudeModulationRange, data.performance.breathControlPitchBiasRange, data.performance.breathControlEgBiasRange, ...this._formatVoiceName(data.voiceName), data.pitchEnvelopeGenerator.pitchEgRate1, data.pitchEnvelopeGenerator.pitchEgRate2, data.pitchEnvelopeGenerator.pitchEgRate3, data.pitchEnvelopeGenerator.pitchEgLevel1, data.pitchEnvelopeGenerator.pitchEgLevel2, data.pitchEnvelopeGenerator.pitchEgLevel3);
return res;
}
/**
* Resolve VMEM format chunk.
* @param {DX21Voice} data
* @return {number[]}
*/
_resolveVMEM(data) {
const res = [];
for (const i of [3, 1, 2, 0]) {
res.push(data.op[i].envelopeGenerator.attackRate, data.op[i].envelopeGenerator.decay1Rate, data.op[i].envelopeGenerator.decay2Rate, data.op[i].envelopeGenerator.releaseRate, data.op[i].envelopeGenerator.decay1Level, data.op[i].keyboardScalingLevel, ((data.op[i].amplitudeModulationEnable << 6) +
(data.op[i].egBiasSensitivity << 3) +
data.op[i].keyVelocity), data.op[i].outputLevel, data.op[i].oscillatorFrequency, ((data.op[i].keyboardScalingRate << 3) +
data.op[i].detune));
}
res.push(((data.lfo.lfoSync << 6) +
(data.feedbackLevel << 3) +
data.algorithm), data.lfo.lfoSpeed, data.lfo.lfoDelay, data.lfo.pitchModulationDepth, data.lfo.amplitudeModulationDepth, ((data.lfo.pitchModulationSensitivity << 5) +
(data.lfo.amplitudeModulationSensitivity << 2) +
data.lfo.lfoWave), data.transpose, data.performance.pitchBendRange, ((data.performance.chorusSwitch << 4) +
(data.performance.playMode << 3) +
(data.performance.sustainFootSwitch << 2) +
(data.performance.portamentoFootSwitch << 1) +
data.performance.portamentoMode), data.performance.portamentoTime, data.performance.footVolume, data.performance.modulationWheelPitchModulationRange, data.performance.modulationWheelAmplitudeModulationRange, data.performance.breathControlPitchModulationRange, data.performance.breathControlAmplitudeModulationRange, data.performance.breathControlPitchBiasRange, data.performance.breathControlEgBiasRange, ...this._formatVoiceName(data.voiceName), data.pitchEnvelopeGenerator.pitchEgRate1, data.pitchEnvelopeGenerator.pitchEgRate2, data.pitchEnvelopeGenerator.pitchEgRate3, data.pitchEnvelopeGenerator.pitchEgLevel1, data.pitchEnvelopeGenerator.pitchEgLevel2, data.pitchEnvelopeGenerator.pitchEgLevel3);
return res;
}
/**
* Create a checksum.
* @param {data} data
* @return {number}
*/
_checksum(data) {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i];
}
return sum > 0 ? -sum & 127 : sum & 127;
}
/**
* Format voice name to 10-byte ASCII.
* @param {string} voiceName
* @return {number[]}
*/
_formatVoiceName(voiceName) {
const res = Array(10).fill(32);
for (let i = 0; i < Math.min(voiceName.length, 10); i++) {
res[i] = voiceName.charCodeAt(i);
}
return res;
}
}
exports.DX21 = DX21;