Usage of UDO in while loop

Hello

I’m creating a custom orc with opcodes for setting up a Launchcontrol midi controller.

Currently I have the following setup, which is working fine, but I think it would be more optimal to set this all up in a loop, rather than manually by hand. I guess it’s not too much hassle to do once, but it seems ripe for the computer to do that.

Firstly here is the working setup. There are essentially 2 UDOs:

launchcontrol_unit_cc - is a wrapper for ctrl7, with niceties like state initialisation, debugging and a rough soft-takeover function:

opcode launchcontrol_unit_cc, k, ikiiki
	ichn, kccn, imin, imax, kctrlinitstate, idebug xin
	print ichn, imin, imax, idebug

	ktakeover init 0	
	iccn = i(kccn)
	ictrlinitstate = i(kctrlinitstate)
	
	; have to run a reinit as the k-rate input variables kccn and kctrlinitstate
	; are 0 on initial pass and the ctrl7 opcode needs them converted to i-rate
	recalc:
		prints "recalc'd\n"
		iccn = i(kccn)
		ictrlinitstate = i(kctrlinitstate)
;		print iccn ,ictrlinitstate			
		rireturn
			
	reinit recalc
	
;	print iccn ,ictrlinitstate
	
	kctrl = ctrl7:k(ichn, iccn, imin, imax)
	
	; implement soft-takeover (is decent? not really...)
	if kctrl > (ictrlinitstate-2) && kctrl < (ictrlinitstate+2) then 
		ktakeover = 1
	endif
	
	if ktakeover == 1 then
;		kctrlout = port(kctrl, 0.002)
		kctrlout = kctrl
	else 
		kctrlout = ictrlinitstate
	endif
	
	if idebug == 1 && changed2(kctrlout) == 1 then
		println "Chan %d : CC%d : %f", ichn, kccn, kctrlout
	endif
	
	xout kctrlout
endop

launchcontrolarray - is a parent controller UDO for the unit UDOs above:

opcode launchcontrolarray, k[], ii
	ibase, idebug xin
	kctrls[] init 16

	; top row dials
	kctrls[0] = launchcontrol_unit_cc(1, 57, 0, 127, i(gkcntrlsorig, 0), idebug)
	kctrls[1] = launchcontrol_unit_cc(1, 58, 0, 127, i(gkcntrlsorig, 1), idebug)
	kctrls[2] = launchcontrol_unit_cc(1, 59, 0, 127, i(gkcntrlsorig, 2), idebug)
	kctrls[3] = launchcontrol_unit_cc(1, 60, 0, 127, i(gkcntrlsorig, 3), idebug)
	kctrls[4] = launchcontrol_unit_cc(1, 61, 0, 127, i(gkcntrlsorig, 4), idebug)
	kctrls[5] = launchcontrol_unit_cc(1, 62, 0, 127, i(gkcntrlsorig, 5), idebug)
	kctrls[6] = launchcontrol_unit_cc(1, 63, 0, 127, i(gkcntrlsorig, 6), idebug)
	kctrls[7] = launchcontrol_unit_cc(1, 64, 0, 127, i(gkcntrlsorig, 7), idebug)
	; bottom row dials
;	kctrls[8] = launchcontrol_unit_cc(1, 65, 0, 127, i(gkcntrlsorig, 8), idebug)
;	kctrls[9] = launchcontrol_unit_cc(1, 66, 0, 127, i(gkcntrlsorig, 9), idebug)
;	kctrls[10] = launchcontrol_unit_cc(1, 67, 0, 127, i(gkcntrlsorig, 10) idebug)
;	kctrls[11] = launchcontrol_unit_cc(1, 68, 0, 127, i(gkcntrlsorig, 11), idebug)
;	kctrls[12] = launchcontrol_unit_cc(1, 69, 0, 127, i(gkcntrlsorig, 12), idebug)
;	kctrls[13] = launchcontrol_unit_cc(1, 70, 0, 127, i(gkcntrlsorig, 13), idebug)
;	kctrls[14] = launchcontrol_unit_cc(1, 71, 0, 127, i(gkcntrlsorig, 14), idebug)
;	kctrls[15] = launchcontrol_unit_cc(1, 72, 0, 127, i(gkcntrlsorig, 15), idebug)
	
	xout kctrls
endop

and this is all started in this instrument:

instr LaunchTestArray
	prints "LaunchTestArray\n"
	idebug = p4	
	ibasecc = p5

	gkcntrls launchcontrolarray ibasecc, idebug
endin
schedule("LaunchTestArray", 2, -1, 1, 57)

N.B
gkcntrls - is a global k-rate array that holds the current state of each launchcontrol_unit_cc
gkcntrlsorig - is an initial state to start things off (actually ready from a txt file in ftload that’s part of some automatic state saving stuff - not so relevant here)

