diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index ab22ce781a..3c5a24e0c6 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -1364,7 +1364,7 @@ public: void import_pt_rest (PTFFormat& ptf); bool import_sndfile_as_region (std::string path, SrcQuality quality, timepos_t& pos, SourceList& sources, ImportStatus& status, uint32_t current, uint32_t total); - struct ptflookup { + typedef struct ptflookup { uint16_t index1; uint16_t index2; PBD::ID id; @@ -1372,9 +1372,8 @@ public: bool operator ==(const struct ptflookup& other) { return (this->index1 == other.index1); } - }; - std::vector ptfwavpair; - SourceList pt_imported_sources; + } PtfLookup; + std::vector ptfregpair; enum TimingTypes { OverallProcess = 0, diff --git a/libs/ardour/import_pt.cc b/libs/ardour/import_pt.cc index 2c779e69de..6d89013ef3 100644 --- a/libs/ardour/import_pt.cc +++ b/libs/ardour/import_pt.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2023 Damien Zammit + * Copyright (C) 2018-2025 Damien Zammit * * 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 @@ -73,190 +73,222 @@ struct PlaylistState { XMLNode* before; }; -bool -Session::import_sndfile_as_region (string path, SrcQuality quality, timepos_t& pos, SourceList& sources, ImportStatus& status, uint32_t current, uint32_t total) +static bool +import_pt_sndfile (Session* s, PTFFormat& ptf, PTFFormat::wav_t& w, std::string path, std::vector& wavchans, + SourceList& sources, ImportStatus& status, uint32_t current, uint32_t total) { - /* Import the source */ + bool ok = true; + Session::PtfLookup p; + status.paths.clear(); - status.paths.push_back(path); status.current = current; status.total = total; status.freeze = false; - status.quality = quality; + status.quality = SrcBest; status.replace_existing_source = false; status.split_midi_channels = false; status.import_markers = false; - status.done = false; status.cancel = false; - import_files(status); - status.progress = 1.0; - sources.clear(); + /* Check if no sound file was passed in */ + if (path == "") { + /* ptformat knows length of sources *in PT sample rate* + * BUT if ardour user later resolves missing file, + * it won't be resampled, so we can only do this + * when sample rates are matching + */ + if (s->sample_rate () == ptf.sessionrate ()) { + /* Insert reference to missing source */ + samplecnt_t sourcelen = w.length; + XMLNode srcxml (X_("Source")); + srcxml.set_property ("name", w.filename); + srcxml.set_property ("type", "audio"); + srcxml.set_property ("id", PBD::ID ().to_s ()); + std::shared_ptr source = SourceFactory::createSilent (*s, srcxml, sourcelen, s->sample_rate ()); + sources.push_back (source); - /* FIXME: There is no way to tell if cancel button was pressed - * or if the file failed to import, just that one of these occurred. - * We want status.cancel to reflect the user's choice only - */ - if (status.cancel && status.current > current) { - /* Succeeded to import file, assume user hit cancel */ - return false; - } else if (status.cancel && status.current == current) { - /* Failed to import file, assume user did not hit cancel */ - status.cancel = false; - return false; - } + p.index1 = w.index; + p.index2 = 0; /* unused */ + p.id = sources.front ()->id (); + wavchans.push_back (p); - sources.push_back(status.sources.front()); - - /* Put the source on a region */ - vector > regions; - string region_name; - bool use_timestamp; - - use_timestamp = (pos == timepos_t::max (Temporal::AudioTime)); - - /* take all the sources we have and package them up as a region */ - - region_name = region_name_from_path (status.paths.front(), (sources.size() > 1), false); - - /* we checked in import_sndfiles() that there were not too many */ - - while (RegionFactory::region_by_name (region_name)) { - region_name = bump_name_once (region_name, '.'); - } - - PropertyList plist; - - plist.add (ARDOUR::Properties::start, timepos_t (0)); - plist.add (ARDOUR::Properties::length, timecnt_t (sources[0]->length (), pos)); - plist.add (ARDOUR::Properties::name, region_name); - plist.add (ARDOUR::Properties::layer, 0); - plist.add (ARDOUR::Properties::whole_file, true); - plist.add (ARDOUR::Properties::external, true); - - std::shared_ptr r = RegionFactory::create (sources, plist); - - if (use_timestamp && std::dynamic_pointer_cast(r)) { - std::dynamic_pointer_cast(r)->special_set_position(sources[0]->natural_position()); - } - - regions.push_back (r); - - /* if we're creating a new track, name it after the cleaned-up - * and "merged" region name. - */ - - int n = 0; - - for (vector >::iterator r = regions.begin(); r != regions.end(); ++r, ++n) { - std::shared_ptr ar = std::dynamic_pointer_cast (*r); - - if (use_timestamp) { - if (ar) { - - /* get timestamp for this region */ - - const std::shared_ptr s (ar->sources().front()); - const std::shared_ptr as = std::dynamic_pointer_cast (s); - - assert (as); - - if (as->natural_position() != 0) { - pos = as->natural_position(); - } else { - pos = timepos_t (pos.time_domain ()); - } - } else { - /* should really get first position in MIDI file, but for now, use 0 */ - pos = timepos_t (pos.time_domain()); - } + warning << string_compose (_("PT Import : MISSING `%1`, inserting ref to missing source"), w.filename) << endmsg; + } else { + /* no sound file and mismatching sample rate to ptf */ + warning << string_compose (_("PT Import : MISSING `%1`, please check Audio Files"), w.filename) << endmsg; + ok = false; } + status.done = false; + } else { + /* Import the source */ + status.paths.push_back(path); + status.done = false; + s->import_files(status); + + /* FIXME: There is no way to tell if cancel button was pressed + * or if the file failed to import, just that one of these occurred. + * We want status.cancel to reflect the user's choice only + */ + if (status.cancel && status.current > current) { + /* Succeeded to import file, assume user hit cancel */ + return false; + } else if (status.cancel && status.current == current) { + /* Failed to import file, assume user did not hit cancel */ + status.cancel = false; + return false; + } + + assert (status.sources.size () > 0); + + sources.push_back(status.sources.front()); + + p.index1 = w.index; + p.index2 = 0; /* unused */ + p.id = sources.front ()->id (); + wavchans.push_back (p); } - for (SourceList::iterator x = sources.begin(); x != sources.end(); ++x) { - SourceFactory::setup_peakfile (*x, true); - } - - return true; + return ok; } - -void -Session::import_pt_sources (PTFFormat& ptf, ImportStatus& status) +static bool +import_pt_source_channels_or_empty (Session* s, PTFFormat& ptf, std::vector& ws, std::vector& wavchans, + SourceList& ch_sources, ImportStatus& status, uint32_t current, uint32_t total) { + bool ok, onefailed; string fullpath; - bool ok = false; - bool onefailed = false; - timepos_t pos = timepos_t::max (Temporal::AudioTime); - vector::const_iterator w; - uint32_t wth = 0; + onefailed = false; + for (std::vector::iterator w = ws.begin (); w != ws.end (); ++w) { + ok = true; - SourceList just_one_src; - - ptfwavpair.clear(); - pt_imported_sources.clear(); - status.clear(); - - for (w = ptf.audiofiles ().begin (); w != ptf.audiofiles ().end () && !status.cancel; ++w) { - struct ptflookup p; - wth++; - ok = false; /* Try audio file */ fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path ()), "Audio Files"); fullpath = Glib::build_filename (fullpath, w->filename); if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS)) { - just_one_src.clear(); - ok = import_sndfile_as_region (fullpath, SrcBest, pos, just_one_src, status, wth, ptf.audiofiles ().size ()); + /* fullpath has a valid audio file - load it */ + ok = import_pt_sndfile (s, ptf, *w, fullpath, wavchans, ch_sources, status, current, total); } else { /* Try fade file */ fullpath = Glib::build_filename (Glib::path_get_dirname (ptf.path ()), "Fade Files"); fullpath = Glib::build_filename (fullpath, w->filename); if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS)) { - just_one_src.clear(); - ok = import_sndfile_as_region (fullpath, SrcBest, pos, just_one_src, status, wth, ptf.audiofiles ().size ()); + /* fullpath has a valid fade file - load it */ + ok = import_pt_sndfile (s, ptf, *w, fullpath, wavchans, ch_sources, status, current, total); } else { - onefailed = true; - - /* ptformat knows length of sources *in PT sample rate* - * BUT if ardour user later resolves missing file, - * it won't be resampled, so we can only do this - * when sample rates are matching - */ - if (sample_rate () == ptf.sessionrate ()) { - /* Insert reference to missing source */ - samplecnt_t sourcelen = w->length; - XMLNode srcxml (X_("Source")); - srcxml.set_property ("name", w->filename); - srcxml.set_property ("type", "audio"); - srcxml.set_property ("id", PBD::ID ().to_s ()); - std::shared_ptr source = SourceFactory::createSilent (*this, srcxml, sourcelen, sample_rate ()); - p.index1 = w->index; - p.id = source->id (); - ptfwavpair.push_back (p); - pt_imported_sources.push_back (source); - warning << string_compose (_("PT Import : MISSING `%1`, inserting ref to missing source"), fullpath) << endmsg; - } else { - warning << string_compose (_("PT Import : MISSING `%1`, please check Audio Files"), fullpath) << endmsg; - } + /* no sound file - fill source with silence */ + ok = import_pt_sndfile (s, ptf, *w, "", wavchans, ch_sources, status, current, total); } } - if (ok) { - p.index1 = w->index; - p.id = just_one_src.back ()->id (); - - ptfwavpair.push_back (p); - pt_imported_sources.push_back (just_one_src.back ()); - } else { + if (!ok) { onefailed = true; + } else { + current++; } } - if (pt_imported_sources.empty ()) { + if (onefailed) + return false; + + /* now we have ch_sources with either silent sources or populated with sound file backed sources, + * and wavchans with vector of matching ids per channel */ + return true; +} + +void +Session::import_pt_sources (PTFFormat& ptf, ImportStatus& status) +{ + bool ok = false; + bool onefailed = false; + bool allfailed = true; + int src_cnt = 1; + string base_name; + map > multi_ch; + vector ptfwavpair; + SourceList source_group; + vector > regions; + timepos_t pos; + + status.clear (); + ptfregpair.clear (); + + /* Collect multi channel info from sources */ + for (vector::const_iterator w = ptf.audiofiles ().begin (); + w != ptf.audiofiles ().end (); ++w) { + base_name = region_name_from_path (w->filename, true, false); + multi_ch[base_name].push_back(*w); + } + + /* Import all other regions for potentially single or multi channel grouped sources */ + for (map >::iterator m = multi_ch.begin (); m != multi_ch.end (); ++m) { + ptfwavpair.clear (); + source_group.clear (); + ok = import_pt_source_channels_or_empty (this, ptf, multi_ch[(*m).first], ptfwavpair, source_group, + status, src_cnt, ptf.audiofiles ().size ()); + if (!ok) { + onefailed = true; + continue; + } else { + allfailed = false; + src_cnt += multi_ch[(*m).first].size (); /* progress bar is 1-based */ + } + + /* Import whole_file region for potentially single or multi channel grouped sources */ + { + Session::PtfLookup rp; + PropertyList plist; + + plist.add (ARDOUR::Properties::start, timepos_t (0)); + plist.add (ARDOUR::Properties::length, multi_ch[(*m).first][0].length); + plist.add (ARDOUR::Properties::name, (*m).first); + plist.add (ARDOUR::Properties::layer, 0); + plist.add (ARDOUR::Properties::whole_file, true); + plist.add (ARDOUR::Properties::external, true); + + std::shared_ptr rg = RegionFactory::create (source_group, plist); + regions.push_back (rg); + + rp.id = regions.back ()->id (); + rp.index1 = -1; /* Special: this region is maybe from two merged srcs */ + ptfregpair.push_back (rp); + } + + /* Create regions only for this multi channel source group */ + for (vector::const_iterator r = ptf.regions ().begin (); + r != ptf.regions ().end (); ++r) { + for (vector::iterator p = ptfwavpair.begin (); + p != ptfwavpair.end (); ++p) { + if (p->index1 == r->wave.index) { + /* Create an ardour region from multi channel source group */ + Session::PtfLookup rp; + PropertyList plist; + + plist.add (ARDOUR::Properties::start, timepos_t (r->sampleoffset)); + plist.add (ARDOUR::Properties::length, r->length); + plist.add (ARDOUR::Properties::name, (*m).first); + plist.add (ARDOUR::Properties::layer, 0); + plist.add (ARDOUR::Properties::whole_file, false); + plist.add (ARDOUR::Properties::external, true); + + std::shared_ptr rg = RegionFactory::create (source_group, plist); + regions.push_back (rg); + + rp.id = regions.back ()->id (); + rp.index1 = r->index; + ptfregpair.push_back (rp); + } + } + } + } + + if (allfailed) { error << _("Failed to find any audio for PT import") << endmsg; } else if (onefailed) { warning << _("Failed to load one or more of the audio files for PT import, see above list") << endmsg; } else { + for (SourceList::iterator x = status.sources.begin (); x != status.sources.end (); ++x) { + SourceFactory::setup_peakfile (*x, true); + } info << _("All audio files found for PT import!") << endmsg; } @@ -270,102 +302,99 @@ Session::import_pt_sources (PTFFormat& ptf, ImportStatus& status) void Session::import_pt_rest (PTFFormat& ptf) { - vector > regions; + bool ok; std::shared_ptr track; ARDOUR::PluginInfoPtr instrument; vector to_import; string fullpath; uint32_t srate = sample_rate (); timepos_t latest = timepos_t (0); - bool ok; - vector ptfregpair; + SourceList all_ch_srcs; - SourceList just_one_src; RouteList routes; list > tracks; std::shared_ptr existing_track; - uint16_t nth = 0; - struct ptflookup utr; + Session::PtfLookup utr; vector uniquetr; vector playlists; vector::iterator pl; - just_one_src.clear(); + all_ch_srcs.clear(); uniquetr.clear(); - ptfregpair.clear(); to_import.clear(); - regions.clear(); playlists.clear(); - for (vector::const_iterator a = ptf.regions ().begin (); - a != ptf.regions ().end (); ++a) { - for (vector::iterator p = ptfwavpair.begin (); - p != ptfwavpair.end (); ++p) { - if ((p->index1 == a->wave.index) && (strcmp (a->wave.filename.c_str (), "") != 0)) { - for (SourceList::iterator x = pt_imported_sources.begin (); x != pt_imported_sources.end (); ++x) { - if ((*x)->id () == p->id) { - /* Matched an uncreated ptf region to ardour region */ - struct ptflookup rp; - PropertyList plist; + std::map > track_map; - plist.add (ARDOUR::Properties::start, timepos_t (a->sampleoffset)); - plist.add (ARDOUR::Properties::length, a->length); - plist.add (ARDOUR::Properties::name, a->name); - plist.add (ARDOUR::Properties::layer, 0); - plist.add (ARDOUR::Properties::whole_file, false); - plist.add (ARDOUR::Properties::external, true); - - just_one_src.clear (); - just_one_src.push_back (*x); - - std::shared_ptr r = RegionFactory::create (just_one_src, plist); - regions.push_back (r); - - rp.id = regions.back ()->id (); - rp.index1 = a->index; - ptfregpair.push_back (rp); - } - } - } - } - } - - std::map> track_map; + /* name -> */ + std::map > tr_multi; /* Check for no audio */ if (ptf.tracks ().size () == 0) { goto no_audio_tracks; } - /* Create all PT tracks if not already present and freeze all playlists of tracks we will touch */ - nth = -1; + /* Initialise index sentinels so we can match on .second in the next loop */ for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { - if (a->index != nth) { - nth++; - if (!(existing_track = dynamic_pointer_cast (route_by_name (a->name)))) { - /* Create missing track */ - DEBUG_TRACE (DEBUG::PTImport, string_compose ("Create tr(%1) '%2'\n", nth, a->name)); - ok = new_audio_routes_tracks_bulk (routes, - tracks, - 1, 2, 0, 1, - a->name.c_str (), - PresentationInfo::max_order, - Normal - ); - if (ok) { - existing_track = tracks.back(); - track_map[a->name] = existing_track; - std::shared_ptr playlist = existing_track->playlist(); + tr_multi[a->name].second = -1; + } - PlaylistState before; - before.playlist = playlist; - before.before = &playlist->get_state(); - playlist->clear_changes (); - playlist->freeze (); - playlists.push_back(before); - } + /* Count the occurrences of unique indexes with the same track name, these are multichannel tracks */ + for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { + if (tr_multi[a->name].second != a->index) { + tr_multi[a->name].first++; + tr_multi[a->name].second = a->index; + } + } + + /* Freeze playlists of tracks that already exist in ardour that we will touch */ + for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { + if (existing_track = dynamic_pointer_cast (route_by_name (a->name))) { + if (track_map[a->name] != existing_track) { + track_map[a->name] = existing_track; + std::shared_ptr playlist = existing_track->playlist(); + + PlaylistState before; + before.playlist = playlist; + before.before = &playlist->get_state(); + playlist->clear_changes (); + playlist->freeze (); + playlists.push_back(before); + } + } + } + + /* Create all remaining missing PT tracks and freeze playlists of those */ + for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { + if (!track_map[a->name]) { + + /* Create missing track */ + DEBUG_TRACE (DEBUG::PTImport, string_compose ("Create tr(%1) %2ch '%3'\n", tr_multi[a->name].second, tr_multi[a->name].first, a->name)); + ok = new_audio_routes_tracks_bulk (routes, + tracks, + tr_multi[a->name].first, + std::max (2, tr_multi[a->name].first), + 0, + 1, + a->name.c_str (), + PresentationInfo::max_order, + Normal + ); + + if (ok) { + existing_track = tracks.back(); + + track_map[a->name] = existing_track; + std::shared_ptr playlist = existing_track->playlist(); + + PlaylistState before; + before.playlist = playlist; + before.before = &playlist->get_state(); + playlist->clear_changes (); + playlist->freeze (); + playlists.push_back(before); } } } @@ -375,32 +404,36 @@ Session::import_pt_rest (PTFFormat& ptf) add_routes (routes, true, true, PresentationInfo::max_order); } - /* Add regions */ + /* Add regions (already done) */ + + /* Iterate over all pt region -> track entries */ for (vector::const_iterator a = ptf.tracks ().begin (); a != ptf.tracks ().end (); ++a) { - for (vector::iterator p = ptfregpair.begin (); - p != ptfregpair.end (); ++p) { + /* Select only one relevant pt track from a multichannel track */ + if (a->index == tr_multi[a->name].second) { + for (vector::iterator p = ptfregpair.begin (); + p != ptfregpair.end (); ++p) { + if (p->index1 == a->reg.index) { - if (p->index1 == a->reg.index) { + /* Matched a ptf active region to an ardour region */ + std::shared_ptr r = RegionFactory::region_by_id (p->id); + DEBUG_TRACE (DEBUG::PTImport, string_compose ("wav(%1) reg(%2) tr(%3)-%4ch '%5'\n", a->reg.name, a->reg.index, a->index, tr_multi[a->name].first, a->name)); - /* Matched a ptf active region to an ardour region */ - std::shared_ptr r = RegionFactory::region_by_id (p->id); - DEBUG_TRACE (DEBUG::PTImport, string_compose ("wav(%1) reg(%2) tr(%3) '%4'\n", a->reg.wave.filename, a->reg.index, a->index, a->name)); + /* Use audio track we know exists */ + existing_track = track_map[a->name]; + assert (existing_track); - /* Use audio track we know exists */ - existing_track = track_map[a->name]; - assert (existing_track); + /* Put on existing track */ + std::shared_ptr playlist = existing_track->playlist (); + std::shared_ptr copy (RegionFactory::create (r, true)); + playlist->clear_changes (); + playlist->add_region (copy, timepos_t (a->reg.startpos)); + //add_command (new StatefulDiffCommand (playlist)); - /* Put on existing track */ - std::shared_ptr playlist = existing_track->playlist (); - std::shared_ptr copy (RegionFactory::create (r, true)); - playlist->clear_changes (); - playlist->add_region (copy, timepos_t (a->reg.startpos)); - //add_command (new StatefulDiffCommand (playlist)); - - /* Collect latest end of all regions */ - timepos_t end_of_region = timepos_t (a->reg.startpos + a->reg.length); - if (latest < end_of_region) { - latest = end_of_region; + /* Collect latest end of all regions */ + timepos_t end_of_region = timepos_t (a->reg.startpos + a->reg.length); + if (latest < end_of_region) { + latest = end_of_region; + } } } } @@ -435,7 +468,7 @@ no_audio_tracks: std::map > midi_tracks; /* MIDI - Create unique midi tracks and a lookup table for used tracks */ for (vector::iterator a = uniquetr.begin (); a != uniquetr.end (); ++a) { - struct ptflookup miditr; + Session::PtfLookup miditr; list > mt (new_midi_track ( ChanCount (DataType::MIDI, 1), ChanCount (DataType::MIDI, 1),