
/*
 * CINELERRA
 * Copyright (C) 1997-2011 Adam Williams <broadcast at earthling dot net>
 *
 * 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
 *
 */

#include "bcdisplayinfo.h"
#include "bcsignals.h"
#include "clip.h"
#include "cursors.h"
#include "bchash.h"
#include "filexml.h"
#include "graphic.h"
#include "keys.h"
#include "language.h"
#include "samples.h"
#include "theme.h"
#include "units.h"
#include "vframe.h"

#include <math.h>
#include <string.h>


// Canvas parameters
#define MAJOR_DIVISIONS 7
#define MINOR_DIVISIONS 5
#define LINE_W4 xS(12)
#define LINE_W3 xS(10)
#define LINE_W2 xS(5)
#define LINE_W1 xS(2)








REGISTER_PLUGIN(GraphicEQ)







GraphicPoint::GraphicPoint()
{
	freq = 0;
	value = 0.0;
}








GraphicConfig::GraphicConfig()
{
	window_size = 4096;
//	wetness = INFINITYGAIN;
}

GraphicConfig::~GraphicConfig()
{
	points.remove_all_objects();
}


int GraphicConfig::equivalent(GraphicConfig &that)
{
	if(that.points.size() != points.size() ||
		window_size != that.window_size) return 0;

	for(int i = 0; i < points.size(); i++)
	{
		if(that.points.get(i)->freq != points.get(i)->freq ||
			!EQUIV(that.points.get(i)->value, points.get(i)->value))
			return 0;
	}


	return 1;
}

void GraphicConfig::copy_from(GraphicConfig &that)
{
	points.remove_all_objects();
	for(int i = 0; i < that.points.size(); i++)
	{
		GraphicPoint *point;
		points.append(point = new GraphicPoint);
		point->freq = that.points.get(i)->freq;
		point->value = that.points.get(i)->value;
	}

	window_size = that.window_size;
}

void GraphicConfig::interpolate(GraphicConfig &prev,
	GraphicConfig &next,
	int64_t prev_frame,
	int64_t next_frame,
	int64_t current_frame)
{
	double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
	double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);

// Get current set of points from previous configuration
	copy_from(prev);


// Interpolate between current set of points and next set
	for(int i = 0; i < MIN(next.points.size(), points.size()); i++)
	{
		points.get(i)->freq = (int)(prev.points.get(i)->freq *
			prev_scale +
			next.points.get(i)->freq *
			next_scale);
		points.get(i)->value = prev.points.get(i)->value *
			prev_scale +
			next.points.get(i)->value *
			next_scale;
	}
}


void GraphicConfig::delete_point(int number)
{
	points.remove_object_number(number);
}







GraphicCanvas::GraphicCanvas(GraphicEQ *plugin,
	GraphicGUI *gui,
	int x,
	int y,
	int w,
	int h)
 : BC_SubWindow(x,
 	y,
	w,
	h,
	BLACK)
{
	this->plugin = plugin;
	this->gui = gui;
	state = GraphicCanvas::NONE;
}

GraphicCanvas::~GraphicCanvas()
{
	temp_points.remove_all_objects();
}

int GraphicCanvas::button_press_event()
{
	process(1, 0, 0);
	if(state == GraphicCanvas::DRAG_POINT)
		return 1;
	else
		return 0;
}

int GraphicCanvas::cursor_motion_event()
{
	process(0, 1, 0);

	if(state == GraphicCanvas::DRAG_POINT)
	{
		return 1;
	}
	else
		return 0;
}

int GraphicCanvas::button_release_event()
{
	if(state == GraphicCanvas::DRAG_POINT && plugin->active_point >= 0)
	{
// Delete point if out of order
		int point_number = plugin->active_point;
		GraphicPoint *active_point = temp_points.get(point_number);


		for(int i = 0; i < temp_points.size(); i++)
		{
			GraphicPoint *point = temp_points.get(i);
			if((point->freq <= active_point->freq && i > point_number) ||
				(point->freq >= active_point->freq && i < point_number))
			{
				temp_points.remove_object_number(point_number);
				plugin->active_point = -1;
				process(0, 0, 1);
				break;
			}
		}

		save_temps();
		plugin->send_configure_change();
	}

	state = GraphicCanvas::NONE;
	return 0;
}

