f@0
|
1 //
|
f@0
|
2 // AccessiblePeakMeter.cpp
|
f@0
|
3 //
|
f@0
|
4 // Author: Fiore Martin
|
f@0
|
5 // Started from IPlugMultiTargets example in WDL-OL, by Oli Larkin - https://github.com/olilarkin/wdl-ol
|
f@0
|
6 //
|
f@0
|
7 // Licensed under the Cockos WDL License, see README.txt
|
f@0
|
8 //
|
f@0
|
9
|
f@0
|
10 #include "AccessiblePeakMeter.h"
|
f@0
|
11 #include "IPlug_include_in_plug_src.h"
|
f@0
|
12 #include "resource.h"
|
f@0
|
13
|
f@0
|
14
|
f@0
|
15 #include "IControl.h"
|
f@0
|
16 #include "IBitmapMonoText.h"
|
f@0
|
17 #include "AccessiblePeakMeter_controls.h"
|
f@0
|
18
|
f@0
|
19 #include <memory>
|
f@0
|
20
|
f@0
|
21 inline double midi2Freq(int note) {
|
f@0
|
22 return 440. * pow(2., (note - 69.) / 12.);
|
f@0
|
23 }
|
f@0
|
24
|
f@0
|
25 double toDBMeter(double val, double range)
|
f@0
|
26 {
|
f@0
|
27 double db;
|
f@0
|
28 if (val > 0)
|
f@0
|
29 db = ::AmpToDB(val);
|
f@0
|
30 else
|
f@0
|
31 db = -999;
|
f@0
|
32 return BOUNDED((db + 60) / range,0,1);
|
f@0
|
33 }
|
f@0
|
34
|
f@0
|
35 /* reference points for controls layout, by changing these numbers only
|
f@0
|
36 the widgets can be moved around and all the other bits (top/left/right
|
f@0
|
37 borders, labels etc.) will follow. X and Y refer to the top-left coord */
|
f@0
|
38 enum ELayout {
|
f@0
|
39 lDryX = 20,
|
f@0
|
40 lDryY = 10,
|
f@0
|
41 lWetX = 85,
|
f@0
|
42 lWetY = 10,
|
f@0
|
43
|
f@0
|
44 lFaderLen = 190,
|
f@0
|
45 lPeakMeterX = 180,
|
f@0
|
46 lPeaklMeterY = 30,
|
f@0
|
47
|
f@0
|
48 lSonifTypeX = 20,
|
f@0
|
49 lSonifTypeY = 200,
|
f@0
|
50
|
f@0
|
51 lDecayRateX = 20,
|
f@0
|
52 lDecayRateY = 90
|
f@0
|
53 };
|
f@0
|
54
|
f@0
|
55 enum EParams
|
f@0
|
56 {
|
f@0
|
57 kDry = 0,
|
f@0
|
58 kWet,
|
f@0
|
59 kThreshold,
|
f@0
|
60 kSonificationType,
|
f@0
|
61 kMeterDecayRate,
|
f@0
|
62 kNumParams
|
f@0
|
63 };
|
f@0
|
64
|
f@0
|
65 AccessiblePeakMeter::AccessiblePeakMeter(IPlugInstanceInfo instanceInfo)
|
f@0
|
66 : IPLUG_CTOR(kNumParams, NUM_PRESETS, instanceInfo),
|
f@0
|
67 mDry(DRY_DEFAULT),
|
f@0
|
68 mWet(WET_DEFAULT),
|
f@0
|
69 mMeterDecayRate(1.0),
|
f@0
|
70 mThreshold(1.0),
|
f@0
|
71 mSampleRate(44100.),
|
f@0
|
72 mSonificationType(SONIFICATION_TYPE_CLIPPING)
|
f@0
|
73
|
f@0
|
74 {
|
f@0
|
75 TRACE;
|
f@0
|
76
|
f@0
|
77 //arguments are: name, defaultVal, minVal, maxVal, step, label
|
f@0
|
78 GetParam(kDry)->InitDouble("Dry", DRY_DEFAULT, -61.0, 0., 0.2, "dB");
|
f@0
|
79 GetParam(kDry)->SetDisplayText(-61.0, " -inf");
|
f@0
|
80 GetParam(kWet)->InitDouble("Wet", WET_DEFAULT, -61.0, 0., 0.2, "dB");
|
f@0
|
81 GetParam(kWet)->SetDisplayText(-61.0, " -inf");
|
f@0
|
82 GetParam(kThreshold)->InitDouble("Threshold", 0.0, -60.0, 6.2, 0.2, "dB");
|
f@0
|
83 GetParam(kSonificationType)->InitEnum("Sonification Type", SONIFICATION_TYPE_DEFAULT, 2);
|
f@0
|
84 GetParam(kSonificationType)->SetDisplayText(SONIFICATION_TYPE_CLIPPING, "Clipping");
|
f@0
|
85 GetParam(kSonificationType)->SetDisplayText(SONIFICATION_TYPE_CONTINUOUS, "Continuous");
|
f@0
|
86
|
f@0
|
87 GetParam(kMeterDecayRate)->InitDouble("Decay", 1.0, 0.05, 1.0, 0.05, "sec.");
|
f@0
|
88
|
f@0
|
89 IGraphics* pGraphics = MakeGraphics(this, GUI_WIDTH, GUI_HEIGHT);
|
f@0
|
90 pGraphics->AttachBackground(BG_ID, BG_FN);
|
f@0
|
91
|
f@0
|
92 /* load bitmaps for fader, knob and switch button */
|
f@0
|
93 IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, NUM_KNOB_FRAMES);
|
f@0
|
94 IBitmap faderBmap = pGraphics->LoadIBitmap(FADER_ID, FADER_FN);
|
f@0
|
95 IBitmap aSwitch = pGraphics->LoadIBitmap(SWITCH_ID, SWITCH_FN,2);
|
f@0
|
96
|
f@0
|
97 //pGraphics->AttachKeyCatcher(new IKeyCatcher(this, IRECT(0, 0, GUI_WIDTH, GUI_HEIGHT)));
|
f@0
|
98
|
f@0
|
99 /* text has info about the font-size, font-type etc. */
|
f@0
|
100 IText text = IText(14);
|
f@0
|
101 /* attach sonification type switch to the GUI */
|
f@0
|
102 pGraphics->AttachControl(new ISwitchPopUpControl(this, lSonifTypeX ,lSonifTypeY, kSonificationType, &aSwitch));
|
f@0
|
103 pGraphics->AttachControl(new ITextControl(this, IRECT(lSonifTypeX+10, lSonifTypeY - 20, lSonifTypeX + 110, lSonifTypeY ), &text, "Sonification Type"));
|
f@0
|
104
|
f@0
|
105 /* attach dry and wet knobs to GUI */
|
f@0
|
106 pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lDryX, lDryY, lDryX + 52, lDryY + 48 + 20 + 20), kDry, &knob, &text, 27));
|
f@0
|
107 pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lWetX, lWetY, lWetX + 52, lWetY + 48 + 20 + 20), kWet, &knob, &text, 27));
|
f@0
|
108
|
f@0
|
109 /* attach decay rate knob to the GUI */
|
f@0
|
110 pGraphics->AttachControl(new IKnobMultiControlText(this, IRECT(lDecayRateX, lDecayRateY, lDecayRateX + 48, lDecayRateY + 48 + 20 + 20), kMeterDecayRate, &knob, &text, 33));
|
f@0
|
111
|
f@0
|
112 /* attach fader display, which shows the fader value, to GUI */
|
f@0
|
113 ITextControl *faderText = new ITextControl(this, IRECT(lPeakMeterX+60, lPeaklMeterY + lFaderLen, lPeakMeterX + faderBmap.W + 95, lPeaklMeterY + lFaderLen + 20), &text);
|
f@0
|
114 pGraphics->AttachControl(faderText);
|
f@0
|
115
|
f@0
|
116 /* attach the fader to GUI */
|
f@0
|
117 pGraphics->AttachControl(new IFaderVertText(this, lPeakMeterX, lPeaklMeterY, lFaderLen, kThreshold, &faderBmap, faderText));
|
f@0
|
118
|
f@0
|
119 pGraphics->AttachControl(new ITextControl(this, IRECT(lPeakMeterX, lPeaklMeterY - 20, lPeakMeterX + 100, lPeaklMeterY), &text, "Peak Level Meter"));
|
f@0
|
120 pGraphics->AttachControl(new ITextControl(this, IRECT(lPeakMeterX-20, lPeaklMeterY + lFaderLen, lPeakMeterX + 75, lPeaklMeterY + lFaderLen + 20), &text, "Threshold: "));
|
f@0
|
121
|
f@0
|
122 /* attach peak meters to GUI */
|
f@0
|
123 /* half the bitmap height is added to the peak meters on both top and bottom to prevent the fader
|
f@0
|
124 - a triangle pointing at half the height of the bitmap - from overflowing the peak meters */
|
f@0
|
125 int halfFaderBmapLen = faderBmap.W / 2;
|
f@0
|
126 mMeterIdx[0] = pGraphics->AttachControl(new IPeakMeterVert(this, IRECT(lPeakMeterX + 25, lPeaklMeterY + halfFaderBmapLen, lPeakMeterX + 45, lPeaklMeterY + 170 + halfFaderBmapLen),
|
f@0
|
127 GetParam(kThreshold)->GetDefaultNormalized()));
|
f@0
|
128 mMeterIdx[1] = pGraphics->AttachControl(new IPeakMeterVert(this,
|
f@0
|
129 IRECT(lPeakMeterX + 50, lPeaklMeterY + halfFaderBmapLen, lPeakMeterX + 70, lPeaklMeterY + lFaderLen - halfFaderBmapLen), GetParam(kThreshold)->GetDefaultNormalized()));
|
f@0
|
130
|
f@0
|
131 AttachGraphics(pGraphics);
|
f@0
|
132
|
f@0
|
133 //kDry, kWet, kThreshold, kSonificationType, kMeterDecayRate,
|
f@0
|
134 MakePreset("Detect Clipping", DRY_DEFAULT, WET_DEFAULT, THRESHOLD_DEFAULT, SONIFICATION_TYPE_CLIPPING, METERDECAY_DEFAULT);
|
f@0
|
135 MakePreset("Sonify Audio", DRY_DEFAULT, WET_DEFAULT, THRESHOLD_DEFAULT, SONIFICATION_TYPE_CONTINUOUS, METERDECAY_DEFAULT);
|
f@0
|
136
|
f@0
|
137 }
|
f@0
|
138
|
f@0
|
139 AccessiblePeakMeter::~AccessiblePeakMeter() { }
|
f@0
|
140
|
f@0
|
141
|
f@0
|
142 void AccessiblePeakMeter::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
|
f@0
|
143 {
|
f@0
|
144 if(mSonificationType == SONIFICATION_TYPE_CONTINUOUS) {
|
f@0
|
145 addContinuousSonification(inputs, outputs, nFrames);
|
f@0
|
146 } else {
|
f@0
|
147 addClippingSonification(inputs, outputs, nFrames);
|
f@0
|
148 }
|
f@0
|
149 }
|
f@0
|
150
|
f@0
|
151
|
f@0
|
152 void AccessiblePeakMeter::Reset()
|
f@0
|
153 {
|
f@0
|
154 TRACE;
|
f@0
|
155 IMutexLock lock(this);
|
f@0
|
156
|
f@0
|
157 mSampleRate = GetSampleRate();
|
f@0
|
158
|
f@0
|
159 for (int i = 0; i < MAX_CHANNELS; i++) {
|
f@0
|
160 mPrevPeak[i] = 0.0;
|
f@0
|
161 }
|
f@0
|
162
|
f@0
|
163 if (!sDacThread.started){
|
f@0
|
164 sDacThread.started = true;
|
f@0
|
165 sDacThread.t = std::move(std::thread(DacRoutine));
|
f@0
|
166 }
|
f@0
|
167 }
|
f@0
|
168
|
f@0
|
169 void AccessiblePeakMeter::OnParamChange(int paramIdx)
|
f@0
|
170 {
|
f@0
|
171 IMutexLock lock(this);
|
f@0
|
172
|
f@0
|
173 switch (paramIdx)
|
f@0
|
174 {
|
f@0
|
175 case kDry :
|
f@0
|
176 if (GetParam(kDry)->Value() < -60.5){
|
f@0
|
177 mDry = 0.0;
|
f@0
|
178 }
|
f@0
|
179 else {
|
f@0
|
180 mDry = ::DBToAmp(GetParam(kDry)->Value());
|
f@0
|
181 }
|
f@0
|
182 break;
|
f@0
|
183
|
f@0
|
184 case kWet:
|
f@0
|
185 if (GetParam(kWet)->Value() < -60.5){
|
f@0
|
186 mWet = 0.0;
|
f@0
|
187 }
|
f@0
|
188 else{
|
f@0
|
189 mWet = ::DBToAmp(GetParam(kWet)->Value());
|
f@0
|
190 }
|
f@0
|
191 break;
|
f@0
|
192
|
f@0
|
193 case kThreshold:
|
f@0
|
194 mThreshold = GetParam(kThreshold)->DBToAmp();
|
f@0
|
195 break;
|
f@0
|
196
|
f@0
|
197 case kMeterDecayRate :
|
f@0
|
198 mMeterDecayRate = 1.0 / GetParam(kMeterDecayRate)->Value();
|
f@0
|
199 break;
|
f@0
|
200
|
f@0
|
201 case kSonificationType:
|
f@0
|
202 mSonificationType = GetParam(kSonificationType)->Int();
|
f@0
|
203
|
f@0
|
204 for (int i = 0; i < MAX_CHANNELS; i++) {
|
f@0
|
205 mPrevPeak[i] = 0.0;
|
f@0
|
206 }
|
f@0
|
207
|
f@0
|
208 sDacMutex.lock();
|
f@0
|
209 sDacSynced.sonificationType = mSonificationType;
|
f@0
|
210 sDacMutex.unlock();
|
f@0
|
211
|
f@0
|
212 break;
|
f@0
|
213
|
f@0
|
214 default:
|
f@0
|
215 break;
|
f@0
|
216 }
|
f@0
|
217 }
|
f@0
|
218
|
f@0
|
219 void AccessiblePeakMeter::addClippingSonification(double** inputs, double** outputs, int nFrames) {
|
f@0
|
220 // Mutex is already locked for us.
|
f@0
|
221
|
f@0
|
222 double clippingDiff[MAX_CHANNELS] = { 0.0, 0.0 };
|
f@0
|
223
|
f@0
|
224 for (unsigned int channel = 0; channel < NInChannels(); channel++) {
|
f@0
|
225 double* in = inputs[channel];
|
f@0
|
226 double* out = outputs[channel];
|
f@0
|
227 double peak = 0.0;
|
f@0
|
228
|
f@0
|
229 /* find the max absolute value in the block of samples */
|
f@0
|
230 for (int offset = 0; offset < nFrames; ++offset, ++in, ++out) {
|
f@0
|
231 /* find the peak of this block */
|
f@0
|
232 peak = IPMAX(peak, fabs(*in));
|
f@0
|
233 /* write the input buffer to the output */
|
f@0
|
234 *out = mDry * (*in);
|
f@0
|
235 }
|
f@0
|
236
|
f@0
|
237 if (peak > mThreshold) {
|
f@0
|
238 double difftoThrs = fabs(::AmpToDB(peak) - ::AmpToDB(mThreshold));
|
f@0
|
239 /* clipDiff will be rounded downward later, but if it's very very
|
f@0
|
240 close to the ceil, then let it be the ceil.
|
f@0
|
241 */
|
f@0
|
242 const double ceilClippingDiff = ceil(difftoThrs);
|
f@0
|
243 if (ceilClippingDiff - difftoThrs < CLIPPING_CEILING_SNAP){
|
f@0
|
244 difftoThrs = ceilClippingDiff;
|
f@0
|
245 }
|
f@0
|
246
|
f@0
|
247 clippingDiff[channel] = BOUNDED(difftoThrs, 0.0, 12.0);
|
f@0
|
248 }
|
f@0
|
249
|
f@0
|
250 /* now draw the peak meter with the maximum of this block of samples */
|
f@0
|
251 const double deltaT = nFrames / mSampleRate;
|
f@0
|
252 const double decayAmount = deltaT * mMeterDecayRate;
|
f@0
|
253
|
f@0
|
254 peak = ::toDBMeter(peak, DB_RANGE);
|
f@0
|
255
|
f@0
|
256 /* max between new peak and old peak decay wins */
|
f@0
|
257 peak = IPMAX(peak, mPrevPeak[channel] - decayAmount);
|
f@0
|
258
|
f@0
|
259 /* save the peaks for next block of samples */
|
f@0
|
260 mPrevPeak[channel] = peak;
|
f@0
|
261
|
f@0
|
262
|
f@0
|
263 /* update the GUI */
|
f@0
|
264 if (GetGUI()){
|
f@0
|
265 GetGUI()->SetControlFromPlug(mMeterIdx[channel], peak);
|
f@0
|
266 }
|
f@0
|
267 }
|
f@0
|
268
|
f@0
|
269 /* pass the data related to this block over to the sonification thread */
|
f@0
|
270 sDacMutex.lock();
|
f@0
|
271 sDacSynced.wet = mWet;
|
f@0
|
272 sDacSynced.maxClippingDiff[0] = clippingDiff[0];
|
f@0
|
273 sDacSynced.maxClippingDiff[1] = clippingDiff[1];
|
f@0
|
274 sDacMutex.unlock();
|
f@0
|
275
|
f@0
|
276 }
|
f@0
|
277
|
f@0
|
278 void AccessiblePeakMeter::addContinuousSonification(double** inputs, double** outputs, int nFrames) {
|
f@0
|
279 // Mutex is already locked for us.
|
f@0
|
280
|
f@0
|
281 const int nChannels = NInChannels();
|
f@0
|
282
|
f@0
|
283 const double deltaT = nFrames / mSampleRate;
|
f@0
|
284 const double decayAmount = deltaT * mMeterDecayRate;
|
f@0
|
285 double sonifyFreq[MAX_CHANNELS] = {0.0, 0.0};
|
f@0
|
286
|
f@0
|
287 for (int channel = 0; channel < nChannels; channel++){
|
f@0
|
288
|
f@0
|
289 double peak = 0.0;
|
f@0
|
290 double *in = inputs[channel];
|
f@0
|
291 double *out = outputs[channel];
|
f@0
|
292
|
f@0
|
293 /* find the max absolute value in the block of samples and write output */
|
f@0
|
294 for (int offset = 0; offset < nFrames; ++offset, ++in, ++out) {
|
f@0
|
295 /* find the peak of this block */
|
f@0
|
296 peak = IPMAX(peak, fabs(*in));
|
f@0
|
297 /* write the input buffer to the output */
|
f@0
|
298 *out = mDry * (*in);
|
f@0
|
299 }
|
f@0
|
300
|
f@0
|
301 /* pick the max between new audio and peak meter decaying */
|
f@0
|
302 peak = ::toDBMeter(peak, DB_RANGE);
|
f@0
|
303 peak = IPMAX(peak, mPrevPeak[channel] - decayAmount);
|
f@0
|
304
|
f@0
|
305 /* set the sonification frequency according to the last peak value */
|
f@0
|
306 sonifyFreq[channel] = SONIFICATION_RANGE * peak;
|
f@0
|
307
|
f@0
|
308 /* save the peaks for next block of samples */
|
f@0
|
309 mPrevPeak[channel] = peak;
|
f@0
|
310
|
f@0
|
311 /* update the GUI */
|
f@0
|
312 if (GetGUI()){
|
f@0
|
313 GetGUI()->SetControlFromPlug(mMeterIdx[channel], peak);
|
f@0
|
314 }
|
f@0
|
315
|
f@0
|
316 }
|
f@0
|
317
|
f@0
|
318 /* pass the data related to this block over to the sonification thread */
|
f@0
|
319 sDacMutex.lock();
|
f@0
|
320 sDacSynced.sonifFreq[0] = sonifyFreq[0];
|
f@0
|
321 sDacSynced.sonifFreq[1] = sonifyFreq[1];
|
f@0
|
322 sDacSynced.wet = mWet;
|
f@0
|
323 sDacMutex.unlock();
|
f@0
|
324 }
|
f@0
|
325
|
f@0
|
326 /* Global scope function executed by the thread that plays the sonification to the dac */
|
f@0
|
327 void DacRoutine(){
|
f@0
|
328
|
f@0
|
329 /* the sound card handle */
|
f@0
|
330 std::unique_ptr<stk::RtWvOut> sDac;
|
f@0
|
331
|
f@0
|
332 try {
|
f@0
|
333 // Define and open the default realtime output device for two-channels playback
|
f@0
|
334 sDac.reset(new stk::RtWvOut(MAX_CHANNELS, stk::Stk::sampleRate(), 0, DAC_BUFFER_SIZE));
|
f@0
|
335 }
|
f@0
|
336 catch (stk::StkError &) {
|
f@0
|
337 exit(1);
|
f@0
|
338 }
|
f@0
|
339
|
f@0
|
340 /* buffer to calculate the blocks of DAC_BUFFER_SIZE samples.
|
f@0
|
341 The content of the buffer is then fed to the sound card
|
f@0
|
342 */
|
f@0
|
343 stk::StkFrames frames(DAC_BUFFER_SIZE, MAX_CHANNELS);
|
f@0
|
344
|
f@0
|
345 while (true){
|
f@0
|
346
|
f@0
|
347 /* init local variables to be filled with shared variables' content */
|
f@0
|
348 double freqs[MAX_CHANNELS] = { 0.0, 0.0 };
|
f@0
|
349 double clippingDiffs[MAX_CHANNELS] = { 0.0, 0.0 };
|
f@0
|
350 double wet = 0.0;
|
f@0
|
351 bool die = false;
|
f@0
|
352 int sonificationType;
|
f@0
|
353
|
f@0
|
354 /* read the shared variables all together into local variables */
|
f@0
|
355 sDacMutex.lock();
|
f@0
|
356
|
f@0
|
357 sonificationType = sDacSynced.sonificationType;
|
f@0
|
358
|
f@0
|
359 for (int i = 0; i < MAX_CHANNELS; i++){
|
f@0
|
360 freqs[i] = sDacSynced.sonifFreq[i];
|
f@0
|
361 clippingDiffs[i] = sDacSynced.maxClippingDiff[i];
|
f@0
|
362 }
|
f@0
|
363
|
f@0
|
364 wet = sDacSynced.wet;
|
f@0
|
365
|
f@0
|
366 die = sDacSynced.die;
|
f@0
|
367
|
f@0
|
368 sDacMutex.unlock();
|
f@0
|
369
|
f@0
|
370 /* check if the thread has to stop. Called when the user exits the Daw */
|
f@0
|
371 if (die){
|
f@0
|
372 return;
|
f@0
|
373 }
|
f@0
|
374
|
f@0
|
375 /* reset the ugen if sonification type has changed */
|
f@0
|
376 if (sonificationType != sPrevSonificationType) {
|
f@0
|
377
|
f@0
|
378 for (int i = 0; i < MAX_CHANNELS; i++){
|
f@0
|
379 sSonification.ugen[i].reset();
|
f@0
|
380 sSonification.ugen[i].setFrequency(sonificationType == SONIFICATION_TYPE_CLIPPING ? 440 : 0);
|
f@0
|
381 }
|
f@0
|
382
|
f@0
|
383 sPrevSonificationType = sonificationType;
|
f@0
|
384 }
|
f@0
|
385
|
f@0
|
386 if (sonificationType == SONIFICATION_TYPE_CONTINUOUS) {
|
f@0
|
387 /* write the next block of samples to send to the soundcard */
|
f@0
|
388 for (int nFrame = 0; nFrame < DAC_BUFFER_SIZE; nFrame++){
|
f@0
|
389 for (int channel = 0; channel < MAX_CHANNELS; channel++){
|
f@0
|
390
|
f@0
|
391 sSonification.ugen[channel].setFrequency(freqs[channel]);
|
f@0
|
392 /* If level goes below audible level just hush the sonification. *
|
f@0
|
393 * this avoids DC offset when sonification frequency gets too low. *
|
f@0
|
394 * Use an envelope to bring the sonification volume down gently */
|
f@0
|
395 if (freqs[channel] < AccessiblePeakMeter::MIN_SONIFICATION_FREQ){
|
f@0
|
396
|
f@0
|
397 if (sSonification.continous.isOn[channel]){ // if it's on and level's low, turn it off
|
f@0
|
398 sSonification.continous.isOn[channel] = false;
|
f@0
|
399 sSonification.continous.envelope[channel].setTarget(0.0);
|
f@0
|
400 }
|
f@0
|
401
|
f@0
|
402 }
|
f@0
|
403 else if (!sSonification.continous.isOn[channel]){ // if it's off and level's high, turn it on
|
f@0
|
404
|
f@0
|
405 sSonification.continous.envelope[channel].setValue(1.0);
|
f@0
|
406 sSonification.continous.isOn[channel] = true;
|
f@0
|
407
|
f@0
|
408 }
|
f@0
|
409
|
f@0
|
410 double tick = sSonification.ugen[channel].tick();
|
f@0
|
411 tick *= sSonification.continous.envelope[channel].tick(); // apply envelope
|
f@0
|
412 tick *= wet; // apply wet parameter, controlled by the user
|
f@0
|
413 frames(nFrame, channel) = tick;
|
f@0
|
414 }
|
f@0
|
415 }
|
f@0
|
416 }
|
f@0
|
417 else { // sonificationType = AccessiblePeakMeter::SONIFICATION_TYPE_CLIPPING
|
f@0
|
418
|
f@0
|
419 for (int channel = 0; channel < MAX_CHANNELS; channel++){
|
f@0
|
420
|
f@0
|
421 if (clippingDiffs[channel] > 0.0){
|
f@0
|
422 if (clippingDiffs[channel] > sSonification.clipping.maxDiff[channel]){
|
f@0
|
423 sSonification.clipping.maxDiff[channel] = clippingDiffs[channel];
|
f@0
|
424 }
|
f@0
|
425
|
f@0
|
426 /* sonify the difference between the amplitude and threshold *
|
f@0
|
427 * one db (rounded downward) is one tone, up to one octave */
|
f@0
|
428 sSonification.ugen[channel].setFrequency(midi2Freq(69 + (int)(sSonification.clipping.maxDiff[channel])));
|
f@0
|
429 sSonification.clipping.envelope[channel].keyOn();
|
f@0
|
430 }
|
f@0
|
431
|
f@0
|
432 }
|
f@0
|
433
|
f@0
|
434 for (int nFrame = 0; nFrame < DAC_BUFFER_SIZE; nFrame++){
|
f@0
|
435 for (int channel = 0; channel < MAX_CHANNELS; channel++){
|
f@0
|
436
|
f@0
|
437 /* when attack is done switch immediately to RELEASE (keyOff) *
|
f@0
|
438 * this way the evelope goes like attack->release->silence */
|
f@0
|
439 if (sSonification.clipping.envelope[channel].getState() == stk::ADSR::DECAY) {
|
f@0
|
440 sSonification.clipping.envelope[channel].keyOff();
|
f@0
|
441 }
|
f@0
|
442
|
f@0
|
443 /* write the sonification in the frames object */
|
f@0
|
444 if (sSonification.clipping.envelope[channel].getState() == stk::ADSR::ATTACK ||
|
f@0
|
445 sSonification.clipping.envelope[channel].getState() == stk::ADSR::RELEASE) {
|
f@0
|
446
|
f@0
|
447 const double env = sSonification.clipping.envelope[channel].tick();
|
f@0
|
448 double tick = sSonification.ugen[channel].tick() * env;
|
f@0
|
449 tick *= wet;
|
f@0
|
450 frames(nFrame, channel) = tick;
|
f@0
|
451
|
f@0
|
452 }
|
f@0
|
453 else { // no sonification
|
f@0
|
454
|
f@0
|
455 sSonification.clipping.maxDiff[channel] = 0.0; // reset max clipping diff
|
f@0
|
456 frames(nFrame, channel) = 0.0;
|
f@0
|
457
|
f@0
|
458 }
|
f@0
|
459 }
|
f@0
|
460 }
|
f@0
|
461
|
f@0
|
462 }
|
f@0
|
463
|
f@0
|
464 /* play this block to the default soundcard */
|
f@0
|
465 sDac->tick(frames);
|
f@0
|
466 }
|
f@0
|
467
|
f@0
|
468 }
|
f@0
|
469
|
f@0
|
470 //Called by the standalone wrapper if someone clicks about
|
f@0
|
471 bool AccessiblePeakMeter::HostRequestingAboutBox()
|
f@0
|
472 {
|
f@0
|
473 IMutexLock lock(this);
|
f@0
|
474 if(GetGUI())
|
f@0
|
475 {
|
f@0
|
476 // do nothing
|
f@0
|
477 }
|
f@0
|
478 return true;
|
f@0
|
479 }
|
f@0
|
480
|
f@0
|
481
|
f@0
|
482
|
f@0
|
483 const double AccessiblePeakMeter::DRY_DEFAULT = 0.0;
|
f@0
|
484 const double AccessiblePeakMeter::WET_DEFAULT = -6.0;
|
f@0
|
485 const int AccessiblePeakMeter::SONIFICATION_TYPE_DEFAULT = 1;
|
f@0
|
486 const double AccessiblePeakMeter::METERDECAY_DEFAULT = 60.0;
|
f@0
|
487 const double AccessiblePeakMeter::THRESHOLD_DEFAULT = 0.0;
|
f@0
|
488
|
f@0
|
489 const double AccessiblePeakMeter::DB_RANGE = 66.0;
|
f@0
|
490 const double AccessiblePeakMeter::SONIFICATION_RANGE = 2000;
|
f@0
|
491 const double AccessiblePeakMeter::MIN_SONIFICATION_FREQ = 20.0;
|
f@0
|
492 const double AccessiblePeakMeter::CLIPPING_CEILING_SNAP = 0.05;
|
f@0
|
493 const int AccessiblePeakMeter::NUM_KNOB_FRAMES = 60;
|
f@0
|
494
|