#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