#define BOX_SIZE xS(10)

int GraphicCanvas::freq_to_y(int freq,
	ArrayList<GraphicPoint*> *points,
	double *envelope)
{
	int center_y = get_h() / 2;
	double magnitude = plugin->freq_to_magnitude(freq, points, envelope);
	double magnitude_db = DB::todb(magnitude);
	if(magnitude_db < -MAXMAGNITUDE) magnitude_db = -MAXMAGNITUDE;
	int y = (int)(center_y - magnitude_db * center_y / MAXMAGNITUDE);
//printf("GraphicCanvas::freq_to_y magnitude=%f magnitude_db=%f y=%d\n",
//magnitude, magnitude_db, y);
	return y;
}


void GraphicCanvas::new_temps()
{
// Copy configuration from plugin for editing
	temp_points.remove_all_objects();
	for(int i = 0; i < plugin->config.points.size(); i++)
	{
		GraphicPoint *point = new GraphicPoint;
		*point = *plugin->config.points.get(i);
		temp_points.append(point);
	}

	plugin->calculate_envelope(&temp_points, temp_envelope);
}

void GraphicCanvas::save_temps()
{
	plugin->config.points.remove_all_objects();
	for(int i = 0; i < temp_points.size(); i++)
	{
		GraphicPoint *point;
		plugin->config.points.append(point = new GraphicPoint);
		*point = *temp_points.get(i);
	}
}


void GraphicCanvas::insert_point(GraphicPoint *point)
{
	int done = 0;

	temp_points.append(point);
	while(!done)
	{
		done = 1;
		for(int i = 0; i < temp_points.size() - 1; i++)
		{
			if(temp_points.get(i)->freq > temp_points.get(i + 1)->freq)
			{
				GraphicPoint *point = temp_points.get(i);
				temp_points.set(i, temp_points.get(i + 1));
				temp_points.set(i + 1, point);
				done = 0;
			}
		}
	}
}


void GraphicCanvas::process(int buttonpress, int motion, int draw)
{
	int got_button = 0;
	int center_y = get_h() / 2;
	int out_of_order = 0;
	ArrayList<GraphicPoint*> *points;
	double *envelope;
	//const int debug = 0;


	if(state == GraphicCanvas::NONE)
	{
		points = &plugin->config.points;
		envelope = plugin->envelope;
	}
	else
	{
		points = &temp_points;
		envelope = temp_envelope;
	}

	plugin->calculate_envelope(points, envelope);


// spectrogram
	if(draw)
	{
		clear_box(0, 0, get_w(), get_h());
		int niquist = plugin->PluginAClient::project_sample_rate / 2;

// delete frames up to current tracking position
		double tracking_position = plugin->get_tracking_position();
		GraphicGUIFrame *frame = (GraphicGUIFrame *)
			plugin->get_gui_frame(tracking_position, 1);
		if( frame ) {
			int y1 = 0, y2 = 0;
			set_color(MEGREY);

			for(int i = 0; i < get_w(); i++)
			{
				int freq = Freq::tofreq(i * TOTALFREQS / get_w());
				int index = (int64_t)freq * (int64_t)frame->window_size / 2 / niquist;
				if(index < frame->window_size / 2)
				{
					double magnitude = frame->data[index] /
						frame->freq_max *
						frame->time_max;
					y2 = (int)(get_h() -
						(DB::todb(magnitude) - INFINITYGAIN) *
						get_h() /
						-INFINITYGAIN);
					CLAMP(y2, 0, get_h() - 1);
					if(i > 0)
					{
						draw_line(i - 1, y1, i, y2);
//printf(" %.0f", frame->data[index]);
					}
					y1 = y2;
				}
			}
			delete frame;
		}
	}


// Determine if active point is out of order
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = points->get(plugin->active_point);
		for(int i = 0; i < points->size(); i++)
		{
			if(i == plugin->active_point)
			{
				if( (i < points->size() - 1 &&
					active_point->freq >= points->get(i + 1)->freq) ||
				    (i > 0 &&
					active_point->freq <= points->get(i - 1)->freq) )
				{
					out_of_order = 1;
				}
				break;
			}
		}
	}


	if(motion)
	{
		if(state == GraphicCanvas::DRAG_POINT)
		{
			int point_x = get_cursor_x() + x_diff;
			int point_y = get_cursor_y() + y_diff;
			CLAMP(point_x, 0, get_w());
			CLAMP(point_y, 0, get_h());

			int frequency = Freq::tofreq(point_x * TOTALFREQS / get_w());
			double magnitude_db = (double)(center_y - point_y) * MAXMAGNITUDE / center_y;
			int minfreq = Freq::tofreq(0);
			int maxfreq = Freq::tofreq(TOTALFREQS - 1);

			CLAMP(frequency, minfreq, maxfreq);
			CLAMP(magnitude_db, -MAXMAGNITUDE, MAXMAGNITUDE);
			if(plugin->active_point >= 0)
			{
				GraphicPoint *active_point = points->get(plugin->active_point);
				active_point->freq = frequency;
				active_point->value = magnitude_db;
			}

// Redraw with new value
			process(0, 0, 1);
			save_temps();
			plugin->send_configure_change();
			gui->update_textboxes();
			return;
		}
	}

