UNPKG

dxex

Version:

Create and parse sysex for Yamaha DX synthesizers.

356 lines (355 loc) 16 kB
"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;