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