aboutsummaryrefslogtreecommitdiff
path: root/site/udo/chord_detect.udo
blob: da7202703059d3a66df047e6d3cec8f40d231ba4 (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
#ifndef UDO_CHORDDETECT
#define UDO_CHORDDETECT ##
/*
	Polyphonic note tracking

	This file is part of the SONICS UDO collection by Richard Knight 2023
		License: GPL-2.0-or-later
		http://1bpm.net
*/



gichorddetect_ampthresh = 0.01

/*
	Internal opcode
	Set up pvsbin recursively for each bin up to imaxbin, and add a confirmation to inotefn for the nearest MIDI note detected

	_polydetect_bin fsig, imaxbin, inotefn [, ibin=1]

	fsig		the signal from pvsanal to perform pitch detection on
	imaxbin		maximum number of bins in the fsig which denotes the level of recursion required
	inotefn		ftable to store note confirmations in
	ibin		current bin for pvsbin to examine
*/
opcode _polydetect_bin, 0, fiip
	fsig, imaxbin, inotefn, ibin xin
	kamp, kfreq pvsbin fsig, ibin
	
	if (kamp > gichorddetect_ampthresh) then
		knote = ftom(kfreq, 1)
		tablew table:k(knote, inotefn) + 1, knote, inotefn
	endif

	if (ibin + 1 < imaxbin) then
		_polydetect_bin fsig, imaxbin, inotefn, ibin + 1
	endif
endop


/*
	Internal opcode
	Rank the notes in inotefn and output the detected MIDI notes along with a certainty ratio

	kout[], kcertainty[] _polydetect_ranknotes inotefn, imaxnotes

	kout[]			array of detected MIDI note numbers, of size imaxnotes
	kcertainty[]	array of detected note certainty ratios, of size imaxnotes
	inotefn			ftable with note confirmations
	imaxnotes		maximum number of notes to look for, ie polyphony level
*/
opcode _polydetect_ranknotes, k[]k[], ii
	inotefn, imaxnotes xin
	ilen = ftlen(inotefn)
	kout[] init imaxnotes
	
	kcertainty[] init imaxnotes
	ktotal = 0
	kindex = 0
	kmax = 0
	knonzeronum = 0
	while (kindex < ilen) do
		kval = tab:k(kindex, inotefn)
		if (kval > 0) then
			ktotal += kval
			knonzeronum += 1
		endif
		kindex += 1
	od

	if (knonzeronum != 0) then
		kavg = ktotal / knonzeronum
		knum = 0
		kindex = 0
		while (kindex < ilen) do
			kval = tab:k(kindex, inotefn)
			kmax = max:k(kmax, kval)
			if (kval > kavg) then
				knum += 1
				if (knum >= imaxnotes) then
					goto writeout
				endif
			endif
			kindex += 1
		od
	else
		goto complete
	endif

writeout:
	kindex = 0
	kwriteindex = 0
	while (kindex < ilen) do
		kval = tab:k(kindex, inotefn)
		if (kval > kmax/2) then ;kavg) then
			kout[kwriteindex] = kindex
			kcertainty[kwriteindex] = kval / kmax
			kwriteindex += 1
			if (kwriteindex >= imaxnotes) then
				goto complete
			endif
		endif
		kindex += 1
	od
complete:
	xout kout, kcertainty
endop


/*
	Detect the nearest MIDI notes in an audio signal to an arbitrary level of polyphony

	kchanged, kout[], kcertainty[] polydetect ain, imaxnotes, iupdateksmps
	
	kchanged		trigger output as 1 if the detected notes has changed from the last output
	kout[]			array of detected MIDI note numbers, of size imaxnotes
	kcertainty[]	array of detected note certainty ratios, of size imaxnotes
	ain				input audio to examine
	imaxnotes		maximum number of notes to look for, ie polyphony level
	iupdateksmps	analysis window size in ksmps
*/
opcode polydetect, kk[]k[], aii
	ain, imaxnotes, iupdateksmps xin
	ir = 1024
	inotefn ftgentmp 0, 0, 128, 2, 0
	ktimek timeinstk

	fsig pvsanal ain, ir, ir/4, ir, 1
	_polydetect_bin fsig, ir/8, inotefn ; quarter of spectrum

	klast[] init imaxnotes
	kchanged = 0

	if (ktimek % iupdateksmps == 0) then
		kout[], kcertainty[] _polydetect_ranknotes inotefn, imaxnotes
		
		kcompare[] cmp klast, "==", kout

		if (sumarray(kcompare) == lenarray(kcompare)) then
			kchanged = 0
		else
			kchanged = 1
			klast = kout
		endif

		ftset inotefn, k(0)
	endif
	xout kchanged, kout, kcertainty
endop




#end