aboutsummaryrefslogtreecommitdiff
path: root/site/udo/tabproc.udo
blob: 1fd4e49011aca223d23a290cd21750a8afedbe88 (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
#ifndef UDO_TABPROC
#define UDO_TABPROC ##
/*
	JIT style table processor
	
	This file is part of the SONICS UDO collection by Richard Knight 2022
		License: GPL-2.0-or-later
		http://1bpm.net
*/

#include "lagdetect.udo"

#define TP_DFLT_RATE #10#

gitabproc_instancetrack = 0  ; internal instance tracking for channel names



opcode tabproc_profiler, kkk, ij
	instrnum, istabletime xin
	istabletime = (istabletime == -1) ? 5 : istabletime

	ichannelbase = gitabproc_instancetrack
	gitabproc_instancetrack += 1
	
	kblocksize init 16384
	kprocrate init $TP_DFLT_RATE
	kdone init 0

	; actual processing at iprocrate
	if (kdone == 0) then
		klagging, ktimesincelag lagdetect
		if (klagging == 1) then
				; reduce block size first, then reduce processing rate
				if (kblocksize > 128) then
					kblocksize -= 128
				elseif (kprocrate > 1) then
					kprocrate -= 1
				endif
		endif

		if (ktimesincelag >= istabletime) then
			kdone = 1
			kgoto complete
		endif

		ktrig metro kprocrate
		if (ktrig == 1) then
			kcount = 0
			while (kcount < kblocksize) do
				atest oscil 1, 1000
					
				; set channels and call subinstrument for processing
				SchannelL = sprintf("tabproc%dL", ichannelbase)
				SchannelR = sprintf("tabproc%dR", ichannelbase)
				chnset atest, SchannelL
				chnset atest, SchannelR
				aL, aR subinstr instrnum, SchannelL, SchannelR			
				kcount += 1
			od
		endif

	endif
complete:
	xout kdone, kblocksize, kprocrate
endop


opcode tabproc_profiler, kkk, S
	Sinstrname xin
	kdone, kblocksize, kprocrate tabproc_profiler nstrnum(Sinstrname)
	xout kdone, kblocksize, kprocrate
endop



/*
	Process a table with a subinstrument at deferred k-rate.
	The table ifn is read and passed to the instrument Sinstrname/instrnum which should perform processing and use the outs() or out() opcode to return audio to be written to ifn.
	Sinstrument is passed the input audio on channels with the names passed as p4 (left) and p5 (right, if the sound is stereo). 
	Hence the instrument should implement something such as
		aL = chnget:a(strget(p4))
		aR = chnget:a(strget(p5))
	in order to deal with the incoming audio.

	The table can be read in reverse by specifying ireverseread=1. Similarly it can be written back to in reverse with ireversewrite=1.

	tabproc performs processing in chunks at a given rate in order to minimise CPU usage and allow other instruments to continue uninterrupted (without this, it 
	would consume all available CPU and block the performance until complete). The blocksize iblocksize is how many k-cycles are processed in each chunk, and iprocrate
	determines the frequency at which chunks are processed. Defaults are provided but these will be best altered depending on the load of Sinstrument and the
	available CPU.
	iautoadjust can be set to 1 to attempt to automatically reduce iblocksize and iprocrate if it is detected that the CPU cannot keep up. This is done by comparing
	the realtime clock against Csound's instrument time. If the difference is over iautotimethreshold, then a reduction of block size/frequency is made.


	kdone, kpercent tabproc ifn, instrnum [, ireverseread=0, ireversewrite=0, iautoadjust=0, iblocksize=2048, iprocrate=TP_DFLT_RATE, iautotimethreshold=TP_DFLT_TTHRESH]
	kdone, kpercent tabproc ifn, Sinstrname [, ireverseread=0, ireversewrite=0, iautoadjust=0, iblocksize=2048, iprocrate=TP_DFLT_RATE, iautotimethreshold=TP_DFLT_TTHRESH]


	kdone				output trigger when the processing has completed
	kpercent			percent of the table that has been processed
	
	ifn					table containing audio (typically GEN1 but could be any). Mono and stereo are supported
	instrnum			the instrument number to be invoked to process the table
	Sinstrname			the instrument name to be invoked to process the table
	ireverseread		set to 1 in order to read the table in reverse
	ireversewrite		set to 1 in order to write back to the table in reverse
	iautoadjust			set to 1 to enable automatic reduction of iblocksize and iprocrate if processing lag is detected
	iblocksize			number of k-cycles of audio to be processed in each run
	iprocrate			frequency at which to perform block processing
	iautotimethreshold	time lag at which to automatically adjust, if iautoadjust is specified
	
*/
opcode tabproc, kk, iiooojjj
	ifn, instrnum, ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold xin
	kblocksize init (iblocksize == -1) ? 2048 : iblocksize 
	kprocrate init ((iprocrate == -1) ? $TP_DFLT_RATE : iprocrate)
	iautotimethreshold = (iautotimethreshold == -1) ? $LAG_DFLT_TTHRESH : iautotimethreshold

	ichannelbase = gitabproc_instancetrack
	gitabproc_instancetrack += 1

	ichannels = ftchnls(ifn)
	isr = ftsr(ifn)
	ilenraw = ftlen(ifn)
	ilen = ilenraw / ichannels

	kpos init 0
	kpercent = 0
	kdone init 0

	
	if (kdone == 0) then
		; auto adjust block size to account when cpu can't keep up
		if (iautoadjust == 1 && lagdetect:k(iautotimethreshold) == 1) then
			if (kblocksize > 128) then
					kblocksize -= 128
				elseif (kprocrate > 1) then
					kprocrate -= 1
				endif
		endif
	

		; each block processing at iprocrate
		ktrig metro kprocrate
		if (ktrig == 1) then
			kcount = 0
			while (kcount < kblocksize) do
				apos = round:a(lphasor(isr / sr)) ; rounded to deal with stereo offset if required

				; if complete, set kdone
				if (downsamp(apos) >= ilen) then ;?  >= ilenraw) then
					kpercent = 100
					kdone = 1
					kgoto complete
				endif

				; read and write position calcs for stereo / reverse
				aposbase = apos * ichannels
				if (ireverseread == 1) then
					aposrR = ilen - aposbase
					aposrL = (ichannels == 2) ? aposrR + 1 : aposrR
				else
					aposrL = aposbase
					aposrR = aposbase + 1
				endif

				if (ireversewrite == 1) then
					aposwR = ilen - aposbase
					aposwL = (ichannels == 2) ? aposwR + 1 : aposwR
				else
					aposwL = aposbase
					aposwR = aposbase + 1
				endif

				; read source table
				aL table aposrL, ifn
				if (ichannels == 2) then
					aR table aposrR, ifn
				else
					aR = aL
				endif
					
				; set channels and call subinstrument for processing
				SchannelL = sprintf("tabproc%dL", ichannelbase)
				SchannelR = sprintf("tabproc%dR", ichannelbase)
				chnset aL, SchannelL
				chnset aR, SchannelR
				aL, aR subinstr instrnum, SchannelL, SchannelR

				; write back to table
				tablew aL, aposwL, ifn
				if (ichannels == 2) then
					tablew aR, aposwR, ifn
				endif
			
				kcount += 1
			od
		endif

		; calculate percent complete
		kpercent = (100 / ilenraw) * downsamp(apos)
	endif
	
complete:
	xout kdone, kpercent
endop


; derived opcode for named instruments
opcode tabproc, kk, iSooojjj
	ifn, Sinstrname, ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold xin
	kdone, kpercent tabproc ifn, nstrnum(Sinstrname), ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold
	xout kdone, kpercent
endop

#end