diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index ca0bdacf..1e1a1afb 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1651,18 +1651,33 @@ export const { ccv } = registerControl('ccv'); export const { ctlNum } = registerControl('ctlNum'); // TODO: ctlVal? +/** + * MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message. + * @name nrpnn + * @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127) + * @example + * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() + */ +export const { nrpnn } = registerControl('nrpnn'); +/** + * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message. + * @name nrpv + * @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127) + * @example + * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() + */ +export const { nrpv } = registerControl('nrpv'); + /** * MIDI program number: Sends a MIDI program change message. * * @name progNum * @param {number | Pattern} program MIDI program number (0-127) + * @example + * note("c4").progNum(10).midichan(1).midi() */ export const { progNum } = registerControl('progNum'); -export const { polyTouch } = registerControl('polyTouch'); -export const { midibend } = registerControl('midibend'); -export const { miditouch } = registerControl('miditouch'); - /** * MIDI sysex: Sends a MIDI sysex message. * @name sysex @@ -1694,3 +1709,24 @@ export const { sysexid } = registerControl('sysexid'); * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() */ export const { sysexdata } = registerControl('sysexdata'); + + +/** + * MIDI pitch bend: Sends a MIDI pitch bend message. + * @name midibend + * @param {number | Pattern} midibend MIDI pitch bend (-1 - 1) + * @example + * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi() + */ +export const { midibend } = registerControl('midibend'); +/** + * MIDI key after touch: Sends a MIDI key after touch message. + * @name miditouch + * @param {number | Pattern} miditouch MIDI key after touch (0-1) + * @example + * note("c4").miditouch(sine.slow(4).range(0,1)).midi() + */ +export const { miditouch } = registerControl('miditouch'); + +// TODO: what is this? +export const { polyTouch } = registerControl('polyTouch'); diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index fa7b2b6d..01b66395 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -153,6 +153,9 @@ Pattern.prototype.midi = function (output) { ccv, midichan = 1, midicmd, + midibend, + miditouch, + polyTouch, //?? gain = 1, velocity = 0.9, progNum, @@ -217,18 +220,7 @@ Pattern.prototype.midi = function (output) { // list of manufacturer ids can be found here : https://midi.org/sysexidtable // if sysexid is an array the first byte is 0x00 - // if (sysex !== undefined) { - // console.log('sysex', sysex); - // if (Array.isArray(sysex)) { - // if (Array.isArray(sysex[0])) { - // //device.sendSysex(sysex[0], sysex[1], { time: timeOffsetString }); - // } else { - // //device.sendSysex(sysex[0], sysex[1], { time: timeOffsetString }); - // } - // } - // } 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'); @@ -260,6 +252,35 @@ Pattern.prototype.midi = function (output) { device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString }); } + // Handle NRPN non-registered parameter number + if (nrpnn !== undefined && nrpv !== undefined) { + if (Array.isArray(nrpnn)) { + if (!nrpnn.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) { + throw new Error('all nrpnn bytes must be integers between 0 and 255'); + } + } else if (!Number.isInteger(nrpv) || nrpv < 0 || nrpv > 255) { + throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers'); + } + + device.sendNRPN(nrpnn, nrpv, midichan, { time: timeOffsetString }); + } + + // Handle midibend + if (midibend !== undefined) { + if (typeof midibend !== 'number' || midibend < 1 || midibend > -1) { + throw new Error('expected midibend to be a number between 1 and -1'); + } + device.sendPitchBend(midibend, midichan, { time: timeOffsetString }); + } + + // Handle miditouch + if (miditouch !== undefined) { + if (typeof miditouch !== 'number' || miditouch < 1 || miditouch > 0) { + throw new Error('expected miditouch to be a number between 1 and 0'); + } + device.sendKeyAfterTouch(miditouch, midichan, { time: timeOffsetString }); + } + // Handle midicmd if (hap.whole.begin + 0 === 0) { // we need to start here because we have the timing info