2 Copyright (C) 2000 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "libardour-config.h"
36 #include <samplerate.h>
40 #include <boost/scoped_array.hpp>
41 #include <boost/shared_array.hpp>
43 #include "pbd/basename.h"
44 #include "pbd/convert.h"
46 #include "evoral/SMF.hpp"
48 #include "ardour/analyser.h"
49 #include "ardour/ardour.h"
50 #include "ardour/audio_diskstream.h"
51 #include "ardour/audioengine.h"
52 #include "ardour/audioregion.h"
53 #include "ardour/import_status.h"
54 #include "ardour/region_factory.h"
55 #include "ardour/resampled_source.h"
56 #include "ardour/session.h"
57 #include "ardour/session_directory.h"
58 #include "ardour/smf_source.h"
59 #include "ardour/sndfile_helpers.h"
60 #include "ardour/sndfileimportable.h"
61 #include "ardour/sndfilesource.h"
62 #include "ardour/source_factory.h"
63 #include "ardour/tempo.h"
66 #include "ardour/caimportable.h"
72 using namespace ARDOUR;
75 static boost::shared_ptr<ImportableSource>
76 open_importable_source (const string& path, nframes_t samplerate, ARDOUR::SrcQuality quality)
78 /* try libsndfile first, because it can get BWF info from .wav, which ExtAudioFile cannot.
79 We don't necessarily need that information in an ImportableSource, but it keeps the
80 logic the same as in SourceFactory::create()
84 boost::shared_ptr<SndFileImportableSource> source(new SndFileImportableSource(path));
86 if (source->samplerate() == samplerate) {
90 /* rewrap as a resampled source */
92 return boost::shared_ptr<ImportableSource>(new ResampledImportableSource(source, samplerate, quality));
99 /* libsndfile failed, see if we can use CoreAudio to handle the IO */
101 CAImportableSource* src = new CAImportableSource(path);
102 boost::shared_ptr<CAImportableSource> source (src);
104 if (source->samplerate() == samplerate) {
108 /* rewrap as a resampled source */
110 return boost::shared_ptr<ImportableSource>(new ResampledImportableSource(source, samplerate, quality));
120 get_non_existent_filename (DataType type, const bool allow_replacing, const std::string& destdir, const std::string& basename, uint channel, uint channels)
122 char buf[PATH_MAX+1];
123 bool goodfile = false;
124 string base(basename);
125 const char* ext = (type == DataType::AUDIO) ? "wav" : "mid";
129 if (type == DataType::AUDIO && channels == 2) {
131 snprintf (buf, sizeof(buf), "%s-L.wav", base.c_str());
133 snprintf (buf, sizeof(buf), "%s-R.wav", base.c_str());
135 } else if (channels > 1) {
136 snprintf (buf, sizeof(buf), "%s-c%d.%s", base.c_str(), channel, ext);
138 snprintf (buf, sizeof(buf), "%s.%s", base.c_str(), ext);
142 string tempname = destdir + "/" + buf;
143 if (!allow_replacing && Glib::file_test (tempname, Glib::FILE_TEST_EXISTS)) {
145 /* if the file already exists, we must come up with
146 * a new name for it. for now we just keep appending
157 } while ( !goodfile);
162 static vector<string>
163 get_paths_for_new_sources (const bool allow_replacing, const string& import_file_path, const string& session_dir, uint channels)
165 vector<string> new_paths;
166 const string basename = basename_nosuffix (import_file_path);
168 SessionDirectory sdir(session_dir);
170 for (uint n = 0; n < channels; ++n) {
172 const DataType type = (import_file_path.rfind(".mid") != string::npos)
173 ? DataType::MIDI : DataType::AUDIO;
175 std::string filepath = (type == DataType::MIDI)
176 ? sdir.midi_path().to_string() : sdir.sound_path().to_string();
179 filepath += get_non_existent_filename (type, allow_replacing, filepath, basename, n, channels);
180 new_paths.push_back (filepath);
187 map_existing_mono_sources (const vector<string>& new_paths, Session& /*sess*/,
188 uint /*samplerate*/, vector<boost::shared_ptr<Source> >& newfiles, Session *session)
190 for (vector<string>::const_iterator i = new_paths.begin();
191 i != new_paths.end(); ++i)
193 boost::shared_ptr<Source> source = session->source_by_path_and_channel(*i, 0);
196 error << string_compose(_("Could not find a source for %1 even though we are updating this file!"), (*i)) << endl;
200 newfiles.push_back(boost::dynamic_pointer_cast<Source>(source));
206 create_mono_sources_for_writing (const vector<string>& new_paths, Session& sess,
207 uint samplerate, vector<boost::shared_ptr<Source> >& newfiles,
208 framepos_t timeline_position)
210 for (vector<string>::const_iterator i = new_paths.begin(); i != new_paths.end(); ++i)
212 boost::shared_ptr<Source> source;
216 const DataType type = ((*i).rfind(".mid") != string::npos)
217 ? DataType::MIDI : DataType::AUDIO;
220 source = SourceFactory::createWritable (type, sess,
222 false, // destructive
225 catch (const failed_constructor& err)
227 error << string_compose (_("Unable to create file %1 during import"), *i) << endmsg;
231 newfiles.push_back(boost::dynamic_pointer_cast<Source>(source));
233 /* for audio files, reset the timeline position so that any BWF-ish
234 information in the original files we are importing from is maintained.
237 boost::shared_ptr<AudioFileSource> afs;
238 afs = boost::dynamic_pointer_cast<AudioFileSource>(source);
239 afs->set_timeline_position(timeline_position);
245 compose_status_message (const string& path,
246 uint file_samplerate,
247 uint session_samplerate,
251 if (file_samplerate != session_samplerate) {
252 return string_compose (_("resampling %1 from %2kHz to %3kHz\n(%4 of %5)"),
253 Glib::path_get_basename (path),
254 file_samplerate/1000.0f,
255 session_samplerate/1000.0f,
256 current_file, total_files);
259 return string_compose (_("copying %1\n(%2 of %3)"),
260 Glib::path_get_basename (path),
261 current_file, total_files);
265 write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status,
266 vector<boost::shared_ptr<Source> >& newfiles)
268 const nframes_t nframes = ResampledImportableSource::blocksize;
269 boost::shared_ptr<AudioFileSource> afs;
270 uint channels = source->channels();
272 boost::scoped_array<float> data(new float[nframes * channels]);
273 vector<boost::shared_array<Sample> > channel_data;
275 for (uint n = 0; n < channels; ++n) {
276 channel_data.push_back(boost::shared_array<Sample>(new Sample[nframes]));
280 status.progress = 0.0f;
282 while (!status.cancel) {
284 nframes_t nread, nfread;
288 if ((nread = source->read (data.get(), nframes)) == 0) {
291 nfread = nread / channels;
295 for (chn = 0; chn < channels; ++chn) {
298 for (x = chn, n = 0; n < nfread; x += channels, ++n) {
299 channel_data[chn][n] = (Sample) data[x];
305 for (chn = 0; chn < channels; ++chn) {
306 if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(newfiles[chn])) != 0) {
307 afs->write (channel_data[chn].get(), nfread);
312 status.progress = read_count / (source->ratio () * source->length() * channels);
317 write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status,
318 vector<boost::shared_ptr<Source> >& newfiles)
320 uint32_t buf_size = 4;
321 uint8_t* buf = (uint8_t*)malloc(buf_size);
323 status.progress = 0.0f;
327 for (unsigned i = 1; i <= source->num_tracks(); ++i) {
328 boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(newfiles[i-1]);
331 source->seek_to_track(i);
334 uint32_t delta_t = 0;
337 while (!status.cancel) {
340 int ret = source->read_event(&delta_t, &size, &buf);
344 if (ret < 0) { // EOT
350 if (ret == 0) { // Meta
354 smfs->append_event_unlocked_beats(Evoral::Event<double>(0,
355 (double)t / (double)source->ppqn(),
359 if (status.progress < 0.99)
360 status.progress += 0.01;
363 const nframes64_t pos = 0;
364 const double length_beats = ceil(t / (double)source->ppqn());
365 BeatsFramesConverter converter(smfs->session().tempo_map(), pos);
366 smfs->update_length(pos, converter.to(length_beats));
375 error << "Corrupt MIDI file " << source->file_path() << endl;
380 remove_file_source (boost::shared_ptr<Source> source)
382 ::unlink (source->path().c_str());
385 // This function is still unable to cleanly update an existing source, even though
386 // it is possible to set the ImportStatus flag accordingly. The functinality
387 // is disabled at the GUI until the Source implementations are able to provide
388 // the necessary API.
390 Session::import_audiofiles (ImportStatus& status)
393 typedef vector<boost::shared_ptr<Source> > Sources;
394 Sources all_new_sources;
395 boost::shared_ptr<AudioFileSource> afs;
396 boost::shared_ptr<SMFSource> smfs;
399 status.sources.clear ();
401 for (vector<Glib::ustring>::iterator p = status.paths.begin();
402 p != status.paths.end() && !status.cancel;
405 boost::shared_ptr<ImportableSource> source;
406 std::auto_ptr<Evoral::SMF> smf_reader;
407 const DataType type = ((*p).rfind(".mid") != string::npos) ?
408 DataType::MIDI : DataType::AUDIO;
410 if (type == DataType::AUDIO) {
412 source = open_importable_source (*p, frame_rate(), status.quality);
413 channels = source->channels();
414 } catch (const failed_constructor& err) {
415 error << string_compose(_("Import: cannot open input sound file \"%1\""), (*p)) << endmsg;
416 status.done = status.cancel = true;
422 smf_reader = std::auto_ptr<Evoral::SMF>(new Evoral::SMF());
423 smf_reader->open(*p);
424 channels = smf_reader->num_tracks();
426 error << _("Import: error opening MIDI file") << endmsg;
427 status.done = status.cancel = true;
432 vector<string> new_paths = get_paths_for_new_sources (status.replace_existing_source, *p,
433 get_best_session_directory_for_new_source (),
436 framepos_t natural_position = source ? source->natural_position() : 0;
438 if (status.replace_existing_source) {
439 fatal << "THIS IS NOT IMPLEMENTED YET, IT SHOULD NEVER GET CALLED!!! DYING!" << endmsg;
440 status.cancel = !map_existing_mono_sources (new_paths, *this, frame_rate(), newfiles, this);
442 status.cancel = !create_mono_sources_for_writing (new_paths, *this, frame_rate(), newfiles, natural_position);
445 // copy on cancel/failure so that any files that were created will be removed below
446 std::copy (newfiles.begin(), newfiles.end(), std::back_inserter(all_new_sources));
452 for (Sources::iterator i = newfiles.begin(); i != newfiles.end(); ++i) {
453 if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(*i)) != 0) {
454 afs->prepare_for_peakfile_writes ();
458 if (source) { // audio
459 status.doing_what = compose_status_message (*p, source->samplerate(),
460 frame_rate(), cnt, status.total);
461 write_audio_data_to_new_files (source.get(), status, newfiles);
462 } else if (smf_reader.get()) { // midi
463 status.doing_what = string_compose(_("Loading MIDI file %1"), *p);
464 write_midi_data_to_new_files (smf_reader.get(), status, newfiles);
468 if (!status.cancel) {
472 now = localtime (&xnow);
473 status.freeze = true;
475 /* flush the final length(s) to the header(s) */
477 for (Sources::iterator x = all_new_sources.begin(); x != all_new_sources.end(); ) {
478 if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(*x)) != 0) {
479 afs->update_header((*x)->natural_position(), *now, xnow);
480 afs->done_with_peakfile_writes ();
482 /* now that there is data there, requeue the file for analysis */
484 if (Config->get_auto_analyse_audio()) {
485 Analyser::queue_source_for_analysis (boost::static_pointer_cast<Source>(*x), false);
489 /* don't create tracks for empty MIDI sources (channels) */
491 if ((smfs = boost::dynamic_pointer_cast<SMFSource>(*x)) != 0 && smfs->is_empty()) {
492 x = all_new_sources.erase(x);
498 /* save state so that we don't lose these new Sources */
502 std::copy (all_new_sources.begin(), all_new_sources.end(), std::back_inserter(status.sources));
504 // this can throw...but it seems very unlikely
505 std::for_each (all_new_sources.begin(), all_new_sources.end(), remove_file_source);