annotate AccessibleSpectrumAnalyser.cpp @ 1:2ca5d7440b5c tip

added README
author Fiore Martin <f.martin@qmul.ac.uk>
date Fri, 26 Feb 2016 16:11:20 +0000
parents 3004dd663202
children
rev   line source
f@0 1 #include "AccessibleSpectrumAnalyser.h"
f@0 2 #include "IPlug_include_in_plug_src.h"
f@0 3 #include "IControl.h"
f@0 4 #include "resource.h"
f@0 5 #include "IBitmapMonoText.h"
f@0 6
f@0 7 #include <cmath>
f@0 8
f@0 9 #include "FreqView.h"
f@0 10 #include "Controls.h"
f@0 11
f@0 12 const int kNumPrograms = 1;
f@0 13
f@0 14 #define FFT_FORWARD false
f@0 15
f@0 16 enum EParams
f@0 17 {
f@0 18 kThreshold = 0,
f@0 19 kSelStart,
f@0 20 kSelSize,
f@0 21 kDry,
f@0 22 kWet,
f@0 23 kNumParams
f@0 24 };
f@0 25
f@0 26 enum ELayout
f@0 27 {
f@0 28 kWidth = GUI_WIDTH,
f@0 29 kHeight = GUI_HEIGHT,
f@0 30
f@0 31 lKnobSizeW = 52,
f@0 32 lKnobSizeH = 48 + 19 + 19, // 48 for image, 19 for text
f@0 33
f@0 34 lKnobsY = 315,
f@0 35
f@0 36 lThresX = 40,
f@0 37
f@0 38 lSelStartX = 250,
f@0 39 lSelSizeX = 345,
f@0 40
f@0 41 lDryX = 540,
f@0 42 lWetX = 610,
f@0 43
f@0 44 kKnobFrames = 60
f@0 45 };
f@0 46
f@0 47 AccessibleSpectrumAnalyser::AccessibleSpectrumAnalyser(IPlugInstanceInfo instanceInfo) :
f@0 48 IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo),
f@0 49 mDry(-6.0),
f@0 50 mWet(-6.0),
f@0 51 mFFTBufferIndx(0),
f@0 52 mNormFactor(0),
f@0 53 mBinSizeHz(44100/(2*kFFTbufSize))
f@0 54 {
f@0 55 TRACE;
f@0 56
f@0 57 /* init hanning window lookup table */
f@0 58
f@0 59 mHannTable.reserve(kFFTbufSize);
f@0 60
f@0 61 for (int i = 0; i < kFFTbufSize; i++){
f@0 62 mHannTable.push_back( 0.5 * (1. - std::cos(k2pi * i / (kFFTbufSize - 1) )) );
f@0 63 }
f@0 64
f@0 65 for (int i = 0; i < kFFTbufSize; i++) {
f@0 66 mNormFactor += 0.5 * (1. - std::cos(k2pi * i / (double)(kFFTbufSize - 1)));
f@0 67 }
f@0 68
f@0 69 /* init FFT engine */
f@0 70 WDL_fft_init();
f@0 71
f@0 72 /* init parameters */
f@0 73 //arguments are: name, defaultVal, minVal, maxVal, step, label, group, shape
f@0 74
f@0 75 GetParam(kThreshold)->InitDouble("Threshold", 0.0, -60.0, 6.2, 10, "dB");
f@0 76
f@0 77 GetParam(kSelStart)->InitDouble("Selection Start", 20, 20, 44100/2, 10, "Hz");
f@0 78
f@0 79 GetParam(kSelSize)->InitDouble("Selection Size", 50, kMinSelectionSize, 10000, 10, "Hz");
f@0 80
f@0 81 GetParam(kDry)->InitDouble("Dry", -6., -61.0, 0., 0.2, "dB");
f@0 82 GetParam(kDry)->SetDisplayText(-61.0, " -inf");
f@0 83
f@0 84 GetParam(kWet)->InitDouble("Wet", -6., -61.0, 0., 0.2, "dB");
f@0 85 GetParam(kWet)->SetDisplayText(-61.0, " -inf");
f@0 86
f@0 87
f@0 88 IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight);
f@0 89 pGraphics->AttachPanelBackground(&kBgColor);
f@0 90
f@0 91 mFreqView = new FreqView(this, IRECT(4, 40, GUI_WIDTH - 4, 300), kFFTbufSize/2);
f@0 92 pGraphics->AttachControl(mFreqView);
f@0 93
f@0 94 IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, kKnobFrames);
f@0 95 /* text has info about the font-size, font-type etc. */
f@0 96 IText text = IText(14);
f@0 97
f@0 98 /* attach dry and wet knobs to GUI */
f@0 99 pGraphics->AttachControl(new IKnobMultiControlText(this,
f@0 100 IRECT(lThresX, lKnobsY, lThresX + lKnobSizeW, lKnobsY + lKnobSizeH ),
f@0 101 kThreshold,
f@0 102 &knob,
f@0 103 &text,
f@0 104 27) );
f@0 105
f@0 106 pGraphics->AttachControl(new IKnobMultiControlText(this,
f@0 107 IRECT(lSelStartX, lKnobsY, lSelStartX + lKnobSizeW, lKnobsY + lKnobSizeH ),
f@0 108 kSelStart,
f@0 109 &knob,
f@0 110 &text,
f@0 111 27) );
f@0 112
f@0 113 pGraphics->AttachControl(new IKnobMultiControlText(this,
f@0 114 IRECT(lSelSizeX, lKnobsY, lSelSizeX + lKnobSizeW, lKnobsY + lKnobSizeH ),
f@0 115 kSelSize,
f@0 116 &knob,
f@0 117 &text,
f@0 118 27) );
f@0 119
f@0 120 pGraphics->AttachControl(new IKnobMultiControlText(this,
f@0 121 IRECT(lDryX, lKnobsY, lDryX + lKnobSizeW, lKnobsY + lKnobSizeH ),
f@0 122 kDry,
f@0 123 &knob,
f@0 124 &text,
f@0 125 27) );
f@0 126
f@0 127 pGraphics->AttachControl(new IKnobMultiControlText(this,
f@0 128 IRECT(lWetX, lKnobsY, lWetX + lKnobSizeW, lKnobsY + lKnobSizeH),
f@0 129 kWet,
f@0 130 &knob,
f@0 131 &text,
f@0 132 27) );
f@0 133
f@0 134
f@0 135 AttachGraphics(pGraphics);
f@0 136 //MakePreset("preset 1", ... );
f@0 137 MakeDefaultPreset((char *) "-", kNumPrograms);
f@0 138
f@0 139 }
f@0 140
f@0 141 AccessibleSpectrumAnalyser::~AccessibleSpectrumAnalyser() {}
f@0 142
f@0 143 /*
f@0 144 from Ask Justin Frankel:
f@0 145 first, call WDL_fft_init(). Then create a buffer of WDL_FFT_COMPLEX, populate its real components with some audio,
f@0 146 the imaginary with 0s, then optionally apply a windowing function, then call WDL_fft(),
f@0 147 then you can access the results using WDL_fft_permute() to get the correct indices.
f@0 148 */
f@0 149
f@0 150 void AccessibleSpectrumAnalyser::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
f@0 151 {
f@0 152 // Mutex is already locked for us.
f@0 153
f@0 154 double* in1 = inputs[0];
f@0 155 double* in2 = inputs[1];
f@0 156 double* out1 = outputs[0];
f@0 157 double* out2 = outputs[1];
f@0 158
f@0 159 for (int f = 0; f < nFrames; ++f, ++in1, ++in2, ++out1, ++out2){
f@0 160
f@0 161 mFFTbuffer[mFFTBufferIndx].re = (*in1 + *in2) / 2;
f@0 162 /* apply Hann window */
f@0 163 mFFTbuffer[mFFTBufferIndx].re *= mHannTable[mFFTBufferIndx];
f@0 164 mFFTbuffer[mFFTBufferIndx].im = 0;
f@0 165
f@0 166 /* when gathered enough samples perform FFT */
f@0 167 if (mFFTBufferIndx == kFFTbufSize - 1){
f@0 168
f@0 169 WDL_fft(mFFTbuffer, kFFTbufSize, FFT_FORWARD);
f@0 170
f@0 171 float bins[kFFTbufSize / 2];
f@0 172
f@0 173 bool thresholdPassed = false;
f@0 174 double maxClippingDiff = 0.0;
f@0 175 int maxClippingBinIndx = 0;
f@0 176
f@0 177 for (int i = 0; i < mFFTBufferIndx/2; i++){
f@0 178
f@0 179 int j = WDL_fft_permute(kFFTbufSize, i); // j is the index after permutation
f@0 180
f@0 181 /* fill the bins to plot with the magnitudes of the FFT*/
f@0 182 bins[i] = magnitude(mFFTbuffer[j]);
f@0 183
f@0 184 /* check bins within selection against threshold for sonification */
f@0 185 if( binWithinSelection(i) && bins[i] > mThreshold ){
f@0 186
f@0 187 thresholdPassed = true;
f@0 188
f@0 189 /* find the clipping amount in dB */
f@0 190 double clippingDiff = fabs(::AmpToDB(bins[i]) - ::AmpToDB(mThreshold));
f@0 191
f@0 192 /* clipDiff will be rounded to the floor later (with a cast to int),
f@0 193 but if it's very very close to the ceil, then let it be the ceil. */
f@0 194 const double ceilClippingDiff = ceil(clippingDiff);
f@0 195 if ( ceilClippingDiff - clippingDiff < kClippingCeilingSnap ){
f@0 196 clippingDiff = ceilClippingDiff;
f@0 197 }
f@0 198
f@0 199 /* bound the difference to 12 semitones to prevent the sonification from going too high */
f@0 200 clippingDiff = BOUNDED(clippingDiff, 0.0, 12.0);
f@0 201
f@0 202 if( clippingDiff > maxClippingDiff){
f@0 203 maxClippingDiff = clippingDiff;
f@0 204 maxClippingBinIndx = i;
f@0 205 }
f@0 206 }
f@0 207
f@0 208 }
f@0 209
f@0 210 if(thresholdPassed){
f@0 211 /* sonify the difference between the amplitude and threshold of highest bin
f@0 212 one db (rounded downward) is one tone, up to one octave (12 semitones) */
f@0 213 mSonification.ugen.setFrequency(midi2Freq(69 + int(maxClippingDiff)));
f@0 214
f@0 215 /* pan the sonification according to the position of the bin in the spectrum */
f@0 216 mSonification.panning.calculatePanningCoeff( double(maxClippingBinIndx)/(mFFTBufferIndx/2) );
f@0 217 mSonification.envelope.keyOn();
f@0 218 }
f@0 219
f@0 220 /* copy the bids in freqView for the graphics */
f@0 221 mFreqView->setBins(bins);
f@0 222 mFFTBufferIndx = 0;
f@0 223
f@0 224 }else{
f@0 225
f@0 226 /* increment index to gather more bins in the FFT buffer */
f@0 227 mFFTBufferIndx++;
f@0 228
f@0 229 }
f@0 230
f@0 231
f@0 232 /* now add the sonification to the audio */
f@0 233
f@0 234 /* when attack is done switch immediately to RELEASE (keyOff) *
f@0 235 * so it goes like: attack->release->silence */
f@0 236 if (mSonification.envelope.getState() == stk::ADSR::DECAY) {
f@0 237 mSonification.envelope.keyOff();
f@0 238 }
f@0 239
f@0 240 /* add the sonification to the mix */
f@0 241 if (mSonification.envelope.getState() == stk::ADSR::ATTACK ||
f@0 242 mSonification.envelope.getState() == stk::ADSR::RELEASE) {
f@0 243
f@0 244 const double env = mSonification.envelope.tick();
f@0 245 const double tick = mSonification.ugen.tick() * env;
f@0 246
f@0 247
f@0 248 *out1 = mix(*in1, tick) * mSonification.panning.left;
f@0 249 *out2 = mix(*in2, tick) * mSonification.panning.right;
f@0 250
f@0 251 } else { // no sonification
f@0 252
f@0 253 *out1 = mix(*in1, 0.0);
f@0 254 *out2 = mix(*in2, 0.0);
f@0 255
f@0 256 }
f@0 257 }
f@0 258 }
f@0 259
f@0 260 void AccessibleSpectrumAnalyser::Reset()
f@0 261 {
f@0 262 TRACE;
f@0 263 IMutexLock lock(this);
f@0 264
f@0 265 mSonification.reset(GetSampleRate());
f@0 266 mBinSizeHz = GetSampleRate() / kFFTbufSize;
f@0 267
f@0 268 mFreqView->setSampleRate(GetSampleRate());
f@0 269 mSelectionEnd = calculateSelectionEndFromView();
f@0 270 //double sr = GetSampleRate();
f@0 271 }
f@0 272
f@0 273 void AccessibleSpectrumAnalyser::OnParamChange(int paramIdx)
f@0 274 {
f@0 275 IMutexLock lock(this);
f@0 276
f@0 277 switch (paramIdx)
f@0 278 {
f@0 279
f@0 280 case kDry:
f@0 281 if (GetParam(kDry)->Value() < -60.5 ){
f@0 282 mDry = 0.0; /* if the level goes below 60.5 dB, just bring it to silence */
f@0 283 }
f@0 284 else {
f@0 285 mDry = ::DBToAmp(GetParam(kDry)->Value());
f@0 286 }
f@0 287 break;
f@0 288
f@0 289
f@0 290 case kWet:
f@0 291
f@0 292 if (GetParam(kWet)->Value() < -60.5 ){
f@0 293 mWet = 0.0;
f@0 294 }
f@0 295 else {
f@0 296 mWet = ::DBToAmp(GetParam(kWet)->Value());
f@0 297 }
f@0 298 break;
f@0 299
f@0 300
f@0 301 case kSelStart:
f@0 302 mFreqView->setSelectionStart(GetParam(kSelStart)->Value());
f@0 303 mSelectionEnd = calculateSelectionEndFromView();
f@0 304 break;
f@0 305
f@0 306 case kSelSize:
f@0 307 mFreqView->setSelectionSize(GetParam(kSelSize)->Value());
f@0 308 mSelectionEnd = calculateSelectionEndFromView();
f@0 309 break;
f@0 310
f@0 311 case kThreshold:
f@0 312 /* save threshold in amp for the sonification and in db for freq view */
f@0 313 mThreshold = GetParam(kThreshold)->DBToAmp();
f@0 314 mFreqView->setThreshold(GetParam(kThreshold)->Value());
f@0 315 break;
f@0 316
f@0 317 default:
f@0 318 break;
f@0 319 }
f@0 320 }
f@0 321
f@0 322
f@0 323
f@0 324 void AccessibleSpectrumAnalyser::Sonification::reset(double srate)
f@0 325 {
f@0 326 ugen.reset();
f@0 327
f@0 328 envelope.setValue(0.0);
f@0 329 envelope.setReleaseTime(0.2);
f@0 330 envelope.setAttackTime(0.01);
f@0 331 envelope.setSustainLevel(1);
f@0 332
f@0 333 if (srate > 0.0){
f@0 334 ugen.setSampleRate(srate);
f@0 335 envelope.setSampleRate(srate);
f@0 336 }
f@0 337 }
f@0 338
f@0 339
f@0 340 const double AccessibleSpectrumAnalyser::k2pi = 2 * PI;
f@0 341 const IColor AccessibleSpectrumAnalyser::kBgColor(255, 6, 120, 21);
f@0 342 const double AccessibleSpectrumAnalyser::kClippingCeilingSnap = 0.05;
f@0 343 const double AccessibleSpectrumAnalyser::kMinSelectionSize = 50;
f@0 344
f@0 345
f@0 346