Chris@0
|
1
|
Chris@0
|
2 #include "Azi.h"
|
Chris@0
|
3
|
Chris@1
|
4 #include <cmath>
|
Chris@3
|
5 #include <iostream>
|
Chris@7
|
6 #include <complex>
|
Chris@24
|
7 #include <algorithm>
|
Chris@25
|
8 #include <climits>
|
Chris@1
|
9
|
Chris@1
|
10 using std::vector;
|
Chris@7
|
11 using std::complex;
|
Chris@3
|
12 using std::cerr;
|
Chris@3
|
13 using std::endl;
|
Chris@0
|
14
|
Chris@0
|
15 Azi::Azi(float inputSampleRate) :
|
Chris@1
|
16 Plugin(inputSampleRate),
|
Chris@14
|
17 m_width(128)
|
Chris@0
|
18 {
|
Chris@0
|
19 }
|
Chris@0
|
20
|
Chris@0
|
21 Azi::~Azi()
|
Chris@0
|
22 {
|
Chris@0
|
23 }
|
Chris@0
|
24
|
Chris@0
|
25 string
|
Chris@0
|
26 Azi::getIdentifier() const
|
Chris@0
|
27 {
|
Chris@0
|
28 return "azi";
|
Chris@0
|
29 }
|
Chris@0
|
30
|
Chris@0
|
31 string
|
Chris@0
|
32 Azi::getName() const
|
Chris@0
|
33 {
|
Chris@0
|
34 return "Stereo Plan";
|
Chris@0
|
35 }
|
Chris@0
|
36
|
Chris@0
|
37 string
|
Chris@0
|
38 Azi::getDescription() const
|
Chris@0
|
39 {
|
Chris@29
|
40 return "Return a stereo plan decomposition of the audio. The returned feature grid covers the stereo plan from left-channel-only (first bin) to right-channel-only (last bin), with each value indicating what proportion of signal energy is found at that point on the plan at that moment. The input should consist of two channels containing left and right channel signals.";
|
Chris@0
|
41 }
|
Chris@0
|
42
|
Chris@0
|
43 string
|
Chris@0
|
44 Azi::getMaker() const
|
Chris@0
|
45 {
|
Chris@26
|
46 return "Chris Cannam";
|
Chris@0
|
47 }
|
Chris@0
|
48
|
Chris@0
|
49 int
|
Chris@0
|
50 Azi::getPluginVersion() const
|
Chris@0
|
51 {
|
Chris@0
|
52 // Increment this each time you release a version that behaves
|
Chris@0
|
53 // differently from the previous one
|
Chris@0
|
54 return 1;
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 string
|
Chris@0
|
58 Azi::getCopyright() const
|
Chris@0
|
59 {
|
Chris@29
|
60 return "Freely redistributable (BSD license)";
|
Chris@0
|
61 }
|
Chris@0
|
62
|
Chris@0
|
63 Azi::InputDomain
|
Chris@0
|
64 Azi::getInputDomain() const
|
Chris@0
|
65 {
|
Chris@10
|
66 return FrequencyDomain;
|
Chris@0
|
67 }
|
Chris@0
|
68
|
Chris@0
|
69 size_t
|
Chris@0
|
70 Azi::getPreferredBlockSize() const
|
Chris@0
|
71 {
|
Chris@12
|
72 return 8192;
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 size_t
|
Chris@0
|
76 Azi::getPreferredStepSize() const
|
Chris@0
|
77 {
|
Chris@12
|
78 return 256;
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 size_t
|
Chris@0
|
82 Azi::getMinChannelCount() const
|
Chris@0
|
83 {
|
Chris@25
|
84 return 1; // pointless, but supported
|
Chris@0
|
85 }
|
Chris@0
|
86
|
Chris@0
|
87 size_t
|
Chris@0
|
88 Azi::getMaxChannelCount() const
|
Chris@0
|
89 {
|
Chris@1
|
90 return 2;
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 Azi::ParameterList
|
Chris@0
|
94 Azi::getParameterDescriptors() const
|
Chris@0
|
95 {
|
Chris@0
|
96 ParameterList list;
|
Chris@0
|
97
|
Chris@0
|
98 // If the plugin has no adjustable parameters, return an empty
|
Chris@0
|
99 // list here (and there's no need to provide implementations of
|
Chris@0
|
100 // getParameter and setParameter in that case either).
|
Chris@0
|
101
|
Chris@0
|
102 // Note that it is your responsibility to make sure the parameters
|
Chris@0
|
103 // start off having their default values (e.g. in the constructor
|
Chris@0
|
104 // above). The host needs to know the default value so it can do
|
Chris@0
|
105 // things like provide a "reset to default" function, but it will
|
Chris@0
|
106 // not explicitly set your parameters to their defaults for you if
|
Chris@0
|
107 // they have not changed in the mean time.
|
Chris@0
|
108
|
Chris@0
|
109 return list;
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 float
|
Chris@25
|
113 Azi::getParameter(string) const
|
Chris@0
|
114 {
|
Chris@0
|
115 return 0;
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 void
|
Chris@25
|
119 Azi::setParameter(string, float)
|
Chris@0
|
120 {
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 Azi::ProgramList
|
Chris@0
|
124 Azi::getPrograms() const
|
Chris@0
|
125 {
|
Chris@0
|
126 ProgramList list;
|
Chris@0
|
127
|
Chris@0
|
128 // If you have no programs, return an empty list (or simply don't
|
Chris@0
|
129 // implement this function or getCurrentProgram/selectProgram)
|
Chris@0
|
130
|
Chris@0
|
131 return list;
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 string
|
Chris@0
|
135 Azi::getCurrentProgram() const
|
Chris@0
|
136 {
|
Chris@0
|
137 return ""; // no programs
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 void
|
Chris@25
|
141 Azi::selectProgram(string)
|
Chris@0
|
142 {
|
Chris@0
|
143 }
|
Chris@0
|
144
|
Chris@0
|
145 Azi::OutputList
|
Chris@0
|
146 Azi::getOutputDescriptors() const
|
Chris@0
|
147 {
|
Chris@0
|
148 OutputList list;
|
Chris@0
|
149
|
Chris@0
|
150 // See OutputDescriptor documentation for the possibilities here.
|
Chris@0
|
151 // Every plugin must have at least one output.
|
Chris@0
|
152
|
Chris@0
|
153 OutputDescriptor d;
|
Chris@1
|
154 d.identifier = "plan";
|
Chris@1
|
155 d.name = "Plan";
|
Chris@0
|
156 d.description = "";
|
Chris@0
|
157 d.unit = "";
|
Chris@0
|
158 d.hasFixedBinCount = true;
|
Chris@16
|
159 d.binCount = m_width * 2 + 3; // include a 1-bin "margin" at top and bottom
|
Chris@0
|
160 d.hasKnownExtents = false;
|
Chris@0
|
161 d.isQuantized = false;
|
Chris@0
|
162 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
Chris@0
|
163 d.hasDuration = false;
|
Chris@27
|
164
|
Chris@27
|
165 char buf[100];
|
Chris@28
|
166 for (int i = 0; i < int(d.binCount); ++i) {
|
Chris@27
|
167 if (i == 0) {
|
Chris@27
|
168 d.binNames.push_back("Left");
|
Chris@28
|
169 } else if (i + 1 == int(d.binCount)) {
|
Chris@27
|
170 d.binNames.push_back("Right");
|
Chris@27
|
171 } else if (i == m_width + 1) {
|
Chris@27
|
172 d.binNames.push_back("Centre");
|
Chris@27
|
173 } else {
|
Chris@27
|
174 int p = int(round(double(i - m_width - 1) /
|
Chris@27
|
175 double(m_width) * 100.0));
|
Chris@27
|
176 if (p > 0) {
|
Chris@27
|
177 sprintf(buf, "R %03d", p);
|
Chris@27
|
178 } else {
|
Chris@27
|
179 sprintf(buf, "L %03d", -p);
|
Chris@27
|
180 }
|
Chris@27
|
181 d.binNames.push_back(buf);
|
Chris@27
|
182 }
|
Chris@27
|
183 }
|
Chris@27
|
184
|
Chris@0
|
185 list.push_back(d);
|
Chris@0
|
186
|
Chris@0
|
187 return list;
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 bool
|
Chris@25
|
191 Azi::initialise(size_t channels, size_t, size_t blockSize)
|
Chris@0
|
192 {
|
Chris@0
|
193 if (channels < getMinChannelCount() ||
|
Chris@0
|
194 channels > getMaxChannelCount()) return false;
|
Chris@0
|
195
|
Chris@25
|
196 if (blockSize > INT_MAX) return false;
|
Chris@25
|
197
|
Chris@25
|
198 m_channels = int(channels);
|
Chris@25
|
199 m_blockSize = int(blockSize);
|
Chris@0
|
200
|
Chris@0
|
201 return true;
|
Chris@0
|
202 }
|
Chris@0
|
203
|
Chris@0
|
204 void
|
Chris@0
|
205 Azi::reset()
|
Chris@0
|
206 {
|
Chris@0
|
207 // Clear buffers, reset stored values, etc
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@1
|
210 float
|
Chris@3
|
211 Azi::rms(const vector<float> &buffer)
|
Chris@1
|
212 {
|
Chris@1
|
213 float sum = 0;
|
Chris@3
|
214 for (int i = 0; i < int(buffer.size()); ++i) {
|
Chris@1
|
215 sum += buffer[i] * buffer[i];
|
Chris@1
|
216 }
|
Chris@25
|
217 return sqrtf(sum / float(buffer.size()));
|
Chris@1
|
218 }
|
Chris@1
|
219
|
Chris@0
|
220 Azi::FeatureSet
|
Chris@25
|
221 Azi::process(const float *const *inputBuffers, Vamp::RealTime)
|
Chris@0
|
222 {
|
Chris@8
|
223 vector<float> left, right;
|
Chris@13
|
224
|
Chris@13
|
225 int n = int(m_blockSize/2 + 1);
|
Chris@13
|
226
|
Chris@14
|
227 vector<float> plan(m_width * 2 + 3, 0.f);
|
Chris@14
|
228
|
Chris@14
|
229 const float *inleft = inputBuffers[0];
|
Chris@25
|
230 const float *inright = (m_channels == 2 ? inputBuffers[1] : inleft);
|
Chris@13
|
231
|
Chris@13
|
232 for (int i = 0; i < n; ++i) {
|
Chris@13
|
233
|
Chris@13
|
234 int ri = i*2, ii = i*2 + 1;
|
Chris@13
|
235
|
Chris@14
|
236 float lmag = sqrtf(inleft[ri] * inleft[ri] + inleft[ii] * inleft[ii]);
|
Chris@14
|
237 float rmag = sqrtf(inright[ri] * inright[ri] + inright[ii] * inright[ii]);
|
Chris@13
|
238
|
Chris@13
|
239 // lmag = 0.0, rmag = 1.0 -> min cancelled is at +1.0
|
Chris@13
|
240 // lmag = 1.0, rmag = 0.0 -> at -1.0 [val at 0.0 = 1.0]
|
Chris@13
|
241 // lmag = 0.5, rmag = 0.2 -> at -0.6 [val at 0.0 = 0.3]
|
Chris@13
|
242 // lmag = 0.5, rmag = 1.0 -> at +0.5 [val at 0.0 = 0.5]
|
Chris@13
|
243 // lmag = 1.0, rmag = 1.0 -> at +0.0
|
Chris@13
|
244
|
Chris@13
|
245 // if lmag == rmag -> 0.0
|
Chris@13
|
246 // else if lmag > rmag -> negative
|
Chris@13
|
247 // else -> positive
|
Chris@13
|
248
|
Chris@13
|
249 // val at 0.0 = larger - smaller
|
Chris@13
|
250 // mag of null = 1.0 - (smaller / larger)
|
Chris@13
|
251
|
Chris@13
|
252 float larger = std::max(lmag, rmag);
|
Chris@13
|
253 float smaller = std::min(lmag, rmag);
|
Chris@13
|
254
|
Chris@14
|
255 float pan = 0.0;
|
Chris@13
|
256
|
Chris@13
|
257 if (larger > smaller) {
|
Chris@25
|
258 float abspan = 1.f - (smaller / larger);
|
Chris@14
|
259 if (lmag > rmag) pan = -abspan;
|
Chris@14
|
260 else pan = abspan;
|
Chris@13
|
261 }
|
Chris@13
|
262
|
Chris@14
|
263 float leftGain = 1.f, rightGain = 1.f;
|
Chris@14
|
264 if (pan > 0.f) leftGain *= 1.f - pan;
|
Chris@14
|
265 if (pan < 0.f) rightGain *= pan + 1.f;
|
Chris@13
|
266
|
Chris@25
|
267 float wid = float(m_width);
|
Chris@25
|
268 float pos = -pan * wid + wid;
|
Chris@14
|
269 float mag = leftGain * lmag + rightGain * rmag;
|
Chris@13
|
270
|
Chris@14
|
271 float ipos = floorf(pos);
|
Chris@14
|
272
|
Chris@14
|
273 plan[int(ipos) + 1] += mag * (1.f - (pos - ipos));
|
Chris@14
|
274 plan[int(ipos) + 2] += mag * (pos - ipos);
|
Chris@3
|
275 }
|
Chris@1
|
276
|
Chris@2
|
277 FeatureSet fs;
|
Chris@2
|
278 Feature f;
|
Chris@7
|
279 f.values = plan;
|
Chris@1
|
280
|
Chris@1
|
281 fs[0].push_back(f);
|
Chris@1
|
282
|
Chris@1
|
283 return fs;
|
Chris@0
|
284 }
|
Chris@0
|
285
|
Chris@0
|
286 Azi::FeatureSet
|
Chris@0
|
287 Azi::getRemainingFeatures()
|
Chris@0
|
288 {
|
Chris@0
|
289 return FeatureSet();
|
Chris@0
|
290 }
|
Chris@0
|
291
|