From 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 13 Apr 2025 18:48:02 +0100 Subject: initial --- site/udo/midimap.udo | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100755 site/udo/midimap.udo (limited to 'site/udo/midimap.udo') diff --git a/site/udo/midimap.udo b/site/udo/midimap.udo new file mode 100755 index 0000000..63be6cf --- /dev/null +++ b/site/udo/midimap.udo @@ -0,0 +1,247 @@ +#ifndef UDO_MIDI +#define UDO_MIDI ## +/* + MIDI control to named channel mapper + Currently only handling one channel + + This file is part of the SONICS UDO collection by Richard Knight 2022 + License: GPL-2.0-or-later + http://1bpm.net +*/ +#include "array_tools.udo" +#include "table_tools.udo" + +gSmidimap_channels[] init 128 ; channel names with index correlating to MIDI CC number +gimidimap_values = ftgen(0, 0, -384, -2, 0) ; min and max values for scaling, and default/saved value, with index correlating to MIDI CC number + + +/* + Handle incoming MIDI messages and write to channel with scaling if defined in gSmidimap_channels and gkmidimap_values +*/ +instr midimap_handler + kstatus, kchan, kdata1, kdata2 midiin + if (kstatus == 176) then + if (strcmpk(gSmidimap_channels[kdata1], "") != 0) then + kvalue = scale(kdata2, table:k(kdata1+128, gimidimap_values), table:k(kdata1, gimidimap_values), 127, 0) + chnset kvalue, gSmidimap_channels[kdata1] + ;outvalue strcmpk(gSmidimap_channels[kdata1], "_retval"), kvalue + endif +#ifndef MIDI_NOTE_HANDLER_INSTRUMENT + endif +#else + elseif (kstatus == 144) then + schedulek("_midi_note_handler", 0, 1, 1, kchan, kdata1, kdata2) + elseif (kstatus == 128) then + schedulek("_midi_note_handler", 0, 1, 0, kchan, kdata1, kdata2) + endif +#end +endin +alwayson("midimap_handler") + + +instr _midi_note_handler + ionoff = p4 + ichannel = p5 + inote = p6 + ivelocity = p7 + instrnum = nstrnum("$MIDI_NOTE_HANDLER_INSTRUMENT") + (ichannel / 100) + (inote / 100000) + if (ionoff == 0) then + turnoff2 instrnum, 4, 1 + else + schedule(instrnum, 0, -1, ichannel, inote, ivelocity) + endif + turnoff +endin + + +/* + Register a MIDI CC to channel mapping, replacing any current mapping + + p4 CC number + p5 channel name + p6 minimum value to scale to + p7 maximum value to scale to + p8 default/save value +*/ +instr midimap_register + icc = p4 + Schannel = p5 + imin = p6 + imax = p7 + ivalue = p8 + gSmidimap_channels[icc] = Schannel + tablew imin, icc, gimidimap_values + tablew imax, icc+128, gimidimap_values + tablew ivalue, icc+256, gimidimap_values + turnoff +endin + + +/* + Register a MIDI CC to a channel mapping, replacing any current mapping, based on user input to specify the CC number. + Threshold (p7) can be supplied to account for noisy devices that may emit unwanted CCs + + p4 channel name + p5 minimum value to scale to + p6 maximum value to scale to + p7 default/save value + p8 how many points the CC must be moved to count as registered (defaults to 1 if not supplied) + p9 optional instrument name or number to call when learning completed; called with p3 = -1 +*/ +instr midimap_learn + Schannel = p4 + imin = p5 + imax = p6 + ivalue = p7 + ithreshold = p8 + if (qnan(p9) == 1) then ; if onComplete provided + ionComplete = nstrnum(strget(p9)) + elseif (p9 > 0) then + ionComplete = p9 + else + ionComplete = -1 + endif + + if (ithreshold < 1) then + ithreshold = 1 + endif + kvals[] init 128 + prints sprintf("Learn mode: move a controller to assign to channel '%s'\n", Schannel) + + kstatus, kchan, kcc, kvalue midiin + + if (kstatus == 176) then + if (ithreshold > 1 && kvals[kcc] == 0) then + kvals[kcc] = kvalue + elseif (ithreshold == 1 || abs:k(kvals[kcc] - kvalue) >= ithreshold) then + gSmidimap_channels[kcc] = Schannel + tablew imin, kcc, gimidimap_values + tablew imax, kcc+128, gimidimap_values + tablew ivalue, kcc+256, gimidimap_values + printf "Controller %d assigned to channel '%s'\n", 1, kcc, Schannel + if (ionComplete != -1) then + schedulek(ionComplete, 0, -1) + endif + turnoff + endif + endif +endin + + +/* + Abort learning mode; turnoff midimap_learn +*/ +instr midimap_learn_abort + turnoff2 "midimap_learn", 0, 0 + turnoff +endin + + +/* + Set CCs and channels to default/saved value +*/ +opcode _midimap_setvalues, 0, 0 + icc = 0 + while (icc < lenarray(gSmidimap_channels)) do + if (strcmp(gSmidimap_channels[icc], "") != 0) then + imin table icc, gimidimap_values + imax table icc+128, gimidimap_values + ivalue table icc+256, gimidimap_values + chnset ivalue, gSmidimap_channels[icc] + outic 1, icc, ivalue, imin, imax + endif + icc += 1 + od +endop + + +/* + Gather CC values and set default/saved value +*/ +opcode _midimap_getvalues, 0, 0 + icc = 0 + while (icc < lenarray(gSmidimap_channels)) do + if (strcmp(gSmidimap_channels[icc], "") != 0) then + imin table icc, gimidimap_values + imax table icc+128, gimidimap_values + ivalue ctrl7 1, icc, imin, imax + tablew ivalue, icc+256, gimidimap_values + endif + icc += 1 + od +endop + + +/* + Save map state to file + + p4 file path + p5 1 = save current CC values; 0 = do not save values +*/ +instr midimap_savestate_fs + Sfile = p4 + isavevalues = p5 + if (isavevalues == 1) then + _midimap_getvalues() + endif + Serial = sprintf("%s\n%s", arr_serialise(gSmidimap_channels), tab_serialise(gimidimap_values)) + fprints Sfile, Serial + turnoff +endin + + +/* + Load map state from file + + p4 file path +*/ +instr midimap_loadstate_fs + Sfile = p4 +read: + Sline, ilinenum readfi Sfile + if (ilinenum == 1) then + gSmidimap_channels arr_unserialise Sline + igoto read + elseif (ilinenum == 2) then + tab_unserialise Sline, gimidimap_values + endif + _midimap_setvalues() + turnoff +endin + + + +#ifdef USING_DB +/* + Save map state to database + + p4 state name + p5 1 = save current CC values; 0 = do not save values +*/ +instr midimap_savestate_db + Sname = p4 + isavevalues = p5 + if (isavevalues == 1) then + _midimap_getvalues() + endif + pgdb_array_save strcat(Sname, ".channels"), "midimap", gSmidimap_channels + pgdb_table_save strcat(Sname, ".values"), "midimap", gimidimap_values + turnoff +endin + + +/* + Load map state from database + + p4 state name +*/ +instr midimap_loadstate_db + Sname = p4 + gSmidimap_channels pgdb_array_get strcat(Sname, ".channels"), "midimap" + i_ pgdb_table_get strcat(Sname, ".values"), "midimap", gimidimap_values + _midimap_setvalues() + turnoff +endin +#end ; USING_DB + +#end -- cgit v1.2.3