aboutsummaryrefslogtreecommitdiff
path: root/site/udo/midimap.udo
blob: 63be6cfa40cdc182515715112578cc680ab7f78f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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