// Magnitude bars
	if(draw)
	{
		set_color(GREEN);
		set_line_dashes(1);
		for(int i = 1; i < MAJOR_DIVISIONS; i++)
		{
			int y = i * get_h() / (MAJOR_DIVISIONS - 1);
			draw_line(0, y, get_w(), y);
		}
		set_line_dashes(0);
	}

	int y1 = 0;
	if(draw) set_color(WHITE);

// Control points, cursor change and control point selection
	int new_cursor = CROSS_CURSOR;
	for(int i = 0; i < points->size(); i++)
	{
		GraphicPoint *point = points->get(i);
		int x = Freq::fromfreq(point->freq) * get_w() / TOTALFREQS;
		int y = freq_to_y(point->freq, points, envelope);

		if(draw)
		{
			y1 = y;
// Draw point under cursor if out of order
			if(i == plugin->active_point && out_of_order)
				y1 = get_cursor_y() + y_diff;

			if(i == plugin->active_point)
				draw_box(x - BOX_SIZE / 2, y1 - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
			else
				draw_rectangle(x - BOX_SIZE / 2, y1 - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
		}

		if(motion &&
			state == GraphicCanvas::NONE &&
			is_event_win() &&
			cursor_inside())
		{
			if(get_cursor_x() >= x - BOX_SIZE / 2 &&
				get_cursor_y() >= y - BOX_SIZE / 2 &&
				get_cursor_x() < x + BOX_SIZE / 2 &&
				get_cursor_y() < y + BOX_SIZE / 2)
			{
				new_cursor = UPRIGHT_ARROW_CURSOR;
			}
		}

		if(buttonpress &&
			state == GraphicCanvas::NONE &&
			is_event_win() &&
			cursor_inside() &&
			!got_button)
		{
			if(get_cursor_x() >= x - BOX_SIZE / 2 &&
				get_cursor_y() >= y - BOX_SIZE / 2 &&
				get_cursor_x() < x + BOX_SIZE / 2 &&
				get_cursor_y() < y + BOX_SIZE / 2)
			{
				plugin->active_point = i;
				state = GraphicCanvas::DRAG_POINT;
				new_temps();
				points = &temp_points;
				envelope = temp_envelope;

				x_diff = x - get_cursor_x();
				y_diff = y - get_cursor_y();
				got_button = 1;
				process(0, 0, 1);
				save_temps();
				plugin->send_configure_change();
				gui->update_textboxes();
			}
		}
	}

	if(motion && new_cursor != get_cursor())
	{
		set_cursor(new_cursor, 0, 1);
	}

// Envelope line;
	y1 = 0;
	set_line_width(2);
	for(int i = 0; i < get_w(); i++)
	{
		int y = freq_to_y(Freq::tofreq(i * TOTALFREQS / get_w()),
			points,
			envelope);

		if(draw)
		{
			if(i > 0) draw_line(i - 1, y1, i, y);
		}


		y1 = y;
	}
	set_line_width(1);

	if(buttonpress && !got_button)
	{
		if(is_event_win() && cursor_inside())
		{
			GraphicPoint *new_point = new GraphicPoint;
			new_point->freq = Freq::tofreq(get_cursor_x() *
				TOTALFREQS /
				get_w());
			new_point->value = (double)(center_y - get_cursor_y()) *
				MAXMAGNITUDE /
				center_y;
			state = GraphicCanvas::DRAG_POINT;
			new_temps();
			points = &temp_points;
			envelope = temp_envelope;

			insert_point(new_point);
			plugin->active_point = points->number_of(new_point);
			x_diff = 0;
			y_diff = 0;

// Redraw with new point
			process(0, 0, 1);
			save_temps();
			plugin->send_configure_change();
			gui->update_textboxes();
		}
	}


	if(draw)
	{
		flash();
	}
}









FreqTextBox::FreqTextBox(GraphicEQ *plugin,
	GraphicGUI *gui,
	int x,
	int y,
	int w)
 : BC_TextBox(x, y, w, 1, "")
{
	this->plugin = plugin;
	this->gui = gui;
}

int FreqTextBox::handle_event()
{
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = plugin->config.points.get(plugin->active_point);
		if(atoi(get_text()) != active_point->freq)
		{
			active_point->freq = atoi(get_text());
			gui->update_canvas();
			plugin->send_configure_change();
			return 1;
		}
	}

	return 0;
}

