Add cc to midicmd, add API Reference for midi related controls

This commit is contained in:
nkymut 2025-01-24 05:37:11 +08:00
parent 57c48f0c45
commit d06a75a2cd
2 changed files with 110 additions and 24 deletions

View File

@ -1509,22 +1509,10 @@ export const { scram } = registerControl('scram');
export const { binshift } = registerControl('binshift');
export const { hbrick } = registerControl('hbrick');
export const { lbrick } = registerControl('lbrick');
export const { midichan } = registerControl('midichan');
export const { control } = registerControl('control');
export const { ccn } = registerControl('ccn');
export const { ccv } = registerControl('ccv');
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');
export const { ctlNum } = registerControl('ctlNum');
export const { frameRate } = registerControl('frameRate');
export const { frames } = registerControl('frames');
export const { hours } = registerControl('hours');
export const { midicmd } = registerControl('midicmd');
export const { minutes } = registerControl('minutes');
export const { progNum } = registerControl('progNum');
export const { seconds } = registerControl('seconds');
export const { songPtr } = registerControl('songPtr');
export const { uid } = registerControl('uid');
@ -1616,6 +1604,73 @@ export const ar = register('ar', (t, pat) => {
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
//MIDI
/**
* MIDI channel: Sets the MIDI channel for the event.
*
* @name midichan
* @param {number | Pattern} channel MIDI channel number (0-15)
* @example
* note("c4").midichan(1).midi()
*/
export const { midichan } = registerControl('midichan');
export const { midicmd } = registerControl('midicmd');
/**
* MIDI control: Sends a MIDI control change message.
*
* @name control
* @param {number | Pattern} MIDI control number (0-127)
* @param {number | Pattern} MIDI controller value (0-127)
*/
export const control = register('control', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('control expects an array of [ccn, ccv]');
}
const [_ccn, _ccv] = args;
return pat.ccn(_ccn).ccv(_ccv);
});
/**
* MIDI control number: Sends a MIDI control change message.
*
* @name ccn
* @param {number | Pattern} MIDI control number (0-127)
*/
export const { ccn } = registerControl('ccn');
/**
* MIDI control value: Sends a MIDI control change message.
*
* @name ccv
* @param {number | Pattern} MIDI control value (0-127)
*/
export const { ccv } = registerControl('ccv');
export const { ctlNum } = registerControl('ctlNum');
// TODO: ctlVal?
/**
* MIDI program number: Sends a MIDI program change message.
*
* @name progNum
* @param {number | Pattern} program MIDI program number (0-127)
*/
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
* @param {number | Pattern} id Sysex ID
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysex("0x77, "0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const sysex = register('sysex', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('sysex expects an array of [id, data]');
@ -1623,3 +1678,19 @@ export const sysex = register('sysex', (args, pat) => {
const [id, data] = args;
return pat.sysexid(id).sysexdata(data);
});
/**
* MIDI sysex ID: Sends a MIDI sysex identifier message.
* @name sysexid
* @param {number | Pattern} id Sysex ID
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexid } = registerControl('sysexid');
/**
* MIDI sysex data: Sends a MIDI sysex message.
* @name sysexdata
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexdata } = registerControl('sysexdata');

View File

@ -93,6 +93,12 @@ if (typeof window !== 'undefined') {
});
}
/**
* MIDI output: Opens a MIDI output port.
* @param {string | number} output MIDI device name or index defaulting to 0
* @example
* note("c4").midichan(1).midi("IAC Driver Bus 1")
*/
Pattern.prototype.midi = function (output) {
if (isPattern(output)) {
throw new Error(
@ -105,6 +111,7 @@ Pattern.prototype.midi = function (output) {
let isController = false;
let mapping = {};
//TODO: MIDI mapping related
if (typeof output === 'object') {
const { port, controller = false, ...remainingProps } = output;
portName = port;
@ -187,26 +194,16 @@ Pattern.prototype.midi = function (output) {
const ccvLsb = Math.round(value * 16383) & 0b1111111;
device.sendControlChange(ccnLsb, ccvLsb, paramSpec.channel || midichan, { time: timeOffsetString });
}
} else if (paramSpec.pc !== undefined) {
} else if (paramSpec.progNum !== undefined) {
if (typeof value !== 'number' || value < 0 || value > 127) {
throw new Error(`Expected ${name} to be a number between 0 and 127 for program change`);
}
device.sendProgramChange(value, paramSpec.channel || midichan, { 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 });
// }
}
});
}
// Handle program change
if (progNum !== undefined) {
if (typeof progNum !== 'number' || progNum < 0 || progNum > 127) {
@ -283,6 +280,16 @@ Pattern.prototype.midi = function (output) {
} else {
device.sendProgramChange(midicmd[1], midichan, { time: timeOffsetString });
}
} else if (midicmd[0] === 'cc') {
if (midicmd.length === 2) {
if (typeof midicmd[0] !== 'number' || midicmd[0] < 0 || midicmd[0] > 127) {
throw new Error('expected ccn (control change number) to be a number between 0 and 127');
}
if (typeof midicmd[1] !== 'number' || midicmd[1] < 0 || midicmd[1] > 127) {
throw new Error('expected ccv (control change value) to be a number between 0 and 127');
}
device.sendControlChange(midicmd[0], midicmd[1], midichan, { time: timeOffsetString });
}
}
}
});
@ -291,6 +298,14 @@ Pattern.prototype.midi = function (output) {
let listeners = {};
const refs = {};
/**
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
* @param {string | number} input MIDI device name or index defaulting to 0
* @returns {Function}
* @example
* let cc = await midin("IAC Driver Bus 1")
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
*/
export async function midin(input) {
if (isPattern(input)) {
throw new Error(