Hope this all makes sense!

OK so here is the ‘automated’ version of launchcontrolarray UDO:

opcode launchcontrolarray, k[], ii
	ibase, idebug xin
	kcnt = 0
	kctrls[] init 16

	while kcnt < 8 do
		;printks "initial value from save state: %f\n", 0, gkcntrlsorig[kcnt]
		;printks "CC number: %d\n", 0.1, 57+kcnt

		kctrls[kcnt] = launchcontrol_unit_cc(1, ibase+kcnt, 0, 127, gkcntrlsorig[kcnt], idebug)
		kcnt += 1
	od

	xout kctrls
endop

The problem is that launchcontrol_unit_cc needs a reinit pass to get the proper values for kccn and kctrlinitstate, but everything goes into an endless loop, no doubt caused by the k-rate while loop. I imagine the while loop recreates the launchcontrol_unit_cc udo at each pass?

I have had this working if I hard code the values for kccn and kctrlinitstate, but that’s only going to work for one cc number. So the issue I can see is around the initialisation of the k-rate variables, and having to reinitialise them to get any value other than zero.

I’m not clear whether this can be achieved using a i-rate loop to setup the udos and have their values write to the kctrls array and so on? If that is done , then it think the launchcontrol_unit_cc won’t be running at k-rate and will effectively only output one value.

Is what I’m trying to achieve possible?

Sorry for the long post. I can upload a CSD file if that make things simpler. :slight_smile:

Cheers

p.s. it relates to this post Bitten by i - #3 by tjingboem

I’m pretty new to this UDO stuff so hopefully I didn’t mess this up too bad but it appears the problem might be that the
kcnt = 0
kctrls[] init 16
variables in the launchcontrolarray UDO may be getting constantly re-initialized & creating the endless loop.

If init as global var instead (the originals are commented out) the loop stops, albeit after 9 passes not 8, and the print values for iccn do increment, from 57 to 64. Whereas when I first tried this (kludged together) code it looped endlessly.

<CsoundSynthesizer>
<CsOptions>
-o dac
</CsOptions>
<CsInstruments>

sr = 48000
ksmps = 32
nchnls = 2
0dbfs  = 1

gkcntrls[]     init 16
gkcntrlsorig[] init 16
gkcnt          init 0


opcode launchcontrol_unit_cc, k, ikiiki
	ichn, kccn, imin, imax, kctrlinitstate, idebug xin
	print ichn, imin, imax, idebug

	ktakeover init 0	
	iccn = i(kccn)
	ictrlinitstate = i(kctrlinitstate)
	
	; have to run a reinit as the k-rate input variables kccn and kctrlinitstate
	; are 0 on initial pass and the ctrl7 opcode needs them converted to i-rate
	recalc:
		prints "recalc'd\n"
		iccn = i(kccn)
		ictrlinitstate = i(kctrlinitstate)
;		print iccn ,ictrlinitstate			
		rireturn
			
	reinit recalc
	
;	print iccn ,ictrlinitstate
	
	kctrl = ctrl7:k(ichn, iccn, imin, imax)
	
	; implement soft-takeover (is decent? not really...)
	if kctrl > (ictrlinitstate-2) && kctrl < (ictrlinitstate+2) then 
		ktakeover = 1
	endif
	
	if ktakeover == 1 then
;		kctrlout = port(kctrl, 0.002)
		kctrlout = kctrl
	else 
		kctrlout = ictrlinitstate
	endif
	
	if idebug == 1 && changed2(kctrlout) == 1 then
		println "Chan %d : CC%d : %f", ichn, kccn, kctrlout
	endif
	
	xout kctrlout
endop


opcode launchcontrolarray, k[], ii
	ibase, idebug xin
;    kcnt = 0
;    kctrls[] init 16
	while gkcnt < 8 do
		;printks "initial value from save state: %f\n", 0, gkcntrlsorig[kcnt]
		;printks "CC number: %d\n", 0.1, 57+kcnt

		gkcntrls[gkcnt] = launchcontrol_unit_cc(1, ibase+gkcnt, 0, 127, gkcntrlsorig[gkcnt], idebug)
		gkcnt += 1
	od

	xout gkcntrls
endop

instr LaunchTestArray
	prints "LaunchTestArray\n"
	idebug = p4	
	ibasecc = p5

	gkcntrls launchcontrolarray ibasecc, idebug
endin
schedule("LaunchTestArray", 2, -1, 1, 57)

</CsInstruments>
<CsScore>

</CsScore>
</CsoundSynthesizer>

Of course I can’t properly test it and I kinda just threw the pieces together so don’t know if this will work in the long run. Curious to find out though, this stuff is quite new to me but always a worthwhile challenge to learn from.

