Get sysex working

split sysex message into sysexid and sysexdata
sysexid is a device identification number or array
sysexdata is an array of data to be sent to the device
This commit is contained in:
nkymut 2025-01-18 14:31:11 +08:00
parent e085819fe2
commit 13a4512601
5 changed files with 190 additions and 29 deletions

View File

@ -1514,7 +1514,8 @@ export const { control } = registerControl('control');
export const { ccn } = registerControl('ccn');
export const { ccv } = registerControl('ccv');
export const { pc } = registerControl('pc');
export const { sysex } = registerControl('sysex');
export const { sysexid } = registerControl('sysexid');
export const { sysexdata } = registerControl('sysexdata');
export const { polyTouch } = registerControl('polyTouch');
export const { midibend } = registerControl('midibend');
export const { miditouch } = registerControl('miditouch');

View File

@ -76,19 +76,17 @@ The exact sound that each program number maps to depends on your MIDI device's c
## sysex (System Exclusive Message)
The `sysex` control sends MIDI System Exclusive (SysEx) messages to your MIDI device.
ysEx messages are device-specific commands that allow deeper control over synthesizer parameters.
sysEx messages are device-specific commands that allow deeper control over synthesizer parameters.
The value should be an array of numbers between 0-255 representing the SysEx data bytes.
```javascript
// Send a simple SysEx message
sysex([0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7]).midi()
```
let id = 0x43; //Yamaha
//let id = "0x00:0x20:0x32"; //Behringer ID can be an array of numbers
```javascript
//Send SysEx while playing notes
note("c3 e3 g3")
.sysex("<[0xF0,0x7E,0x7F,0x09,0x01,0xF7] [0xF0,0x7E,0x7F,0x09,0x02,0xF7]>")
.midi()
let data = "0x79:0x09:0x11:0x0A:0x00:0x00"; // Set NSX-39 voice to say "Aa"
note("c d e f e d c").sysex(id, data).midi();
```
The exact format of SysEx messages depends on your MIDI device's specification.

130
packages/midi/gm.mjs Normal file
View File

@ -0,0 +1,130 @@
export const gm = {
piano: 0,
bright_acoustic_piano: 1,
electric_grand_piano: 2,
honky_tonk_piano: 3,
epiano1: 4,
epiano2: 5,
harpsichord: 6,
clavinet: 7,
celesta: 8,
glockenspiel: 9,
music_box: 10,
vibraphone: 11,
marimba: 12,
xylophone: 13,
tubular_bells: 14,
dulcimer: 15,
drawbar_organ: 16,
percussive_organ: 17,
rock_organ: 18,
church_organ: 19,
reed_organ: 20,
accordion: 21,
harmonica: 22,
bandoneon: 23,
acoustic_guitar_nylon: 24,
acoustic_guitar_steel: 25,
electric_guitar_jazz: 26,
electric_guitar_clean: 27,
electric_guitar_muted: 28,
overdriven_guitar: 29,
distortion_guitar: 30,
guitar_harmonics: 31,
acoustic_bass: 32,
electric_bass_finger: 33,
electric_bass_pick: 34,
fretless_bass: 35,
slap_bass_1: 36,
slap_bass_2: 37,
synth_bass_1: 38,
synth_bass_2: 39,
violin: 40,
viola: 41,
cello: 42,
contrabass: 43,
tremolo_strings: 44,
pizzicato_strings: 45,
orchestral_harp: 46,
timpani: 47,
string_ensemble_1: 48,
string_ensemble_2: 49,
synth_strings_1: 50,
synth_strings_2: 51,
choir_aahs: 52,
voice_oohs: 53,
synth_choir: 54,
orchestra_hit: 55,
trumpet: 56,
trombone: 57,
tuba: 58,
muted_trumpet: 59,
french_horn: 60,
brass_section: 61,
synth_brass_1: 62,
synth_brass_2: 63,
soprano_sax: 64,
alto_sax: 65,
tenor_sax: 66,
baritone_sax: 67,
oboe: 68,
english_horn: 69,
bassoon: 70,
clarinet: 71,
piccolo: 72,
flute: 73,
recorder: 74,
pan_flute: 75,
blown_bottle: 76,
shakuhachi: 77,
whistle: 78,
ocarina: 79,
lead_1_square: 80,
lead_2_sawtooth: 81,
lead_3_calliope: 82,
lead_4_chiff: 83,
lead_5_charang: 84,
lead_6_voice: 85,
lead_7_fifths: 86,
lead_8_bass_lead: 87,
pad_1_new_age: 88,
pad_2_warm: 89,
pad_3_polysynth: 90,
pad_4_choir: 91,
pad_5_bowed: 92,
pad_6_metallic: 93,
pad_7_halo: 94,
pad_8_sweep: 95,
fx_1_rain: 96,
fx_2_soundtrack: 97,
fx_3_crystal: 98,
fx_4_atmosphere: 99,
fx_5_brightness: 100,
fx_6_goblins: 101,
fx_7_echoes: 102,
fx_8_sci_fi: 103,
sitar: 104,
banjo: 105,
shamisen: 106,
koto: 107,
kalimba: 108,
bagpipe: 109,
fiddle: 110,
shanai: 111,
tinkle_bell: 112,
agogo: 113,
steel_drums: 114,
woodblock: 115,
taiko_drum: 116,
melodic_tom: 117,
synth_drum: 118,
reverse_cymbal: 119,
guitar_fret_noise: 120,
breath_noise: 121,
seashore: 122,
bird_tweet: 123,
telephone: 124,
helicopter: 125,
applause: 126,
gunshot: 127,
};