void FreqTextBox::update(int freq)
{
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = plugin->config.points.get(plugin->active_point);
		if(atoi(get_text()) != active_point->freq)
		{
			char string[BCTEXTLEN];
			sprintf(string, "%d", active_point->freq);
			BC_TextBox::update(string);
		}
	}
}




ValueTextBox::ValueTextBox(GraphicEQ *plugin,
	GraphicGUI *gui,
	int x,
	int y,
	int w)
 : BC_TextBox(x, y, w, 1, "")
{
	this->plugin = plugin;
	this->gui = gui;
}

int ValueTextBox::handle_event()
{
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = plugin->config.points.get(plugin->active_point);
		if(!EQUIV(atof(get_text()), active_point->value))
		{
			active_point->value = atof(get_text());
			gui->update_canvas();
			plugin->send_configure_change();
			return 1;
		}
	}

	return 0;
}

void ValueTextBox::update(float value)
{
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = plugin->config.points.get(plugin->active_point);
		if(!EQUIV(atof(get_text()), active_point->value))
		{
			char string[BCTEXTLEN];
			sprintf(string, "%.04f", active_point->value);
			BC_TextBox::update(string);
		}
	}
}

GraphicReset::GraphicReset(GraphicEQ *plugin,
	GraphicGUI *gui,
	int x,
	int y)
 : BC_GenericButton(x, y, _("Reset"))
{
	this->plugin = plugin;
	this->gui = gui;
}

int GraphicReset::handle_event()
{
	plugin->config.points.remove_all_objects();
	plugin->active_point = -1;
	gui->update_canvas();
	gui->update_textboxes();
	plugin->send_configure_change();
	return 1;
}





GraphicSize::GraphicSize(GraphicGUI *window, GraphicEQ *plugin, int x, int y)
 : BC_PopupMenu(x, y, xS(120), "4096", 1)
{
	this->plugin = plugin;
	this->window = window;
}


int GraphicSize::handle_event()
{
	plugin->config.window_size = atoi(get_text());
	plugin->send_configure_change();

	window->update_canvas();
	return 1;
}

void GraphicSize::create_objects()
{
	add_item(new BC_MenuItem("2048"));
	add_item(new BC_MenuItem("4096"));
	add_item(new BC_MenuItem("8192"));
	add_item(new BC_MenuItem("16384"));
	add_item(new BC_MenuItem("32768"));
	add_item(new BC_MenuItem("65536"));
	add_item(new BC_MenuItem("131072"));
	add_item(new BC_MenuItem("262144"));
}

void GraphicSize::update(int size)
{
	char string[BCTEXTLEN];
	sprintf(string, "%d", size);
	set_text(string);
}




//
//
// GraphicWetness::GraphicWetness(GraphicGUI *window, GraphicEQ *plugin, int x, int y)
//  : BC_FPot(x, y, plugin->config.wetness, INFINITYGAIN, 0)
// {
// 	this->plugin = plugin;
// 	this->window = window;
// }
//
// int GraphicWetness::handle_event()
// {
// 	plugin->config.wetness = get_value();
// 	plugin->send_configure_change();
// 	window->update_canvas();
// 	return 1;
// }
//
//
//






