/* * CINELERRA * Copyright (C) 2008 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 "assets.h" #include "audioconfig.h" #include "audiodevice.h" #include "file.h" #include "filexml.h" #include "mwindow.h" #include "patchbay.h" #include "playbackengine.h" #include "preferences.h" #include "recconfirmdelete.h" #include "record.h" #include "recordengine.h" #include "recordgui.h" #include "recordlabel.h" #include "recordpreview.h" #include "recordthread.h" #include "recordmonitor.h" #include "units.h" #include "videodevice.h" #include #include #define _(String) gettext(String) #define gettext_noop(String) String #define N_(String) gettext_noop (String) RecordEngine::RecordEngine(MWindow *mwindow, Record *record) { this->mwindow = mwindow; this->record = record; } RecordEngine::RecordEngine(MWindow *mwindow, Record *record, File *file, Asset *asset, RecordLabels *labels) { this->mwindow = mwindow; this->record = record; this->file = file; this->labels = labels; this->asset = asset; is_saving = 0; is_previewing = 0; is_duplexing = 0; is_monitoring = 0; prev_label = -1; next_label = -1; if(record->do_audio) adevice = new AudioDevice; else adevice = 0; if(record->do_video) vdevice = new VideoDevice(mwindow); else vdevice = 0; } RecordEngine::~RecordEngine() { delete monitor_thread; delete record_thread; delete preview_thread; if(adevice) delete adevice; if(vdevice) delete vdevice; } int RecordEngine::initialize() { // monitor_thread = new RecordThread(mwindow, record, this); monitor_thread->create_objects(); // record_thread = new RecordThread(mwindow, record, this); record_thread->create_objects(); preview_thread = new RecordPreview(record, this); preview_thread->initialize(); // put at end of file total_length = -1; if(record->do_audio) current_position = file->get_audio_length(); else if(record->do_video) current_position = Units::tosamples((float)(file->get_video_length(record->get_framerate())), record->get_samplerate(), record->get_framerate()); file->seek_end(); duplex_thread = mwindow->playback_engine; // initialize seek buttons jump_delay[0] = 100; jump_delay[1] = 50; jump_delay[2] = 25; jump_delay[3] = 10; jump_delay[4] = 5; current_jump_jumps[0] = 20; current_jump_jumps[1] = 40; current_jump_jumps[2] = 60; current_jump_jumps[3] = 80; current_jump_jumps[4] = 100; } int RecordEngine::run_script(FileXML *script) { int result = 0, script_result = 0; char string[1024]; while(!result && !script_result) { result = script->read_tag(); if(!result) { if(script->tag.title_is("set_mode")) { set_record_mode(script->tag.get_property_text(0)); mode_to_text(string, get_record_mode()); gui->rec_mode_menu->set_text(string); } else if(script->tag.title_is("set_duration")) { record->set_loop_duration((long)record->get_samplerate() * script->tag.get_property_int(0)); gui->update_duration_boxes(); } else if(script->tag.title_is("start_recording")) { gui->unlock_window(); start_saving(); gui->lock_window(); } else if(script->tag.title_is("set_monitor_video")) { set_monitor_video(script->tag.get_property_int(0)); if(!script->tag.get_property_int(0) && record->video_window_open) { record->video_window_open = 0; gui->monitor_video_window->window->hide_window(); } } else if(script->tag.title_is("set_monitor_audio")) { set_monitor_audio(script->tag.get_property_int(0)); } else if(script->tag.title_is("quit_when_completed")) { record_thread->quit_when_completed = 1; } else if(script->tag.title_is("ok")) { script_result = 1; } } } return script_result; } // ============================================= accounting long RecordEngine::get_dc_offset(int offset) { return record->dc_offset[offset]; } int RecordEngine::set_dc_offset(long new_offset, int number) { adevice->set_dc_offset(new_offset, number); } long int RecordEngine::get_dc_offset(long *dc_offset, RecordGUIDCOffsetText **dc_offset_text) { return adevice->get_dc_offset(dc_offset, dc_offset_text); } int RecordEngine::set_gui(RecordGUI *gui) { this->gui = gui; update_position(current_position); } int RecordEngine::get_duplex_enable() { return record->enable_duplex(); } // ================================================ operations int RecordEngine::open_input_devices(int duplex) { int audio_opened = 0; int video_opened = 0; AudioConfig *aconfig /* = mwindow->preferences->aconfig */; // Initialize sharing if(record->do_audio && record->do_video) { vdevice->set_adevice(adevice); adevice->set_vdevice(vdevice); } // Initialize audio if(record->do_audio) { if(record->get_software_positioning()) adevice->set_software_positioning(); for(int i = 0; i < asset->channels; i++) { adevice->set_dc_offset(record->dc_offset[i], i); } } // Duplex is only needed if the timeline and the recording have audio if(duplex && record->do_audio && mwindow->patches->total_playable_atracks()) { // duplex device is identical to input device if(aconfig->audio_in_driver == aconfig->audio_duplex_driver && !strcmp(aconfig->oss_in_device, aconfig->oss_duplex_device) && aconfig->oss_in_bits == aconfig->oss_duplex_bits && aconfig->oss_in_channels == aconfig->oss_duplex_channels) { // adevice->open_duplex(mwindow->preferences->aconfig, // record->get_samplerate(), // get_in_length()); audio_opened = 1; } else // two separate devices { // adevice->open_output(mwindow->preferences->aconfig, // record->get_samplerate(), // record->get_out_length()); } } if(record->do_audio && !audio_opened) { // adevice->open_input(mwindow->preferences->aconfig, // record->get_samplerate(), // get_in_length()); } // Initialize video if(record->do_video) { // vdevice->open_input(mwindow->preferences->vconfig, // record->frame_w, // record->frame_h, // record->video_x, // record->video_y, // record->video_zoom, // get_frame_rate()); // vdevice->set_field_order(record->reverse_interlace); // if(record->get_current_channel()) // vdevice->set_channel(record->get_current_channel()); // set_video_picture(); } return 0; } int RecordEngine::close_input_devices() { if(record->do_audio) adevice->close_all(); if(record->do_video) vdevice->close_all(); return 0; } int RecordEngine::start_monitor() { monitor_timer.update(); open_input_devices(0); monitor_thread->start_recording(0, 0); is_monitoring = 1; return 0; } int RecordEngine::stop_monitor() { // if(is_monitoring) // { // is_monitoring = 0; // monitor_thread->stop_recording(); // } return 0; } int RecordEngine::pause_monitor() { if(is_monitoring) { is_monitoring = 0; monitor_thread->pause_recording(); } return 0; } int RecordEngine::resume_monitor() { if(!is_monitoring) { is_monitoring = 1; monitor_timer.update(); open_input_devices(0); monitor_thread->resume_recording(); } return 0; } int RecordEngine::start_saving(int duplex) { if(!is_saving) { pause_monitor(); record_timer.update(); open_input_devices(duplex); duplex = record->enable_duplex() && duplex; // start the duplex engine if necessary // OSS < 3.9 crashes if recording starts before playback // OSS >= 3.9 crashes if playback starts before recording if(duplex) { long start, end; record->get_duplex_range(&start, &end); duplex_thread->reset_parameters(); duplex_thread->arm_playback(0, 0, 1, adevice); duplex_thread->start_playback(); is_duplexing = 1; } // record_thread->start_recording(); is_saving = 1; } return 0; } int RecordEngine::save_frame() { if(!is_saving) { pause_monitor(); record_timer.update(); record->do_audio = 0; open_input_devices(0); // start the duplex engine if necessary record_thread->start_recording(0, 0); is_saving = 1; } return 0; } int RecordEngine::stop_saving(int no_monitor) { if(is_saving) { // automatically stops duplex here and resumes monitor record_thread->stop_recording(no_monitor); } return 0; } int RecordEngine::stop_duplex() { if(is_duplexing) { is_duplexing = 0; duplex_thread->stop_playback(0); // OSS can't resume recording if buffers underrun // so stop playback first } return 0; } int RecordEngine::start_preview() { if(!is_previewing) { stop_operation(); pause_monitor(); preview_timer.update(); open_output_devices(); preview_thread->start_preview(current_position, file); is_previewing = 1; } return 0; } int RecordEngine::stop_preview(int no_monitor) { if(is_previewing) { preview_thread->stop_preview(no_monitor); // preview engine automatically triggers monitor when finished } return 0; } int RecordEngine::stop_operation(int no_monitor) { // Resumes monitoring after stopping if(is_saving) stop_saving(no_monitor); else if(is_previewing) stop_preview(no_monitor); return 0; } int RecordEngine::set_video_picture() { if(record->do_video && vdevice) vdevice->set_picture(record->video_brightness, record->video_hue, record->video_color, record->video_contrast, record->video_whiteness); return 0; } int RecordEngine::open_output_devices() { if(record->do_audio) { // adevice->open_output(mwindow->preferences->aconfig, // record->get_samplerate(), // record->get_out_length()); if(record->get_software_positioning()) adevice->set_software_positioning(); } // Video is already open for monitoring return 0; } int RecordEngine::close_output_devices() { if(record->do_audio) adevice->close_all(); // Video is already open for monitoring return 0; } int RecordEngine::lock_window() { gui->lock_window(); } int RecordEngine::unlock_window() { gui->unlock_window(); } int RecordEngine::update_position(long new_position) { if(new_position < 0) new_position = 0; // fread error in filepcm current_position = new_position; gui->update_position(new_position); if(new_position > total_length) { total_length = new_position; // gui->update_total_length(new_position); } if(prev_label != labels->get_prev_label(new_position)) { prev_label = labels->get_prev_label(new_position); gui->update_prev_label(prev_label); } if(next_label != labels->get_next_label(new_position)) { next_label = labels->get_next_label(new_position); gui->update_next_label(next_label); } } int RecordEngine::goto_prev_label() { if(!is_saving) { stop_operation(); long new_position; new_position = labels->goto_prev_label(current_position); if(new_position != -1) { // if(record->do_audio) file->set_audio_position(new_position); if(record->do_video) file->set_video_position(Units::toframes(new_position, record->get_samplerate(), record->get_framerate()), record->get_framerate()); update_position(new_position); } } } int RecordEngine::goto_next_label() { if(!is_saving) { stop_operation(); long new_position; new_position = labels->goto_next_label(current_position); if(new_position != -1 && new_position <= total_length) { // if(record->do_audio) file->set_audio_position(new_position); if(record->do_video) file->set_video_position(Units::toframes(new_position, record->get_samplerate(), record->get_framerate()), record->get_framerate()); update_position(new_position); } } return 0; } int RecordEngine::toggle_label() { labels->toggle_label(current_position); update_position(current_position); return 0; } int RecordEngine::calibrate_dc_offset() { if(record->do_audio) { get_dc_offset(record->dc_offset, gui->dc_offset_text); } return 0; } int RecordEngine::calibrate_dc_offset(long new_value, int channel) { if(record->do_audio) { set_dc_offset(new_value, channel); record->dc_offset[channel] = new_value; } return 0; } int RecordEngine::reset_over() { } int RecordEngine::set_done(int value) { stop_operation(1); stop_monitor(); gui->set_done(value); } int RecordEngine::start_over() { if((record->do_audio && file->get_audio_length() > 0) || (record->do_video && file->get_video_length(record->get_framerate()) > 0)) { RecConfirmDelete dialog(mwindow); dialog.create_objects(_("start over")); int result = dialog.run_window(); if(!result) { stop_operation(); // remove file file->close_file(); remove(asset->path); // start the engine over labels->delete_new_labels(); update_position(0); total_length = 0; // gui->update_total_length(0); record->startsource_sample = 0; record->startsource_frame = 0; } } } int RecordEngine::change_channel(Channel *channel) { if(record->do_video && vdevice) return vdevice->set_channel(channel); else return 0; } ArrayList* RecordEngine::get_video_inputs() { if(record->do_video && vdevice) return vdevice->get_inputs(); else return 0; } int RecordEngine::get_vu_format() { return record->get_vu_format(); } int RecordEngine::get_dither() { return record->default_asset->dither * record->default_asset->bits; } int RecordEngine::get_input_channels() { return asset->channels; } int RecordEngine::get_format(char *string) { File file; strcpy(string, file.formattostr(mwindow->plugindb, asset->format)); } int RecordEngine::get_samplerate() { return asset->rate; } int RecordEngine::get_bits() { return asset->bits; } int RecordEngine::get_time_format() { return record->get_time_format(); } float RecordEngine::get_frame_rate() { return record->get_frame_rate(); } int RecordEngine::get_loop_hr() { return record->loop_duration / asset->rate / 3600; } int RecordEngine::get_loop_min() { return record->loop_duration / asset->rate / 60 - (long)get_loop_hr() * 60; } int RecordEngine::get_loop_sec() { return record->loop_duration / asset->rate - (long)get_loop_hr() * 3600 - (long)get_loop_min() * 60; } long RecordEngine::get_loop_duration() { return record->loop_duration; } float RecordEngine::get_min_db() { return record->get_min_db(); } int RecordEngine::get_meter_over_hold(int divisions) { return divisions * 15; } int RecordEngine::get_meter_peak_hold(int divisions) { return divisions * 2; } int RecordEngine::get_meter_speed() { return record->get_meter_speed(); } float RecordEngine::get_frames_per_foot() { /* return mwindow->preferences->frames_per_foot; */ } int RecordEngine::set_monitor_video(int value) { } int RecordEngine::set_monitor_audio(int value) { } int RecordEngine::set_record_mode(char *text) { record->record_mode = text_to_mode(text); } int RecordEngine::get_record_mode(char *text) { mode_to_text(text, record->record_mode); } int RecordEngine::get_record_mode() { return record->record_mode; } int RecordEngine::mode_to_text(char *string, int mode) { switch(mode) { case 0: sprintf(string, _("Untimed")); break; case 1: sprintf(string, _("Timed")); break; case 2: sprintf(string, _("Loop")); break; } } int RecordEngine::text_to_mode(char *string) { if(!strcasecmp(string, _("Untimed"))) return 0; if(!strcasecmp(string, _("Timed"))) return 1; if(!strcasecmp(string, _("Loop"))) return 2; } long RecordEngine::get_current_delay() { if(current_jump_jump > 0) current_jump_jump--; if(current_jump_jump == 0 && current_jump_delay < /*JUMP_DELAYS*/ 1) { current_jump_delay++; current_jump_jump = current_jump_jumps[current_jump_delay]; } return jump_delay[current_jump_delay]; } int RecordEngine::reset_current_delay() { current_jump_delay = 0; current_jump_jump = current_jump_jumps[current_jump_delay]; } int RecordEngine::set_loop_duration() { record->set_loop_duration((long)record->get_samplerate() * (atol(gui->loop_sec->get_text()) + atol(gui->loop_min->get_text()) * 60 + atol(gui->loop_hr->get_text()) * 3600)); } // Remember to change meters if you change this. // Return the size of the fragments to read from the audio device. int RecordEngine::get_in_length() { long fragment_size = 1; while(fragment_size < asset->rate / record->get_meter_speed()) fragment_size *= 2; fragment_size /= 2; return fragment_size; } // Different absolute positions are defined for each operation so threads // can end at different times without screwing up the frame synchronization. long RecordEngine::absolute_monitor_position() { if(is_monitoring) { if(record->do_audio) { // return monitor_thread->absolute_position(); } else { return (long)((float)monitor_timer.get_difference() / 1000 * record->get_samplerate()); } } else return -1; } long RecordEngine::absolute_preview_position() { if(is_previewing) { if(record->do_audio) { return preview_thread->absolute_position(); } else { return (long)((float)preview_timer.get_difference() / 1000 * record->get_samplerate()); } } else return -1; } long RecordEngine::absolute_record_position() { if(is_saving) { if(record->do_audio) { // return record_thread->absolute_position(); } else { return (long)((float)record_timer.get_difference() / 1000 * record->get_samplerate()); } } else return -1; }