Mercurial > hg > asa
view 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 |
line wrap: on
line source
#include "AccessibleSpectrumAnalyser.h" #include "IPlug_include_in_plug_src.h" #include "IControl.h" #include "resource.h" #include "IBitmapMonoText.h" #include <cmath> #include "FreqView.h" #include "Controls.h" const int kNumPrograms = 1; #define FFT_FORWARD false enum EParams { kThreshold = 0, kSelStart, kSelSize, kDry, kWet, kNumParams }; enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT, lKnobSizeW = 52, lKnobSizeH = 48 + 19 + 19, // 48 for image, 19 for text lKnobsY = 315, lThresX = 40, lSelStartX = 250, lSelSizeX = 345, lDryX = 540, lWetX = 610, kKnobFrames = 60 }; AccessibleSpectrumAnalyser::AccessibleSpectrumAnalyser(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), mDry(-6.0), mWet(-6.0), mFFTBufferIndx(0), mNormFactor(0), mBinSizeHz(44100/(2*kFFTbufSize)) { TRACE; /* init hanning window lookup table */ mHannTable.reserve(kFFTbufSize); for (int i = 0; i < kFFTbufSize; i++){ mHannTable.push_back( 0.5 * (1. - std::cos(k2pi * i / (kFFTbufSize - 1) )) ); } for (int i = 0; i < kFFTbufSize; i++) { mNormFactor += 0.5 * (1. - std::cos(k2pi * i / (double)(kFFTbufSize - 1))); } /* init FFT engine */ WDL_fft_init(); /* init parameters */ //arguments are: name, defaultVal, minVal, maxVal, step, label, group, shape GetParam(kThreshold)->InitDouble("Threshold", 0.0, -60.0, 6.2, 10, "dB"); GetParam(kSelStart)->InitDouble("Selection Start", 20, 20, 44100/2, 10, "Hz"); GetParam(kSelSize)->InitDouble("Selection Size", 50, kMinSelectionSize, 10000, 10, "Hz"); GetParam(kDry)->InitDouble("Dry", -6., -61.0, 0., 0.2, "dB"); GetParam(kDry)->SetDisplayText(-61.0, " -inf"); GetParam(kWet)->InitDouble("Wet", -6., -61.0, 0., 0.2, "dB"); GetParam(kWet)->SetDisplayText(-61.0, " -inf"); IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachPanelBackground(&kBgColor); mFreqView = new FreqView(this, IRECT(4, 40, GUI_WIDTH - 4, 300), kFFTbufSize/2); pGraphics->AttachControl(mFreqView); IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, kKnobFrames); /* text has info about the font-size, font-type etc. */ IText text = IText(14); /* attach dry and wet knobs to GUI */ pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lThresX, lKnobsY, lThresX + lKnobSizeW, lKnobsY + lKnobSizeH ), kThreshold, &knob, &text, 27) ); pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lSelStartX, lKnobsY, lSelStartX + lKnobSizeW, lKnobsY + lKnobSizeH ), kSelStart, &knob, &text, 27) ); pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lSelSizeX, lKnobsY, lSelSizeX + lKnobSizeW, lKnobsY + lKnobSizeH ), kSelSize, &knob, &text, 27) ); pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lDryX, lKnobsY, lDryX + lKnobSizeW, lKnobsY + lKnobSizeH ), kDry, &knob, &text, 27) ); pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lWetX, lKnobsY, lWetX + lKnobSizeW, lKnobsY + lKnobSizeH), kWet, &knob, &text, 27) ); AttachGraphics(pGraphics); //MakePreset("preset 1", ... ); MakeDefaultPreset((char *) "-", kNumPrograms); } AccessibleSpectrumAnalyser::~AccessibleSpectrumAnalyser() {} /* from Ask Justin Frankel: first, call WDL_fft_init(). Then create a buffer of WDL_FFT_COMPLEX, populate its real components with some audio, the imaginary with 0s, then optionally apply a windowing function, then call WDL_fft(), then you can access the results using WDL_fft_permute() to get the correct indices. */ void AccessibleSpectrumAnalyser::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames) { // Mutex is already locked for us. double* in1 = inputs[0]; double* in2 = inputs[1]; double* out1 = outputs[0]; double* out2 = outputs[1]; for (int f = 0; f < nFrames; ++f, ++in1, ++in2, ++out1, ++out2){ mFFTbuffer[mFFTBufferIndx].re = (*in1 + *in2) / 2; /* apply Hann window */ mFFTbuffer[mFFTBufferIndx].re *= mHannTable[mFFTBufferIndx]; mFFTbuffer[mFFTBufferIndx].im = 0; /* when gathered enough samples perform FFT */ if (mFFTBufferIndx == kFFTbufSize - 1){ WDL_fft(mFFTbuffer, kFFTbufSize, FFT_FORWARD); float bins[kFFTbufSize / 2]; bool thresholdPassed = false; double maxClippingDiff = 0.0; int maxClippingBinIndx = 0; for (int i = 0; i < mFFTBufferIndx/2; i++){ int j = WDL_fft_permute(kFFTbufSize, i); // j is the index after permutation /* fill the bins to plot with the magnitudes of the FFT*/ bins[i] = magnitude(mFFTbuffer[j]); /* check bins within selection against threshold for sonification */ if( binWithinSelection(i) && bins[i] > mThreshold ){ thresholdPassed = true; /* find the clipping amount in dB */ double clippingDiff = fabs(::AmpToDB(bins[i]) - ::AmpToDB(mThreshold)); /* clipDiff will be rounded to the floor later (with a cast to int), but if it's very very close to the ceil, then let it be the ceil. */ const double ceilClippingDiff = ceil(clippingDiff); if ( ceilClippingDiff - clippingDiff < kClippingCeilingSnap ){ clippingDiff = ceilClippingDiff; } /* bound the difference to 12 semitones to prevent the sonification from going too high */ clippingDiff = BOUNDED(clippingDiff, 0.0, 12.0); if( clippingDiff > maxClippingDiff){ maxClippingDiff = clippingDiff; maxClippingBinIndx = i; } } } if(thresholdPassed){ /* sonify the difference between the amplitude and threshold of highest bin one db (rounded downward) is one tone, up to one octave (12 semitones) */ mSonification.ugen.setFrequency(midi2Freq(69 + int(maxClippingDiff))); /* pan the sonification according to the position of the bin in the spectrum */ mSonification.panning.calculatePanningCoeff( double(maxClippingBinIndx)/(mFFTBufferIndx/2) ); mSonification.envelope.keyOn(); } /* copy the bids in freqView for the graphics */ mFreqView->setBins(bins); mFFTBufferIndx = 0; }else{ /* increment index to gather more bins in the FFT buffer */ mFFTBufferIndx++; } /* now add the sonification to the audio */ /* when attack is done switch immediately to RELEASE (keyOff) * * so it goes like: attack->release->silence */ if (mSonification.envelope.getState() == stk::ADSR::DECAY) { mSonification.envelope.keyOff(); } /* add the sonification to the mix */ if (mSonification.envelope.getState() == stk::ADSR::ATTACK || mSonification.envelope.getState() == stk::ADSR::RELEASE) { const double env = mSonification.envelope.tick(); const double tick = mSonification.ugen.tick() * env; *out1 = mix(*in1, tick) * mSonification.panning.left; *out2 = mix(*in2, tick) * mSonification.panning.right; } else { // no sonification *out1 = mix(*in1, 0.0); *out2 = mix(*in2, 0.0); } } } void AccessibleSpectrumAnalyser::Reset() { TRACE; IMutexLock lock(this); mSonification.reset(GetSampleRate()); mBinSizeHz = GetSampleRate() / kFFTbufSize; mFreqView->setSampleRate(GetSampleRate()); mSelectionEnd = calculateSelectionEndFromView(); //double sr = GetSampleRate(); } void AccessibleSpectrumAnalyser::OnParamChange(int paramIdx) { IMutexLock lock(this); switch (paramIdx) { case kDry: if (GetParam(kDry)->Value() < -60.5 ){ mDry = 0.0; /* if the level goes below 60.5 dB, just bring it to silence */ } else { mDry = ::DBToAmp(GetParam(kDry)->Value()); } break; case kWet: if (GetParam(kWet)->Value() < -60.5 ){ mWet = 0.0; } else { mWet = ::DBToAmp(GetParam(kWet)->Value()); } break; case kSelStart: mFreqView->setSelectionStart(GetParam(kSelStart)->Value()); mSelectionEnd = calculateSelectionEndFromView(); break; case kSelSize: mFreqView->setSelectionSize(GetParam(kSelSize)->Value()); mSelectionEnd = calculateSelectionEndFromView(); break; case kThreshold: /* save threshold in amp for the sonification and in db for freq view */ mThreshold = GetParam(kThreshold)->DBToAmp(); mFreqView->setThreshold(GetParam(kThreshold)->Value()); break; default: break; } } void AccessibleSpectrumAnalyser::Sonification::reset(double srate) { ugen.reset(); envelope.setValue(0.0); envelope.setReleaseTime(0.2); envelope.setAttackTime(0.01); envelope.setSustainLevel(1); if (srate > 0.0){ ugen.setSampleRate(srate); envelope.setSampleRate(srate); } } const double AccessibleSpectrumAnalyser::k2pi = 2 * PI; const IColor AccessibleSpectrumAnalyser::kBgColor(255, 6, 120, 21); const double AccessibleSpectrumAnalyser::kClippingCeilingSnap = 0.05; const double AccessibleSpectrumAnalyser::kMinSelectionSize = 50;