1import { Invoke } from './utils.mjs'; 2import { Pattern, getEventOffsetMs, noteToMidi } from '@strudel/core'; 3 4const ON_MESSAGE = 0x90; 5const OFF_MESSAGE = 0x80; 6const CC_MESSAGE = 0xb0; 7 8Pattern.prototype.midi = function (output) { 9 return this.onTrigger((hap, currentTime, cps, targetTime) => { 10 let { note, nrpnn, nrpv, ccn, ccv, velocity = 0.9, gain = 1 } = hap.value; 11 //magic number to get audio engine to line up, can probably be calculated somehow 12 const latencyMs = 34; 13 const offset = getEventOffsetMs(targetTime, currentTime) + latencyMs; 14 velocity = Math.floor(gain * velocity * 100); 15 const duration = Math.floor((hap.duration.valueOf() / cps) * 1000 - 10); 16 const roundedOffset = Math.round(offset); 17 const midichan = (hap.value.midichan ?? 1) - 1; 18 const requestedport = output ?? 'IAC'; 19 const messagesfromjs = []; 20 if (note != null) { 21 const midiNumber = typeof note === 'number' ? note : noteToMidi(note); 22 messagesfromjs.push({ 23 requestedport, 24 message: [ON_MESSAGE + midichan, midiNumber, velocity], 25 offset: roundedOffset, 26 }); 27 messagesfromjs.push({ 28 requestedport, 29 message: [OFF_MESSAGE + midichan, midiNumber, velocity], 30 offset: roundedOffset + duration, 31 }); 32 } 33 if (ccv && ccn) { 34 if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) { 35 throw new Error('expected ccv to be a number between 0 and 1'); 36 } 37 if (!['string', 'number'].includes(typeof ccn)) { 38 throw new Error('expected ccn to be a number or a string'); 39 } 40 const scaled = Math.round(ccv * 127); 41 messagesfromjs.push({ 42 requestedport, 43 message: [CC_MESSAGE + midichan, ccn, scaled], 44 offset: roundedOffset, 45 }); 46 } 47 // invoke is temporarily blocking, run in an async process 48 if (messagesfromjs.length) { 49 setTimeout(() => { 50 Invoke('sendmidi', { messagesfromjs }); 51 }); 52 } 53 }); 54};