
/*
 * 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 "aedit.h"
#include "amodule.h"
#include "arender.h"
#include "assets.h"
#include "atrack.h"
#include "audiodevice.h"
#include "bcsignals.h"
#include "condition.h"
#include "edit.h"
#include "edits.h"
#include "edl.h"
#include "edlsession.h"
#include "file.h"
#include "levelwindow.h"
#include "playabletracks.h"
#include "plugin.h"
#include "preferences.h"
#include "renderengine.h"
#include "samples.h"
#include "thread.h"
#include "tracks.h"
#include "transportque.h"
#include "virtualaconsole.h"
#include "virtualanode.h"
#include "virtualnode.h"


VirtualAConsole::VirtualAConsole(RenderEngine *renderengine, ARender *arender)
 : VirtualConsole(renderengine, arender, TRACK_AUDIO)
{
	this->arender = arender;
	output_temp = 0;
	output_allocation = 0;
}

VirtualAConsole::~VirtualAConsole()
{
	if(output_temp) delete output_temp;
}


void VirtualAConsole::get_playable_tracks()
{
	if(!playable_tracks)
		playable_tracks = new PlayableTracks(renderengine->get_edl(),
			commonrender->current_position,
			renderengine->command->get_direction(),
			TRACK_AUDIO,
			1);
}


VirtualNode* VirtualAConsole::new_entry_node(Track *track,
	Module *module,
	int track_number)
{
	return new VirtualANode(renderengine,
		this, module, 0, track, 0);
	return 0;
}



int VirtualAConsole::process_buffer(int64_t len,
	int64_t start_position)
{
	int result = 0;
	const int debug = 0;
if(debug) printf("VirtualAConsole::process_buffer %d this=%p len=%jd\n",
  __LINE__, this, len);


// clear output buffers
	for(int i = 0; i < MAX_CHANNELS; i++)
	{
// if(debug) printf("VirtualAConsole::process_buffer 2 %d %p %jd\n",
// i,
// arender->audio_out[i],
// len);

		if(arender->audio_out[i])
		{
			bzero(arender->audio_out[i]->get_data(), len * sizeof(double));
		}
	}

// Create temporary output
	if(output_temp && output_allocation < len)
	{
		delete output_temp;
		output_temp = 0;
	}

	if(!output_temp)
	{
		output_temp = new Samples(len);
		output_allocation = len;
	}
if(debug) printf("VirtualAConsole::process_buffer %d\n", __LINE__);


// Reset plugin rendering status
	reset_attachments();
//printf("VirtualAConsole::process_buffer 1 %p\n", output_temp);

if(debug) printf("VirtualAConsole::process_buffer %d\n", __LINE__);


// Render exit nodes
	for(int i = 0; i < exit_nodes.total; i++)
	{
		VirtualANode *node = (VirtualANode*)exit_nodes.values[i];
		Track *track = node->track;

		result |= node->render(output_temp,
			len,
			start_position + track->nudge,
			renderengine->get_edl()->session->sample_rate);
	}
if(debug) printf("VirtualAConsole::process_buffer %d\n", __LINE__);



// get peaks and limit volume in the fragment
	for(int i = 0; i < MAX_CHANNELS; i++)
	{
		if(arender->audio_out[i])
		{
			double *current_buffer = arender->audio_out[i]->get_data();


			for(int j = 0; j < len; )
			{
				int meter_render_end;
// Get length to test for meter and limit
				if(renderengine->command->realtime)
					meter_render_end = j + arender->meter_render_fragment;
				else
					meter_render_end = len;

				if(meter_render_end > len)
					meter_render_end =  len;

				double peak = 0;
				while( j < meter_render_end ) {
// Level history comes before clipping to get over status
					double *sample = &current_buffer[j++];
					if( fabs(*sample) > peak ) peak = fabs(*sample);
// Make the output device clip it
// 					if(*sample > 1) *sample = 1;
// 					else
// 					if(*sample < -1) *sample = -1;
				}

				if( renderengine->command->realtime ) {
					int direction = renderengine->command->get_direction();
					int64_t pos = direction == PLAY_REVERSE ?
						start_position - j :
						start_position + j ;
					arender->meter_history->set_peak(i, peak, pos);
				}
			}
		}
	}

if(debug) printf("VirtualAConsole::process_buffer %d\n", __LINE__);





// Pack channels, fix speed and send to device.
	if(!renderengine->is_nested &&
		renderengine->command->realtime &&
		!interrupt)
	{
// speed parameters
		float speed = renderengine->command->get_speed();
// length compensated for speed
		int real_output_len = 0;
		double *audio_out_packed[MAX_CHANNELS];
		int audio_channels = renderengine->get_edl()->session->audio_channels;

		for( int i=0; i<audio_channels; ++i )
			audio_out_packed[i] = arender->audio_out[i]->get_data();

// Time stretch the fragment to the real_output size
		for( int i=0; i<audio_channels; ++i ) {
			double *current_buffer = audio_out_packed[i];
			if( speed > 1 ) {  // buffer gets shorter
				int in = 0, out = 0;
				while( in < len ) {
					int next = (out+1) * speed;
					if( next > len) next = len;
					double sample = current_buffer[in];
					for( int i=in; ++i<next; ) sample += current_buffer[i];
					int l = next - in;
					if( l > 1 ) sample /= l;
					current_buffer[out++] = sample;
					in = next;
				}
				real_output_len = out;
			}
			else if( speed < 1 ) {  // buffer gets longer
				real_output_len = len / speed;
				int in = len, out = real_output_len;
				while( in > 0 && out > 0 ) {
					double sample = current_buffer[--in];
					int next = in / speed;
					while( out > next ) current_buffer[--out] = sample;
				}
			}
			else
				real_output_len = len;
		}

// Wait until video is ready
		if( arender->first_buffer ) {
			renderengine->first_frame_lock->lock("VirtualAConsole::process_buffer");
			arender->first_buffer = 0;
		}
		if( !renderengine->audio->get_interrupted() ) {
			renderengine->audio->write_buffer(audio_out_packed, audio_channels,
				real_output_len);
		}

		if( renderengine->audio->get_interrupted() )
			interrupt = 1;
	}

	return result;
}


int VirtualAConsole::init_rendering(int duplicate)
{
	return 0;
}


int VirtualAConsole::send_last_output_buffer()
{
	renderengine->audio->set_last_buffer();
	return 0;
}

