/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+
#+     Glade / Gtk Programming
#+
#+     Copyright (C) 2020 - 2025 by Kevin C. O'Kane
#+
#+     Kevin C. O'Kane
#+     kc.okane@gmail.com
#+     https://www.cs.uni.edu/~okane
#+     http://threadsafebooks.com/
#+
#+ This program is free software; you can redistribute it and/or modify
#+ it under the terms of the GNU General Public License as published by
#+ the Free Software Foundation; either version 2 of the License, or
#+ (at your option) any later version.
#+
#+ This program is distributed in the hope that it will be useful,
#+ but WITHOUT ANY WARRANTY; without even the implied warranty of
#+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#+ GNU General Public License for more details.
#+
#+ You should have received a copy of the GNU General Public License
#+ along with this program; if not, write to the Free Software
#+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

// August 4, 2025

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>


#include "functions.h"

#include "global-extern-variables.h"


#include <vlc/libvlc.h>
#include <vlc/libvlc_renderer_discoverer.h>
#include <vlc/libvlc_media.h>
#include <vlc/libvlc_media_player.h>

#define SLASH '/'

static	const pa_sample_spec sampSpec = {
	.format = PA_SAMPLE_FLOAT32LE,
	.rate = 44100,
	.channels = 2
	};

//--------------------------------------------------------------------
//	main timer process
//	called every 100 milliseconds to update the meteres and graphs.
//--------------------------------------------------------------------

gboolean	pulse_timer_handler() { // check pulse every 100 millisecs

	static int rx = 0;
	double ra = 0.0;

	static int lx = 0;
	double la = 0.0;

	rUtil = log10(RightChan + 1) * FACTOR;
	lUtil = log10(LeftChan + 1) * FACTOR;

	LeftChan = RightChan = 0.0;

//----------------------
//	shift the data
//----------------------

	for (int i = 99; i > 0; i--) rchan[i] = rchan[i-1];
	for (int i = 99; i > 0; i--) lchan[i] = lchan[i-1];

	rchan[0] = rUtil;
	lchan[0] = lUtil;

//--------------------------
//	que draw events
//--------------------------

	gtk_widget_queue_draw (draw1);
	gtk_widget_queue_draw (draw2);
	return TRUE;
	}

//--------------------------
//	draw meters
//--------------------------

void	needles(cairo_t *cr, double hor, double ver, double len, double t1, double t2, int cFlg, double Max) {

	double x, x1, x2, y, y1, y2;
	double X = 0.0;

	ver = ver + 1.0;

	if (t1 > M_PI) t1 = M_PI;

//------------------------------------------------------------------------------
//	color is same as needle until past needle point and then color is gray
//------------------------------------------------------------------------------

	X = 0.0;

	if (!legacy_vumeters) {

		if (cFlg == 0) cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);		// black
		else if (cFlg == 1) cairo_set_source_rgb(cr, 1.0, 1.0, 0.3);		// yellow
		else if (cFlg == 2) cairo_set_source_rgb(cr, 1.0, 0., 0.);	// red
		else if (cFlg == 3) cairo_set_source_rgb(cr, 0.5, 1.0, 0.5);	// green
		else if (cFlg == 4) cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);	// gray

		cairo_set_line_width(cr, 2.5);

//--------------------------------------------
//	draw all short and long hash marks
//--------------------------------------------

		for (int i = 0; i< 21; i++ ) {

//-------------------------------
//		long hash marks
//-------------------------------

			if (! (i % 2) ) {

				cairo_arc (cr, hor, ver, len + 10, -M_PI, -M_PI + X);	// further point
				cairo_get_current_point (cr, &x, &y);

				cairo_arc (cr, hor, ver, len, -M_PI, -M_PI + X);	// nearer point
				cairo_get_current_point (cr, &x1, &y1);

				cairo_new_path (cr);
				cairo_move_to(cr, x, y);
				cairo_line_to (cr, x1, y1 );
				cairo_stroke (cr);
				}

//-----------------------------------
//		short hash marks
//-----------------------------------

			else {
				cairo_arc (cr, hor, ver, len + 2, -M_PI, -M_PI + X); // further point
				cairo_get_current_point (cr, &x, &y);

				cairo_arc (cr, hor, ver, len - 1, -M_PI, -M_PI + X); // nearer point length
				cairo_get_current_point (cr, &x1, &y1);
				}

			if (X > t1) {	// are we past the level point?

//--------------------------------------------------------------
//			no more colored short hash marks
//--------------------------------------------------------------

				cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);	// gray
				cairo_new_path (cr);
				cairo_move_to(cr, x, y);
				cairo_line_to (cr, x1, y1 );
				cairo_stroke (cr);

//----------------------------------------------------------------------------
//			short marks from here only for balance
//----------------------------------------------------------------------------

				if (cFlg != 4) {
					X = X + M_PI / 20.0;	// keep incrementing
					continue;
					}
				}

