
/*
 * CINELERRA
 * Copyright (C) 2008 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 "aattachmentpoint.h"
#include "aedit.h"
#include "amodule.h"
#include "arender.h"
#include "atrack.h"
#include "automation.h"
#include "clip.h"
#include "edits.h"
#include "edl.h"
#include "edlsession.h"
#include "floatautos.h"
#include "mwindow.h"
#include "module.h"
#include "panauto.h"
#include "plugin.h"
#include "renderengine.h"
#include "samples.h"
#include "track.h"
#include "transition.h"
#include "transportque.h"
#include "virtualaconsole.h"
#include "virtualanode.h"


#include <string.h>

VirtualANode::VirtualANode(RenderEngine *renderengine,
		VirtualConsole *vconsole,
		Module *real_module,
		Plugin *real_plugin,
		Track *track,
		VirtualNode *parent_module)
 : VirtualNode(renderengine,
 		vconsole,
		real_module,
		real_plugin,
		track,
		parent_module)
{
	for(int i = 0; i < MAXCHANNELS; i++)
	{
		pan_before[i] = pan_after[i] = 0;
	}
}

VirtualANode::~VirtualANode()
{
}





VirtualNode* VirtualANode::create_module(Plugin *real_plugin,
							Module *real_module,
							Track *track)
{
	return new VirtualANode(renderengine,
		vconsole,
		real_module,
		0,
		track,
		this);
}


VirtualNode* VirtualANode::create_plugin(Plugin *real_plugin)
{
	return new VirtualANode(renderengine,
		vconsole,
		0,
		real_plugin,
		track,
		this);
}



int VirtualANode::read_data(Samples *output_temp,
	int64_t size,
	int64_t start_position,
	int64_t sample_rate)
{
	VirtualNode *previous_plugin = 0;

// Current edit in parent track
	AEdit *parent_edit = 0;
	if(parent_node && parent_node->track && renderengine)
	{
		int64_t edl_rate = renderengine->get_edl()->session->sample_rate;
		int64_t start_position_project = (int64_t)(start_position *
			edl_rate /
			sample_rate +
			0.5);
		parent_edit = (AEdit*)parent_node->track->edits->editof(start_position_project,
			renderengine->command->get_direction(),
			0);
	}


	if(vconsole->debug_tree)
		printf("  VirtualANode::read_data position=%jd rate=%jd"
			" title=%s parent_node=%p parent_edit=%p\n",
			start_position, sample_rate, track->title,
			parent_node, parent_edit);


// This is a plugin on parent module with a preceeding effect.
// Get data from preceeding effect on parent module.
	if(parent_node &&
		(previous_plugin = parent_node->get_previous_plugin(this)))
	{
		((VirtualANode*)previous_plugin)->render(output_temp,
			size,
			start_position,
			sample_rate);
	}
	else
// The current node is the first plugin on parent module.
// The parent module has an edit to read from or the current node
// has no source to read from.
// Read data from parent module
	if(parent_node && (parent_edit || !real_module))
	{
		((VirtualANode*)parent_node)->read_data(output_temp,
			size,
			start_position,
			sample_rate);
	}
	else
	if(real_module)
// This is the first node in the tree
	{
		((AModule*)real_module)->render(output_temp,
			size,
			start_position,
			renderengine->command->get_direction(),
			sample_rate,
			0);
	}
	return 0;
}

int VirtualANode::render(Samples *output_temp,
	int64_t size,
	int64_t start_position,
	int64_t sample_rate)
{
	const int debug = 0;
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
	ARender *arender = ((VirtualAConsole*)vconsole)->arender;
	if(real_module)
	{
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
		render_as_module(arender->audio_out,
			output_temp,
			size,
			start_position,
			sample_rate);
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
	}
	else
	if(real_plugin)
	{
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
		render_as_plugin(output_temp,
			size,
			start_position,
			sample_rate);
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
	}
if(debug) printf("VirtualANode::render %d this=%p\n", __LINE__, this);
	return 0;
}

void VirtualANode::render_as_plugin(Samples *output_temp,
	int64_t size,
	int64_t start_position,
	int64_t sample_rate)
{
	if(!attachment ||
		!real_plugin ||
		!real_plugin->on) return;

// If we're the first plugin in the parent module, data needs to be read from
// what comes before the parent module.  Otherwise, data needs to come from the
// previous plugin.
	((AAttachmentPoint*)attachment)->render(
		output_temp,
		plugin_buffer_number,
		start_position,
		size,
	  	sample_rate);
}

int VirtualANode::render_as_module(Samples **audio_out,
				Samples *output_temp,
				int64_t len,
				int64_t start_position,
				int64_t sample_rate)
{
	int direction = renderengine->command->get_direction();
	EDL *edl = vconsole->renderengine->get_edl();
	const int debug = 0;

// Process last subnode.  This calls read_data, propogates up the chain
// of subnodes, and finishes the chain.
	if(subnodes.total)
	{
		VirtualANode *node = (VirtualANode*)subnodes.values[subnodes.total - 1];
		node->render(output_temp,
			len,
			start_position,
			sample_rate);
	}
	else
// Read data from previous entity
	{
		read_data(output_temp,
			len,
			start_position,
			sample_rate);
	}

// for(int k = 0; k < len; k++)
// {
// 	output_temp->get_data()[k] = (k / 10) % 2;
// }

if(debug) printf("VirtualANode::render_as_module %d\n", __LINE__);
	render_fade(output_temp->get_data(),
				len,
				start_position,
				sample_rate,
				track->automation->autos[AUTOMATION_FADE],
				direction,
				0);
if(debug) printf("VirtualANode::render_as_module %d\n", __LINE__);

// Get the peak but don't limit
// Calculate position relative to project for meters
	int64_t project_sample_rate = edl->session->sample_rate;
	int64_t start_position_project = start_position *
		project_sample_rate /
		sample_rate;
if(debug) printf("VirtualANode::render_as_module %d\n", __LINE__);
	if(real_module && renderengine->command->realtime)
	{
		ARender *arender = ((VirtualAConsole*)vconsole)->arender;
// Starting sample of meter block
		int64_t meter_render_start;
// Ending sample of meter block
		int64_t meter_render_end;
// Number of samples in each meter fragment normalized to requested rate
		int meter_render_fragment = arender->meter_render_fragment *
			sample_rate /
			project_sample_rate;


// Scan fragment in meter sized fragments
		for(int i = 0; i < len; )
		{
			meter_render_start = i;
			meter_render_end = i + meter_render_fragment;
			if(meter_render_end > len)
				meter_render_end = len;
// Number of samples into the fragment this meter sized fragment is,
// normalized to project sample rate.
			int64_t meter_render_start_project = meter_render_start *
				project_sample_rate / sample_rate;

// Scan meter sized fragment
			double peak = 0, *output_samples = output_temp->get_data();
			while( i < meter_render_end ) {
				double sample = fabs(output_samples[i++]);
				if( sample > peak ) peak = sample;
			}

			MeterHistory *meter_history = ((AModule*)real_module)->meter_history;
			int64_t pos = (direction == PLAY_FORWARD) ?
				(start_position_project + meter_render_start_project) :
				(start_position_project - meter_render_start_project) ;
			meter_history->set_peak(0, peak, pos);
		}
	}
if(debug) printf("VirtualANode::render_as_module %d\n", __LINE__);

// process pans and copy the output to the output channels
// Keep rendering unmuted fragments until finished.
	int mute_position = 0;

	for(int i = 0; i < len; )
	{
		int mute_constant;
		int mute_fragment = len - i;
		int mute_fragment_project = mute_fragment *
			project_sample_rate /
			sample_rate;
		start_position_project = start_position +
			((direction == PLAY_FORWARD) ? i : -i);
		start_position_project = start_position_project *
			project_sample_rate /
			sample_rate;

// How many samples until the next mute?
		get_mute_fragment(start_position_project,
				mute_constant,
				mute_fragment_project,
				(Autos*)track->automation->autos[AUTOMATION_MUTE],
				direction,
				0);
// Fragment is playable
		if(!mute_constant)
		{
			for(int j = 0;
				j < MAX_CHANNELS;
				j++)
			{
				if(audio_out[j])
				{
					double *buffer = audio_out[j]->get_data();

					render_pan(output_temp->get_data() + mute_position,
								buffer + mute_position,
								mute_fragment,
								start_position,
								sample_rate,
								(Autos*)track->automation->autos[AUTOMATION_PAN],
								j,
								direction,
								0);
//printf("VirtualANode::render_as_module %d %lld\n", __LINE__, start_position);
				}
			}
		}

		len -= mute_fragment;
		i += mute_fragment;
		mute_position += mute_fragment;
	}
if(debug) printf("VirtualANode::render_as_module %d\n", __LINE__);

	return 0;
}

int VirtualANode::render_fade(double *buffer, int64_t len,
	int64_t input_position, int64_t sample_rate,
	Autos *autos, int direction, int use_nudge)
{
	double fade_value = 0.0;
	FloatAuto *previous = 0;
	FloatAuto *next = 0;
	EDL *edl = vconsole->renderengine->get_edl();
	const int debug = 0;
	int64_t project_sample_rate = edl->session->sample_rate;
	double to_project_rate = (double)project_sample_rate / sample_rate;
	double to_sample_rate = (double)sample_rate / project_sample_rate;

	if(use_nudge)
		input_position += track->nudge * to_sample_rate;

if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
// Normalize input position to project sample rate here.
// Automation functions are general to video and audio so it
// can't normalize itself.
	int64_t input_pos = input_position * to_project_rate;
	int64_t len_project = len * to_project_rate;

if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
	if(((FloatAutos*)autos)->automation_is_constant(
		input_pos, len_project, direction, fade_value))
	{
if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
		double value = fade_value<=INFINITYGAIN ?
			 0. : DB::fromdb(fade_value);
		if( EQUIV(value, 0.) ) {
			for(int64_t i = 0; i < len; i++)
				buffer[i] = 0;
		}
		else if( !EQUIV(value, 1.) ) {
			for(int64_t i = 0; i < len; i++)
				buffer[i] *= value;
		}
if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
	}
	else
	{
if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
		for(int64_t i = 0; i < len; i++)
		{
			input_pos = input_position * to_project_rate;

			fade_value = ((FloatAutos*)autos)->get_value(
				input_pos, direction, previous, next);

			double value = fade_value<=INFINITYGAIN ?
				 0. : DB::fromdb(fade_value);
			if( EQUIV(value,0.) )
				buffer[i] = 0;
			else if( !EQUIV(value,1.) ) {
				buffer[i] *= value;
			}

			if(direction == PLAY_FORWARD)
				input_position++;
			else
				input_position--;
		}
if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);
	}
if(debug) printf("VirtualANode::render_fade %d\n", __LINE__);

	return 0;
}

int VirtualANode::render_pan(double *input, // start of input fragment
	double *output,            // start of output fragment
	int64_t fragment_len,      // fragment length in input scale
	int64_t input_position,    // starting sample of input buffer in project
	int64_t sample_rate,       // sample rate of input_position
	Autos *autos,
	int channel,
	int direction,
	int use_nudge)
{
	double slope = 0.0;
	double intercept = 1.0;
	EDL *edl = vconsole->renderengine->get_edl();
	int64_t project_sample_rate = edl->session->sample_rate;
	double to_project_rate = (double)project_sample_rate / sample_rate;
	double to_sample_rate = (double)sample_rate / project_sample_rate;
	if(use_nudge)
		input_position += track->nudge * to_sample_rate;

	for(int i = 0; i < fragment_len; )
	{
		int64_t len = fragment_len - i;
		int64_t slope_len = len * to_project_rate;
		int64_t input_pos = input_position * to_project_rate;

// Get slope intercept formula for next fragment
		get_pan_automation(slope, intercept, input_pos,
			slope_len, autos, channel, direction);

		slope_len *= to_sample_rate;
		slope = slope * to_sample_rate;
		slope_len = MIN(slope_len, len);

//printf("VirtualANode::render_pan 3 %d %jd %f %p %p\n", i, slope_len, slope, output, input);
		if(!EQUIV(slope, 0)) {
			for(double j = 0; j < slope_len; j++, i++) {
				value = slope * j + intercept;
				output[i] += input[i] * value;
			}
		}
		else if( EQUIV(intercept, 1) ) {
			for(int64_t j = 0; j < slope_len; j++, i++)
				output[i] += input[i];
		}
		else if( !EQUIV(intercept, 0) ) {
			for(int64_t j = 0; j < slope_len; j++, i++)
				output[i] += input[i] * intercept;
		}
		else
			i += slope_len;

		if(direction == PLAY_FORWARD)
			input_position += slope_len;
		else
			input_position -= slope_len;

//printf("VirtualANode::render_pan 4\n");
	}

	return 0;
}


void VirtualANode::get_pan_automation(double &slope,
	double &intercept,
	int64_t input_position,
	int64_t &slope_len,
	Autos *autos,
	int channel,
	int direction)
{
	intercept = 0;
	slope = 0;

	Auto *prev_keyframe = 0, *next_keyframe = 0;
	prev_keyframe = autos->get_prev_auto(input_position, direction, prev_keyframe);
	next_keyframe = autos->get_next_auto(input_position, direction, next_keyframe);
	PanAuto *prev_pan_auto = (PanAuto *)prev_keyframe;
	PanAuto *next_pan_auto = (PanAuto *)next_keyframe;

	if(direction == PLAY_FORWARD)
	{
// Two distinct automation points within range
		if(next_pan_auto->position > prev_pan_auto->position)
		{
			slope = ((double)next_pan_auto->values[channel] - prev_pan_auto->values[channel]) /
				((double)next_pan_auto->position - prev_pan_auto->position);
			intercept = ((double)input_position - prev_pan_auto->position) * slope +
				prev_pan_auto->values[channel];

			if(next_pan_auto->position < input_position + slope_len)
				slope_len = next_pan_auto->position - input_position;
		}
		else
// One automation point within range
		{
			slope = 0;
			intercept = prev_pan_auto->values[channel];
		}
	}
	else
	{
// Two distinct automation points within range
		if(next_pan_auto->position < prev_pan_auto->position)
		{
			slope = ((double)next_pan_auto->values[channel] - prev_pan_auto->values[channel]) /
				((double)next_pan_auto->position - prev_pan_auto->position);
			intercept = ((double)input_position - prev_pan_auto->position) * slope +
				prev_pan_auto->values[channel];

			if(next_pan_auto->position > input_position - slope_len)
				slope_len = input_position - next_pan_auto->position;
		}
		else
// One automation point within range
		{
			slope = 0;
			intercept = next_pan_auto->values[channel];
		}
	}
}
