/*
 * Copyright (c) 1990-1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
static const char rcsid[] =
    "@(#) $Header: vu.cc,v 1.16 96/03/16 13:12:55 van Exp $ (LBL)";

#include "config.h"
#include <math.h>
#include "vu.h"
#include "Tcl.h"

int VUMeter::command(int argc, const char*const* argv)
{
	if (argc == 3) {
		if (strcmp(argv[1], "set") == 0) {
			set(atof(argv[2]));
			redraw();
			return (TCL_OK);
		}
	}
	return (TkWidget::command(argc, argv));
}

void VUMeter::resize()
{
	topblk_ = peak_ = 0;
	setpeak_ = 0;
	peakcnt_ = 0;
	ntop_ = 0;
	nlevel_ = int(height_ / barht_);
	const char* hotLevel = Tk_GetOption(tk_, "hotLevel", "VatVU");
	hot_level_ = nlevel_ * atoi(hotLevel) / 100;
}

inline void VUMeter::rect(Display* dpy, Drawable win, GC gc,
			  int x, int y, int w, int h) const
{
	XFillRectangle(dpy, win, gc,
		       x, height_ - y - h, w, h);
}

void VUMeter::draw()
{
	Display* dpy = Tk_Display(tk_);
	Drawable window = Tk_WindowId(tk_);
	int w = width_ - 2 * gap_;

	rect(dpy, window, bg_, 0, 0, width_, height_);
	int y = 0;
	for (int i = 0; i < topblk_; ++i) {
		rect(dpy, window, fg_, gap_, y, w, barht_ - gap_);
		y += barht_;
	}
	if (peak_ > 0) {
		y = (peak_ - 1) * barht_;
		GC gc = (peak_ >= hot_level_) ? hot_ : gray_;
		rect(dpy, window, gc, gap_, y, w, barht_ - gap_);
	}
}

void VUMeter::update()
{
	int w = width_ - 2 * gap_;
	int lblk = topblk_;
	int tblk = ntop_;
	int dif = tblk - topblk_;
	if (dif == 0)
		return;
	topblk_ = tblk;
	Display* dpy = Tk_Display(tk_);
	Drawable window = Tk_WindowId(tk_);
	if (dif < 0) {
		/* lblk is above tblk */
		int ymin = tblk * barht_;
		int ymax = lblk * barht_;
		if (lblk == peak_)
			ymax -= barht_;
		int h = ymax - ymin;
		if (h > 0)
			rect(dpy, window, bg_, gap_, ymin, w, h);
		if (setpeak_) {
			if (tblk == 0)
				return;
			setpeak_ = 0;
			int y = (peak_ - 1) * barht_;
			rect(dpy, window, bg_, gap_, y, w, barht_ - gap_);
			peak_ = tblk;
			y = (tblk - 1) * barht_;
			if (mono_)
				/*
				 * For mono, need to clear background
				 * first since we do a stippled fill.
				 */
				rect(dpy, window, bg_,
				     gap_, y, w, barht_ - gap_);
			GC gc = (tblk >= hot_level_) ? hot_ : gray_;
			rect(dpy, window, gc, gap_, y, w, barht_ - gap_);
		}
		return;
	}
	if (peak_ < tblk) {
		setpeak_ = 0;
		peak_ = tblk;
		int y = (tblk - 1) * barht_;
		GC gc = (tblk >= hot_level_) ? hot_ : gray_;
		rect(dpy, window, gc, gap_, y, w, barht_ - gap_);
	}
	if (tblk == peak_)
		--tblk;
	if (lblk > 0)
		/*
		 * Start one lower than we have to since the old
		 * top block might be the peak indicator.
		 */
		--lblk;

	int y = lblk * barht_;
	for (int i = lblk + 1; i <= tblk; ++i) {
		rect(dpy, window, fg_, gap_, y, w, barht_ - gap_);
		y += barht_;
	}
}

GC VUMeter::bg_;
GC VUMeter::fg_;
GC VUMeter::gray_;
GC VUMeter::hot_;

VUMeter::VUMeter(const char* path, double s, double f, int width) :
	TkWidget(path, "VatVU", width, 100),
	value_(0.),
	alpha_(f),
	scale_(s),
	barht_(width / 2),
	gap_(2),
	nlevel_(0),
	hot_level_(0),
	topblk_(0),
	ntop_(0),
	peak_(0),
	peakcnt_(0),
	setpeak_(0)
{
	if (bg_ == 0) {
		Tk_Uid bg = Tk_GetOption(tk_, "background", "VatVU");
		Tk_Uid fg = Tk_GetOption(tk_, "foreground", "VatVU");
		Tk_Uid gr = Tk_GetOption(tk_, "peak", "VatVU");
		Tk_Uid hot = Tk_GetOption(tk_, "hot", "VatVU");

		bg_ = lookup_gc(0, bg, bg);
		fg_ = lookup_gc(0, fg, bg);
		gray_ = lookup_gc(0, gr, bg);
		hot_ = lookup_gc(0, hot, bg);
		if (hot_ == 0)
			/*XXX error message */
			hot_ = fg_;
	}
}

void VUMeter::set(double d)
{
	d = warp(d);
	d *= scale_;
	if (d > 1.0)
		d = 1.0;
	else if (d < 0.0)
		d = 0.0;

	register double v = value_;
	v += alpha_ * (d - v);
	value_ = v;
	int nblk = int(double(nlevel_) * v + 0.5);
	if (nblk != ntop_) {
		ntop_ = nblk;
		if (--peakcnt_ < 0) {
			peakcnt_ = 25;
			setpeak_ = 1;
		}
		/*
		 * Call update() here instead of redraw() since the meter
		 * needs to be animated in real-time.  Using redraw
		 * makes for very sluggish action.
		 */
		if (Tk_IsMapped(tk_)) {
#ifdef notdef
			/*XXX can't do this because damage_ is private */
			damage_ = 0;
#endif
			update();
		}
	}
}

DBMeter::DBMeter(const char* path,
		 double scale, double lpfgain, int width)
	: VUMeter(path, 1 / log10(scale), lpfgain, width)
{
}

double DBMeter::warp(double d) const
{
	if (d != 0.) {
		if (d < 0)
			d = -d;
		d = log10(d);
	}
	return (d);
}

LinearMeter::LinearMeter(const char* path,
			 double scale, double lpfgain, int width)
	: VUMeter(path, scale, lpfgain, width)
{
}

double LinearMeter::warp(double d) const
{
	if (d < 0)
		d = -d;
	return (d);
}

static class MeterCommand : public TclObject {
public:
	MeterCommand() : TclObject("meter") {}
	virtual int command(int argc, const char*const* argv);
} meter_command;

int MeterCommand::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if (argc == 2) {
		/*
		 * the value '81' for meter full scale is because we want to
		 * peg the meter if the peak amplitude of some tone hits the
		 * u-law clipping level (127).  Since the mean of abs(sin(x))
		 * over an integral number of periods is 2/pi, the meter
		 * fullscale is set to 127*2/3.14 = 81.
		 */
		LinearMeter* p = new LinearMeter(argv[1], 1./81., 0.75, 20);
		tcl.result(p->name());
		return (TCL_OK);
	}
	return (TclObject::command(argc, argv));
}
