/* Copyright (C) 2008-2009 Paul Davis Author: Sakari Bergen 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ardour/export_handler.h" #include #include "pbd/convert.h" #include "pbd/filesystem.h" #include "ardour/ardour.h" #include "ardour/configuration.h" #include "ardour/export_graph_builder.h" #include "ardour/export_timespan.h" #include "ardour/export_channel_configuration.h" #include "ardour/export_status.h" #include "ardour/export_format_specification.h" #include "ardour/export_filename.h" #include "ardour/export_failed.h" #include "i18n.h" using namespace std; using namespace PBD; namespace ARDOUR { /*** ExportElementFactory ***/ ExportElementFactory::ExportElementFactory (Session & session) : session (session) { } ExportElementFactory::~ExportElementFactory () { } ExportElementFactory::TimespanPtr ExportElementFactory::add_timespan () { return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate())); } ExportElementFactory::ChannelConfigPtr ExportElementFactory::add_channel_config () { return ChannelConfigPtr (new ExportChannelConfiguration (session)); } ExportElementFactory::FormatPtr ExportElementFactory::add_format () { return FormatPtr (new ExportFormatSpecification (session)); } ExportElementFactory::FormatPtr ExportElementFactory::add_format (XMLNode const & state) { return FormatPtr (new ExportFormatSpecification (session, state)); } ExportElementFactory::FormatPtr ExportElementFactory::add_format_copy (FormatPtr other) { return FormatPtr (new ExportFormatSpecification (*other)); } ExportElementFactory::FilenamePtr ExportElementFactory::add_filename () { return FilenamePtr (new ExportFilename (session)); } ExportElementFactory::FilenamePtr ExportElementFactory::add_filename_copy (FilenamePtr other) { return FilenamePtr (new ExportFilename (*other)); } /*** ExportHandler ***/ ExportHandler::ExportHandler (Session & session) : ExportElementFactory (session) , session (session) , graph_builder (new ExportGraphBuilder (session)) , export_status (session.get_export_status ()) , realtime (false) , normalizing (false) , cue_tracknum (0) , cue_indexnum (0) { } ExportHandler::~ExportHandler () { // TODO remove files that were written but not finsihed } bool ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename, boost::shared_ptr broadcast_info) { FileSpec spec (channel_config, format, filename, broadcast_info); ConfigPair pair (timespan, spec); config_map.insert (pair); return true; } void ExportHandler::do_export (bool rt) { /* Count timespans */ export_status->init(); std::set timespan_set; for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) { timespan_set.insert (it->first); } export_status->total_timespans = timespan_set.size(); /* Start export */ realtime = rt; start_timespan (); } void ExportHandler::start_timespan () { export_status->timespan++; if (config_map.empty()) { // freewheeling has to be stopped from outside the process cycle export_status->running = false; return; } current_timespan = config_map.begin()->first; /* Register file configurations to graph builder */ timespan_bounds = config_map.equal_range (current_timespan); graph_builder->reset (); for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) { // Filenames can be shared across timespans FileSpec & spec = it->second; spec.filename->set_timespan (it->first); graph_builder->add_config (spec); } /* start export */ normalizing = false; session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1)); process_position = current_timespan->get_start(); session.start_audio_export (process_position, realtime); } int ExportHandler::process (nframes_t frames) { if (!export_status->running) { return 0; } else if (normalizing) { return process_normalize (); } else { return process_timespan (frames); } } int ExportHandler::process_timespan (nframes_t frames) { /* update position */ nframes_t frames_to_read = 0; framepos_t const start = current_timespan->get_start(); framepos_t const end = current_timespan->get_end(); bool const last_cycle = (process_position + frames >= end); if (last_cycle) { frames_to_read = end - process_position; export_status->stop = true; normalizing = true; } else { frames_to_read = frames; } process_position += frames_to_read; export_status->progress = (float) (process_position - start) / (end - start); /* Do actual processing */ return graph_builder->process (frames_to_read, last_cycle); } int ExportHandler::process_normalize () { if (graph_builder->process_normalize ()) { finish_timespan (); } return 0; } void ExportHandler::finish_timespan () { while (config_map.begin() != timespan_bounds.second) { config_map.erase (config_map.begin()); } start_timespan (); } /*** CD Marker sutff ***/ struct LocationSortByStart { bool operator() (Location *a, Location *b) { return a->start() < b->start(); } }; void ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format) { string filepath; string basename = Glib::path_get_basename(filename); size_t ext_pos = basename.rfind('.'); if (ext_pos != string::npos) { basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */ } void (ExportHandler::*header_func) (CDMarkerStatus &); void (ExportHandler::*track_func) (CDMarkerStatus &); void (ExportHandler::*index_func) (CDMarkerStatus &); switch (format) { case CDMarkerTOC: filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc"); header_func = &ExportHandler::write_toc_header; track_func = &ExportHandler::write_track_info_toc; index_func = &ExportHandler::write_index_info_toc; break; case CDMarkerCUE: filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue"); header_func = &ExportHandler::write_cue_header; track_func = &ExportHandler::write_track_info_cue; index_func = &ExportHandler::write_index_info_cue; break; default: return; } CDMarkerStatus status (filepath, timespan, file_format, filename); if (!status.out) { error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg; return; } (this->*header_func) (status); /* Get locations and sort */ Locations::LocationList const & locations (session.locations()->list()); Locations::LocationList::const_iterator i; Locations::LocationList temp; for (i = locations.begin(); i != locations.end(); ++i) { if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) { temp.push_back (*i); } } if (temp.empty()) { // TODO One index marker for whole thing return; } LocationSortByStart cmp; temp.sort (cmp); Locations::LocationList::const_iterator nexti; /* Start actual marker stuff */ framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start(); status.track_position = last_start_time - timespan->get_start(); for (i = temp.begin(); i != temp.end(); ++i) { status.marker = *i; if ((*i)->start() < last_end_time) { if ((*i)->is_mark()) { /* Index within track */ status.index_position = (*i)->start() - timespan->get_start(); (this->*index_func) (status); } continue; } /* A track, defined by a cd range marker or a cd location marker outside of a cd range */ status.track_position = last_end_time - timespan->get_start(); status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap status.track_duration = 0; if ((*i)->is_mark()) { // a mark track location needs to look ahead to the next marker's start to determine length nexti = i; ++nexti; if (nexti != temp.end()) { status.track_duration = (*nexti)->start() - last_end_time; last_start_time = (*i)->start(); last_end_time = (*nexti)->start(); } else { // this was the last marker, use timespan end status.track_duration = timespan->get_end() - last_end_time; last_start_time = (*i)->start(); last_end_time = timespan->get_end(); } } else { // range status.track_duration = (*i)->end() - last_end_time; last_start_time = (*i)->start(); last_end_time = (*i)->end(); } (this->*track_func) (status); } } void ExportHandler::write_cue_header (CDMarkerStatus & status) { string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name(); status.out << "REM Cue file generated by Ardour" << endl; status.out << "TITLE \"" << title << "\"" << endl; /* The cue sheet syntax has originally five file types: WAVE : 44.1 kHz, 16 Bit (little endian) AIFF : 44.1 kHz, 16 Bit (big endian) BINARY : 44.1 kHz, 16 Bit (little endian) MOTOROLA : 44.1 kHz, 16 Bit (big endian) MP3 We want to use cue sheets not only as CD images but also as general playlyist format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the soundfile's header shows it anyway. But for the raw formats, i.e. BINARY and MOTOROLA we do care, because no header would tell us about a different format. For all other formats we just make up our own file type. MP3 is not supported at the moment. */ status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" "; if (!status.format->format_name().compare ("WAV")) { status.out << "WAVE"; } else if (status.format->format_id() == ExportFormatBase::F_RAW && status.format->sample_format() == ExportFormatBase::SF_16 && status.format->sample_rate() == ExportFormatBase::SR_44_1) { // Format is RAW 16bit 44.1kHz if (status.format->endianness() == ExportFormatBase::E_Little) { status.out << "BINARY"; } else { status.out << "MOTOROLA"; } } else { // AIFF should return "AIFF" status.out << status.format->format_name(); } status.out << endl; } void ExportHandler::write_toc_header (CDMarkerStatus & status) { string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name(); status.out << "CD_DA" << endl; status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl; status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl; } void ExportHandler::write_track_info_cue (CDMarkerStatus & status) { gchar buf[18]; snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number); status.out << buf << endl; status.out << " FLAGS" ; if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) { status.out << " SCMS "; } else { status.out << " DCP "; } if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) { status.out << " PRE"; } status.out << endl; if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { status.out << " ISRC " << status.marker->cd_info["isrc"] << endl; } if (status.marker->name() != "") { status.out << " TITLE \"" << status.marker->name() << "\"" << endl; } if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) { status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl; } if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) { status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl; } if (status.track_position != status.track_start_frame) { frames_to_cd_frames_string (buf, status.track_position); status.out << " INDEX 00" << buf << endl; } frames_to_cd_frames_string (buf, status.track_start_frame); status.out << " INDEX 01" << buf << endl; status.index_number = 2; status.track_number++; } void ExportHandler::write_track_info_toc (CDMarkerStatus & status) { gchar buf[18]; status.out << endl << "TRACK AUDIO" << endl; if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) { status.out << "NO "; } status.out << "COPY" << endl; if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) { status.out << "PRE_EMPHASIS" << endl; } else { status.out << "NO PRE_EMPHASIS" << endl; } if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl; } status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl; if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) { status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl; } if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) { status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl; } if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { status.out << " ISRC \""; status.out << status.marker->cd_info["isrc"].substr(0,2) << "-"; status.out << status.marker->cd_info["isrc"].substr(2,3) << "-"; status.out << status.marker->cd_info["isrc"].substr(5,2) << "-"; status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl; } status.out << " }" << endl << "}" << endl; frames_to_cd_frames_string (buf, status.track_position); status.out << "FILE \"" << status.filename << "\" " << buf; frames_to_cd_frames_string (buf, status.track_duration); status.out << buf << endl; frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position); status.out << "START" << buf << endl; } void ExportHandler::write_index_info_cue (CDMarkerStatus & status) { gchar buf[18]; snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum); status.out << buf; frames_to_cd_frames_string (buf, status.index_position); status.out << buf << endl; cue_indexnum++; } void ExportHandler::write_index_info_toc (CDMarkerStatus & status) { gchar buf[18]; frames_to_cd_frames_string (buf, status.index_position - status.track_position); status.out << "INDEX" << buf << endl; } void ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when) { framecnt_t remainder; nframes_t fr = session.nominal_frame_rate(); int mins, secs, frames; mins = when / (60 * fr); remainder = when - (mins * 60 * fr); secs = remainder / fr; remainder -= secs * fr; frames = remainder / (fr / 75); sprintf (buf, " %02d:%02d:%02d", mins, secs, frames); } } // namespace ARDOUR