//-----------------------------------------------------------------
//		draw a short hash mark using numbers from above.
//-----------------------------------------------------------------

			cairo_new_path (cr);
			cairo_move_to(cr, x, y);
			cairo_line_to (cr, x1, y1 );
			cairo_stroke (cr);

			X = X + M_PI / 20.0; // advance point on arc
			}

//----------------------------
//	RMS level
//----------------------------

		if (Max > 0.0) {

			cairo_set_line_width(cr, 5.0);

			if (Max > M_PI) Max = M_PI;

			if (cFlg == 1) cairo_set_source_rgb(cr, 1.0, 1.0, 0.3);		// yellow
			else if (cFlg == 2) cairo_set_source_rgb(cr, 1.0, 0., 0.);	// red
			else if (cFlg == 3) cairo_set_source_rgb(cr, 0.5, 1.0, 0.5);	// green
			else if (cFlg == 4) cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);	// gray

			cairo_arc (cr, hor, ver, len + 10, -M_PI, -M_PI + Max);		// further point
			cairo_get_current_point (cr, &x, &y);

			cairo_arc (cr, hor, ver, len + 16, -M_PI, -M_PI + Max);		// nearer point
			cairo_get_current_point (cr, &x1, &y1);

			cairo_new_path (cr);
			cairo_move_to(cr, x, y);
			cairo_line_to (cr, x1, y1 );
			cairo_stroke (cr);
			}

//--------------------
//	Max level
//--------------------

		if (t2 > 0.0) {
			cairo_set_line_width(cr, 2.5);

			if (t2 > M_PI) t2 = M_PI;

			if (cFlg == 1) cairo_set_source_rgb(cr, 1.0, 1.0, 0.3);		// yellow
			else if (cFlg == 2) cairo_set_source_rgb(cr, 1.0, 0., 0.);	// red
			else if (cFlg == 3) cairo_set_source_rgb(cr, 0.5, 1.0, 0.5);	// green
			else if (cFlg == 4) cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);	// gray

			cairo_arc (cr, hor, ver, len + 10, -M_PI, -M_PI + t2);		// further point
			cairo_get_current_point (cr, &x, &y);

			cairo_arc (cr, hor, ver, len + 16, -M_PI, -M_PI + t2);		// nearer point
			cairo_get_current_point (cr, &x1, &y1);

			cairo_new_path (cr);
			cairo_move_to(cr, x, y);
			cairo_line_to (cr, x1, y1 );
			cairo_stroke (cr);
			}

		}

//----------------
//	draw needle
//----------------

	cairo_set_line_width(cr, 2.5);

	if (cFlg == 0) cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);		// black
	else if (cFlg == 1) cairo_set_source_rgb(cr, 1.0, 1.0, 0.3);		// yellow
	else if (cFlg == 2) cairo_set_source_rgb(cr, 1.0, 0., 0.);	// red
	else if (cFlg == 3) cairo_set_source_rgb(cr, 0.5, 1.0, 0.5);	// green
	else if (cFlg == 4) cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);	// black

	cairo_set_line_width(cr, 3.5);

//-------------------------------------
//	arc from left horizon to t1
//-------------------------------------

	cairo_arc (cr, hor, ver, len, -M_PI, -M_PI + t1);

//----------------------------------------
//	get coordinates of point on arc
//----------------------------------------

	cairo_get_current_point (cr, &x, &y);