GraphicGUI::GraphicGUI(GraphicEQ *plugin)
 : PluginClientWindow(plugin,
	plugin->w,
	plugin->h,
	xS(320),
	yS(200),
	1)
{
	this->plugin = plugin;
}

GraphicGUI::~GraphicGUI()
{
}


void GraphicGUI::create_objects()
{
	int margin = plugin->get_theme()->widget_border;
	int x = get_text_width(SMALLFONT, "-00") + LINE_W4 + margin;
	int y = margin;
	int freq_h = get_text_height(SMALLFONT) + LINE_W4;

	add_subwindow(canvas = new GraphicCanvas(plugin,
		this,
		x,
		y,
		get_w() - x - margin,
		get_h() -
//			BC_Pot::calculate_h() -
			BC_TextBox::calculate_h(this, MEDIUMFONT, 1, 1) -
			margin * 3 -
			y -
			freq_h));
	y += canvas->get_h() + freq_h + margin;

//	int x1 = x;
//	int y1 = y;
	add_subwindow(freq_title = new BC_Title(x, y, _("Frequency:")));
	x += freq_title->get_w() + margin;
	add_subwindow(freq_text = new FreqTextBox(plugin, this, x, y, xS(100)));
	x += freq_text->get_w() + margin;

	add_subwindow(level_title = new BC_Title(x, y, _("Level:")));
	x += level_title->get_w() + margin;
	add_subwindow(value_text = new ValueTextBox(plugin, this, x, y, xS(100)));
	x += value_text->get_w() + margin;

	add_subwindow(reset = new GraphicReset(plugin, this, x, y));
	x += reset->get_w() + margin;


//	x = x1;
//	y += value_text->get_h() + margin;

	add_subwindow(size_title = new BC_Title(x, y, _("Window size:")));
	x += size_title->get_w() + margin;
	add_subwindow(size = new GraphicSize(this, plugin, x, y));
	size->create_objects();
	size->update(plugin->config.window_size);
	x += size->get_w() + margin;

//	add_subwindow(title = new BC_Title(x, y, "Wetness:"));
//	x += title->get_w() + margin;
// 	add_subwindow(wetness = new GraphicWetness(this, plugin,
// 		x,
// 		y));

	draw_ticks();
	update_canvas();
	show_window();
}


int GraphicGUI::resize_event(int w, int h)
{
	int difference = h - get_h();
	int canvas_xdiff = get_w() - canvas->get_w();
	int canvas_ydiff = get_h() - canvas->get_h();

	canvas->reposition_window(canvas->get_x(),
		canvas->get_y(),
		w - canvas_xdiff,
		h - canvas_ydiff);
	freq_text->reposition_window(freq_text->get_x(),
		freq_text->get_y() + difference);
	value_text->reposition_window(value_text->get_x(),
		value_text->get_y() + difference);
	freq_title->reposition_window(freq_title->get_x(),
		freq_title->get_y() + difference);
	level_title->reposition_window(level_title->get_x(),
		level_title->get_y() + difference);
	size_title->reposition_window(size_title->get_x(),
		size_title->get_y() + difference);
	reset->reposition_window(reset->get_x(),
		reset->get_y() + difference);
	size->reposition_window(size->get_x(),
		size->get_y() + difference);

	draw_ticks();
	update_canvas();
	flash();

	plugin->w = w;
	plugin->h = h;
	plugin->send_configure_change();
	return 1;
}






int GraphicGUI::keypress_event()
{
	if(get_keypress() == BACKSPACE ||
		get_keypress() == DELETE)
	{
		if(plugin->active_point_exists())
		{
			int point_number = -1;
			for(int i = 0; i < plugin->config.points.size(); i++)
			{
				if(i == plugin->active_point)
				{
					point_number = i;
					break;
				}
			}

			if(point_number >= 0)
			{
				plugin->config.delete_point(point_number);
				canvas->process(0, 0, 1);
				plugin->send_configure_change();
				return 1;
			}
		}
	}
	return context_help_check_and_show();
}


