From 8759c498ed1f6076a1fa18905b21c234d6e4a202 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:23:09 +0200 Subject: [PATCH] c2js - midi output and device select (#204) * update c2js template for midi device selection * wasm midi_out implemented, no test * scriptprocessornode * clean up shared code * midi_utils * consistency * revert attempt to remove dublicate code, channel bugfix, cleanup * consistency * consistency * changelog and channel --------- Co-authored-by: Reinissance --- CHANGELOG.md | 2 + hvcc/generators/c2js/template/hv_worklet.js | 221 ++++++++++++------ .../c2js/template/hv_worklet_start.js | 2 +- hvcc/generators/c2js/template/hv_wrapper.js | 221 ++++++++++++------ hvcc/generators/c2js/template/index.html | 193 ++++++++++----- 5 files changed, 438 insertions(+), 201 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ceb868..ad9db6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Bugfixes: * Daisy template newline +* JS: midi out and device select + 0.12.1 ----- diff --git a/hvcc/generators/c2js/template/hv_worklet.js b/hvcc/generators/c2js/template/hv_worklet.js index c043f1a4..ab7d597b 100644 --- a/hvcc/generators/c2js/template/hv_worklet.js +++ b/hvcc/generators/c2js/template/hv_worklet.js @@ -108,13 +108,22 @@ class {{name}}_AudioLibWorklet extends AudioWorkletProcessor { var self = this; // typedef void (HvSendHook_t) (HeavyContextInterface *context, const char *sendName, hv_uint32_t sendHash, const HvMessage *msg); var sendHook = addFunction(function(context, sendName, sendHash, msg) { + // Filter out MIDI messages + const midiMessage = sendMidiOut(UTF8ToString(sendName), msg); + if (midiMessage.length > 0) { + self.port.postMessage({ + type: 'midiOut', + payload: midiMessage + }); + } else { // Converts sendhook callback to (sendName, float) message self.port.postMessage({ type: 'sendHook', payload: [UTF8ToString(sendName), _hv_msg_getFloat(msg, 0)] }); - }, - "viiii" + } + }, + "viiii" ); _hv_setSendHook(this.heavyContext, sendHook); } @@ -132,73 +141,7 @@ class {{name}}_AudioLibWorklet extends AudioWorkletProcessor { } sendMidi(message) { - if (this.heavyContext) { - var command = message[0] & 0xF0; - var channel = message[0] & 0x0F; - var data1 = message[1]; - var data2 = message[2]; - - // all events to [midiin] - for (var i = 1; i <= 2; i++) { - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_MIDIIN, 0, - message[i], - channel - ); - } - - // realtime events to [midirealtimein] - if (MIDI_REALTIME.includes(message[0])) { - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_MIDIREALTIMEIN, 0, - message[0] - ); - } - - switch(command) { - case 0x80: // note off - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_NOTEIN, 0, - data1, - 0, - channel); - break; - case 0x90: // note on - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_NOTEIN, 0, - data1, - data2, - channel); - break; - case 0xA0: // polyphonic aftertouch - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_POLYTOUCHIN, 0, - data2, // pressure - data1, // note - channel); - break; - case 0xB0: // control change - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_CTLIN, 0, - data2, // value - data1, // cc number - channel); - break; - case 0xC0: // program change - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_PGMIN, 0, - data1, - channel); - break; - case 0xD0: // aftertouch - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_TOUCHIN, 0, - data1, - channel); - break; - case 0xE0: // pitch bend - // combine 7bit lsb and msb into 32bit int - var value = (data2 << 7) | data1; - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_BENDIN, 0, - value, - channel); - break; - default: - // console.error('No handler for midi message: ', message); - } - } + sendMidiIn(this.heavyContext, message); } sendStringToReceiver(name, message) { @@ -265,9 +208,145 @@ var tableHashes = { registerProcessor("{{name}}_AudioLibWorklet", {{name}}_AudioLibWorklet); +// midi_utils + +function sendMidiIn(hv_context, message) { + if (hv_context) { + var command = message[0] & 0xF0; + var channel = message[0] & 0x0F; + var data1 = message[1]; + var data2 = message[2]; + + // all events to [midiin] + for (var i = 1; i <= 2; i++) { + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_MIDIIN, 0, + message[i], + channel + ); + } + + // realtime events to [midirealtimein] + if (MIDI_REALTIME.includes(message[0])) { + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_MIDIREALTIMEIN, 0, + message[0] + ); + } + + switch(command) { + case 0x80: // note off + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_NOTEIN, 0, + data1, + 0, + channel); + break; + case 0x90: // note on + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_NOTEIN, 0, + data1, + data2, + channel); + break; + case 0xA0: // polyphonic aftertouch + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_POLYTOUCHIN, 0, + data2, // pressure + data1, // note + channel); + break; + case 0xB0: // control change + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_CTLIN, 0, + data2, // value + data1, // cc number + channel); + break; + case 0xC0: // program change + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_PGMIN, 0, + data1, + channel); + break; + case 0xD0: // aftertouch + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_TOUCHIN, 0, + data1, + channel); + break; + case 0xE0: // pitch bend + // combine 7bit lsb and msb into 32bit int + var value = (data2 << 7) | data1; + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_BENDIN, 0, + value, + channel); + break; + default: + // console.error('No handler for midi message: ', message); + } + } + } + +function sendMidiOut(sendName, msg) { + switch (sendName) { + case "__hv_noteout": + var note = _hv_msg_getFloat(msg, 0); + var velocity = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return [ + ((velocity > 0) ? 144 : 128) | channel, + note, + velocity + ] + case "__hv_ctlout": + var value = _hv_msg_getFloat(msg, 0); + var cc = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return [ + 176 | channel, + cc, + value + ] + case "__hv_pgmout": + var program = _hv_msg_getFloat(msg, 0); + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 192 | channel, + program + ] + case "__hv_touchout": + var pressure = _hv_msg_getFloat(msg, 0); + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 208 | channel, + pressure, + ] + case "__hv_polytouchout": + var value = _hv_msg_getFloat(msg, 0); + var note = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return[ + 160 | channel, + note, + value + ] + case "__hv_bendout": + var value = _hv_msg_getFloat(msg, 0); + let lsb = value & 0x7F; + let msb = (value >> 7) & 0x7F; + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 224 | channel, + lsb, + msb + ] + case "__hv_midiout": + let firstByte = _hv_msg_getFloat(msg, 0); + return (firstByte === 192 || firstByte === 208) ? + [_hv_msg_getFloat(msg, 0), _hv_msg_getFloat(msg, 1)] : + [_hv_msg_getFloat(msg, 0), _hv_msg_getFloat(msg, 1), _hv_msg_getFloat(msg, 2)]; + default: + console.warn(`Unhandled sendName: ${sendName}`); + return []; + } +} + /* - * MIDI Constants - */ +* MIDI Constants +*/ const HV_HASH_NOTEIN = 0x67E37CA3; const HV_HASH_CTLIN = 0x41BE0f9C; diff --git a/hvcc/generators/c2js/template/hv_worklet_start.js b/hvcc/generators/c2js/template/hv_worklet_start.js index e9cff20d..cb90a8ae 100644 --- a/hvcc/generators/c2js/template/hv_worklet_start.js +++ b/hvcc/generators/c2js/template/hv_worklet_start.js @@ -8,4 +8,4 @@ var self = { function importScripts(){ console.warn('importScripts should not be called in an AudioWorklet', arguments); -} \ No newline at end of file +} diff --git a/hvcc/generators/c2js/template/hv_wrapper.js b/hvcc/generators/c2js/template/hv_wrapper.js index 3b2f85e0..0c4fd981 100644 --- a/hvcc/generators/c2js/template/hv_wrapper.js +++ b/hvcc/generators/c2js/template/hv_wrapper.js @@ -22,6 +22,7 @@ var AudioLibLoader = function() { * @param options.sendHook (Function) callback that gets triggered for messages sent via @hv_param/@hv_event */ AudioLibLoader.prototype.init = function(options) { + // use provided web audio context or create a new one this.webAudioContext = options.webAudioContext || (new (window.AudioContext || window.webkitAudioContext || null)); @@ -43,6 +44,8 @@ AudioLibLoader.prototype.init = function(options) { options.printHook(event.data.payload); } else if (event.data.type === 'sendHook' && options.sendHook) { options.sendHook(event.data.payload[0], event.data.payload[1]); + } else if (event.data.type === 'midiOut' && options.sendHook) { + options.sendHook("midiOutMessage", event.data.payload); } else { console.log('Unhandled message from {{name}}_AudioLibWorklet:', event.data); } @@ -244,8 +247,13 @@ var tableHashes = { if (hook) { // typedef void (HvSendHook_t) (HeavyContextInterface *context, const char *sendName, hv_uint32_t sendHash, const HvMessage *msg); var sendHook = addFunction(function(context, sendName, sendHash, msg) { - // Converts sendhook callback to (sendName, float) message - hook(UTF8ToString(sendName), _hv_msg_getFloat(msg, 0)); + const midiMessage = sendMidiOut(UTF8ToString(sendName), msg); + if (midiMessage.length > 0) { + hook("midiOutMessage", midiMessage); + } else { + // Converts sendhook callback to (sendName, float) message + hook(UTF8ToString(sendName), _hv_msg_getFloat(msg, 0)); + } }, "viiii" ); @@ -260,73 +268,7 @@ var tableHashes = { } {{name}}_AudioLib.prototype.sendMidi = function(message) { - if (this.heavyContext) { - var command = message[0] & 0xF0; - var channel = message[0] & 0x0F; - var data1 = message[1]; - var data2 = message[2]; - - // all events to [midiin] - for (var i = 1; i <= 2; i++) { - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_MIDIIN, 0, - message[i], - channel - ); - } - - // realtime events to [midirealtimein] - if (MIDI_REALTIME.includes(message[0])) { - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_MIDIREALTIMEIN, 0, - message[0] - ); - } - - switch(command) { - case 0x80: // note off - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_NOTEIN, 0, - data1, - 0, - channel); - break; - case 0x90: // note on - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_NOTEIN, 0, - data1, - data2, - channel); - break; - case 0xA0: // polyphonic aftertouch - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_POLYTOUCHIN, 0, - data2, // pressure - data1, // note - channel); - break; - case 0xB0: // control change - _hv_sendMessageToReceiverFFF(this.heavyContext, HV_HASH_CTLIN, 0, - data2, // value - data1, // cc number - channel); - break; - case 0xC0: // program change - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_PGMIN, 0, - data1, - channel); - break; - case 0xD0: // aftertouch - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_TOUCHIN, 0, - data1, - channel); - break; - case 0xE0: // pitch bend - // combine 7bit lsb and msb into 32bit int - var value = (data2 << 7) | data1; - _hv_sendMessageToReceiverFF(this.heavyContext, HV_HASH_BENDIN, 0, - value, - channel); - break; - default: - // console.error('No handler for midi message: ', message); - } - } + sendMidiIn(this.heavyContext, message); } {{name}}_AudioLib.prototype.setFloatParameter = function(name, floatValue) { @@ -368,9 +310,146 @@ var tableHashes = { Module.{{name}}_AudioLib = {{name}}_AudioLib; + +// midi_utils + +function sendMidiIn(hv_context, message) { + if (hv_context) { + var command = message[0] & 0xF0; + var channel = message[0] & 0x0F; + var data1 = message[1]; + var data2 = message[2]; + + // all events to [midiin] + for (var i = 1; i <= 2; i++) { + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_MIDIIN, 0, + message[i], + channel + ); + } + + // realtime events to [midirealtimein] + if (MIDI_REALTIME.includes(message[0])) { + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_MIDIREALTIMEIN, 0, + message[0] + ); + } + + switch(command) { + case 0x80: // note off + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_NOTEIN, 0, + data1, + 0, + channel); + break; + case 0x90: // note on + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_NOTEIN, 0, + data1, + data2, + channel); + break; + case 0xA0: // polyphonic aftertouch + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_POLYTOUCHIN, 0, + data2, // pressure + data1, // note + channel); + break; + case 0xB0: // control change + _hv_sendMessageToReceiverFFF(hv_context, HV_HASH_CTLIN, 0, + data2, // value + data1, // cc number + channel); + break; + case 0xC0: // program change + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_PGMIN, 0, + data1, + channel); + break; + case 0xD0: // aftertouch + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_TOUCHIN, 0, + data1, + channel); + break; + case 0xE0: // pitch bend + // combine 7bit lsb and msb into 32bit int + var value = (data2 << 7) | data1; + _hv_sendMessageToReceiverFF(hv_context, HV_HASH_BENDIN, 0, + value, + channel); + break; + default: + // console.error('No handler for midi message: ', message); + } + } + } + +function sendMidiOut(sendName, msg) { + switch (sendName) { + case "__hv_noteout": + var note = _hv_msg_getFloat(msg, 0); + var velocity = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return [ + ((velocity > 0) ? 144 : 128) | channel, + note, + velocity + ] + case "__hv_ctlout": + var value = _hv_msg_getFloat(msg, 0); + var cc = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return [ + 176 | channel, + cc, + value + ] + case "__hv_pgmout": + var program = _hv_msg_getFloat(msg, 0); + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 192 | channel, + program + ] + case "__hv_touchout": + var pressure = _hv_msg_getFloat(msg, 0); + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 208 | channel, + pressure, + ] + case "__hv_polytouchout": + var value = _hv_msg_getFloat(msg, 0); + var note = _hv_msg_getFloat(msg, 1); + var channel = _hv_msg_getFloat(msg, 2) % 16; // no pd midi ports + return[ + 160 | channel, + note, + value + ] + case "__hv_bendout": + var value = _hv_msg_getFloat(msg, 0); + let lsb = value & 0x7F; + let msb = (value >> 7) & 0x7F; + var channel = _hv_msg_getFloat(msg, 1) % 16; // no pd midi ports + return [ + 224 | channel, + lsb, + msb + ] + case "__hv_midiout": + let firstByte = _hv_msg_getFloat(msg, 0); + return (firstByte === 192 || firstByte === 208) ? + [_hv_msg_getFloat(msg, 0), _hv_msg_getFloat(msg, 1)] : + [_hv_msg_getFloat(msg, 0), _hv_msg_getFloat(msg, 1), _hv_msg_getFloat(msg, 2)]; + default: + console.warn(`Unhandled sendName: ${sendName}`); + return []; + } +} + /* - * MIDI Constants - */ +* MIDI Constants +*/ const HV_HASH_NOTEIN = 0x67E37CA3; const HV_HASH_CTLIN = 0x41BE0f9C; diff --git a/hvcc/generators/c2js/template/index.html b/hvcc/generators/c2js/template/index.html index da6cbf82..aadcfc47 100644 --- a/hvcc/generators/c2js/template/index.html +++ b/hvcc/generators/c2js/template/index.html @@ -10,6 +10,7 @@ {% endfor -%}