//-----------------------------------------------
//	draw a line from the origin to the point
//-----------------------------------------------

	cairo_new_path (cr);
	cairo_move_to(cr, x, y);
	cairo_line_to (cr, hor, ver );
	cairo_stroke (cr);

	if (legacy_vumeters) return;

//--------------
//	arcs
//--------------

	if (cFlg == 4) {  // balance meter - always show arc

//------------------------------------------------------------------------
//		leans left - M_PI_2 is PI/2 -> -M_PI_2 is center of arc
//------------------------------------------------------------------------

		if ( t1 < M_PI_2) {

			cairo_set_source_rgb(cr, 1.0, 1.0, 0.3);	// yellow

//------------------------------------------
//			from left to center
//------------------------------------------

			cairo_arc (cr, hor, ver, len-2, -M_PI + t1, -M_PI_2); // left to center
			cairo_line_to (cr, hor, ver );
			cairo_fill(cr); // fill in arc
			cairo_stroke (cr);
			}

//------------------------------------------------------------------------
//		leans right - M_PI_2 is PI/2 -> -M_PI_2 is center of arc
//------------------------------------------------------------------------

		else  {
			cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); // red

//------------------------------------------
//			from center to right
//------------------------------------------

//--------------------------------------------------------------------
//			following adds line from current point to arc as well.
//			current point is the last cairo_line_to above (the origin)
//--------------------------------------------------------------------

			cairo_arc (cr, hor, ver, len-2, -M_PI_2, -M_PI + t1); // center to right
			cairo_line_to (cr, hor, ver );
			cairo_fill(cr); // fill in arc
			cairo_stroke (cr);
			}

		cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);	// gray

		}

//-----------------------------
//	other arcs is enabled
//-----------------------------

	if (cFlg != 4 && showArc) {

//--------------------------------------------
//		draw arc from left horizon to t1
//--------------------------------------------

//--------------------------------------------------------------------
//		following adds line from current point to arc as well.
//		current point is the last cairo_line_to above (the origin)
//--------------------------------------------------------------------

		cairo_arc (cr, hor, ver, len-2, -M_PI, -M_PI + t1);
		cairo_line_to (cr, hor, ver );
		cairo_fill(cr); // fill in arc
		cairo_stroke (cr);

		}

//-------------------------
//	spindle - full arc
//-------------------------

//--------------------------------------------------------------------
//	following adds line from current point to arc as well.
//	current point is the last cairo_line_to above (the origin)
//--------------------------------------------------------------------

	cairo_arc (cr, hor, ver, 4.0, -M_PI, M_PI );
	cairo_line_to (cr, hor, ver );
	cairo_fill(cr); // fill in arc

	}

#define DRAW_HEIGHT 120.0

float UPPER_MAX = DRAW_HEIGHT - DRAW_HEIGHT / 10.0;
float UPPER_MAX_MAX = DRAW_HEIGHT - DRAW_HEIGHT / 20.0;

#define SPECTRUM_PEAK DRAW_HEIGHT - 30.0 // maximum graph level for freq spectrum (clamp level)`

#define SCALE_BASELINE_SPECTRUM DRAW_HEIGHT / 160.0
#define SCALE_MIDLINE_SPECTRUM DRAW_HEIGHT / 250.0

#define SCALE_BASELINE_VOLUME DRAW_HEIGHT / 150.0
#define MIDLINE_VOL_SCALE 0.5

//--------------------------------------
//	colors of warning level limits
//--------------------------------------

#define UPPER_MAX_MAX_COLOR cairo_set_source_rgb(cr, 1.0, 0.0, 1.0);
#define UPPER_MAX_COLOR cairo_set_source_rgb(cr, 1.0, 0.7, 0.7);

#define RED 1
#define YELLOW 2
#define WHITE 3
#define G_OFF 4
#define G_OFF1 4	// bar horizontal offset
#define UPPER 0
#define LOWER 1

#define BAR_WIDTH1 2.9

//----------------------------------------------------
//
// draw frequency spectrum or volume level graphs
//
//----------------------------------------------------