void GraphicGUI::draw_ticks()
{
	//int x = canvas->get_x() - 5 - get_text_width(SMALLFONT, "-00");
	int y = canvas->get_y() - 1;
	int x1 = canvas->get_x() - LINE_W3;
	int x2 = canvas->get_x() - LINE_W2;
	int x3 = canvas->get_x() - LINE_W1;
	char string[BCTEXTLEN];

// Amplitude
	set_font(SMALLFONT);
	int major_division = canvas->get_h() / (MAJOR_DIVISIONS - 1);
	for(int i = 0; i < MAJOR_DIVISIONS; i++)
	{
		int current_db = (MAJOR_DIVISIONS - 1 - i) * (MAX_DB - MIN_DB) / (MAJOR_DIVISIONS - 1) + MIN_DB;
		if(current_db == MIN_DB)
			sprintf(string, "oo");
		else
		if(current_db <= 0.0)
			sprintf(string, "%d", current_db);
		else
			sprintf(string, "+%d", current_db);

		set_color(BLACK);
		int y1 = y + 1 + i * canvas->get_h() / (MAJOR_DIVISIONS - 1);
		int x4 = canvas->get_x() - LINE_W4 - get_text_width(SMALLFONT, string);
		draw_text(x4 + 1, y1 + get_text_ascent(SMALLFONT) / 2 + 1, string);
		draw_line(x1 + 1, y1 + 1, x3 + 1, y1 + 1);
		set_color(RED);
		draw_text(x4, y1 + get_text_ascent(SMALLFONT) / 2, string);
		draw_line(x1, y1, x3, y1);


		if(i < MAJOR_DIVISIONS - 1)
		{
			for(int j = 0; j < MINOR_DIVISIONS; j++)
			{
				int y2 = y1 + j * major_division / MINOR_DIVISIONS;
				set_color(BLACK);
				draw_line(x2 + 1, y2 + 1, x3 + 1, y2 + 1);
				set_color(RED);
				draw_line(x2, y2, x3, y2);
			}
		}
	}


// Frequency
	for(int i = 0; i <= MAJOR_DIVISIONS; i++)
	{
		int freq = Freq::tofreq(i * TOTALFREQS / MAJOR_DIVISIONS);
		char string[BCTEXTLEN];
		x1 = canvas->get_x() + i * canvas->get_w() / MAJOR_DIVISIONS;
		int y1 = canvas->get_y() + canvas->get_h() + LINE_W1 - 1;
		sprintf(string, "%d", freq);
		int x2 = x1 - get_text_width(SMALLFONT, string);
		int y2 = canvas->get_y() + canvas->get_h() + LINE_W2 - 1;
		int y3 = canvas->get_y() + canvas->get_h() + LINE_W3 - 1;
		int y4 = canvas->get_y() + canvas->get_h() + get_text_height(SMALLFONT) + LINE_W4 - 1;

		set_color(BLACK);
		draw_text(x2 + 1, y4 + 1, string);
		draw_line(x1 + 1, y3 + 1, x1 + 1, y1 + 1);

		set_color(RED);
		draw_text(x2, y4, string);
		draw_line(x1, y3, x1, y1);

		if(i < MAJOR_DIVISIONS)
		{
			for(int j = 0; j < MINOR_DIVISIONS; j++)
			{
				int x3 = (int)(x1 +
					(canvas->get_w() / MAJOR_DIVISIONS) -
					exp(-(double)j * 0.7) *
					(canvas->get_w() / MAJOR_DIVISIONS));
				set_color(BLACK);
				draw_line(x3 + 1, y2 + 1, x3 + 1, y1 + 1);
				set_color(RED);
				draw_line(x3, y2, x3, y1);
			}
		}
	}
}

void GraphicGUI::update_canvas()
{
	canvas->process(0, 0, 1);
}

void GraphicGUI::update_textboxes()
{
	if(plugin->active_point_exists())
	{
		GraphicPoint *active_point = plugin->config.points.get(plugin->active_point);
		freq_text->update(active_point->freq);
		value_text->update(active_point->value);
	}
}


GraphicEQ::GraphicEQ(PluginServer *server)
 : PluginAClient(server)
{
	last_frame = 0;
	fft = 0;
	need_reconfigure = 1;
	active_point = -1;
	w = xS(640);
	h = yS(480);
}

