/* * CINELERRA * Copyright (C) 2009-2013 Adam Williams * * 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 "aplugin.h" #include "arender.h" #include "asset.h" #include "atrack.h" #include "automation.h" #include "bcsignals.h" #include "cache.h" #include "clip.h" #include "edits.h" #include "edl.h" #include "edlsession.h" #include "file.h" #include "filexml.h" #include "floatautos.h" #include "language.h" #include "module.h" #include "patch.h" #include "plugin.h" #include "pluginarray.h" #include "preferences.h" #include "renderengine.h" #include "mainsession.h" #include "samples.h" #include "sharedlocation.h" #include "theme.h" #include "transition.h" #include "transportque.h" #include AModuleResample::AModuleResample(AModule *module) : Resample() { this->module = module; bzero(nested_output, sizeof(Samples*) * MAX_CHANNELS); nested_allocation = 0; } AModuleResample::~AModuleResample() { for(int i = 0; i < MAX_CHANNELS; i++) delete nested_output[i]; } int AModuleResample::read_samples(Samples *buffer, int64_t start, int64_t len) { int result = 0; if(module->asset) { // Files only read going forward. if(get_direction() == PLAY_REVERSE) { start -= len; } //printf("AModuleResample::read_samples start=%jd len=%jd\n", start, len); module->file->set_audio_position(start); module->file->set_channel(module->channel); result = module->file->read_samples(buffer, len); // Reverse buffer so resampling filter renders forward. if(get_direction() == PLAY_REVERSE) Resample::reverse_buffer(buffer->get_data(), len); } else if(module->nested_edl) { // Nested EDL generates reversed buffer. for(int i = 0; i < module->nested_edl->session->audio_channels; i++) { if(nested_allocation < len) { delete nested_output[i]; nested_output[i] = 0; } if(!nested_output[i]) { nested_output[i] = new Samples(len); } } result = module->nested_renderengine->arender->process_buffer( nested_output, len, start); // printf("AModuleResample::read_samples buffer=%p module=%p len=%d\n", // buffer, // module, // len); memcpy(buffer->get_data(), nested_output[module->channel]->get_data(), len * sizeof(double)); } return result; } AModule::AModule(RenderEngine *renderengine, CommonRender *commonrender, PluginArray *plugin_array, Track *track) : Module(renderengine, commonrender, plugin_array, track) { data_type = TRACK_AUDIO; transition_temp = 0; speed_temp = 0; bzero(nested_output, sizeof(Samples*) * MAX_CHANNELS); meter_history = new MeterHistory(); nested_allocation = 0; resample = 0; asset = 0; file = 0; } AModule::~AModule() { delete transition_temp; delete speed_temp; delete meter_history; for(int i = 0; i < MAX_CHANNELS; i++) delete nested_output[i]; delete resample; } AttachmentPoint* AModule::new_attachment(Plugin *plugin) { return new AAttachmentPoint(renderengine, plugin); } void AModule::create_objects() { Module::create_objects(); // Not needed in pluginarray if( commonrender ) { meter_history->init(1, ((ARender*)commonrender)->total_peaks); meter_history->reset_channel(0); } } int AModule::get_buffer_size() { if(renderengine) return renderengine->fragment_len; else return plugin_array->get_bufsize(); } CICache* AModule::get_cache() { if(renderengine) return renderengine->get_acache(); else return cache; } int AModule::import_samples(AEdit *edit, int64_t start_project, int64_t edit_startproject, int64_t edit_startsource, int direction, int sample_rate, Samples *buffer, int64_t fragment_len) { const int debug = 0; int result = 0; // start in EDL samplerate int64_t start_source = start_project - edit_startproject + edit_startsource; // fragment size adjusted for speed curve int64_t speed_fragment_len = fragment_len; // boundaries of input fragment required for speed curve double max_position = 0; double min_position = 0; double speed_position = edit_startsource; // position in source where speed curve starts reading double speed_position1 = speed_position; // position in source where speed curve finishes double speed_position2 = speed_position; // Need speed curve processing int have_speed = 0; // Temporary buffer for rendering speed curve Samples *speed_buffer = buffer; if( nested_edl && edit->channel >= nested_edl->session->audio_channels ) return 1; this->channel = edit->channel; int dir = direction == PLAY_FORWARD ? 1 : -1; // apply speed curve to source position so the timeline agrees with the playback if( track->has_speed() ) { // get speed adjusted position from start of edit. FloatAuto *previous = 0, *next = 0; FloatAutos *speed_autos = (FloatAutos*)track->automation->autos[AUTOMATION_SPEED]; speed_position += speed_autos->automation_integral(edit_startproject, start_project-edit_startproject, PLAY_FORWARD); speed_position1 = speed_position; // calculate boundaries of input fragment required for speed curve max_position = speed_position; min_position = speed_position; int64_t pos = start_project; for( int64_t i=0; iget_value(pos, direction, previous, next); speed_position += dir*speed; if(speed_position > max_position) max_position = speed_position; if(speed_position < min_position) min_position = speed_position; } speed_position2 = speed_position; if(speed_position2 < speed_position1) { max_position += 1.0; // min_position -= 1.0; speed_fragment_len = (int64_t)(max_position - min_position); } else { max_position += 1.0; speed_fragment_len = (int64_t)(max_position - min_position); } //printf("AModule::import_samples %d %f %f %f %f\n", // __LINE__, min_position, max_position, speed_position1, speed_position2); // new start of source to read from file start_source = (int64_t)min_position; have_speed = 1; // swap in the temp buffer if(speed_temp && speed_temp->get_allocated() < speed_fragment_len) { delete speed_temp; speed_temp = 0; } if(!speed_temp) speed_temp = new Samples(speed_fragment_len); speed_buffer = speed_temp; } if( speed_fragment_len == 0 ) return 1; // Source is a nested EDL if( edit->nested_edl ) { int command = direction == PLAY_REVERSE ? NORMAL_REWIND : NORMAL_FWD; asset = 0; if( !nested_edl || nested_edl->id != edit->nested_edl->id ) { nested_edl = edit->nested_edl; if( nested_renderengine ) { delete nested_renderengine; nested_renderengine = 0; } if( !nested_command ) nested_command = new TransportCommand; if( !nested_renderengine ) { nested_command->command = command; nested_command->get_edl()->copy_all(nested_edl); nested_command->change_type = CHANGE_ALL; nested_command->realtime = renderengine->command->realtime; nested_renderengine = new RenderEngine(0, get_preferences(), 0, 1); nested_renderengine->set_acache(get_cache()); // Must use a private cache for the audio // if(!cache) // { // cache = new CICache(get_preferences()); // private_cache = 1; // } // nested_renderengine->set_acache(cache); nested_renderengine->arm_command(nested_command); } } if(debug) printf("AModule::import_samples %d speed_fragment_len=%d\n", __LINE__, (int)speed_fragment_len); // Allocate output buffers for all channels for( int i=0; isession->audio_channels; ++i ) { if( nested_allocation < speed_fragment_len ) { delete nested_output[i]; nested_output[i] = 0; } if(!nested_output[i]) nested_output[i] = new Samples(speed_fragment_len); } if( nested_allocation < speed_fragment_len ) nested_allocation = speed_fragment_len; // Update direction command nested_renderengine->command->command = command; // Render the segment if( !nested_renderengine->arender ) bzero(speed_buffer->get_data(), speed_fragment_len * sizeof(double)); else if(sample_rate != nested_edl->session->sample_rate) { if( !resample ) resample = new AModuleResample(this); result = resample->resample(speed_buffer, speed_fragment_len, nested_edl->session->sample_rate, sample_rate, start_source, direction); // Resample reverses to keep it running forward. } else { // Render without resampling result = nested_renderengine->arender->process_buffer( nested_output, speed_fragment_len, start_source); memcpy(speed_buffer->get_data(), nested_output[edit->channel]->get_data(), speed_fragment_len * sizeof(double)); // Reverse fragment so ::render can apply transitions going forward. if( direction == PLAY_REVERSE ) { Resample::reverse_buffer(speed_buffer->get_data(), speed_fragment_len); } } } else if( edit->asset ) { // Source is an asset nested_edl = 0; asset = edit->asset; get_cache()->age(); if( nested_renderengine ) { delete nested_renderengine; nested_renderengine = 0; } if( !(file = get_cache()->check_out( asset, get_edl())) ) { // couldn't open source file / skip the edit printf(_("AModule::import_samples Couldn't open %s.\n"), asset->path); result = 1; } else { result = 0; if( sample_rate != asset->sample_rate ) { // Read through sample rate converter. if( !resample ) resample = new AModuleResample(this); result = resample->resample(speed_buffer, speed_fragment_len, asset->sample_rate, sample_rate, start_source, direction); // Resample reverses to keep it running forward. } else { file->set_audio_position(start_source); file->set_channel(edit->channel); result = file->read_samples(speed_buffer, speed_fragment_len); // Reverse fragment so ::render can apply transitions going forward. if(direction == PLAY_REVERSE) Resample::reverse_buffer(speed_buffer->get_data(), speed_fragment_len); } get_cache()->check_in(asset); file = 0; } } else { nested_edl = 0; asset = 0; if( speed_fragment_len > 0 ) bzero(speed_buffer->get_data(), speed_fragment_len * sizeof(double)); } // Stretch it to fit the speed curve // Need overlapping buffers to get the interpolation to work, but this // screws up sequential effects. if( have_speed ) { double *buffer_samples = buffer->get_data(); if( speed_fragment_len > 0 ) { FloatAuto *previous = 0; FloatAuto *next = 0; FloatAutos *speed_autos = (FloatAutos*)track->automation->autos[AUTOMATION_SPEED]; double *speed_samples = speed_buffer->get_data(); int len1 = speed_fragment_len-1; int out_offset = dir>0 ? 0 : fragment_len-1; speed_position = speed_position1; int64_t speed_pos = speed_position; int64_t pos = start_project; for( int64_t i=0; iget_value(pos, direction, previous, next); double next_position = speed_position + dir*speed; int64_t next_pos = next_position; int d = next_pos >= speed_pos ? 1 : -1; int k = speed_pos - start_source; double sample = speed_samples[bclip(k, 0,len1)]; int total = abs(next_pos - speed_pos); if( total > 1 ) { for( int j=total; --j>0; ) { k += d; sample += speed_samples[bclip(k, 0,len1)]; } sample /= total; } #if 0 else if( total < 1 ) { k += d; double next_sample = speed_samples[bclip(k, 0,len1)]; double v = speed_position - speed_pos; sample = (1.-v) * sample + v * next_sample; } #endif buffer_samples[out_offset] = sample; out_offset += dir; speed_position = next_position; speed_pos = next_pos; } } } return result; } int AModule::render(Samples *buffer, int64_t input_len, int64_t start_position, int direction, int sample_rate, int use_nudge) { int64_t edl_rate = get_edl()->session->sample_rate; const int debug = 0; if(debug) printf("AModule::render %d\n", __LINE__); if(use_nudge) start_position += track->nudge * sample_rate / edl_rate; AEdit *playable_edit; int64_t end_position; if(direction == PLAY_FORWARD) end_position = start_position + input_len; else end_position = start_position - input_len; int buffer_offset = 0; int result = 0; // // Flip range around so the source is always read forward. // if(direction == PLAY_REVERSE) // { // start_project -= input_len; // end_position -= input_len; // } // Clear buffer bzero(buffer->get_data(), input_len * sizeof(double)); // The EDL is normalized to the requested sample rate because // the requested rate may be the project sample rate and a sample rate // might as well be directly from the source rate to the requested rate. // Get first edit containing the range if(direction == PLAY_FORWARD) playable_edit = (AEdit*)track->edits->first; else playable_edit = (AEdit*)track->edits->last; if(debug) printf("AModule::render %d\n", __LINE__); while(playable_edit) { int64_t edit_start = playable_edit->startproject; int64_t edit_end = playable_edit->startproject + playable_edit->length; // Normalize to requested rate edit_start = edit_start * sample_rate / edl_rate; edit_end = edit_end * sample_rate / edl_rate; if(direction == PLAY_FORWARD) { if(start_position < edit_end && end_position > edit_start) { break; } playable_edit = (AEdit*)playable_edit->next; } else { if(end_position < edit_end && start_position > edit_start) { break; } playable_edit = (AEdit*)playable_edit->previous; } } if(debug) printf("AModule::render %d\n", __LINE__); // Fill output one fragment at a time while(start_position != end_position) { int64_t fragment_len = input_len; if(debug) printf("AModule::render %d %jd %jd\n", __LINE__, start_position, end_position); // Clamp fragment to end of input if(direction == PLAY_FORWARD && start_position + fragment_len > end_position) fragment_len = end_position - start_position; else if(direction == PLAY_REVERSE && start_position - fragment_len < end_position) fragment_len = start_position - end_position; if(debug) printf("AModule::render %d %jd\n", __LINE__, fragment_len); // Normalize position here since update_transition is a boolean operation. update_transition(start_position * edl_rate / sample_rate, PLAY_FORWARD); if(playable_edit) { AEdit *previous_edit = (AEdit*)playable_edit->previous; // Normalize EDL positions to requested rate int64_t edit_startproject = playable_edit->startproject; int64_t edit_endproject = playable_edit->startproject + playable_edit->length; int64_t edit_startsource = playable_edit->startsource; if(debug) printf("AModule::render %d %jd\n", __LINE__, fragment_len); edit_startproject = edit_startproject * sample_rate / edl_rate; edit_endproject = edit_endproject * sample_rate / edl_rate; edit_startsource = edit_startsource * sample_rate / edl_rate; if(debug) printf("AModule::render %d %jd\n", __LINE__, fragment_len); // Clamp fragment to end of edit if(direction == PLAY_FORWARD && start_position + fragment_len > edit_endproject) fragment_len = edit_endproject - start_position; else if(direction == PLAY_REVERSE && start_position - fragment_len < edit_startproject) fragment_len = start_position - edit_startproject; if(debug) printf("AModule::render %d %jd\n", __LINE__, fragment_len); // Clamp to end of transition int64_t transition_len = 0; if(transition && previous_edit) { transition_len = transition->length * sample_rate / edl_rate; if(direction == PLAY_FORWARD && start_position < edit_startproject + transition_len && start_position + fragment_len > edit_startproject + transition_len) fragment_len = edit_startproject + transition_len - start_position; else if(direction == PLAY_REVERSE && start_position > edit_startproject + transition_len && start_position - fragment_len < edit_startproject + transition_len) fragment_len = start_position - edit_startproject - transition_len; } if(debug) printf("AModule::render %d buffer_offset=%d fragment_len=%jd\n", __LINE__, buffer_offset, fragment_len); Samples output(buffer); output.set_offset(output.get_offset() + buffer_offset); if(import_samples(playable_edit, start_position, edit_startproject, edit_startsource, direction, sample_rate, &output, fragment_len)) result = 1; if(debug) printf("AModule::render %d\n", __LINE__); // Read transition into temp and render if(transition && previous_edit) { int64_t previous_startproject = previous_edit->startproject * sample_rate / edl_rate; int64_t previous_startsource = previous_edit->startsource * sample_rate / edl_rate; // Allocate transition temp size int transition_fragment_len = fragment_len; if(direction == PLAY_FORWARD && fragment_len + start_position > edit_startproject + transition_len) fragment_len = edit_startproject + transition_len - start_position; // Read into temp buffers // Temp + master or temp + temp ? temp + master if(transition_temp && transition_temp->get_allocated() < fragment_len) { delete transition_temp; transition_temp = 0; } if(!transition_temp) { transition_temp = new Samples(fragment_len); } if(debug) printf("AModule::render %d %jd\n", __LINE__, fragment_len); if(transition_fragment_len > 0) { // Previous_edit is always the outgoing segment, regardless of direction import_samples(previous_edit, start_position, previous_startproject, previous_startsource, direction, sample_rate, transition_temp, transition_fragment_len); int64_t current_position; // Reverse buffers here so transitions always render forward. if(direction == PLAY_REVERSE) { Resample::reverse_buffer(output.get_data(), transition_fragment_len); Resample::reverse_buffer(transition_temp->get_data(), transition_fragment_len); current_position = start_position - transition_fragment_len - edit_startproject; } else { current_position = start_position - edit_startproject; } transition_server->process_transition( transition_temp, &output, current_position, transition_fragment_len, transition->length); // Reverse output buffer here so transitions always render forward. if(direction == PLAY_REVERSE) Resample::reverse_buffer(output.get_data(), transition_fragment_len); } } if(debug) printf("AModule::render %d start_position=%jd end_position=%jd fragment_len=%jd\n", __LINE__, start_position, end_position, fragment_len); if(direction == PLAY_REVERSE) { if(playable_edit && start_position - fragment_len <= edit_startproject) playable_edit = (AEdit*)playable_edit->previous; } else { if(playable_edit && start_position + fragment_len >= edit_endproject) playable_edit = (AEdit*)playable_edit->next; } } if(fragment_len > 0) { buffer_offset += fragment_len; if(direction == PLAY_FORWARD) start_position += fragment_len; else start_position -= fragment_len; } } if(debug) printf("AModule::render %d\n", __LINE__); return result; }