gboolean on_draw1_draw (GtkDrawingArea *widget, cairo_t *cr) {

	int noLine = 0;

	if (no_graph) return FALSE;

	if (style == NARROW_SPECTRUM || style == WIDE_SPECTRUM) {

//------------------------
//	frequency graph
//------------------------

		double blchan[100], brchan[100];

		for (int i=0; i<100; i++) {
			blchan[i] = slchan[i];
			brchan[i] = srchan[i];
			}

		spectrum(); 	// scaling is done in spectrum()
		noLine = 1;

		double F;

		for (int i = 0; i < 99; i++) { // make bars less jumpy

			slchan[i] = (slchan[i] + blchan[i]) / 2.0;	// left
			srchan[i] = (srchan[i] + brchan[i]) / 2.0;	// right

			}
		}

//---------------------
//	volume graph
//---------------------

	else {	// volume graph
		if (super_bars)
			for (int i=0; i<100; i++) { // scale and segregate data
				slchan[i] = lchan[i] * SCALE_BASELINE_VOLUME;
				srchan[i] = rchan[i] * SCALE_BASELINE_VOLUME;
				}

		else for (int i=0; i<100; i++) { // segregate data unscaled
				slchan[i] = lchan[i]  * MIDLINE_VOL_SCALE;
				srchan[i] = rchan[i]  * MIDLINE_VOL_SCALE;
				}
		}

//------------------------
//	width of bars
//------------------------

	cairo_set_line_width(cr, BAR_WIDTH1);

	for (int i = 0; i < 99; i++) { // draw graph

		if ((!connect_graph || display_bars) && super_bars) {

			if (slchan[i] > srchan[i] ) { // show prominent color
				color_bars(cr, i, noLine, YELLOW);	// left channel color
				bar_graph(UPPER, slchan, i, cr, connect_graph, display_bars); // draw left channel only
				}
			else {
				color_bars(cr, i, noLine, RED);		// right channel color
				bar_graph(LOWER, srchan, i, cr, connect_graph, display_bars); // draw right channel only
				}
			}

		else {

			color_bars(cr, i, noLine, YELLOW);	// left channel
			bar_graph(UPPER, slchan, i, cr, connect_graph, display_bars);

			color_bars(cr, i, noLine, RED);		// right channel
			bar_graph(LOWER, srchan, i, cr, connect_graph, display_bars);
			}
		}

//-------------------------------------------------
//	midpoint line - none if not centered graph
//-------------------------------------------------

	if (!super_bars) {	// mid point line of centered graph
		cairo_set_line_width(cr, 1.0);
		cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); // green
		cairo_move_to (cr, (double) G_OFF - 2, DRAW_HEIGHT / 2.0 - 10.0 );
		cairo_line_to (cr, (double) G_OFF + 394, DRAW_HEIGHT / 2.0 - 10.0 );
		cairo_stroke (cr);
		}

//---------------------------------
//	frequency legend marks
//---------------------------------

	frequency_marks(cr);

	}

//--------------------------------
//	Select color for bars
//--------------------------------

void	color_bars(cairo_t *cr, int i, int noLine, int color) {

	if ((style == WIDE_SPECTRUM || style == NARROW_SPECTRUM) && !super_bars) rand_colors = 1;
	else rand_colors = 0;

	if (!rand_colors ) {
		if (!noLine && srchan[i] > UPPER_MAX_MAX) UPPER_MAX_MAX_COLOR
			else if (!noLine && srchan[i] > UPPER_MAX) UPPER_MAX_COLOR
				else if (color == RED) cairo_set_source_rgb(cr, 1.0, 0.1, 0.1); // red
				else if (color == YELLOW) cairo_set_source_rgb(cr, 1.0, 1.0, 0.3); // yellow
				else if (color == WHITE) cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); // white
				else cairo_set_source_rgb(cr, 0.2, 0.2, 1.0); // blue
		}

//--------------------------------------------------------------
//	create a color from deep red up to violet depending on i
//--------------------------------------------------------------

	else  {
		float r = (100 - i) / 100.0;
		float b = (i) / 100.0 + 0.1;
		float g;

		if (i == 0) g = 0.0;
		else if (i == 50) g = 1.0;
		else if (i < 50) g = (i % 50) / 50.0;
		else g = ((100 - i) % 50) / 50.0;

		g = g + r/(30.0/i);
		r = r + b/(i/40.0);
		cairo_set_source_rgb(cr, r, g, b);
		}
	}