View File

@ -1,3 +1,4 @@
import './midi.mjs';
export * from './midi.mjs';
export * from './gm.mjs';

View File

@ -8,6 +8,7 @@ import * as _WebMidi from 'webmidi';
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi } from '@strudel/core';
import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi;
@ -43,13 +44,16 @@ export function enableWebMidi(options = {}) {
resolve(WebMidi);
return;
}
WebMidi.enable((err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
});
WebMidi.enable(
(err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
},
{ sysex: true },
);
});
}
@ -134,7 +138,20 @@ Pattern.prototype.midi = function (output) {
// passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output
const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`;
// destructure value
let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9, pc, sysex } = hap.value;
let {
note,
nrpnn,
nrpv,
ccn,
ccv,
midichan = 1,
midicmd,
gain = 1,
velocity = 0.9,
pc,
sysexid,
sysexdata,
} = hap.value;
velocity = gain * velocity;
@ -173,15 +190,18 @@ Pattern.prototype.midi = function (output) {
throw new Error(`Expected ${name} to be a number between 0 and 127 for program change`);
}
device.sendProgramChange(value, paramSpec.channel || midichan, { time: timeOffsetString });
} else if (paramSpec.sysex) {
if (!Array.isArray(value)) {
throw new Error(`Expected ${name} to be an array of numbers (0-255) for sysex`);
}
if (!value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error(`All sysex bytes in ${name} must be integers between 0 and 255`);
}
device.sendSysex(undefined, value, { time: timeOffsetString });
}
// ToDo: support sysex for mapped parameters
// } else if (paramSpec.sysex) {
// if (!Array.isArray(value)) {
// throw new Error(`Expected ${name} to be an array of numbers (0-255) for sysex`);
// }
// if (!value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
// throw new Error(`All sysex bytes in ${name} must be integers between 0 and 255`);
// }
// device.sendSysex(0x43, value, { time: timeOffsetString });
// //device.sendSysex(0x43, [0x79, 0x09, 0x11, 0x0A, 0x00,0x02], { time: timeOffsetString });
// }
}
});
}
@ -193,14 +213,25 @@ Pattern.prototype.midi = function (output) {
device.sendProgramChange(pc, midichan, { time: timeOffsetString });
}
// Handle sysex
if (sysex !== undefined) {
if (!Array.isArray(sysex)) {
if (sysexid !== undefined && sysexdata !== undefined) {
//console.log('sysex', sysexid, sysexdata);
if (Array.isArray(sysexid)) {
if (!sysexid.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysexid bytes must be integers between 0 and 255');
}
} else if (!Number.isInteger(sysexid) || sysexid < 0 || sysexid > 255) {
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
}
if (!Array.isArray(sysexdata)) {
throw new Error('expected sysex to be an array of numbers (0-255)');
}
if (!sysex.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
if (!sysexdata.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysex bytes must be integers between 0 and 255');
}
device.sendSysex(undefined, sysex, { time: timeOffsetString });
device.sendSysex(sysexid, sysexdata, { time: timeOffsetString });
//device.sendSysex(0x43, [0x79, 0x09, 0x11, 0x0A, 0x00,0x1e], { time: timeOffsetString });
}
// Handle control change