CSL  6.0
JCSL_Widgets.cpp
Go to the documentation of this file.
1 //
2 // JCSL_Widgets.cpp - JUCE widgets for audio by stp@heaveneverywhere.com
3 // What's here:
4 // AudioWaveformDisplay - simple oscilloscope based on the JUCE AudioDemo
5 // AudioSpectrumDisplay - simple spectrogram
6 // RangeSlider - interprets its two markers as base value and range, rather than min/max
7 // i.e., the upper marker is the base value, and the lower one is the random range
8 // VUMeter (based on killerbobjr's MeterComponent)
9 
10 #include "JCSL_Widgets.h"
11 #include <math.h>
12 
13 #define csl_max(a, b) (((a) > (b)) ? (a) : (b))
14 #define csl_min(a, b) (((a) < (b)) ? (a) : (b))
15 
16 // #include "AnalogMeterImages.h" // only needed for fancy analog meters
17 
18 // oscilloscope class
19 
21  bufferPos = 0;
22  bufferSize = CSL_mMaxBufferFrames; // allocate buffer
23  circularBuffer = (float*) calloc (1, sizeof (float) * bufferSize);
24  currentOutputLevel = 0.0f;
25  numSamplesIn = 0;
26  setOpaque (true);
27  whichChannel = -1;
28  indicatorValue = 0;
29 }
30 
32  free (circularBuffer);
33 }
34 
35 // set up oscilloscope state (since constructor takes no args)
36 
37 void AudioWaveformDisplay::initialise(int channel, unsigned rate, unsigned window, bool zeroX) {
38  if (rate)
39  delayInMsec = 1000/rate; // refresh rate
40  else
41  delayInMsec = 0;
42  samplesToAverage = window; // avg range
43  bufferSize = CSL_mMaxBufferFrames / 4; // * window; // allocate buffer
44  circularBuffer = (float*) calloc (1, sizeof (float) * bufferSize);
45  zeroCross = zeroX; // whether to trigger on zero-X
46  whichChannel = channel;
47 // startTimer(delayInMsec); // repaint every so-many msec
48 }
49 
50 // start/stop display
51 
53  startTimer(delayInMsec);
54 }
55 
57  stopTimer();
58 }
59 
60 // main scope draw method
61 
62 void AudioWaveformDisplay::paint (juce::Graphics& g) {
63  g.fillAll (juce::Colours::black);
64  g.setColour (juce::Colours::lightgreen);
65 
66  const float halfHeight = getHeight() * 0.5f;
67  unsigned wid = getWidth();
68  //this is handled in the add_samples call-back now
69 // if ( ! samplesToAverage) { // if no interpolation, subsample the samples
70 // float skip = 1; //(float) samplesToAverage / (float) wid;
71 // for (unsigned x = 0; x < wid; x++) { // drawing loop
72 // float index = (float) x * skip;
73 // const float level = circularBuffer[(unsigned) index];
74 //// g.setPixel(x, (int) (halfHeight - (halfHeight * level)));
75 // g.drawLine ((float) x, halfHeight,
76 // (float) x, halfHeight + (halfHeight * level));
77 // }
78 // return;
79 // }
80  int x0;
81  if (bufferPos > wid)
82  x0 = bufferPos - wid;
83  else
84  x0 = bufferSize - wid;
85 
86  if (zeroCross) { // loop to find zeroX earlier than wid ago
87  float y0, y1;
88  y0 = circularBuffer [x0];
89  while (x0-- > 0) {
90  y1 = y0;
91  y0 = circularBuffer[x0];
92  if ((y0 >= 0.0) && (y1 < 0.0)) // if zero-crossing
93  break;
94  }
95  }
96  x0++;
97  for (unsigned x = 0; x < wid; x++) { // drawing loop
98  if ((x0 + x) >= bufferSize)
99  x0 = 0 - x;
100  const float level = circularBuffer[x0 + x];
101 #ifndef WIN32
102  if ( ! std::isnormal(level)) continue;
103 #endif
104  // g.setPixel(x, (int) (halfHeight - (halfHeight * level)));
105  g.drawLine ((float) x, halfHeight,
106  (float) x, halfHeight + (halfHeight * level));
107  }
108 // bufferPos = 0; // reset buffer writing pos
109 }
110 
111 // timer just redraws
112 
114  repaint();
115 }
116 
117 // adding a sample accumulates values
118 
120  currentOutputLevel += sample; // rather than fabs as in the other scope version
121  if (++numSamplesIn >= samplesToAverage) { // if at end of mini-window
123  numSamplesIn = 0;
124  currentOutputLevel = 0.0f;
125  }
126  if (bufferPos >= bufferSize)
127  bufferPos = 0;
128 }
129 
130 // callaback accumulates samples
131 
132 void AudioWaveformDisplay::audioDeviceIOCallback (const float** inputChannelData, int totalNumInputChannels,
133  float** outputChannelData, int totalNumOutputChannels, int numSamples) {
134  if ( ! samplesToAverage) { // if no interpolation, just hold onto the pointer
135  circularBuffer = outputChannelData[0];
136  numSamplesIn = numSamples;
137  return;
138  }
139  if (whichChannel == -1) { // merge all channels
140  for (int i = 0; i < totalNumOutputChannels; ++i) {
141  if (outputChannelData[i] != 0) {
142  for (int j = 0; j < numSamples; ++j)
143  addSample (outputChannelData[i][j]);
144  break;
145  }
146  }
147  } else { // single channel
148  for (int j = 0; j < numSamples; ++j)
149  addSample (outputChannelData[whichChannel][j]);
150  }
151 }
152 
153 void AudioWaveformDisplay::audioDeviceAboutToStart (double sampleRate, int numSamplesPerBlock) {
154  juce::zeromem (circularBuffer, sizeof (float) * bufferSize);
155 }
156 
158  juce::zeromem (circularBuffer, sizeof (float) * bufferSize);
159 }
160 
161 ///////////////////////////////////////////////////////////////////////
162 //
163 // AudioSpectrumDisplay methods
164 //
165 
167  bufferSize = 512; // # windows to buffer
168  spectrumBuffer = (float **) calloc (1, sizeof (float *) * bufferSize);
169  bufferPos = 0;
170  mLogDisplay = false;
171  mSpectroDisplay = true; // open in spectrogram display
173 }
174 
176  free (spectrumBuffer);
177 }
178 
179 // callaback copies *mag* samples in spectrumBuffer
180 // outputChannelData is the complex spectrum, totalNumOutputChannels the index and
181 // numSamples the buffer size
182 // FFTW half-complex format = r0, r1, r2, ..., rn/2, i(n+1)/2-1, ..., i2, i1
183 
184 void AudioSpectrumDisplay::audioDeviceIOCallback (const float** inputChannelData, int totalNumInputChannels,
185  float** outputChannelData, int totalNumOutputChannels, int numSamples) {
186  float realV, imagV, pixV;
187 
188  bufferSize = numSamples;
189  if ( ! spectrumBuffer[bufferPos])
190  spectrumBuffer[bufferPos] = (float *) malloc(numSamples / 2 * sizeof(float));
191 
192  for (unsigned x = 0; x < numSamples / 2; x++) { // calc/norm loop
193  realV = outputChannelData[0][x + 1];
194  imagV = outputChannelData[0][numSamples - (x + 1)];
195  pixV = hypotf(realV, imagV) * 10.0; // * 10 for scaling
196  spectrumBuffer[bufferPos][x] = pixV;
197  }
198  bufferPos++;
199  if (bufferPos == bufferSize)
200  bufferPos = 0;
201 }
202 
203 // spectrum draw method - draw a full 3D color spectrogram (if mSpectroDisplay)
204 // or just a single spectral slice
205 
206 #define SetPixel(g, x, y) g.fillRect(x, y, 1, 1);
207 
208 void AudioSpectrumDisplay::paint (juce::Graphics& g) {
209  float realV, maxV;
210  g.fillAll (juce::Colours::black);
211 
212  unsigned hite = getHeight();
213  unsigned wid = getWidth();
214 // printf("Paint %d @ %d\n", wid, hite);
215 
216  if (mSpectroDisplay) { // if spectrogram (vs spectral slice)
217  float xScale = (float) wid / numWindows;
218 // printf("Paint %d @ %d (%g)\n", wid, hite, xScale);
219  maxV = 0.0f;
220  for (unsigned x = 0; x < wid; x++) { // window loop
221  if (x >= numWindows)
222  continue;
223  if ( ! spectrumBuffer[x])
224  continue;
225  for (unsigned y = 0; y < hite; y++) { // log loop
226  realV = logf(spectrumBuffer[x][y]);
227  if (realV > maxV)
228  maxV = realV;
229  circularBuffer[y] = realV;
230  }
231  for (unsigned y = 0; y < hite; y++) { // frame loop
232  g.setColour (juce::Colour(circularBuffer[y] / maxV, 1.0f, 1.0f, 1.0f)); // HSVA
233  unsigned yVal = hite - y;
234  unsigned xVal = x * xScale;
235  for (unsigned i = 0; i < xScale; i++) { // draw loop
236  SetPixel(g, xVal+i, yVal);
237  }
238  }
239  }
240  } else { //spectral slices
241  float minV, max2;
242  g.setColour (juce::Colours::lightgreen);
243  unsigned howWide = csl_min(wid, (bufferSize / 2));
244  maxV = 0.0f;
245  for (unsigned x = 0; x < howWide; x++) { // calc/norm loop
246  realV = spectrumBuffer[indicatorValue][x];
247  circularBuffer[x] = realV;
248  if (realV > maxV)
249  maxV = realV;
250  }
251  if (maxV == 0.0f)
252  return;
253 // printf("\tmax = %5.3f\n", maxV);
254 
255  if (mLogDisplay) {
256  max2 = 0.0;
257  minV = 0.0;
258  for (unsigned x = 0; x < howWide; x++) { // log loop
259  realV = logf(circularBuffer[x]);
260  if (realV > max2)
261  max2 = realV;
262  if (realV < minV)
263  minV = realV;
264  circularBuffer[x] = realV;
265  }
266 // printf("\t\tmin = %5.3f, max = %5.3f\n", minV, max2);
267  maxV = max2;
268  }
269  howWide = csl_min(wid, bufferSize);
270  for (unsigned x = 0; x < howWide; x += 2) { // drawing loop
271  realV = hite - (hite * circularBuffer[x / 2] / maxV);
272  g.drawLine ((float) x, hite, (float) x, realV);
273  g.drawLine ((float) x+1, hite, (float) x+1, realV);
274  }
275  }
276 }
277 
278 #ifdef RANGE_SLIDER
279 
280 ///////////////////////////////////////////////////////////////////////
281 //
282 // RangeSlider methods
283 
284 // Answer the base value
285 
286 double RangeSlider::getBaseValue() {
287  return baseValue;
288 }
289 
290 // answer the range (normalized to 0 - 1 -- not certain if there's a single way to do this, or if we need another state flag
291 
292 double RangeSlider::getRangeValue() {
293  return (rangeValue - minimum) / (maximum - minimum);
294 }
295 
296 void RangeSlider::mouseDown (const MouseEvent& e) {
297  float mousePos;
298  float maxPosDistance;
299  float minPosDistance;
300  mouseWasHidden = false;
301  incDecDragged = false;
302 
303  if (isEnabled()) {
304  menuShown = false;
305  if (valueBox != 0)
306  valueBox->hideEditor (true);
307  sliderBeingDragged = 0;
308  mousePos = (float) (isVertical() ? e.y : e.x);
309  maxPosDistance = fabsf (getLinearSliderPos (getMaxValue()) - 0.1f - mousePos);
310  minPosDistance = fabsf (getLinearSliderPos (getMinValue()) + 0.1f - mousePos);
311  if (maxPosDistance <= minPosDistance)
312  sliderBeingDragged = 2;
313  else
314  sliderBeingDragged = 1;
315  }
316  minMaxDiff = getMaxValue() - getMinValue();
317  mouseXWhenLastDragged = e.x;
318  mouseYWhenLastDragged = e.y;
319  if (sliderBeingDragged == 2)
320  valueWhenLastDragged = getValue();
321  else if (sliderBeingDragged == 1)
322  valueWhenLastDragged = baseValue;
323  valueOnMouseDown = valueWhenLastDragged;
324 
325  sendDragStart();
326  mouseDrag (e);
327 }
328 
329 void RangeSlider::mouseDrag (const MouseEvent& e) {
330  if (isEnabled() && ( ! menuShown)) {
331  const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y;
332  double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
333  if (style == LinearVertical)
334  scaledMousePos = 1.0 - scaledMousePos;
335  valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos));
336  mouseXWhenLastDragged = e.x;
337  mouseYWhenLastDragged = e.y;
338  valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
339  if (sliderBeingDragged == 1)
340  setBaseValue(valueWhenLastDragged, ! sendChangeOnlyOnRelease, false);
341  else if (sliderBeingDragged == 2)
342  setRangeValue(valueWhenLastDragged, ! sendChangeOnlyOnRelease, false);
343  mouseXWhenLastDragged = e.x;
344  mouseYWhenLastDragged = e.y;
345  }
346 }
347 
348 void RangeSlider::setBaseValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) {
349 // newValue = jmin (valueMax, newValue);
350 // newValue = jmax (valueMin, newValue);
351 // printf("B: %5.3f\n", newValue);
352  if (baseValue != newValue) {
353  baseValue = newValue;
354  valueMin = newValue;
355  currentValue = newValue;
356  repaint();
357  if (sendUpdateMessage)
358  triggerChangeMessage (sendMessageSynchronously);
359  }
360 }
361 
362 void RangeSlider::setRangeValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) {
363 // newValue = jmin (valueMax, newValue);
364 // newValue = jmax (valueMin, newValue);
365 // printf("R: %5.3f\n", newValue);
366  if (rangeValue != newValue) {
367  rangeValue = newValue;
368  valueMax = newValue;
369  repaint();
370  if (sendUpdateMessage)
371  triggerChangeMessage (sendMessageSynchronously);
372  }
373 // printf("R: %5.3f\n", getRangeValue());
374 }
375 
376 #endif
377 
378 ///////////////////////////////////////////////////////////////////////
379 //
380 // VUMeter methods
381 
382 // Default constructor = 32-segment vertical VU meter
383 
385  Component("VU Meter"),
386  m_img(0),
387  m_value(0),
388  m_skew(1.0f),
389  m_threshold(0.707f),
390  m_meterType(VUMeter::Vertical),
391  m_segments(32),
392  m_markerWidth(2),
393  m_inset(0),
394  m_channel(0),
395  m_raised(false),
396  m_minColour(juce::Colours::green),
397  m_thresholdColour(juce::Colours::yellow),
398  m_maxColour(juce::Colours::red),
399  m_backgroundColour(juce::Colours::black),
400  m_decayTime(33),
401  m_decayPercent(0.707f),
402  m_decayToValue(0),
403  m_hold(2),
404  m_monostable(0),
405  m_background(0),
406  m_overlay(0),
407  m_minPosition(0.1f),
408  m_maxPosition(0.9f),
409  m_needleLength(0),
410  m_needleWidth(2) {
411  startTimer(m_decayTime);
412 }
413 
414 VUMeter::VUMeter(int channel,
415  int type,
416  int segs,
417  int markerWidth,
418  const juce::Colour& min,
419  const juce::Colour& thresh,
420  const juce::Colour& max,
421  const juce::Colour& back,
422  float threshold) :
423  Component("VU Meter"),
424  m_img(0),
425  m_value(0),
426  m_skew(1.0f),
427  m_threshold(threshold),
428  m_meterType(type),
429  m_segments(segs),
430  m_markerWidth(markerWidth),
431  m_inset(0),
432  m_channel(channel),
433  m_raised(false),
434  m_minColour(min),
435  m_thresholdColour(thresh),
436  m_maxColour(max),
437  m_backgroundColour(back),
438  m_decayTime(50),
439  m_decayPercent(0.707f),
440  m_decayToValue(0),
441  m_hold(4),
442  m_monostable(0),
443  m_background(0),
444  m_overlay(0),
445  m_minPosition(0.1f),
446  m_maxPosition(0.9f),
447  m_needleLength(0),
448  m_needleWidth(markerWidth)
449 {
450  if (m_meterType == Analog)
451  if ( ! m_segments) // Minimum analog meter wiper width
452  m_segments = 8;
453  startTimer(m_decayTime);
454 }
455 
456 VUMeter::VUMeter(int channel,
457  juce::Image* background,
458  juce::Image* overlay,
459  float minPosition,
460  float maxPosition,
461  juce::Point<int>& needleCenter,
462  int needleLength,
463  int needleWidth,
464  int arrowLength,
465  int arrowWidth,
466  const juce::Colour& needleColour,
467  int needleDropShadow,
468  int dropDistance) :
469  Component("Meter Component"),
470  m_img(0),
471  m_value(0),
472  m_skew(1.0f),
473  m_threshold(0.707f),
474  m_meterType(Analog),
475  m_segments(0),
476  m_markerWidth(0),
477  m_inset(0),
478  m_channel(channel),
479  m_raised(false),
480  m_minColour(0),
481  m_thresholdColour(0),
482  m_maxColour(0),
483  m_backgroundColour(0),
484  m_decayTime(50),
485  m_decayPercent(0.707f),
486  m_decayToValue(0),
487  m_hold(4),
488  m_monostable(0),
489  m_background(0),
490  m_overlay(0),
491  m_minPosition(minPosition),
492  m_maxPosition(maxPosition),
493  m_needleLength(needleLength),
494  m_needleWidth(needleWidth),
495  m_arrowLength(arrowLength),
496  m_arrowWidth(arrowWidth),
497  m_needleDropShadow(needleDropShadow),
498  m_dropDistance(dropDistance)
499 {
500  m_background = background;
501  m_overlay = overlay;
502  m_needleCenter.setXY(needleCenter.getX(), needleCenter.getY());
503  m_needleColour = needleColour;
504 
505  startTimer(m_decayTime);
506 }
507 
509  stopTimer();
510  deleteAndZero(m_img);
511  deleteAndZero(m_background);
512  deleteAndZero(m_overlay);
513 }
514 
516  deleteAndZero(m_img);
517 
518  // Build our image in memory
519  int w = getWidth() - m_inset*2;
520  int h = getHeight() - m_inset*2;
521  if ((w == 0) || (h == 0))
522  return;
523  if (m_meterType == Horizontal) {
524  m_img = new juce::Image(juce::Image::RGB, w, h, false);
525  juce::Graphics g(*m_img);
526 
527  juce::ColourGradient brLower(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false);
528  g.setGradientFill(brLower);
529  g.fillRect(0, 0, int(w * m_threshold), h);
530 
531  juce::ColourGradient brUpper(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false);
532  g.setGradientFill(brUpper);
533  g.fillRect(int(w * m_threshold), 0, w, h);
534 
535  if (m_segments)
536  {
537  // Break up our image into segments
538  g.setColour(m_backgroundColour);
539  g.fillRect (0, 0, w, m_markerWidth);
540  g.fillRect (0, h-m_markerWidth, w, m_markerWidth);
541  for (int i = 0; i <= m_segments; i++)
542  g.fillRect(i * (w/m_segments), 0, m_markerWidth, h);
543  }
544  } else if (m_meterType == Vertical) {
545  m_img = new juce::Image(juce::Image::RGB, w, h, false);
546  juce::Graphics g(*m_img);
547 
548  int hSize = (int) (h * m_threshold);
549  juce::ColourGradient brLower(m_minColour, 0, h, m_thresholdColour, 0, h - hSize, false);
550  g.setGradientFill(brLower);
551  g.fillRect(0, h - hSize, w, hSize);
552 
553  juce::ColourGradient brUpper(m_thresholdColour, 0, h - hSize, m_maxColour, 0, 0, false);
554  g.setGradientFill(brUpper);
555  g.fillRect(0, 0, w, h - hSize);
556 
557  if (m_segments) {
558  // Break up our image into segments
559  g.setColour(m_backgroundColour);
560  g.fillRect (0, 0, m_markerWidth, h);
561  g.fillRect (w-m_markerWidth, 0, m_markerWidth, h);
562  for (int i = 0; i <= m_segments; i++)
563  g.fillRect(0, i * (h/m_segments), w, m_markerWidth);
564  }
565  }
566  // Only build if no other analog images exist
567  else if (m_meterType == Analog && !(m_background || m_overlay || m_needleLength)) {
568  m_img = new juce::Image(juce::Image::RGB, w, h, false);
569  juce::Graphics g(*m_img);
570 
571  g.setColour(m_backgroundColour);
572  g.fillRect(0, 0, w, h);
573 
574  const double left = 4.71238898; // 270 degrees in radians
575  const double right = 1.57079633; // 90 degrees in radians
576  double startPos = m_minPosition; // Start at 10%
577  double endPos = m_maxPosition; // End at 90%
578 
579  float strokeWidth = m_segments; // We get our wiper width from the segments amount
580  double pos;
581  float radius = csl_max (w/2, h/2) - strokeWidth/2;
582  float angle;
583  float x;
584  float y;
585 
586  // Create an arc with lineTo's (works better than addArc)
587  juce::Path p;
588  for (pos = startPos; pos < endPos; pos += .02) {
589  angle = left + pos * (right - left);
590  x = sin(angle)*radius + w/2;
591  y = cos(angle)*radius + h - m_segments;
592  if (pos == startPos)
593  p.startNewSubPath(x, y);
594  else
595  p.lineTo(x, y);
596  }
597  angle = left + pos * (right - left);
598  p.lineTo(sin(angle)*radius + w/2, cos(angle)*radius + h - m_segments);
599 
600  // Create an image brush of our gradient
601  juce::Image img(juce::Image::RGB, w, h, false);
602  juce::Graphics g2(img);
603 
604  juce::ColourGradient brLower(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false);
605  g2.setGradientFill(brLower);
606  g2.fillRect(0, 0, int(w * m_threshold), h);
607 
608  juce::ColourGradient brUpper(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false);
609  g2.setGradientFill(brUpper);
610  g2.fillRect(int(w * m_threshold), 0, w, h);
611 
612  g.setTiledImageFill(img, 0, 0, 1.0);
613 
614  // Stroke the arc with the gradient
615  g.strokePath(p, juce::PathStrokeType(strokeWidth));
616  }
617 }
618 
619 void VUMeter::setBounds (int x, int y, int width, int height) {
620  Component::setBounds(x, y, width, height);
621  buildImage();
622 }
623 
624 void VUMeter::setFrame(int inset, bool raised) {
625  m_inset=inset;
626  m_raised=raised;
627  buildImage();
628 }
629 
630 void VUMeter::setColours(juce::Colour& min, juce::Colour& threshold, juce::Colour& max, juce::Colour& back) {
631  m_minColour = min;
632  m_thresholdColour = threshold;
633  m_maxColour = max;
634  m_backgroundColour = back;
635  buildImage();
636 }
637 
638 void VUMeter::setValue(float v) {
639  float val = csl_min(csl_max(v, 0.0f), 1.0f);
640  if (m_skew != 1.0 && val > 0.0)
641  val = exp (log (val) / m_skew);
642 
643  if (m_decayTime) {
644  if (m_value < val) {
645  // Sample and hold
647  m_value = val;
648 // repaint();
649  }
650  m_decayToValue = val;
651  } else {
652  if (m_value != val) {
653  m_value = val;
654 // repaint();
655  }
656  }
657 }
658 
659 void VUMeter::setDecay(int decay, int hold, float percent) {
660  m_decayPercent = percent;
661  m_decayTime = decay;
662  m_hold = hold;
663  if (m_decayTime)
664  // Start our decay
665  startTimer(m_decayTime);
666  else
667  stopTimer();
668 }
669 
671  if (m_monostable) // Wait for our hold period
672  m_monostable--;
673  else {
675  if (m_value - m_decayToValue > 0.01f)
676  // Only repaint if there's enough changes
677  repaint();
678  else if (m_value != m_decayToValue) {
679  // Zero out
681  repaint();
682  }
683  }
684 }
685 
686 // This needs to be made more efficient by only repainting when things actually
687 // change, and only painting the area that has changed. Right now, this paints
688 // everything on every repaint request.
689 //
690 void VUMeter::paint (juce::Graphics& g) {
691  int h = getHeight() - m_inset*2;
692  int w = getWidth() - m_inset*2;
693 
694  // Background
695  if ( ! m_background && !m_needleLength) {
696  g.setColour(m_backgroundColour);
697  g.fillRect(m_inset, m_inset, w, h);
698  }
699 
700  // Bevel outline for the entire draw area
701  if (m_inset)
702  juce::LookAndFeel_V1::drawBevel(g, 0, 0, getWidth(), getHeight(),
703  m_inset,
704  m_raised ? juce::Colours::white.withAlpha(0.9f) : juce::Colours::black.withAlpha(0.9f),
705  m_raised ? juce::Colours::black.withAlpha(0.9f) : juce::Colours::white.withAlpha(0.9f));
706 
707  // Blit our prebuilt image
708  g.setOpacity(1.0f);
709  if (m_meterType == Horizontal) {
710  if (m_segments) {
711  float val = float(int(m_value * m_segments)) / m_segments;
712  g.drawImage(*m_img, m_inset, m_inset, w * val, h, 0, 0, w * val, h);
713  } else
714  g.drawImage(*m_img, m_inset, m_inset, w * m_value, h, 0, 0, w * m_value, h);
715  } else if (m_meterType == Vertical) {
716  int hSize;
717  if (m_segments)
718  hSize = h * float(int(m_value * m_segments)) / m_segments;
719  else
720  hSize = h * m_value;
721  g.drawImage(*m_img, m_inset, m_inset + h - hSize, w, hSize, 0, h - hSize, m_img->getWidth(), hSize);
722  } else if (m_meterType == Analog) {
723  if (m_background)
724  g.drawImage(*m_background, m_inset, m_inset, w, h, 0, 0, m_background->getWidth(), m_background->getHeight());
725  else if (m_img)
726  g.drawImage(*m_img, m_inset, m_inset, w, h, 0, 0, m_img->getWidth(), m_img->getHeight());
727 
728  float angle = 4.71238898 + (m_value * (m_maxPosition - m_minPosition) + m_minPosition) * -3.14159265;
729  if (m_needleLength) {
730  g.setColour(m_needleColour);
731 // g.drawArrow(
732 // m_needleCenter.getX() + m_inset,
733 // m_needleCenter.getY() + m_inset,
734 // sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset,
735 // cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset,
736 // m_needleWidth,
737 // m_arrowLength,
738 // m_arrowWidth);
739 
740  if (m_needleDropShadow) {
741  int dropX, dropY, dropPointX, dropPointY;
742  if (m_needleDropShadow == Left) {
743  dropX = -m_dropDistance;
744  dropY = 0;
745  dropPointX = -m_dropDistance;
746  dropPointY = 0;
747  } else if (m_needleDropShadow == Right) {
748  dropX = m_dropDistance;
749  dropY = 0;
750  dropPointX = m_dropDistance;
751  dropPointY = 0;
752  } else if (m_needleDropShadow == Above) {
753  dropX = 0;
754  dropY = 0;
755  dropPointX = 0;
756  dropPointY = -m_dropDistance;
757  } else if (m_needleDropShadow == Below) {
758  dropX = 0;
759  dropY = m_dropDistance;
760  dropPointX = 0;
761  dropPointY = m_dropDistance;
762  }
763  g.setColour(juce::Colours::black.withAlpha(0.2f));
764 // g.drawArrow(
765 // m_needleCenter.getX() + m_inset + dropX,
766 // m_needleCenter.getY() + m_inset + dropY,
767 // sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset + dropPointX,
768 // cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset + dropPointY,
769 // m_needleWidth,
770 // m_arrowLength,
771 // m_arrowWidth);
772  }
773  } else { // Contrasting arrow for built-in analog meter
774  g.setColour(m_backgroundColour.contrasting(1.0f).withAlpha(0.9f));
775 // g.drawArrow(
776 // w/2 + m_inset,
777 // h - m_segments + m_inset,
778 // sin(angle)*jmax (w/2, h/2) + w/2 + m_inset,
779 // cos(angle)*jmax (w/2, h/2) + h - m_segments + m_inset,
780 // m_needleWidth,
781 // m_needleWidth*2,
782 // m_needleWidth*2);
783  }
784  if (m_overlay) {
785  g.setOpacity(1.0f);
786  g.drawImage(*m_overlay, m_inset, m_inset, w, h, 0, 0, m_overlay->getWidth(), m_overlay->getHeight());
787  }
788  }
789 }
790 
791 // The callback simply sets the meter's value to the peak level (no rms yet - fix me)
792 
793 void VUMeter::audioDeviceIOCallback (const float** inputChannelData,
794  int totalNumInputChannels,
795  float** outputChannelData,
796  int totalNumOutputChannels,
797  int numSamples) {
798  float peak = 0.0;
799  float samp = 0.0f;
800  float * sampPtr = outputChannelData[m_channel];
801  for (int j = 0; j < numSamples; j++) {
802  samp = fabs(*sampPtr++);
803  if (samp > peak)
804  peak = samp;
805  }
806  this->setValue(peak * m_scale);
807 }
#define max(a, b)
Definition: matrix.h:156
float m_skew
Definition: JCSL_Widgets.h:194
void audioDeviceIOCallback(const float **inputChannelData, int totalNumInputChannels, float **outputChannelData, int totalNumOutputChannels, int numSamples)
#define csl_min(a, b)
void setDecay(int decay, int hold, float percent=0.707f)
int m_needleLength
Definition: JCSL_Widgets.h:216
int m_decayTime
Definition: JCSL_Widgets.h:206
void audioDeviceAboutToStart(double sampleRate, int numSamplesPerBlock)
#define min(a, b)
Definition: matrix.h:157
int m_inset
Definition: JCSL_Widgets.h:199
float m_decayPercent
Definition: JCSL_Widgets.h:207
juce::Colour m_needleColour
Definition: JCSL_Widgets.h:220
unsigned m_channel
Definition: JCSL_Widgets.h:200
virtual void audioDeviceIOCallback(const float **inputChannelData, int totalNumInputChannels, float **outputChannelData, int totalNumOutputChannels, int numSamples)
float m_threshold
Definition: JCSL_Widgets.h:195
#define csl_max(a, b)
int m_markerWidth
Definition: JCSL_Widgets.h:198
void paint(juce::Graphics &g)
virtual void timerCallback()
void addSample(const float sample)
#define CSL_mMaxBufferFrames
max block size (set large for zooming scopes)
Definition: CSL_Types.h:100
juce::Colour m_minColour
Definition: JCSL_Widgets.h:202
float m_decayToValue
Definition: JCSL_Widgets.h:208
juce::Image * m_background
Definition: JCSL_Widgets.h:211
void buildImage(void)
void setValue(float v)
float sample
(could be changed to int, or double)
Definition: CSL_Types.h:191
int volatile bufferPos
Definition: JCSL_Widgets.h:58
void setBounds(int x, int y, int width, int height)
virtual void paint(juce::Graphics &g)
int m_hold
Definition: JCSL_Widgets.h:209
virtual void paint(juce::Graphics &g)
int m_meterType
Definition: JCSL_Widgets.h:196
int volatile numSamplesIn
Definition: JCSL_Widgets.h:58
juce::Colour m_thresholdColour
Definition: JCSL_Widgets.h:203
juce::Colour m_backgroundColour
Definition: JCSL_Widgets.h:205
juce::Point< int > m_needleCenter
Definition: JCSL_Widgets.h:215
float m_maxPosition
Definition: JCSL_Widgets.h:214
void initialise(int channel, unsigned rate, unsigned window, bool zeroX)
void setColours(juce::Colour &min, juce::Colour &threshold, juce::Colour &max, juce::Colour &back)
float m_minPosition
Definition: JCSL_Widgets.h:213
bool m_raised
Definition: JCSL_Widgets.h:201
unsigned samplesToAverage
Definition: JCSL_Widgets.h:60
float m_value
Definition: JCSL_Widgets.h:193
juce::Colour m_maxColour
Definition: JCSL_Widgets.h:204
int m_dropDistance
Definition: JCSL_Widgets.h:222
juce::Image * m_img
Definition: JCSL_Widgets.h:192
void setFrame(int inset, bool raised=false)
float m_scale
Definition: JCSL_Widgets.h:223
int m_needleDropShadow
Definition: JCSL_Widgets.h:221
int volatile bufferSize
Definition: JCSL_Widgets.h:58
int m_segments
Definition: JCSL_Widgets.h:197
int m_monostable
Definition: JCSL_Widgets.h:210
#define SetPixel(g, x, y)
juce::Image * m_overlay
Definition: JCSL_Widgets.h:212
virtual void audioDeviceIOCallback(const float **inputChannelData, int totalNumInputChannels, float **outputChannelData, int totalNumOutputChannels, int numSamples)