#define G_MAX DRAW_HEIGHT / 2.0 - 10.0	// graph center line location (zero is top)
#define G_MAX2 DRAW_HEIGHT - 20 	// graph center line location (zero is top)
#define G_LIM DRAW_HEIGHT		// bar length limit

//------------------------------------------
//	Process line (i) of the bar graph
//------------------------------------------

void	bar_graph(int lower, double chan[], int i, cairo_t *cr, int connect_graph, int displayBars) {

	if (chan[i] > G_LIM) chan[i] = G_LIM;
	if (chan[i+1] > G_LIM) chan[i+1] = G_LIM;

	if ( connect_graph ) connecting_lines(cr, lower, i, chan);	// connecting horizontal graph lines

	if (displayBars) draw_bars_only(cr, lower, i, chan);		// vertical bars

	}

//-----------------------------
//	draw vertical bars
//-----------------------------

void	draw_bars_only(cairo_t *cr, int lower, int i, double chan[]) {

	if (chan[i + 1] > 1.0 ) {
		if (!super_bars) {	// centered

			if (lower) cairo_move_to (cr, (double) ((i * 4) + G_OFF), G_MAX + (double) chan[i]  );
			else       cairo_move_to (cr, (double) ((i * 4) + G_OFF), G_MAX - (double) chan[i]  );

			cairo_line_to (cr, (double) ((i * 4) + G_OFF), G_MAX ); // baseline line
			cairo_stroke (cr);
			}
		else {			// not centered
			cairo_move_to (cr, (double) ((i * 4) + G_OFF), G_MAX2 - (double) chan[i]  ); // horizontal, vertical
			cairo_line_to (cr, (double) ((i * 4) + G_OFF), G_MAX2 ); // baseline line
			cairo_stroke (cr);
			}
		}
	}

//-------------------------------------------------
//	draw connecting horizontal graph lines
//-------------------------------------------------

void	connecting_lines(cairo_t *cr, int lower, int i, double chan[]) {

	if (i < 98) { // not at the end of the graph

		if (lower) { // below the centerline


			if (!super_bars) { // center line graph
				cairo_move_to (cr, (double) ((i * 4) + G_OFF1), G_MAX + (double) chan[i]  ); // first bar
				cairo_line_to (cr, (double) (((i + 1) * 4) + G_OFF), G_MAX + (double) chan[i + 1] ); // second bar
				}

			else {
				cairo_move_to (cr, (double) ((i * 4) + G_OFF1), G_MAX2 - (double) chan[i]  ); // first bar
				cairo_line_to (cr, (double) (((i + 1) * 4) + G_OFF), G_MAX2 - (double) chan[i + 1] ); // second bar
				}
			}

		else { // above the center line

			if (!super_bars) {
				cairo_move_to (cr, (double) ((i * 4) + G_OFF1), G_MAX - (double) chan[i]  ); // first
				cairo_line_to (cr, (double) (((i + 1) * 4) + G_OFF), G_MAX - (double) chan[i + 1] ); // second
				}
			else { // not center line
				cairo_move_to (cr, (double) ((i * 4) + G_OFF1), G_MAX2 - (double) chan[i]  ); // first
				cairo_line_to (cr, (double) (((i + 1) * 4) + G_OFF), G_MAX2 - (double) chan[i + 1] ); // second
				}
			}

		cairo_stroke (cr); // do it
		}
	}

//-----------------------------
//
//	draw meters and bars
//
//-----------------------------