Best,
Scott

Thanks @ST_Music! I actually missed your reply, sorry, but thanks for it :slight_smile:

I think you were right that everyhting in the udo was gettting reinitialised. I came up with two working solutions in the end:

  1. The first moved away from primarily UDOs, and used a instr containing the launchcontrol_unit_cc udo that writes to the gctrls array, and a master LaunchTestArray instrument to spawn multiple instances of that, so:
ginumdials init 16

instr CC 
	prints "CC \n"
	iccn = p4
	ictrlinitstate = p5
	iid = p6
	idebug = p7
	print iccn, ictrlinitstate, iid, idebug
	gkcntrls[iid] = launchcontrol_unit_cc:k(1, iccn, 0, 127, ictrlinitstate, idebug) 
endin

instr LaunchTestArray
	prints "LaunchTestArray\n"
	idebug = p4	
	ibasecc = p5
	icnt = 0

	while icnt < ginumdials do
		schedule(nsched("CC", icnt), 0, -1, ibasecc+icnt, i(gkcntrlsorig, icnt), icnt, idebug)
		icnt += 1	
	od
endin
schedule("LaunchTestArray", 2, -1, 1, 57)

(nsched is a udo that creates fractional instrument numbers from a named instrument, so, 1.001, 1.002… etc, so we can have multiple instances running concurrently)

and the launchcontrol_unit_cc udo remains pretty much the same:

opcode launchcontrol_unit_cc, k, iiiiii
	ichn, iccn, imin, imax, ictrlinitstate, idebug xin

	ktakeover init 0
	kctrl = ctrl7:k(ichn, iccn, k(imin), k(imax))
	
	; implement soft-takeover (is decent? not really...)
	if kctrl > (ictrlinitstate-2) && kctrl < (ictrlinitstate+2) then 
		ktakeover = 1
	endif
	
	if ktakeover == 1 then
		if idebug == 1 then 
			kctrlout = kctrl		
		else
			kctrlout = port(kctrl, 0.02)
		endif
	else 
		kctrlout = ictrlinitstate
	endif
	
	if idebug == 1 && changed2(kctrlout) == 1 then
		println "Chan %d : CC%d : %f", ichn, iccn, kctrlout
	endif

	xout kctrlout
endop
  1. But as I’m a bit like a dog with a bone, I thought there still has to be a way of just using UDOs for this, and it occurred to be that I’ve use the recursive functionality of UDOs in the past, but mainly for audio stuff. The result is something that feels cleaner and clearer to read:

ginumdials init 16

instr LaunchTestArray
	prints "LaunchTestArray\n"
	ichn = p4
	iccnbase = p5
	imin = p6
	imax = p7
	idebug = p8
	inum = p9

	launchcontrol_unit_cc_recursive ichn, iccnbase, imin, imax, idebug, inum

        ; write values to global vars
	gkfreq = gkcntrls[0]
	gkfilter = gkcntrls[1]
	gkpw = gkcntrls[2]	
	gksampstart = gkcntrls[3]

endin
schedule("LaunchTestArray", 2, -1, 1, 57, 0, 127, 1, ginumdials)

and the tweaked recursive version of the launchcontrol_unit_cc_recursive udo:

opcode launchcontrol_unit_cc_recursive, 0, iiiiiio
	ichn, iccn, imin, imax, idebug, inum, icnt xin
	prints "iccn %f / icnt %d idebug %d\n" , iccn+icnt, icnt	,idebug
	if icnt < inum-1 then	
		launchcontrol_unit_cc_recursive ichn, iccn, imin, imax, idebug, inum, icnt+1
	endif

	ictrlinitstate = i(gkcntrlsorig, icnt)	
	ktakeover init 0
	kctrl = ctrl7:k(ichn, iccn+icnt, k(imin), k(imax))

	; implement soft-takeover (is decent? not really...)
	if kctrl > (ictrlinitstate-2) && kctrl < (ictrlinitstate+2) then 
		ktakeover = 1
	endif
	
	if ktakeover == 1 then
		if idebug == 1 then 
			kctrlout = kctrl		
		else
			kctrlout = port(kctrl, 0.02)
		endif
	else 
		kctrlout = ictrlinitstate
	endif
	
	if idebug == 1 && changed2(kctrlout) == 1 then
		println "Chan %d : CC%d : %f", ichn, iccn+icnt, kctrlout
	endif
	
	; write to the global array
	gkcntrls[icnt] = kctrlout
	
endop

Note the recursive udo no longer outputs a value to the parent but instead writes directly to its slot in the gkcntrls array. That made more sense to me as it would probably require some other way to handle all the cc value data handling, or something. Basically another layer of code.