GraphicEQ::~GraphicEQ()
{

	delete last_frame;
	if(fft) delete fft;
}


int GraphicEQ::is_realtime() { return 1; }

const char* GraphicEQ::plugin_title() { return N_("EQ Graphic"); }

NEW_WINDOW_MACRO(GraphicEQ, GraphicGUI)

LOAD_CONFIGURATION_MACRO(GraphicEQ, GraphicConfig)

int GraphicEQ::active_point_exists()
{
	if(active_point >= 0 && active_point < config.points.size()) return 1;
	return 0;
}


void GraphicEQ::read_data(KeyFrame *keyframe)
{
	FileXML input;
	int result = 0;

	input.set_shared_input(keyframe->xbuf);
	config.points.remove_all_objects();

	while( !(result = input.read_tag()) ) {
		if(input.tag.title_is("GRAPHICEQ")) {
			config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
			if(is_defaults()) {
				w = input.tag.get_property("W", w);
				h = input.tag.get_property("H", h);
			}
		}
		else if(input.tag.title_is("POINT")) {
			GraphicPoint *point;
			config.points.append(point = new GraphicPoint);
			point->freq = input.tag.get_property("X", 0);
			point->value = input.tag.get_property("Y", 0.0);
		}
	}
//	if(!active_point_exists()) active_point = -1;
}




void GraphicEQ::save_data(KeyFrame *keyframe)
{
	FileXML output;
	output.set_shared_output(keyframe->xbuf);

	output.tag.set_title("GRAPHICEQ");
	output.tag.set_property("WINDOW_SIZE", config.window_size);
	output.tag.set_property("W", w);
	output.tag.set_property("H", h);
	output.append_tag();
	output.tag.set_title("/GRAPHICEQ");
	output.append_tag();
	output.append_newline();

	for(int i = 0; i < config.points.total; i++) {
		output.tag.set_title("POINT");
		output.tag.set_property("X", config.points.values[i]->freq);
		output.tag.set_property("Y", config.points.values[i]->value);
		output.append_tag();
		output.tag.set_title("/POINT");
		output.append_tag();
		output.append_newline();
	}

	output.terminate_string();
}


void GraphicEQ::update_gui()
{
	if( !thread ) return;
	GraphicGUI *window = (GraphicGUI *)thread->window;
	window->lock_window("GraphicEQ::update_gui");
//lock here for points, needed by window cursor_motion callback
//  deleted in load_configuration by read_data
	if( load_configuration() &&
	    window->canvas->state != GraphicCanvas::DRAG_POINT ) {
		window->update_canvas();
		window->update_textboxes();
	}
	else if( pending_gui_frame() ) {
		window->update_canvas();
	}
	window->unlock_window();
}

void GraphicEQ::reconfigure()
{
	if(fft && fft->window_size != config.window_size) {
		delete fft;
		fft = 0;
	}

	if(!fft) {
		fft = new GraphicFFT(this);
		fft->initialize(config.window_size);
	}

	calculate_envelope(&config.points, envelope);

	for(int i = 0; i < config.window_size / 2; i++) {
		if(envelope[i] < 0) envelope[i] = 0;
	}

	need_reconfigure = 0;
}

int GraphicEQ::process_buffer(int64_t size,
	Samples *buffer,
	int64_t start_position,
	int sample_rate)
{
	need_reconfigure |= load_configuration();
	if(need_reconfigure) reconfigure();

	fft->process_buffer(start_position, size, buffer, get_direction());


	return 0;
}


double GraphicEQ::freq_to_magnitude(double frequency,
	ArrayList<GraphicPoint*> *points,
	double *envelope)
{
// Calculate using control points
	for(int i = 0; i < points->size(); i++)
	{
		GraphicPoint *point = points->get(i);
		if(point->freq == (int)frequency)
		{
			return DB::fromdb(point->value);
		}
	}


	int nyquist = PluginAClient::project_sample_rate / 2;
	int slot = (int)(frequency * config.window_size / 2 / nyquist);
	if(slot >= config.window_size / 2) slot = config.window_size / 2 - 1;
//printf("GraphicEQ::freq_to_db %d %f\n", slot, envelope[slot]);
	return envelope[slot];
}