gboolean on_draw2_draw (GtkDrawingArea *widget, cairo_t *cr) {

	double hor, ver, t1, len;

	int noLine = 0;

	if (no_meters) return FALSE;

#define MHOR 55.0

//----------------------------------------------
//	average the past X to dampen movement
//----------------------------------------------

	Lx = (lchan[0] + lchan[1] ) / 2.0;
	Rx = (rchan[0] + rchan[1] ) / 2.0;

#define DECAY_FACTOR 250.0

//--------------------------------
//	LEFT Channel meter
//--------------------------------

	if (Lx > CLIP_LEVEL) {
		L_Clipping = 1;
		Lx = 99.0;
		}

	else L_Clipping = 0;

	t1 = (Lx / 100.0) * M_PI;

	if (t1 < 0.1) MaxLM = 0.0;

	else if (t1 > MaxLM) {
		MaxLM = t1;
		}

	else {
		if (MaxLM > 0.0) MaxLM = MaxLM - MaxLM / DECAY_FACTOR;
		}

//.............................

	MaxL[MLr++] = t1 * t1;
	if (MLr == RMX ) MLr = 0;

	double Avg = 0.0;

	for (int i = 0; i < RMX; i++) Avg += MaxL[i];

//------------------------------------------------------
//	position of origin point and length of needle:
//------------------------------------------------------

	if (legacy_vumeters) {
		hor = MHOR + 44.;
		ver = 62.0;
		len = 80.0;
		needles(cr, hor, ver, len, t1, MaxLM, 0, sqrtf(Avg / RMX));
		}
	else {
		hor = MHOR;
		ver = 50.0;
		len = 30.0;
		needles(cr, hor, ver, len, t1, MaxLM, 1, sqrtf(Avg / RMX));
		}


	if (legacy_vumeters) cairo_move_to (cr, hor - 9.0, ver - 2.0 ); // top
	else cairo_move_to (cr, hor - 10.0, ver + 15.0 ); // top

	if (L_Clipping) cairo_show_text(cr, "Peak");
	else cairo_show_text(cr, "Left");
	cairo_stroke (cr);

//--------------------------------
//	RIGHT Channel meter
//--------------------------------

	if (Rx > CLIP_LEVEL) {
		R_Clipping = 1;
		Rx = 99.0;
		}

	else R_Clipping = 0;

	t1 = (Rx / 100.0) * M_PI;

	if (t1 < 0.1) MaxRM = 0.;

	else if (t1 > MaxRM) {
		MaxRM = t1;
		}
	else {
		if (MaxRM > 0.0) MaxRM = MaxRM - MaxRM / DECAY_FACTOR;
		}

	MaxR[MRr++] = t1 * t1;
	if (MRr == RMX ) MRr = 0;

	Avg = 0.0;

	for (int i = 0; i < RMX; i++) Avg += MaxR[i];

	if (legacy_vumeters) {
		hor = MHOR + 240;
		needles(cr, hor, ver, len, t1, MaxLM, 0, sqrtf(Avg / RMX));
		}
	else {
		hor = MHOR + 180;
		needles(cr, hor, ver, len, t1, MaxRM, 2, sqrtf(Avg / RMX));
		}


	if (legacy_vumeters) cairo_move_to (cr, hor - 12.0, ver - 2.0 ); // top
	else cairo_move_to (cr, hor - 14.0, ver + 15.0 ); // top

	if (R_Clipping) cairo_show_text(cr, "Peak");
	else cairo_show_text(cr, "Right");
	cairo_stroke (cr);

//--------------------------
// 	balance meter
//--------------------------

	if (legacy_vumeters) return FALSE;

	hor = MHOR + 90;
	len = 30.0;

	Balance = 50.0 + 2.0 * (Rx - Lx);	// difference & double the effect

	if (Balance > 99.0 ) Balance = 99.0;
	if (Balance < 0.0) Balance = 0.0;
	t1 = (Balance / 100.0) * M_PI;

	needles(cr, hor, ver, len, t1, 0, 4, 0.0);

	cairo_move_to (cr, hor - 18., ver + 15.0 ); // top
	cairo_show_text(cr, "Balance");
	cairo_stroke (cr);

#define HOR_ORIGIN 32.0
#define VER_ORIGIN 78.0
#define BAR_WIDTH 3.25
#define LINE_WIDTH 5
#define LINE_SEP 4.0

	return FALSE; // NO BARS
	cairo_set_line_width(cr, LINE_WIDTH);

//-------------------------
//	media clipping
//-------------------------

	if (L_Clipping) { // display text

		cairo_set_source_rgb(cr, 1.0, 1.0, 0.3); // yellow

		cairo_move_to (cr, HOR_ORIGIN - 30, VER_ORIGIN + 3 ); // top

		cairo_show_text(cr, "Peak");
		cairo_stroke (cr);
		}

	if (R_Clipping) { // display text

		cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); // red

		cairo_move_to (cr, HOR_ORIGIN - 30, VER_ORIGIN + LINE_WIDTH + LINE_SEP + 5 ); // top

		cairo_show_text(cr, "Peak");
		cairo_stroke (cr);
		}

