1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #include "CMetersWindow.h"
22
23 /*
24 * I would put this in fox_compat.h, but then I get 'multiple definitions' linker errors, this also means
25 * that I will need to do something different if I start to use it in more than just CMetersWindow.cpp
26 */
27 #ifndef FXBACKBUFFEREDCANVAS_H
28 // if Jeroen hasn't added it yet to libfox, include my implementation of it
29 #include "FXBackBufferedCanvas.h"
30 #endif
31
32
33 #include <math.h>
34
35 #include <stdexcept>
36 #include <algorithm>
37
38 #include "../backend/CSound_defs.h"
39 #include "../backend/unit_conv.h"
40 #include "../backend/ASoundPlayer.h"
41
42 #include "settings.h"
43
44
45 /*
46 ??? I need to make this more general so I can use it for recording or playback
47 I need to make the analyzer a separate widget
48 and for both the meter and the analyzer I need to push the information to it rather than have it pull from ASoundPlayer
49 so that the meter and analyzer widgets are not tied to using ASoundPlayer
50 */
51
52
53 // color definitions
54 #define M_BACKGROUND (FXRGB(0,0,0))
55 #define M_TEXT_COLOR (FXRGB(220,220,220))
56 #define M_METER_OFF (FXRGB(48,48,48))
57
58 #define M_GREEN (FXRGB(80,255,32))
59 #define M_YELLOW (FXRGB(255,212,48))
60 #define M_RED (FXRGB(255,38,0))
61
62 #define M_BRT_GREEN (FXRGB(144,255,96))
63 #define M_BRT_YELLOW (FXRGB(255,255,112))
64 #define M_BRT_RED (FXRGB(255,38,0))
65
66 #define M_PHASE_METER_AXIS (FXRGB(100,100,100))
67
68 #define MIN_METER_HEIGHT 15
69 #define MIN_METERS_WINDOW_HEIGHT 75
70
71 #define M_RMS_BALANCE_COLOR M_GREEN
72 #define M_PEAK_BALANCE_COLOR M_GREEN
73
74 #define ANALYZER_BAR_WIDTH 3
75
76 #define NUM_LEVEL_METER_TICKS 17
77 #define NUM_BALANCE_METER_TICKS 9
78
79 // --- CLevelMeter -----------------------------------------------------------
80
81 class CLevelMeter : public FXHorizontalFrame
82 {
83 FXDECLARE(CLevelMeter);
84 public:
CLevelMeter(FXComposite * parent)85 CLevelMeter(FXComposite *parent) :
86 FXHorizontalFrame(parent,LAYOUT_FILL_X|LAYOUT_FIX_HEIGHT|LAYOUT_TOP | FRAME_NONE,0,0,0,0, 0,0,0,0, 0,0),
87 statusFont(getApp()->getNormalFont()),
88 canvas(new FXCanvas(this,this,ID_CANVAS,FRAME_NONE | LAYOUT_FILL_X|LAYOUT_FILL_Y)),
89 grandMaxPeakLevelLabel(new FXLabel(this,"",NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_FIX_WIDTH|LAYOUT_FILL_Y,0,0,0,0, 1,1,0,0)),
90 RMSLevel(0),
91 peakLevel(0),
92 maxPeakLevel(0),
93 grandMaxPeakLevel(0),
94 maxPeakFallTimer(0),
95 stipplePattern(NULL)
96 {
97 setBackColor(M_BACKGROUND);
98
99 // create the font to use for numbers
100 FXFontDesc d;
101 statusFont->getFontDesc(d);
102 d.size=60;
103 d.weight=FONTWEIGHT_NORMAL;
104 statusFont=new FXFont(getApp(),d);
105
106
107 grandMaxPeakLevelLabel->setTarget(this);
108 grandMaxPeakLevelLabel->setSelector(ID_GRAND_MAX_PEAK_LEVEL_LABEL);
109 grandMaxPeakLevelLabel->setFont(statusFont);
110 grandMaxPeakLevelLabel->setTextColor(M_TEXT_COLOR);
111 grandMaxPeakLevelLabel->setBackColor(M_BACKGROUND);
112
113
114 //static char pix[]={0x55,0x2a};
115 //stipplePattern=new FXBitmap(getApp(),pix,0,8,2);
116
117 static unsigned char pix[]={0xaa,0x55};
118 stipplePattern=new FXBitmap(getApp(),pix,0,8,2);
119
120 stipplePattern->create();
121
122 }
123
~CLevelMeter()124 ~CLevelMeter()
125 {
126 delete statusFont;
127 }
128
create()129 void create()
130 {
131 FXHorizontalFrame::create();
132 setGrandMaxPeakLevel(0);
133 setHeight(max(statusFont->getFontHeight(),MIN_METER_HEIGHT)); // make meter only as tall as necessary (also with a defined minimum)
134 }
135
onCanvasPaint(FXObject * sender,FXSelector sel,void * ptr)136 long onCanvasPaint(FXObject *sender,FXSelector sel,void *ptr)
137 {
138 FXDCWindow dc(canvas);
139
140 const FXint width=canvas->getWidth();
141 const FXint height=canvas->getHeight();
142
143 // draw NUM_LEVEL_METER_TICKS tick marks above level indication
144 dc.setForeground(M_BACKGROUND);
145 dc.fillRectangle(0,0,width,2);
146 dc.setForeground(M_TEXT_COLOR);
147 for(int t=0;t<NUM_LEVEL_METER_TICKS;t++)
148 {
149 const int x=(width-1)*t/(NUM_LEVEL_METER_TICKS-1);
150 dc.drawLine(x,0,x,1);
151 }
152
153 // draw horz line below level indication
154 dc.setForeground(M_TEXT_COLOR);
155 dc.drawLine(0,height-1,width,height-1);
156
157 // draw gray background underneath the stippled level indication
158 dc.setForeground(M_METER_OFF);
159 dc.fillRectangle(0,2,width,height-3);
160
161
162 // if the global setting is disabled, stop drawing right here
163 if(!gLevelMetersEnabled)
164 return 1;
165
166
167 // draw RMS level indication
168 FXint x=(FXint)(RMSLevel*width/MAX_SAMPLE);
169 dc.setFillStyle(FILL_STIPPLED);
170 dc.setStipple(stipplePattern);
171 if(x>(width*3/4))
172 {
173 dc.setForeground(M_GREEN); // green
174 dc.fillRectangle(0,2,width/2,height-3);
175 dc.setForeground(M_YELLOW); // yellow
176 dc.fillRectangle(width/2,2,width/4,height-3);
177 dc.setForeground(M_RED); // red
178 dc.fillRectangle(width*3/4,2,x-(width*3/4),height-3);
179 }
180 else if(x>(width/2))
181 {
182 dc.setForeground(M_GREEN); // green
183 dc.fillRectangle(0,2,width/2,height-3);
184 dc.setForeground(M_YELLOW); // yellow
185 dc.fillRectangle(width/2,2,x-width/2,height-3);
186 }
187 else
188 {
189 dc.setForeground(M_GREEN); // green
190 dc.fillRectangle(0,2,x,height-3);
191 }
192
193 // draw peak indication
194 FXint y=height/2;
195 x=(FXint)(peakLevel*width/MAX_SAMPLE);
196 dc.setFillStyle(FILL_SOLID);
197 if(x>(width*3/4))
198 {
199 dc.setForeground(M_GREEN);
200 dc.fillRectangle(0,y,width/2,2);
201
202 dc.setForeground(M_YELLOW);
203 dc.fillRectangle(width/2,y,width/4,2);
204
205 dc.setForeground(M_RED);
206 dc.fillRectangle(width*3/4,y,x-(width*3/4),2);
207 }
208 else if(x>(width/2))
209 {
210 dc.setForeground(M_GREEN);
211 dc.fillRectangle(0,y,width/2,2);
212
213 dc.setForeground(M_YELLOW);
214 dc.fillRectangle(width/2,y,x-width/2,2);
215 }
216 else
217 {
218 dc.setForeground(M_GREEN);
219 dc.fillRectangle(0,y,x,2);
220 }
221
222 // draw the max peak indicator
223 x=(FXint)(maxPeakLevel*width/MAX_SAMPLE);
224 dc.setFillStyle(FILL_SOLID);
225 if(x>(width*3/4))
226 dc.setForeground(M_BRT_RED);
227 else if(x>(width/2))
228 dc.setForeground(M_BRT_YELLOW);
229 else
230 dc.setForeground(M_BRT_GREEN);
231 dc.fillRectangle(x-1,2,2,height-3);
232
233 return 1;
234 }
235
setLevel(sample_t _RMSLevel,sample_t _peakLevel)236 void setLevel(sample_t _RMSLevel,sample_t _peakLevel)
237 {
238 RMSLevel=_RMSLevel;
239 peakLevel=_peakLevel;
240
241 // start decreasing the max peak level after maxPeakFallTimer falls below zero
242 if((--maxPeakFallTimer)<0)
243 {
244 maxPeakLevel=maxPeakLevel-(sample_t)(MAX_SAMPLE*gMaxPeakFallRate);
245 maxPeakLevel=maxPeakLevel<0 ? 0 : maxPeakLevel;
246 maxPeakFallTimer=0;
247 }
248
249 // if the peak level is >= the maxPeakLevel then set a new maxPeakLevel and reset the peak file timer
250 if(peakLevel>=maxPeakLevel)
251 {
252 maxPeakLevel=peakLevel;
253 maxPeakFallTimer=gMaxPeakFallDelayTime/gMeterUpdateTime;
254 }
255
256 if(peakLevel>grandMaxPeakLevel)
257 setGrandMaxPeakLevel(peakLevel); // sets the label and everything
258
259 canvas->update(); // flag for repainting
260 }
261
getRMSLevel() const262 sample_t getRMSLevel() const
263 {
264 return RMSLevel;
265 }
266
getPeakLevel() const267 sample_t getPeakLevel() const
268 {
269 return peakLevel;
270 }
271
setGrandMaxPeakLevel(const sample_t maxPeakLevel)272 void setGrandMaxPeakLevel(const sample_t maxPeakLevel)
273 {
274 grandMaxPeakLevel=maxPeakLevel;
275 grandMaxPeakLevelLabel->setText((istring(amp_to_dBFS(grandMaxPeakLevel),3,1,false)+" dBFS").c_str());
276
277 // and make sure grandMaxPeakLevelLabel is wide enough for the text
278 const FXint w=statusFont->getTextWidth(grandMaxPeakLevelLabel->getText().text(),grandMaxPeakLevelLabel->getText().length());
279 if(grandMaxPeakLevelLabel->getWidth()<w+2)
280 grandMaxPeakLevelLabel->setWidth(w+2);
281
282 }
283
onResetGrandMaxPeakLevel(FXObject * sender,FXSelector,void *)284 long onResetGrandMaxPeakLevel(FXObject *sender,FXSelector,void*)
285 {
286 setGrandMaxPeakLevel(0);
287 return 1;
288 }
289
290 enum
291 {
292 ID_CANVAS=FXHorizontalFrame::ID_LAST,
293 ID_GRAND_MAX_PEAK_LEVEL_LABEL
294 };
295
296 protected:
CLevelMeter()297 CLevelMeter() { }
298
299 private:
300 friend class CMetersWindow;
301
302 FXFont *statusFont;
303 FXCanvas *canvas;
304 FXLabel *grandMaxPeakLevelLabel;
305 mix_sample_t RMSLevel,peakLevel,maxPeakLevel,grandMaxPeakLevel;
306 int maxPeakFallTimer;
307 FXBitmap *stipplePattern;
308
309 };
310
311 FXDEFMAP(CLevelMeter) CLevelMeterMap[]=
312 {
313 // Message_Type ID Message_Handler
314 FXMAPFUNC(SEL_PAINT, CLevelMeter::ID_CANVAS, CLevelMeter::onCanvasPaint),
315 FXMAPFUNC(SEL_LEFTBUTTONRELEASE, CLevelMeter::ID_GRAND_MAX_PEAK_LEVEL_LABEL, CLevelMeter::onResetGrandMaxPeakLevel),
316 };
317
318 FXIMPLEMENT(CLevelMeter,FXHorizontalFrame,CLevelMeterMap,ARRAYNUMBER(CLevelMeterMap))
319
320
321
322
323
324 // --- CBalanceMeter -----------------------------------------------------------
325
326 class CBalanceMeter : public FXHorizontalFrame
327 {
328 FXDECLARE(CBalanceMeter);
329 public:
CBalanceMeter(FXComposite * parent)330 CBalanceMeter(FXComposite *parent) :
331 FXHorizontalFrame(parent,LAYOUT_FILL_X|LAYOUT_FIX_HEIGHT | FRAME_NONE, 0,0,0,0, 0,0,0,0, 0,0),
332 statusFont(getApp()->getNormalFont()),
333 leftLabel(new FXLabel(this,"-1.0")),
334 canvas(new FXCanvas(this,this,ID_CANVAS,FRAME_NONE | LAYOUT_FILL_X|LAYOUT_FILL_Y)),
335 rightLabel(new FXLabel(this,"+1.0")),
336 RMSBalance(0),
337 peakBalance(0),
338 stipplePattern(NULL)
339 {
340 setBackColor(M_BACKGROUND);
341
342 // create the font to use for numbers
343 FXFontDesc d;
344 statusFont->getFontDesc(d);
345 d.size=60;
346 d.weight=FONTWEIGHT_NORMAL;
347 statusFont=new FXFont(getApp(),d);
348
349 leftLabel->setFont(statusFont);
350 leftLabel->setTextColor(M_TEXT_COLOR);
351 leftLabel->setBackColor(M_BACKGROUND);
352
353 rightLabel->setFont(statusFont);
354 rightLabel->setTextColor(M_TEXT_COLOR);
355 rightLabel->setBackColor(M_BACKGROUND);
356
357 static unsigned char pix[]={0xaa,0x55};
358 stipplePattern=new FXBitmap(getApp(),pix,0,8,2);
359
360 stipplePattern->create();
361
362 }
363
~CBalanceMeter()364 ~CBalanceMeter()
365 {
366 delete statusFont;
367 }
368
create()369 void create()
370 {
371 FXHorizontalFrame::create();
372 setHeight(max(statusFont->getFontHeight(),MIN_METER_HEIGHT)); // make meter only as tall as necessary (also with a defined minimum)
373 }
374
onCanvasPaint(FXObject * sender,FXSelector sel,void * ptr)375 long onCanvasPaint(FXObject *sender,FXSelector sel,void *ptr)
376 {
377 FXDCWindow dc(canvas);
378
379 const FXint width=canvas->getWidth();
380 const FXint height=canvas->getHeight();
381
382 // draw NUM_LEVEL_METER_TICKS tick marks above level indication
383 dc.setForeground(M_BACKGROUND);
384 dc.fillRectangle(0,0,width,2);
385 dc.setForeground(M_TEXT_COLOR);
386 for(int t=0;t<NUM_BALANCE_METER_TICKS;t++)
387 {
388 const int x=(width-1)*t/(NUM_BALANCE_METER_TICKS-1);
389 dc.drawLine(x,0,x,1);
390 }
391
392 // draw horz line below level indication
393 dc.setForeground(M_TEXT_COLOR);
394 dc.drawLine(0,height-1,width,height-1);
395
396 // draw gray background underneath the stippled level indication
397 dc.setForeground(M_METER_OFF);
398 dc.fillRectangle(0,2,width,height-3);
399
400
401 // if the global setting is disabled, stop drawing right here
402 if(!gLevelMetersEnabled)
403 return 1;
404
405
406 // draw RMS balance indication
407 FXint x=(FXint)(RMSBalance*width)/2;
408 dc.setFillStyle(FILL_STIPPLED);
409 dc.setStipple(stipplePattern);
410
411 dc.setForeground(M_RMS_BALANCE_COLOR);
412 if(x>0)
413 dc.fillRectangle(width/2,2,x,height-3);
414 else // drawing has to be done a little differently when x is negative
415 dc.fillRectangle(width/2+x,2,-x,height-3);
416
417
418 // draw the peak balance indicator
419 FXint y=height/2;
420 x=(FXint)(peakBalance*width)/2;
421 dc.setFillStyle(FILL_SOLID);
422 dc.setForeground(M_PEAK_BALANCE_COLOR);
423 if(x>0)
424 dc.fillRectangle(width/2,y,x,2);
425 else // drawing has to be done a little differently when x is negative
426 dc.fillRectangle(width/2+x,y,-x,2);
427
428 dc.setForeground(M_TEXT_COLOR);
429 dc.drawLine(width/2,2,width/2,height-1);
430
431 return 1;
432 }
433
setBalance(sample_t leftRMSLevel,sample_t rightRMSLevel,sample_t leftPeakLevel,sample_t rightPeakLevel)434 void setBalance(sample_t leftRMSLevel,sample_t rightRMSLevel,sample_t leftPeakLevel,sample_t rightPeakLevel)
435 {
436 RMSBalance=((float)rightRMSLevel-(float)leftRMSLevel)/MAX_SAMPLE;
437 peakBalance=((float)rightPeakLevel-(float)leftPeakLevel)/MAX_SAMPLE;
438 canvas->update(); // flag for repainting
439 }
440
441 enum
442 {
443 ID_CANVAS=FXHorizontalFrame::ID_LAST
444 };
445
446 protected:
CBalanceMeter()447 CBalanceMeter() { }
448
449 private:
450 FXFont *statusFont;
451
452 FXLabel *leftLabel;
453 FXCanvas *canvas;
454 FXLabel *rightLabel;
455
456 float RMSBalance;
457 float peakBalance;
458
459 FXBitmap *stipplePattern;
460
461 };
462
463 FXDEFMAP(CBalanceMeter) CBalanceMeterMap[]=
464 {
465 // Message_Type ID Message_Handler
466 FXMAPFUNC(SEL_PAINT, CBalanceMeter::ID_CANVAS, CBalanceMeter::onCanvasPaint),
467 };
468
469 FXIMPLEMENT(CBalanceMeter,FXHorizontalFrame,CBalanceMeterMap,ARRAYNUMBER(CBalanceMeterMap))
470
471
472
473
474
475 // --- CStereoPhaseMeter -------------------------------------------------------------
476
477 class CStereoPhaseMeter : public FXHorizontalFrame
478 {
479 FXDECLARE(CStereoPhaseMeter);
480 public:
CStereoPhaseMeter(FXComposite * parent,sample_t * _samplingBuffer,size_t _samplingNFrames,unsigned _samplingNChannels,unsigned _samplingLeftChannel,unsigned _samplingRightChannel)481 CStereoPhaseMeter(FXComposite *parent,sample_t *_samplingBuffer,size_t _samplingNFrames,unsigned _samplingNChannels,unsigned _samplingLeftChannel,unsigned _samplingRightChannel) :
482 FXHorizontalFrame(parent,LAYOUT_RIGHT|LAYOUT_FIX_WIDTH|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0, 0,0),
483 canvasFrame(new FXVerticalFrame(this,LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK,0,0,0,0, 2,2,2,2, 0,1)),
484 canvas(new FXBackBufferedCanvas(canvasFrame,this,ID_CANVAS,LAYOUT_FILL_X|LAYOUT_FILL_Y)),
485 zoomDial(new FXDial(this,this,ID_ZOOM_DIAL,DIAL_VERTICAL|DIAL_HAS_NOTCH|LAYOUT_FILL_Y|LAYOUT_FIX_WIDTH,0,0,16,0)),
486
487 statusFont(getApp()->getNormalFont()),
488
489 samplingBuffer(_samplingBuffer),
490 samplingNFrames(_samplingNFrames),
491 samplingNChannels(_samplingNChannels),
492 samplingLeftChannel(_samplingLeftChannel),
493 samplingRightChannel(_samplingRightChannel),
494
495 clear(true)
496 {
497 hide();
498 setBackColor(M_BACKGROUND);
499 canvasFrame->setBackColor(M_BACKGROUND);
500
501 #if REZ_FOX_VERSION<10129
502 canvas->setBackBufferOptions(IMAGE_OWNED|IMAGE_ALPHA); // IMAGE_ALPHA: the alpha channel is not used, but it's a place holder so I can work with 32bit values
503 #else
504 canvas->setBackBufferOptions(IMAGE_OWNED); /* 1.1.29 and later always has 32bit image data */
505 #endif
506
507 zoomDial->setRange(100,400);
508 zoomDial->setValue(100);
509 zoomDial->setRevolutionIncrement((400-100+10)*2);
510 zoomDial->setTipText(_("Adjust Zoom Factor for Stereo Phase Meter\nAll the way down means no zooming"));
511 zoom=((float)zoomDial->getValue())/100.0f;
512
513
514 // create the font to use for numbers
515 FXFontDesc d;
516 statusFont->getFontDesc(d);
517 d.size=60;
518 d.weight=FONTWEIGHT_NORMAL;
519 statusFont=new FXFont(getApp(),d);
520 }
521
~CStereoPhaseMeter()522 ~CStereoPhaseMeter()
523 {
524 delete statusFont;
525 }
526
onZoomDial(FXObject * sender,FXSelector sel,void * ptr)527 long onZoomDial(FXObject *sender,FXSelector sel,void *ptr)
528 {
529 zoom=((float)zoomDial->getValue())/100.0f;
530 canvas->update(); // not really necessary since we're doing it several times a second anyway
531 return 1;
532 }
533
onResize(FXObject * sender,FXSelector sel,void * ptr)534 long onResize(FXObject *sender,FXSelector sel,void *ptr)
535 {
536 // make square
537 resize(getHeight()+getHSpacing()+zoomDial->getWidth(),getHeight());
538 recalcRotateLookup();
539 clearCanvas();
540 return 1;
541 }
542
onCanvasPaint(FXObject * sender,FXSelector sel,void * ptr)543 long onCanvasPaint(FXObject *sender,FXSelector sel,void *ptr)
544 {
545 FXColor *data=(FXColor *)canvas->getBackBufferData();
546
547 // the w and h that we're going to render to (minus some borders and tick marks)
548 const FXint canvasSize=canvas->getWidth(); // w==h is guarenteed in onResize()
549
550 if(clear)
551 {
552 memset(data,0,canvasSize*canvasSize*sizeof(FXColor));
553 clear=false;
554 }
555
556 // if the global setting is disabled, stop drawing right here
557 if(!gStereoPhaseMetersEnabled)
558 return 1;
559
560 // fade previous frame (so we get a ghosting history)
561 for(FXint t=0;t<canvasSize*canvasSize;t++)
562 {
563 const FXColor c=data[t];
564 data[t]= c==0 ? 0 : FXRGB( FXREDVAL(c)*7/8, FXGREENVAL(c)*7/8, FXBLUEVAL(c)*7/8 );
565 }
566
567 // draw the axies
568 for(FXint t=0;t<canvasSize;t++)
569 {
570 // I have no idea with '-canvasSize/2' is necessary but it doesn't look right if I omit it
571 data[t+(canvasSize*canvasSize/2)-canvasSize/2]=M_PHASE_METER_AXIS; // horz
572 data[(t*canvasSize)+canvasSize/2]=M_PHASE_METER_AXIS; // vert
573 }
574
575 // draw the points
576 for(size_t t=0;t<samplingNFrames;t++)
577 {
578 // let x and y be the normalized (1.0) sample values (x:right y:left) then scaled up to the canvas width/height and centered in the square
579 const FXint x=(FXint)( (zoom*samplingBuffer[t*samplingNChannels+samplingRightChannel])*canvasSize/2/MAX_SAMPLE) + canvasSize/2;
580 const FXint y=(FXint)(-(zoom*samplingBuffer[t*samplingNChannels+samplingLeftChannel ])*canvasSize/2/MAX_SAMPLE) + canvasSize/2; // negation because increasing values go down on the screen which is up-side-down from the Cartesian plane
581
582 if(x>=0 && x<canvasSize && y>=0 && y<canvasSize)
583 {
584 if(gStereoPhaseMeterUnrotate)
585 data[unrotateMapping[y*canvasSize+x]]=M_BRT_GREEN;
586 else
587 data[y*canvasSize+x]=M_BRT_GREEN;
588
589 }
590 }
591
592 return 0;
593 }
594
onPopupMenu(FXObject * object,FXSelector sel,void * ptr)595 long onPopupMenu(FXObject *object,FXSelector sel,void *ptr)
596 {
597 FXEvent *event=(FXEvent*)ptr;
598
599 FXMenuPane popupMenu(this);
600 // ??? make sure that these get deleted when gotoMenu is deleted
601 #if REZ_FOX_VERSION>=10119
602 (new FXMenuCheck(&popupMenu,_("Unrotate from Natural 45 Degree Line"),this, ID_UNROTATE))->setCheck(gStereoPhaseMeterUnrotate);
603 #else
604 FXMenuCommand *m=new FXMenuCommand(&popupMenu,_("Unrotate from Natural 45 Degree Line"),NULL,this, ID_UNROTATE);
605 if(gStereoPhaseMeterUnrotate)
606 m->check();
607 else
608 m->uncheck();
609 #endif
610
611 popupMenu.create();
612 popupMenu.popup(NULL,event->root_x,event->root_y);
613 getApp()->runModalWhileShown(&popupMenu);
614
615 return 0;
616 }
617
onUnrotateMenuItem(FXObject * object,FXSelector sel,void * ptr)618 long onUnrotateMenuItem(FXObject *object,FXSelector sel,void *ptr)
619 {
620 #if REZ_FOX_VERSION>=10119
621 gStereoPhaseMeterUnrotate= ((FXMenuCheck *)object)->getCheck() ? true : false;
622 #else
623 if(dynamic_cast<FXMenuCommand *>(object)->isChecked())
624 dynamic_cast<FXMenuCommand *>(object)->uncheck();
625 else
626 dynamic_cast<FXMenuCommand *>(object)->check();
627 gStereoPhaseMeterUnrotate= ((FXMenuCommand *)object)->isChecked() ? true : false;
628 #endif
629 return 0;
630 }
631
updateCanvas()632 void updateCanvas()
633 {
634 canvas->update();
635 }
636
clearCanvas()637 void clearCanvas()
638 {
639 clear=true;
640 }
641
642 enum
643 {
644 ID_CANVAS=FXHorizontalFrame::ID_LAST,
645 ID_ZOOM_DIAL,
646 ID_UNROTATE,
647 };
648
649 protected:
CStereoPhaseMeter()650 CStereoPhaseMeter() : samplingNFrames(0), samplingNChannels(0),samplingLeftChannel(0), samplingRightChannel(0) { }
651
652 private:
653 FXPacker *canvasFrame;
654 FXBackBufferedCanvas *canvas;
655 FXDial *zoomDial;
656 FXFont *statusFont;
657
658 const sample_t *samplingBuffer;
659 const size_t samplingNFrames;
660 const unsigned samplingNChannels;
661 const unsigned samplingLeftChannel;
662 const unsigned samplingRightChannel;
663
664 bool clear;
665
666 float zoom;
667
668 TAutoBuffer<FXint> unrotateMapping; // width*height number of pixels mapping
669
recalcRotateLookup()670 void recalcRotateLookup()
671 {
672 const FXint width=canvas->getWidth();
673 const FXint height=canvas->getHeight();
674
675 unrotateMapping.setSize(width*height);
676
677 const double ang=-M_PI_4; // -45 degrees
678
679 for(FXint sx=0;sx<width;sx++)
680 for(FXint sy=0;sy<height;sy++)
681 {
682 double wx=sx-width/2;
683 double wy=sy-height/2;
684
685 // shrink square so that the corners aren't cut off when rotated
686 // (this also fixes the gaps between pixels if I don't shrink it)
687 wx*=(sqrt(2.0)/2.0);
688 wy*=(sqrt(2.0)/2.0);
689
690 double rot_wx=wx*cos(ang)-wy*sin(ang);
691 double rot_wy=wx*sin(ang)+wy*cos(ang);
692
693 rot_wx+=width/2;
694 rot_wy+=height/2;
695
696 rot_wx=round(rot_wx);
697 rot_wy=round(rot_wy);
698 FXint offset=(FXint)(rot_wy*width+rot_wx);
699 if((rot_wx>=0 && rot_wx<width && rot_wy>=0 && rot_wy<height) && (offset>=0 && offset<(width*height)))
700 unrotateMapping[sy*width+sx]=offset;
701 else
702 unrotateMapping[sy*width+sx]=0;
703 }
704
705 }
706 };
707
708 FXDEFMAP(CStereoPhaseMeter) CStereoPhaseMeterMap[]=
709 {
710 // Message_Type ID Message_Handler
711 FXMAPFUNC(SEL_PAINT, CStereoPhaseMeter::ID_CANVAS, CStereoPhaseMeter::onCanvasPaint),
712 FXMAPFUNC(SEL_CONFIGURE, 0, CStereoPhaseMeter::onResize),
713 FXMAPFUNC(SEL_RIGHTBUTTONPRESS, CStereoPhaseMeter::ID_CANVAS, CStereoPhaseMeter::onPopupMenu),
714 FXMAPFUNC(SEL_COMMAND, CStereoPhaseMeter::ID_UNROTATE, CStereoPhaseMeter::onUnrotateMenuItem),
715 FXMAPFUNC(SEL_CHANGED, CStereoPhaseMeter::ID_ZOOM_DIAL,CStereoPhaseMeter::onZoomDial),
716 };
717
718 FXIMPLEMENT(CStereoPhaseMeter,FXHorizontalFrame,CStereoPhaseMeterMap,ARRAYNUMBER(CStereoPhaseMeterMap))
719
720
721
722
723
724
725
726
727 // --- CAnalyzer -------------------------------------------------------------
728
729 class CAnalyzer : public FXHorizontalFrame
730 {
731 FXDECLARE(CAnalyzer);
732 public:
CAnalyzer(FXComposite * parent)733 CAnalyzer(FXComposite *parent) :
734 FXHorizontalFrame(parent,LAYOUT_RIGHT|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0, 0,0),
735 canvasFrame(new FXVerticalFrame(this,LAYOUT_FIX_WIDTH|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK,0,0,0,0, 2,2,2,2, 0,1)),
736 canvas(new FXBackBufferedCanvas(canvasFrame,this,ID_CANVAS,LAYOUT_FILL_X|LAYOUT_FILL_Y)),
737 labelFrame(new FXHorizontalFrame(canvasFrame,LAYOUT_FILL_X|FRAME_NONE,0,0,0,0, 0,0,0,0, 0,0)),
738
739 zoomDial(new FXDial(this,this,ID_ZOOM_DIAL,DIAL_VERTICAL|DIAL_HAS_NOTCH|LAYOUT_FILL_Y|LAYOUT_FIX_WIDTH,0,0,16,0)),
740
741 statusFont(getApp()->getNormalFont()),
742
743 drawBarFreq(false)
744 {
745 hide();
746 setBackColor(M_BACKGROUND);
747 canvasFrame->setBackColor(M_BACKGROUND);
748 labelFrame->setBackColor(M_BACKGROUND);
749
750 // create the font to use for numbers
751 FXFontDesc d;
752 statusFont->getFontDesc(d);
753 d.size=60;
754 d.weight=FONTWEIGHT_NORMAL;
755 statusFont=new FXFont(getApp(),d);
756
757 zoomDial->setRange(1,200);
758 zoomDial->setValue(25);
759 zoomDial->setRevolutionIncrement(200*2);
760 zoomDial->setTipText(_("Adjust Zoom Factor for Analyzer\nRight-click for Default"));
761 zoom=zoomDial->getValue();
762
763 canvasFrame->setWidth(150);
764 }
765
~CAnalyzer()766 ~CAnalyzer()
767 {
768 delete statusFont;
769 }
770
onZoomDial(FXObject * sender,FXSelector sel,void * ptr)771 long onZoomDial(FXObject *sender,FXSelector sel,void *ptr)
772 {
773 zoom=zoomDial->getValue();
774 canvas->update(); // not really necessary since we're doing it several times a second anyway
775 return 1;
776 }
777
onZoomDialDefault(FXObject * sender,FXSelector sel,void * ptr)778 long onZoomDialDefault(FXObject *sender,FXSelector sel,void *ptr)
779 {
780 zoomDial->setValue(25);
781 return onZoomDial(sender,sel,ptr);
782 }
783
onCanvasPaint(FXObject * sender,FXSelector sel,void * ptr)784 long onCanvasPaint(FXObject *sender,FXSelector sel,void *ptr)
785 {
786 FXDC &dc=*((FXDC*)ptr); // back buffered canvases send the DC to draw onto in ptr
787
788 dc.setForeground(M_BACKGROUND);
789 dc.fillRectangle(0,0,canvas->getWidth(),canvas->getHeight());
790
791 // if the global setting is disabled, stop drawing right here
792 if(!gFrequencyAnalyzerEnabled)
793 return 1;
794
795 // the w and h that we're going to render to (minus some borders and tick marks)
796 const size_t canvasWidth=canvas->getWidth();
797 const size_t canvasHeight=canvas->getHeight();
798
799 const size_t barWidth=analysis.size()>0 ? canvasWidth/analysis.size() : 1;
800
801 // draw vertical octave separator lines
802 dc.setForeground(M_METER_OFF);
803 if(octaveStride>0)
804 {
805 for(size_t x=0;x<canvasWidth;x+=(barWidth*octaveStride))
806 dc.drawLine(x,0,x,canvasHeight);
807 }
808
809 // ??? also probably want some dB labels
810 // draw 5 horz lines up the panel
811 dc.setForeground(M_TEXT_COLOR);
812 for(size_t t=0;t<4;t++)
813 {
814 size_t y=((canvasHeight-1)*t/(4-1));
815 dc.drawLine(0,y,canvasWidth,y);
816 }
817
818
819 // draw frequency analysis bars (or render text if fftw wasn't installed)
820 dc.setForeground(M_GREEN);
821 if(analysis.size()>0)
822 {
823 size_t x=0;
824 const size_t drawBarWidth=max(1,(int)barWidth-1);
825 for(size_t t=0;t<analysis.size();t++)
826 {
827 const size_t barHeight=(size_t)floor(analysis[t]*canvasHeight);
828 dc.fillRectangle(x+1,canvasHeight-barHeight,drawBarWidth,barHeight);
829 x+=barWidth;
830 }
831 }
832 #ifndef HAVE_FFTW
833 else
834 {
835 dc.compat_setFont(getApp()->getNormalFont());
836 dc.drawText(3,3+12,_("Configure with FFTW"),19);
837 dc.drawText(3,20+12,_("for Freq. Analysis"),18);
838 }
839 #endif
840
841
842 // draw the peaks
843 dc.setForeground(M_BRT_YELLOW);
844 size_t x=0;
845 for(size_t t=0;t<peaks.size();t++)
846 {
847 const size_t peakHeight=(size_t)floor(peaks[t]*canvasHeight);
848 const FXint y=canvasHeight-peakHeight-1;
849 dc.drawLine(x+1,y,x+barWidth-1,y);
850 x+=barWidth;
851 }
852
853 // if mouse is over the canvas, then report what frequency is to be displaye
854 if(analysis.size()>0 && drawBarFreq && barFreqIndex<frequencies.size())
855 {
856 const string f=istring(frequencies[barFreqIndex])+"Hz";
857 const int w=statusFont->getTextWidth(f.c_str(),f.length());
858 dc.setForeground(M_METER_OFF);
859 dc.fillRectangle(0,0,w+3,statusFont->getFontHeight()+2);
860 dc.setForeground(M_TEXT_COLOR);
861 dc.compat_setFont(statusFont);
862 dc.drawText(1,statusFont->getFontHeight(),f.c_str(),f.length());
863 }
864
865 return 1;
866 }
867
onCanvasEnter(FXObject * sender,FXSelector sel,void * ptr)868 long onCanvasEnter(FXObject *sender,FXSelector sel,void *ptr)
869 {
870 drawBarFreq=true;
871 return onCanvasMotion(sender,sel,ptr);
872 }
873
onCanvasLeave(FXObject * sender,FXSelector sel,void * ptr)874 long onCanvasLeave(FXObject *sender,FXSelector sel,void *ptr)
875 {
876 drawBarFreq=false;
877 return onCanvasMotion(sender,sel,ptr);
878 }
879
onCanvasMotion(FXObject * sender,FXSelector sel,void * ptr)880 long onCanvasMotion(FXObject *sender,FXSelector sel,void *ptr)
881 {
882 barFreqIndex=((FXEvent *)ptr)->win_x/ANALYZER_BAR_WIDTH;
883 return 1;
884 }
885
setAnalysis(const vector<float> & _analysis,size_t _octaveStride)886 bool setAnalysis(const vector<float> &_analysis,size_t _octaveStride)
887 {
888 analysis=_analysis;
889 for(size_t t=0;t<analysis.size();t++)
890 analysis[t]*=zoom;
891
892 octaveStride=_octaveStride;
893
894 // resize the canvas frame if needed
895 FXint desiredWidth;
896 if(analysis.size()>0)
897 desiredWidth=(analysis.size()*ANALYZER_BAR_WIDTH)+canvasFrame->getPadLeft()+canvasFrame->getPadRight()+4/*for sunken frame*/;
898 else
899 desiredWidth=150; // big enough to render a message about installing fftw
900
901 bool rebuildLabels=false;
902
903 if(canvasFrame->getWidth()!=desiredWidth)
904 {
905 canvasFrame->setWidth(desiredWidth);
906 rebuildLabels=true;
907 }
908
909
910 // rebuild peaks if the number of elements in analysis changed from the last time this was called (should really only do anything the first time this is called)
911 if(peaks.size()!=analysis.size())
912 {
913 peaks.clear();
914 for(size_t t=0;t<analysis.size();t++)
915 {
916 peaks.push_back(0.0f);
917 peakFallDelayTimers.push_back(0);
918 }
919 rebuildLabels=true;
920
921 }
922
923 // make peaks fall
924 for(size_t t=0;t<peaks.size();t++)
925 {
926 peakFallDelayTimers[t]=max(0,peakFallDelayTimers[t]-1);
927 if(peakFallDelayTimers[t]==0) // only decrease the peak when the fall timer has reached zero
928 peaks[t]=max(0.0f,(float)(peaks[t]-gAnalyzerPeakFallRate));
929 }
930
931
932 // update peaks if there is a new max
933 for(size_t t=0;t<analysis.size();t++)
934 {
935 if(peaks[t]<analysis[t])
936 {
937 peaks[t]=analysis[t];
938 peakFallDelayTimers[t]=gAnalyzerPeakFallDelayTime/gMeterUpdateTime;
939 }
940 }
941
942 canvas->update(); // flag for repainting
943
944 return rebuildLabels;
945 }
946
rebuildLabels(const vector<size_t> _frequencies,size_t octaveStride)947 void rebuildLabels(const vector<size_t> _frequencies,size_t octaveStride)
948 {
949 frequencies=_frequencies;
950
951 while(labelFrame->numChildren()>0)
952 delete labelFrame->childAtIndex(0);
953
954 for(size_t t=0;t<analysis.size()/octaveStride;t++)
955 {
956 string text;
957 if(frequencies[t*octaveStride]>=1000)
958 text=istring(frequencies[t*octaveStride]/1000.0,1,1)+"k";
959 else
960 text=istring(frequencies[t*octaveStride]);
961
962 FXLabel *l=new FXLabel(labelFrame,text.c_str(),NULL,JUSTIFY_LEFT|LAYOUT_FIX_WIDTH,0,0,0,0, 0,0,0,0);
963 l->setBackColor(M_BACKGROUND);
964 l->setTextColor(M_TEXT_COLOR);
965 l->setFont(statusFont);
966 if(t!=((analysis.size()/octaveStride)-1))
967 l->setWidth(ANALYZER_BAR_WIDTH*octaveStride);
968 else
969 l->setWidth((ANALYZER_BAR_WIDTH*octaveStride)+(ANALYZER_BAR_WIDTH*(analysis.size()%octaveStride))); // make the last one the remaining width
970 l->create();
971 }
972 }
973
clearCanvas()974 void clearCanvas()
975 {
976 vector<float> d2;
977 setAnalysis(d2,1);
978
979 vector<size_t> d1;
980 rebuildLabels(d1,1);
981 }
982
983 enum
984 {
985 ID_CANVAS=FXHorizontalFrame::ID_LAST,
986 ID_ZOOM_DIAL,
987 };
988
989 protected:
CAnalyzer()990 CAnalyzer() { }
991
992 private:
993 FXPacker *canvasFrame;
994 FXBackBufferedCanvas *canvas;
995 FXPacker *labelFrame;
996 FXDial *zoomDial;
997 FXFont *statusFont;
998
999 vector<float> analysis;
1000 vector<float> peaks;
1001 vector<int> peakFallDelayTimers;
1002 size_t octaveStride;
1003 float zoom;
1004
1005 // used when the mouse is over the canvas to draw what the frequency for the pointed-to bar is
1006 vector<size_t> frequencies;
1007 bool drawBarFreq;
1008 size_t barFreqIndex;
1009 };
1010
1011 FXDEFMAP(CAnalyzer) CAnalyzerMap[]=
1012 {
1013 // Message_Type ID Message_Handler
1014 FXMAPFUNC(SEL_PAINT, CAnalyzer::ID_CANVAS, CAnalyzer::onCanvasPaint),
1015 FXMAPFUNC(SEL_ENTER, CAnalyzer::ID_CANVAS, CAnalyzer::onCanvasEnter),
1016 FXMAPFUNC(SEL_LEAVE, CAnalyzer::ID_CANVAS, CAnalyzer::onCanvasLeave),
1017 FXMAPFUNC(SEL_MOTION, CAnalyzer::ID_CANVAS, CAnalyzer::onCanvasMotion),
1018
1019 FXMAPFUNC(SEL_CHANGED, CAnalyzer::ID_ZOOM_DIAL, CAnalyzer::onZoomDial),
1020 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, CAnalyzer::ID_ZOOM_DIAL, CAnalyzer::onZoomDialDefault),
1021 };
1022
1023 FXIMPLEMENT(CAnalyzer,FXHorizontalFrame,CAnalyzerMap,ARRAYNUMBER(CAnalyzerMap))
1024
1025
1026
1027
1028
1029
1030
1031
1032 // --- CMetersWindow ---------------------------------------------------------
1033
1034 FXDEFMAP(CMetersWindow) CMetersWindowMap[]=
1035 {
1036 // Message_Type ID Message_Handler
1037 FXMAPFUNC(SEL_TIMEOUT, CMetersWindow::ID_UPDATE_TIMEOUT, CMetersWindow::onUpdateMeters),
1038 FXMAPFUNC(SEL_CONFIGURE, CMetersWindow::ID_LABEL_FRAME, CMetersWindow::onLabelFrameConfigure),
1039 FXMAPFUNC(SEL_LEFTBUTTONRELEASE, CMetersWindow::ID_GRAND_MAX_PEAK_LEVEL_LABEL, CMetersWindow::onResetGrandMaxPeakLevels),
1040 };
1041
FXIMPLEMENT(CMetersWindow,FXHorizontalFrame,CMetersWindowMap,ARRAYNUMBER (CMetersWindowMap))1042 FXIMPLEMENT(CMetersWindow,FXHorizontalFrame,CMetersWindowMap,ARRAYNUMBER(CMetersWindowMap))
1043
1044
1045 /*
1046 * To update the meters I add a timeout to be fired every x-th of a second.
1047 */
1048
1049 CMetersWindow::CMetersWindow(FXComposite *parent) :
1050 FXHorizontalFrame(parent,LAYOUT_BOTTOM|LAYOUT_FILL_X|LAYOUT_FIX_HEIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 4,4,2,2, 4,1),
1051 statusFont(getApp()->getNormalFont()),
1052 levelMetersFrame(new FXVerticalFrame(this,LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK,0,0,0,0, 3,3,0,2, 0,1)),
1053 headerFrame(new FXHorizontalFrame(levelMetersFrame,LAYOUT_FILL_X|FRAME_NONE,0,0,0,0, 0,0,0,0, 0,0)),
1054 labelFrame(new FXPacker(headerFrame,LAYOUT_FILL_X|LAYOUT_BOTTOM|FRAME_NONE,0,0,0,0, 0,0,0,0, 0,0)),
1055 grandMaxPeakLevelLabel(new FXLabel(headerFrame,"max",NULL,LABEL_NORMAL|LAYOUT_FIX_WIDTH|LAYOUT_RIGHT,0,0,0,0, 1,1,0,0)),
1056 balanceMetersFrame(new FXHorizontalFrame(levelMetersFrame,FRAME_NONE | LAYOUT_BOTTOM | LAYOUT_FILL_X, 0,0,0,0, 0,0,0,0, 0,0)),
1057 balanceMetersRightMargin(new FXLabel(balanceMetersFrame," ",NULL,LAYOUT_RIGHT | FRAME_NONE | LAYOUT_FIX_WIDTH|LAYOUT_FILL_Y)),
1058 analyzer(new CAnalyzer(this)),
1059 soundPlayer(NULL)
1060 {
1061 // create the font to use for meters
1062 FXFontDesc d;
1063 statusFont->getFontDesc(d);
1064 d.size=60;
1065 d.weight=FONTWEIGHT_NORMAL;
1066 statusFont=new FXFont(getApp(),d);
1067
1068 levelMetersFrame->setBackColor(M_BACKGROUND);
1069 headerFrame->setBackColor(M_BACKGROUND);
1070
1071 // create the labels above the tick marks
1072 labelFrame->setTarget(this);
1073 labelFrame->setSelector(ID_LABEL_FRAME);
1074 labelFrame->setBackColor(M_BACKGROUND);
1075 #define MAKE_DB_LABEL(text) { FXLabel *l=new FXLabel(labelFrame,(text),NULL,LAYOUT_FIX_X|LAYOUT_FIX_Y,0,0,0,0, 0,0,0,0); l->setBackColor(M_BACKGROUND); l->setTextColor(M_TEXT_COLOR); l->setFont(statusFont); }
1076 MAKE_DB_LABEL("dBFS") // create the -infinity label as just the units label
1077 for(int t=1;t<NUM_LEVEL_METER_TICKS;t++)
1078 {
1079 istring s;
1080 if(t>NUM_LEVEL_METER_TICKS/2)
1081 s=istring(round(scalar_to_dB((double)t/(NUM_LEVEL_METER_TICKS-1))*10)/10,3,1); // round to nearest tenth
1082 else
1083 s=istring(round(scalar_to_dB((double)t/(NUM_LEVEL_METER_TICKS-1))),3,1); // round to nearest int
1084
1085 if(s.rfind(".0")!=istring::npos) // remove .0's from the right side
1086 s.erase(s.length()-2,2);
1087 MAKE_DB_LABEL(s.c_str())
1088 }
1089
1090 grandMaxPeakLevelLabel->setTarget(this);
1091 grandMaxPeakLevelLabel->setSelector(ID_GRAND_MAX_PEAK_LEVEL_LABEL);
1092 grandMaxPeakLevelLabel->setFont(statusFont);
1093 grandMaxPeakLevelLabel->setTextColor(M_TEXT_COLOR);
1094 grandMaxPeakLevelLabel->setBackColor(M_BACKGROUND);
1095
1096 balanceMetersFrame->setBackColor(M_BACKGROUND);
1097 balanceMetersRightMargin->setFont(statusFont);
1098 balanceMetersRightMargin->setTextColor(M_TEXT_COLOR);
1099 balanceMetersRightMargin->setBackColor(M_BACKGROUND);
1100
1101
1102 // schedule the first update meters event
1103 #if REZ_FOX_VERSION<10322
1104 timeout=getApp()->addTimeout(this,ID_UPDATE_TIMEOUT,gMeterUpdateTime);
1105 #else
1106 getApp()->addTimeout(this,ID_UPDATE_TIMEOUT,gMeterUpdateTime);
1107 #endif
1108 }
1109
~CMetersWindow()1110 CMetersWindow::~CMetersWindow()
1111 {
1112 #if REZ_FOX_VERSION<10322
1113 getApp()->removeTimeout(timeout);
1114 #else
1115 getApp()->removeTimeout(this,ID_UPDATE_TIMEOUT);
1116 #endif
1117 }
1118
onUpdateMeters(FXObject * sender,FXSelector sel,void * ptr)1119 long CMetersWindow::onUpdateMeters(FXObject *sender,FXSelector sel,void *ptr)
1120 {
1121 if(soundPlayer!=NULL && levelMeters.size()>0 && soundPlayer->isInitialized())
1122 {
1123 if(gLevelMetersEnabled)
1124 {
1125 // set the level for all the level meters
1126 for(size_t t=0;t<levelMeters.size();t++)
1127 levelMeters[t]->setLevel(soundPlayer->getRMSLevel(t),soundPlayer->getPeakLevel(t));
1128
1129 // set the balance meter position
1130 for(size_t t=0;t<balanceMeters.size();t++)
1131 // there is a balance meter for every two level meters
1132 // NOTE: ??? using getPeakLevel doesn't given an accurate peaked balance because the two peaks are from two separate points of time.. I'm not sure if this is a really bad issue right now or not
1133 balanceMeters[t]->setBalance(levelMeters[t*2+0]->getRMSLevel(),levelMeters[t*2+1]->getRMSLevel(),levelMeters[t*2+0]->getPeakLevel(),levelMeters[t*2+1]->getPeakLevel());
1134
1135 // make sure all the levelMeters' grandMaxPeakLabels are the same width
1136 int maxGrandMaxPeakLabelWidth=grandMaxPeakLevelLabel->getWidth();
1137 bool resize=false;
1138 for(size_t t=0;t<levelMeters.size();t++)
1139 {
1140 if(maxGrandMaxPeakLabelWidth<levelMeters[t]->grandMaxPeakLevelLabel->getWidth())
1141 {
1142 maxGrandMaxPeakLabelWidth=levelMeters[t]->grandMaxPeakLevelLabel->getWidth();
1143 resize=true;
1144 }
1145 }
1146
1147 if(resize)
1148 {
1149 grandMaxPeakLevelLabel->setWidth(maxGrandMaxPeakLabelWidth);
1150 for(size_t t=0;t<levelMeters.size();t++)
1151 levelMeters[t]->grandMaxPeakLevelLabel->setWidth(maxGrandMaxPeakLabelWidth);
1152 balanceMetersRightMargin->setWidth(maxGrandMaxPeakLabelWidth);
1153 }
1154 }
1155
1156 if(gStereoPhaseMetersEnabled && !stereoPhaseMeters.empty())
1157 {
1158 soundPlayer->getSamplingForStereoPhaseMeters(samplingForStereoPhaseMeters,samplingForStereoPhaseMeters.getSize());
1159 for(size_t t=0;t<stereoPhaseMeters.size();t++)
1160 stereoPhaseMeters[t]->updateCanvas();
1161 }
1162
1163 if(gFrequencyAnalyzerEnabled)
1164 {
1165 if(analyzer->setAnalysis(soundPlayer->getFrequencyAnalysis(),soundPlayer->getFrequencyAnalysisOctaveStride()))
1166 {
1167 const vector<float> a=soundPlayer->getFrequencyAnalysis();
1168 vector<size_t> frequencies;
1169 for(size_t t=0;t<a.size();t++)
1170 frequencies.push_back(soundPlayer->getFrequency(t));
1171 analyzer->rebuildLabels(frequencies,soundPlayer->getFrequencyAnalysisOctaveStride());
1172 }
1173 }
1174 }
1175
1176 // schedule another update again in METER_UPDATE_RATE milliseconds
1177 #if REZ_FOX_VERSION<10322
1178 timeout=getApp()->addTimeout(this,ID_UPDATE_TIMEOUT,gMeterUpdateTime);
1179 #else
1180 getApp()->addTimeout(this,ID_UPDATE_TIMEOUT,gMeterUpdateTime);
1181 #endif
1182 return 0; // returning 0 because 1 makes it use a ton of CPU (because returning 1 causes FXApp::refresh() to be called)
1183 }
1184
onLabelFrameConfigure(FXObject * sender,FXSelector,void *)1185 long CMetersWindow::onLabelFrameConfigure(FXObject *sender,FXSelector,void*)
1186 {
1187 for(FXint t=0;t<labelFrame->numChildren();t++)
1188 {
1189 const int x=(labelFrame->getWidth()-1)*t/(labelFrame->numChildren()-1);
1190
1191 int w= t==0 ? 0 : labelFrame->childAtIndex(t)->getWidth();
1192 if(t!=labelFrame->numChildren()-1)
1193 w/=2;
1194
1195 labelFrame->childAtIndex(t)->setX((x-w));
1196 }
1197 return 1;
1198 }
1199
onResetGrandMaxPeakLevels(FXObject * sender,FXSelector sel,void * ptr)1200 long CMetersWindow::onResetGrandMaxPeakLevels(FXObject *sender,FXSelector sel,void *ptr)
1201 {
1202 resetGrandMaxPeakLevels();
1203 return 1;
1204 }
1205
setSoundPlayer(ASoundPlayer * _soundPlayer)1206 void CMetersWindow::setSoundPlayer(ASoundPlayer *_soundPlayer)
1207 {
1208 if(soundPlayer!=NULL)
1209 throw runtime_error(string(__func__)+" -- internal error -- sound player already set -- perhaps I need to allow this");
1210 if(_soundPlayer==NULL)
1211 return;
1212
1213 soundPlayer=_soundPlayer;
1214
1215 for(size_t t=0;t<soundPlayer->devices[0].channelCount;t++)
1216 levelMeters.push_back(new CLevelMeter(levelMetersFrame));
1217
1218 for(size_t t=0;t<soundPlayer->devices[0].channelCount/2;t++)
1219 balanceMeters.push_back(new CBalanceMeter(balanceMetersFrame));
1220
1221 samplingForStereoPhaseMeters.setSize(gStereoPhaseMeterPointCount*soundPlayer->devices[0].channelCount);
1222 for(size_t t=0;t<soundPlayer->devices[0].channelCount/2;t++)
1223 {
1224 stereoPhaseMeters.insert(stereoPhaseMeters.begin(),
1225 new CStereoPhaseMeter(
1226 this,
1227 samplingForStereoPhaseMeters,
1228 gStereoPhaseMeterPointCount,
1229 soundPlayer->devices[0].channelCount,
1230 t*2+0,
1231 t*2+1
1232 )
1233 );
1234 }
1235
1236 setHeight(
1237 max(
1238 // +1 for the balance meter(s)
1239 headerFrame->getHeight() + ((soundPlayer->devices[0].channelCount+1) * max(statusFont->getFontHeight(),MIN_METER_HEIGHT+levelMetersFrame->getVSpacing())) + (getVSpacing()*(numChildren()-1)) + (getPadTop()+getPadBottom()+levelMetersFrame->getPadTop()+levelMetersFrame->getPadBottom() + 2+2+2+2/*frame rendering*/),
1240 (unsigned)MIN_METERS_WINDOW_HEIGHT
1241 )
1242 );
1243
1244 // make sure the GUI initially reflects the global settings
1245 enableLevelMeters(isLevelMetersEnabled());
1246 enableStereoPhaseMeters(isStereoPhaseMetersEnabled());
1247 enableFrequencyAnalyzer(isFrequencyAnalyzerEnabled());
1248 }
1249
resetGrandMaxPeakLevels()1250 void CMetersWindow::resetGrandMaxPeakLevels()
1251 {
1252 for(size_t t=0;t<levelMeters.size();t++)
1253 levelMeters[t]->setGrandMaxPeakLevel(0);
1254 }
1255
isLevelMetersEnabled() const1256 bool CMetersWindow::isLevelMetersEnabled() const
1257 {
1258 return gLevelMetersEnabled;
1259 }
1260
enableLevelMeters(bool enable)1261 void CMetersWindow::enableLevelMeters(bool enable)
1262 {
1263 gLevelMetersEnabled=enable;
1264 if(!enable)
1265 {
1266 // just to cause a canvas update
1267 for(size_t t=0;t<levelMeters.size();t++)
1268 levelMeters[t]->setLevel(0,0);
1269 for(size_t t=0;t<balanceMeters.size();t++)
1270 balanceMeters[t]->setBalance(0,0,0,0);
1271 }
1272
1273 /* looks stupid
1274 if(enable)
1275 levelMetersFrame->show();
1276 else
1277 levelMetersFrame->hide();
1278 */
1279 showHideAll();
1280 }
1281
isStereoPhaseMetersEnabled() const1282 bool CMetersWindow::isStereoPhaseMetersEnabled() const
1283 {
1284 return gStereoPhaseMetersEnabled;
1285 }
1286
enableStereoPhaseMeters(bool enable)1287 void CMetersWindow::enableStereoPhaseMeters(bool enable)
1288 {
1289 gStereoPhaseMetersEnabled=enable;
1290 if(!enable)
1291 {
1292 // just to cause a canvas update
1293 for(size_t t=0;t<stereoPhaseMeters.size();t++)
1294 {
1295 if(stereoPhaseMeters[t]->shown())
1296 stereoPhaseMeters[t]->clearCanvas();
1297 }
1298 }
1299
1300 for(size_t t=0;t<stereoPhaseMeters.size();t++)
1301 {
1302 if(enable)
1303 {
1304 // have to call this so that onResize() will get called in order to calculate the unrotateMapping
1305 stereoPhaseMeters[t]->resize(1,1);
1306
1307 stereoPhaseMeters[t]->show();
1308 }
1309 else
1310 stereoPhaseMeters[t]->hide();
1311 }
1312 showHideAll();
1313 }
1314
isFrequencyAnalyzerEnabled() const1315 bool CMetersWindow::isFrequencyAnalyzerEnabled() const
1316 {
1317 return gFrequencyAnalyzerEnabled;
1318 }
1319
enableFrequencyAnalyzer(bool enable)1320 void CMetersWindow::enableFrequencyAnalyzer(bool enable)
1321 {
1322 gFrequencyAnalyzerEnabled=enable;
1323 if(!enable)
1324 {
1325 if(analyzer->shown())
1326 analyzer->clearCanvas();
1327 }
1328
1329 if(enable)
1330 analyzer->show();
1331 else
1332 analyzer->hide();
1333 showHideAll();
1334 }
1335
showHideAll()1336 void CMetersWindow::showHideAll()
1337 {
1338 recalc();
1339 if(isLevelMetersEnabled() || isStereoPhaseMetersEnabled() || isFrequencyAnalyzerEnabled())
1340 show();
1341 else
1342 hide();
1343 }
1344