void GraphicEQ::calculate_envelope(ArrayList<GraphicPoint*> *points,
		double *envelope)
{
	int niquist = PluginAClient::project_sample_rate / 2;

// Make temporary list of just points in order
	ArrayList<GraphicPoint*> temp_points;
	for(int i = 0; i < points->size(); i++)
	{
		temp_points.append(points->get(i));
	}

	for(int i = 0; i < temp_points.size(); i++)
	{
		GraphicPoint *point = temp_points.get(i);
		if(i == active_point)
		{
			GraphicPoint *prev_point = 0;
			GraphicPoint *next_point = 0;
			if(i >= 1) prev_point = temp_points.get(i - 1);
			if(i < temp_points.size() - 1) next_point = temp_points.get(i + 1);
			if( (prev_point && prev_point->freq >= point->freq) ||
				(next_point && next_point->freq <= point->freq) )
				temp_points.remove_number(i);
			break;
		}
	}


// Join each point
	if(temp_points.size())
	{
		GraphicPoint *first_point = temp_points.get(0);
		GraphicPoint *last_point = temp_points.get(temp_points.size() - 1);
		for(int i = 0; i < config.window_size / 2; i++)
		{
			int freq = i * niquist / (config.window_size / 2);
			if(freq <= first_point->freq)
				envelope[i] = first_point->value;
			else
			if(freq >= last_point->freq)
				envelope[i] = last_point->value;
			else
			{
				GraphicPoint *point1 = first_point;
				GraphicPoint *point2 = last_point;
				for(int j = 0; j < temp_points.size(); j++)
				{
					if(temp_points.get(j)->freq <= freq)
						point1 = temp_points.get(j);
					else
					{
						point2 = temp_points.get(j);
						break;
					}
				}

				if(point2->freq != point1->freq)
				{
					int freqslot1 = Freq::fromfreq(point1->freq);
					int freqslot2 = Freq::fromfreq(point2->freq);
					int freqslot = Freq::fromfreq(freq);

					envelope[i] = (double)(freqslot - freqslot1) *
						(point2->value - point1->value) /
						(freqslot2 - freqslot1) +
						point1->value;
				}
				else
					envelope[i] = point1->value;
			}


			if(envelope[i] < MIN_DB + 0.01)
				envelope[i] = 0;
			else
				envelope[i] = DB::fromdb(envelope[i]);
		}
	}
	else
	{
		for(int i = 0; i < config.window_size / 2; i++)
		{
			envelope[i] = 1.0;
		}
	}
}


GraphicGUIFrame::GraphicGUIFrame(int window_size, int sample_rate)
 : PluginClientFrame()
{
	this->window_size = window_size;
	data_size = window_size / 2;
	data = new double[data_size];
	freq_max = 0;
	time_max = 0;
}

GraphicGUIFrame::~GraphicGUIFrame()
{
	delete [] data;
}


GraphicFFT::GraphicFFT(GraphicEQ *plugin)
 : CrossfadeFFT()
{
	this->plugin = plugin;
}

GraphicFFT::~GraphicFFT()
{
}


int GraphicFFT::signal_process()
{
// Create new frame for updating GUI
	frame = new GraphicGUIFrame(window_size,
		plugin->PluginAClient::project_sample_rate);
	plugin->add_gui_frame(frame);

	double freq_max = 0;
	for(int i = 0; i < window_size / 2; i++)
	{
		double result = plugin->envelope[i] * sqrt(freq_real[i] * freq_real[i] + freq_imag[i] * freq_imag[i]);
		double angle = atan2(freq_imag[i], freq_real[i]);
		freq_real[i] = result * cos(angle);
		freq_imag[i] = result * sin(angle);
		frame->data[i] = result;
		if(result > freq_max) freq_max = result;
	}
	frame->freq_max = freq_max;

	symmetry(window_size, freq_real, freq_imag);

	return 0;
}

int GraphicFFT::post_process()
{
	double time_max = 0;
	for(int i = 0; i < window_size; i++)
	{
		if(output_real[i] > time_max) time_max = output_real[i];
	}
	frame->time_max = time_max;
	return 0;
}



int GraphicFFT::read_samples(int64_t output_sample,
	int samples,
	Samples *buffer)
{
	return plugin->read_samples(buffer,
		0,
		plugin->get_samplerate(),
		output_sample,
		samples);
}