//---------------------
//	level bars
//---------------------

	double left_bar;

	left_bar = BAR_WIDTH * Lx;	// bar length

	double right_bar;

	right_bar = BAR_WIDTH * Rx;	// bar length

	cairo_set_source_rgb(cr, 1.0, 1.0, 0.3); // yellow

	cairo_move_to(cr, HOR_ORIGIN, VER_ORIGIN);
	cairo_line_to (cr, left_bar + HOR_ORIGIN, VER_ORIGIN );
	cairo_stroke (cr);

	cairo_set_source_rgb(cr, 1.0, 0.0, 0.); // red

	cairo_move_to(cr, HOR_ORIGIN, VER_ORIGIN + LINE_WIDTH + LINE_SEP);
	cairo_line_to (cr, right_bar + HOR_ORIGIN, VER_ORIGIN + LINE_WIDTH + LINE_SEP );
	cairo_stroke (cr);

//---------------------
// 	microphone bar
//---------------------

	static double mic_bar;
	mic_bar =  BAR_WIDTH * ML;

	cairo_set_line_width(cr, LINE_WIDTH);

	if (ML < 95.0) cairo_set_source_rgb(cr, 0.5, 1.0, 0.5); // green

	else { // clipping

		cairo_set_source_rgb(cr, 0.5, 1.0, 0.5); // green
		cairo_move_to (cr, HOR_ORIGIN - 25, VER_ORIGIN + 2 * LINE_WIDTH + 2 * LINE_SEP + 4 ); // top
		cairo_show_text(cr, "Peak");
		cairo_stroke (cr);

		cairo_set_source_rgb(cr, 0.9, 0.5, 1.0); // pink

		}

	MR = ML = 0.0;

	cairo_move_to(cr, HOR_ORIGIN, VER_ORIGIN + 2 * LINE_WIDTH + 2 * LINE_SEP);
	cairo_line_to (cr, mic_bar + HOR_ORIGIN, VER_ORIGIN + 2 * LINE_WIDTH + 2 * LINE_SEP );
	cairo_stroke (cr);

	return FALSE;
	}

//--------------------------------------------------
//	frequency spectrum service function
//--------------------------------------------------

#define WindowWidth 200
int width = WindowWidth;     // two times the display window - half left, half right
int numBars = WindowWidth /2;

int maxHeight = 100; // display window max from midpoint

void	fftTobars(fftw_complex *fft, int size, int *bars) {

	double strength, real, imagin, scale;
	int amplitude, i, j, bar, incr;

	scale = 0.0003;

	i = 0; // fft index

	for (bar = 0; bar < numBars; bar++) { // average for each bar

		real = fft[i][0] * scale;
		imagin = fft[i][1] * scale;
		strength = real * real + imagin * imagin;
		i++;

		if (strength < 1.00e-10) strength = 1.00e-10; // prevent overflows.

		amplitude = (maxHeight + (int) (10.0 * log10(strength))) * 2.0;

		if (amplitude > maxHeight) amplitude = maxHeight;
		if (amplitude < 0) amplitude = 0;

		bars[bar] = amplitude;

		if (spectrum_width == 1 ) {
			bar++;
			bars[bar] = amplitude; // double up
			}
		}
	}

//------------------------------------------------------
//	Calculate frequency spectrum array
//------------------------------------------------------

void	spectrum() {

	int size = SAMPLE_SIZE/2;

	static float *Hann = NULL;
	float buffer[sampSpec.channels * size];
	static double *in = NULL;
	static fftw_complex *out = NULL;

	in = (double*) fftw_malloc(sizeof(double) * size);
	out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * size);

	if (! Hann) { // used to perform Hann smoothing

		Hann = (float*) malloc(sizeof(float)*size);

		for(int n = 0; n < size; n++)  {
			Hann[n] = 0.5 * (1.0 - cos(2.0 * M_PI * n / (size - 1.0))) ;
			}

		}

//	fftw_plan plan = fftw_plan_dft_r2c_1d(size, in, out, FFTW_MEASURE); // alternates
//	fftw_plan plan = fftw_plan_dft_r2c_1d(size, in, out, FFTW_PATIENT);

	fftw_plan plan = fftw_plan_dft_r2c_1d(size, in, out, FFTW_ESTIMATE);

	int barsL[width / 2], barsR[width / 2];

//	------------
//	left channel
//	------------

//	for (int i=0; i<size; i++) buffer[i] = exchangeBuf[i * 2]; // every other element

	for (int i=0; i<size; i++) in[i] = Hann[i] * exchangeBuf[i * 2]; // every other element

//	for(int i = 0; i < size; i++) in[i] = Hann[i] * buffer[i];

	fftw_execute(plan);

	fftTobars(out, size, barsL);

	double F = 1.0;

	for (int i = 0; i < width / 2; i++) {
		if (barsL[i] < 0.0 ) slchan[i] = 0.0;
		else {
			if (super_bars) {
				barsL[i] *= F;
				F += 0.02;
				slchan[i] = barsL[i] * SCALE_BASELINE_SPECTRUM;
				}
			else {
				barsL[i] *= F;
				F += 0.01;
				slchan[i] = barsL[i] * SCALE_MIDLINE_SPECTRUM;
				}
			if (slchan[i] > SPECTRUM_PEAK) slchan[i] = SPECTRUM_PEAK;
//			else if (slchan[i] < 0.0) slchan[i] = 0.0;
			}
		}

//	-------------
//	right channel
//	-------------

//	for (int i=0; i<size; i++) buffer[i] = exchangeBuf[i * 2 + 1]; // every other element

	for (int i=0; i<size; i++) in[i] = Hann[i] * exchangeBuf[i * 2 + 1]; // every other element

//	for(int i = 0; i < size; i++) in[i] = Hann[i] * buffer[i];

	fftw_execute(plan);
	fftTobars(out, size, barsR);

	F = 1.0;
	for (int i=0; i<width/2; i++) {
		if (barsR[i] < 0.0) srchan[i] = 0.0;
		else {
			if (super_bars) {
				barsR[i] *= F;
				F += 0.02;
				srchan[i] = barsR[i] * SCALE_BASELINE_SPECTRUM;
				}
			else {
				barsR[i] *= F;
				F += 0.01;
				srchan[i] = barsR[i] * SCALE_MIDLINE_SPECTRUM;
				}
			if (srchan[i] > SPECTRUM_PEAK) srchan[i] = SPECTRUM_PEAK;
//			else if (srchan[i] < 0.0) srchan[i] = 0.0;
			}
		}

	fftw_free(in);
	fftw_free(out);

	exchange = 0;
	}


void	frequency_marks(cairo_t *cr) {

// location of freq marks

#define SPECTRUM_FREQ_VERTICAL DRAW_HEIGHT - 10

	if (style == NARROW_SPECTRUM )  { // narrow

		cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); // white

		cairo_move_to (cr, 1.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "kHz");
		cairo_stroke (cr);

		cairo_move_to (cr, 38.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "1");
		cairo_stroke (cr);

		cairo_move_to (cr, 88.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "2.5");
		cairo_stroke (cr);

		cairo_move_to (cr, 185.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "5");
		cairo_stroke (cr);

		cairo_move_to (cr, 270.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "7.5");
		cairo_stroke (cr);

		cairo_move_to (cr, 360.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "10");
		cairo_stroke (cr);

		return;
		}

	else if (style == WIDE_SPECTRUM) {
		cairo_set_source_rgb(cr, 1.2, 1.2, 1.0); // gray

		cairo_move_to (cr, 1.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "kHz");
		cairo_stroke (cr);

		cairo_move_to (cr, 40.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "2.5");
		cairo_stroke (cr);

		cairo_move_to (cr, 88.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "5");
		cairo_stroke (cr);

		cairo_move_to (cr, 130.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "7.5");
		cairo_stroke (cr);

		cairo_move_to (cr, 176.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "10");
		cairo_stroke (cr);

		cairo_move_to (cr, 270.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "15");
		cairo_stroke (cr);

		cairo_move_to (cr, 360.0, SPECTRUM_FREQ_VERTICAL ); // top
		cairo_show_text(cr, "20");
		cairo_stroke (cr);

		}
	}
