0faa490dbb5a5256ecfe6866f75cfa32cbec66ba
[ardour.git] / session_utils / fix_bbtppq.cc
1 /*
2     Copyright (C) 2000-2006 Paul Davis
3
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.
8
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.
13
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.
17 */
18
19 #include <iostream>
20 #include <cstdlib>
21 #include <getopt.h>
22
23 #include <glibmm.h>
24
25 #include "pbd/file_utils.h"
26 #include "pbd/i18n.h"
27 #include "pbd/stateful.h"
28
29 #include "ardour/region_factory.h"
30 #include "ardour/midi_model.h"
31 #include "ardour/midi_region.h"
32 #include "ardour/midi_source.h"
33 #include "ardour/playlist.h"
34 #include "ardour/region.h"
35 #include "ardour/session_directory.h"
36 #include "ardour/source.h"
37 #include "ardour/source_factory.h"
38 #include "ardour/tempo.h"
39
40 #include "evoral/Note.hpp"
41 #include "evoral/Sequence.hpp"
42
43 #include "common.h"
44
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace SessionUtils;
48
49 void
50 session_fail (Session* session)
51 {
52         SessionUtils::unload_session(session);
53         SessionUtils::cleanup();
54         exit (EXIT_FAILURE);
55 }
56
57 bool
58 write_bbt_source_to_source (boost::shared_ptr<MidiSource>  bbt_source, boost::shared_ptr<MidiSource> source,
59                                  const Glib::Threads::Mutex::Lock& source_lock, const double session_offset)
60 {
61         assert (source->empty());
62         const bool old_percussive = bbt_source->model()->percussive();
63
64         bbt_source->model()->set_percussive (false);
65
66         source->mark_streaming_midi_write_started (source_lock, bbt_source->model()->note_mode());
67
68         TempoMap& map (source->session().tempo_map());
69
70         for (Evoral::Sequence<MidiModel::TimeType>::const_iterator i = bbt_source->model()->begin(MidiModel::TimeType(), true); i != bbt_source->model()->end(); ++i) {
71                 const double new_time = map.quarter_note_at_beat ((*i).time().to_double() + map.beat_at_pulse (session_offset)) - (session_offset * 4.0);
72                 Evoral::Event<Evoral::Beats> new_ev (*i, true);
73                 new_ev.set_time (Evoral::Beats (new_time));
74                 source->append_event_beats (source_lock, new_ev);
75         }
76
77         bbt_source->model()->set_percussive (old_percussive);
78         source->mark_streaming_write_completed (source_lock);
79         source->set_timeline_position (bbt_source->timeline_position());
80
81         return true;
82 }
83
84 boost::shared_ptr<MidiSource>
85 ensure_per_region_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
86 {
87         boost::shared_ptr<MidiSource> newsrc;
88
89         /* create a new source if none exists and write corrected events to it.
90            if file exists, assume that it is correct.
91         */
92         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
93                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
94                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
95                         SourceFactory::createExternal(DataType::MIDI, *session,
96                                                       newsrc_path, 1, flags));
97                 if (!newsrc) {
98                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
99                         session_fail (session);
100                 }
101
102                 /* hack flags */
103                 XMLNode* node = new XMLNode (newsrc->get_state());
104
105                 if (node->property ("flags") != 0) {
106                         node->property ("flags")->set_value (enum_2_string (flags));
107                 }
108
109                 newsrc->set_state (*node, PBD::Stateful::loading_state_version);
110
111                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
112                 cout << "for region : " << region->name() << endl;
113
114         } else {
115                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
116                         SourceFactory::createWritable(DataType::MIDI, *session,
117                                                       newsrc_path, false, session->frame_rate()));
118
119                 if (!newsrc) {
120                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
121                         session_fail (session);
122                 }
123
124                 if (!newsrc->empty()) {
125                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
126                         session_fail (session);
127                 }
128
129                 Source::Lock newsrc_lock (newsrc->mutex());
130
131                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
132
133                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
134                 cout << "for region : " <<  region->name() << endl;
135
136         }
137
138         return newsrc;
139 }
140
141 boost::shared_ptr<MidiSource>
142 ensure_per_source_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
143 {
144         boost::shared_ptr<MidiSource> newsrc;
145
146         /* create a new source if none exists and write corrected events to it. */
147         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
148                 /* flags are ignored for external MIDI source */
149                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
150
151                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
152                         SourceFactory::createExternal(DataType::MIDI, *session,
153                                                       newsrc_path, 1, flags));
154
155                 if (!newsrc) {
156                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
157                         session_fail (session);
158                 }
159
160                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
161                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
162         } else {
163
164                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
165                         SourceFactory::createWritable(DataType::MIDI, *session,
166                                                       newsrc_path, false, session->frame_rate()));
167                 if (!newsrc) {
168                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
169                         session_fail (session);
170                 }
171
172                 if (!newsrc->empty()) {
173                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
174                         session_fail (session);
175                 }
176
177                 Source::Lock newsrc_lock (newsrc->mutex());
178
179                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
180
181                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
182                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
183
184         }
185
186         return newsrc;
187 }
188
189 void
190 reset_start (Session* session, boost::shared_ptr<MidiRegion> region)
191 {
192         /* set start_beats to quarter note value from incorrect bbt*/
193         TempoMap& tmap (session->tempo_map());
194         double new_start_qn = (tmap.pulse_at_beat (region->beat()) - tmap.pulse_at_beat (region->beat() - region->start_beats().to_double())) * 4.0;
195
196         /* force a change to start and start_beats */
197         PositionLockStyle old_pls = region->position_lock_style();
198         region->set_position_lock_style (AudioTime);
199         region->set_start (tmap.frame_at_quarter_note (new_start_qn) + 1);
200         region->set_start (tmap.frame_at_quarter_note (new_start_qn));
201         region->set_position_lock_style (old_pls);
202
203 }
204
205 void
206 reset_length (Session* session, boost::shared_ptr<MidiRegion> region)
207 {
208         /* set start_beats & length_beats to quarter note value */
209         TempoMap& tmap (session->tempo_map());
210         double new_length_qn = (tmap.pulse_at_beat (region->beat() + region->length_beats().to_double())
211                                   - tmap.pulse_at_beat (region->beat())) * 4.0;
212
213         /* force a change to length and length_beats */
214         PositionLockStyle old_pls = region->position_lock_style();
215         region->set_position_lock_style (AudioTime);
216         region->set_length (tmap.frame_at_quarter_note (new_length_qn) + 1, 0);
217         region->set_length (tmap.frame_at_quarter_note (new_length_qn), 0);
218         region->set_position_lock_style (old_pls);
219 }
220
221 bool
222 apply_one_source_per_region_fix (Session* session)
223 {
224         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
225
226         if (!region_map.size()) {
227                 return false;
228         }
229
230         /* for every midi region, ensure a new source and switch to it. */
231         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
232                 boost::shared_ptr<MidiRegion> mr;
233
234                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
235
236                         if (!mr->midi_source()->writable()) {
237                                 /* we know the midi dir is writable, so this region is external. leave it alone*/
238                                 cout << mr->source()->name() << "is not writable. skipping." << endl;
239                                 continue;
240                         }
241
242                         reset_start (session, mr);
243                         reset_length (session, mr);
244
245                         string newsrc_filename = mr->name() +  "-a54-compat.mid";
246                         string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
247                         boost::shared_ptr<MidiSource> newsrc = ensure_per_region_source (session, mr, newsrc_path);
248
249                         mr->clobber_sources (newsrc);
250                 }
251         }
252
253         return true;
254 }
255
256 bool
257 apply_one_source_per_source_fix (Session* session)
258 {
259         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
260
261         if (!region_map.size()) {
262                 return false;
263         }
264
265         map<PBD::ID, boost::shared_ptr<MidiSource> > old_source_to_new;
266         /* for every midi region, ensure a converted source exists. */
267         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
268                 boost::shared_ptr<MidiRegion> mr;
269                 map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator src_it;
270
271                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
272
273                         if (!mr->midi_source()->writable()) {
274                                 cout << mr->source()->name() << "is not writable. skipping." << endl;
275                                 continue;
276                         }
277
278                         reset_start (session, mr);
279                         reset_length (session, mr);
280
281                         if ((src_it = old_source_to_new.find (mr->midi_source()->id())) == old_source_to_new.end()) {
282                                 string newsrc_filename = mr->source()->name() +  "-a54-compat.mid";
283                                 string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
284
285                                 boost::shared_ptr<MidiSource> newsrc = ensure_per_source_source (session, mr, newsrc_path);
286
287                                 old_source_to_new.insert (make_pair (mr->midi_source()->id(), newsrc));
288
289                                 mr->midi_source(0)->set_name (newsrc->name());
290                         }
291                 }
292         }
293
294         /* remove new sources from the session. current snapshot is saved.*/
295         cout << UTILNAME << ": clearing new sources." << endl;
296
297         for (map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator i = old_source_to_new.begin(); i != old_source_to_new.end(); ++i) {
298                 session->remove_source (boost::weak_ptr<MidiSource> ((*i).second));
299         }
300
301         return true;
302 }
303
304 static void usage (int status) {
305         // help2man compatible format (standard GNU help-text)
306         printf (UTILNAME " - convert an ardour session with 5.0 - 5.3 midi sources to be compatible with 5.4.\n\n");
307         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <snapshot-name>\n\n");
308         printf ("Options:\n\
309   -h, --help                    display this help and exit\n\
310   -f, --force                   override detection of affected sessions\n\
311   -o, --output <snapshot-name>  output session snapshot name (without file suffix)\n\
312   -V, --version                 print version information and exit\n\
313 \n");
314         printf ("\n\
315 This Ardour-specific utility provides an upgrade path for sessions created or modified with Ardour versions 5.0 - 5.3.\n\
316 It creates a 5.4-compatible snapshot from affected Ardour session files.\n\
317 Affected versions (5.0 - 5.3 inclusive) contain a bug which caused some MIDI region properties and contents\n\
318 to be stored incorrectly (see more below).\n\n\
319 The utility will first determine whether or not a session requires any changes for 5.4 compatibility.\n\
320 If a session is determined to be affected by the bug, the program will take one of two approaches to correcting the problem.\n\n\
321 The first is to write a new MIDI source file for every existing MIDI source in the supplied snapshot.\n\
322 In the second approach, each MIDI region have its source converted and placed in the session midifiles directory\n\
323 as a new source (one source file per region).\n\
324 The second method is only offered if the first approach cannot logically ensure that the results would match the input snapshot.\n\
325 Using the first method even if the second method is offered will usually match the input exactly (partly due to a characteristic of the bug).\n\n\
326 Both methods update MIDI region properties and save a new snapshot in the supplied session-dir, optionally using a supplied snapshot name (-o).\n\
327 The new snapshot may be used on Ardour-5.4.\n\n\
328 Running this utility should not alter any existing files, but it is recommended that you run it on a backup of the session directory.\n\n\
329 EXAMPLE:\n\
330 ardour5-headless-chicken -o bantam ~/studio/leghorn leghorn\n\
331 will create a new snapshot file ~/studio/leghorn/bantam.ardour from ~/studio/leghorn/leghorn.ardour\n\
332 Converted midi sources will be created in ~/studio/leghorn/interchange/leghorn/midifiles/\n\
333 If the output option (-o) is omitted, the string \"-a54-compat\" will be appended to the supplied snapshot name.\n\n\
334 About the Bug\n\
335 If a session from affected versions used MIDI regions and a meter note divisor was set to anything but quarter notes,\n\
336 the source smf files would contain events at a PPQN value derived from BBT beats (using meter note divisor) rather than quarter-note beatss.\n\
337 The region start and length offsets would also be stored incorrectly.\n\
338 If a MIDI session only contains quarter note meter divisors, it will be unaffected.\n\
339 \n");
340
341         printf ("Report bugs to <http://tracker.ardour.org/>\n"
342                 "Website: <http://ardour.org/>\n");
343         ::exit (status);
344 }
345
346 int main (int argc, char* argv[])
347 {
348         string outfile;
349         bool force = false;
350
351         const char *optstring = "hfo:r:V";
352
353         const struct option longopts[] = {
354                 { "help",       0, 0, 'h' },
355                 { "force",      0, 0, 'f' },
356                 { "output",     1, 0, 'o' },
357                 { "version",    0, 0, 'V' },
358         };
359
360         int c = 0;
361         while (EOF != (c = getopt_long (argc, argv,
362                                         optstring, longopts, (int *) 0))) {
363                 switch (c) {
364
365                 case 'f':
366                         force = true;
367                         break;
368
369                 case 'o':
370                         outfile = optarg;
371                         if (outfile.empty()) {
372                                 usage (0);
373                         }
374                         break;
375
376                 case 'V':
377                         printf ("ardour-utils version %s\n\n", VERSIONSTRING);
378                         printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
379                         exit (0);
380                         break;
381
382                 case 'h':
383                         usage (0);
384                         break;
385
386                 default:
387                         usage (EXIT_FAILURE);
388                         break;
389                 }
390         }
391
392         if (optind + 2 > argc) {
393                 usage (EXIT_FAILURE);
394         }
395
396         SessionDirectory* session_dir = new SessionDirectory (argv[optind]);
397         string snapshot_name (argv[optind+1]);
398         string statefile_suffix (X_(".ardour"));
399         string pending_suffix (X_(".pending"));
400
401         XMLTree* state_tree;
402
403         string xmlpath(argv[optind]);
404         string out_snapshot_name;
405
406         if (!outfile.empty()) {
407                 string file_test_path = Glib::build_filename (argv[optind], outfile + statefile_suffix);
408                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
409                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
410                         ::exit (EXIT_FAILURE);
411                 }
412                 out_snapshot_name = outfile;
413         } else {
414                 string file_test_path = Glib::build_filename (argv[optind], snapshot_name + "-a54-compat" + statefile_suffix);
415                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
416                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
417                         ::exit (EXIT_FAILURE);
418                 }
419                 out_snapshot_name = snapshot_name + "-a54-compat";
420         }
421
422         xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix);
423
424         if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
425
426                 /* there is pending state from a crashed capture attempt */
427                 cout << UTILNAME << ": There seems to be pending state for snapshot : " << snapshot_name << endl;
428
429         }
430
431         xmlpath = Glib::build_filename (argv[optind], argv[optind+1]);
432
433         if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
434                 xmlpath = Glib::build_filename (argv[optind], legalize_for_path (argv[optind+1]) + ".ardour");
435                 if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
436                         cout << UTILNAME << ": session file " << xmlpath << " doesn't exist!" << endl;
437                         ::exit (EXIT_FAILURE);
438                 }
439         }
440
441         state_tree = new XMLTree;
442
443         bool writable = PBD::exists_and_writable (xmlpath) && PBD::exists_and_writable(Glib::path_get_dirname(xmlpath));
444
445         if (!writable) {
446                 cout << UTILNAME << ": Error : The session directory must exist and be writable." << endl;
447                 return -1;
448         }
449
450         if (!PBD::exists_and_writable (Glib::path_get_dirname (session_dir->midi_path()))) {
451                 cout << UTILNAME << ": Error : The session midi directory " << session_dir->midi_path() << " must be writable. exiting." << endl;
452                 ::exit (EXIT_FAILURE);
453         }
454
455         if (!state_tree->read (xmlpath)) {
456                 cout << UTILNAME << ": Could not understand session file " << xmlpath << endl;
457                 delete state_tree;
458                 state_tree = 0;
459                 ::exit (EXIT_FAILURE);
460         }
461
462         XMLNode const & root (*state_tree->root());
463
464         if (root.name() != X_("Session")) {
465                 cout << UTILNAME << ": Session file " << xmlpath<< " is not a session" << endl;
466                 delete state_tree;
467                 state_tree = 0;
468                 ::exit (EXIT_FAILURE);
469         }
470
471         XMLProperty const * prop;
472
473         if ((prop = root.property ("version")) == 0) {
474                 /* no version implies very old version of Ardour */
475                 cout << UTILNAME << ": The session " << snapshot_name << " has no version or is too old to be affected. exiting." << endl;
476                 ::exit (EXIT_FAILURE);
477         } else {
478                 if (prop->value().find ('.') != string::npos) {
479                         /* old school version format */
480                         cout << UTILNAME << ": The session " << snapshot_name << " is too old to be affected. exiting." << endl;
481                         ::exit (EXIT_FAILURE);
482                 } else {
483                         PBD::Stateful::loading_state_version = atoi (prop->value().c_str());
484                 }
485         }
486
487         cout <<  UTILNAME << ": Checking snapshot : " << snapshot_name << " in directory : " << session_dir->root_path() << endl;
488
489         bool midi_regions_use_bbt_beats = false;
490
491         if (PBD::Stateful::loading_state_version == 3002 && writable) {
492                 XMLNode* child;
493                 if ((child = find_named_node (root, "ProgramVersion")) != 0) {
494                         if ((prop = child->property ("modified-with")) != 0) {
495                                 string modified_with = prop->value ();
496
497                                 const double modified_with_version = atof (modified_with.substr ( modified_with.find(" ", 0) + 1, string::npos).c_str());
498                                 const int modified_with_revision = atoi (modified_with.substr (modified_with.find("-", 0) + 1, string::npos).c_str());
499
500                                 if (modified_with_version <= 5.3 && !(modified_with_version == 5.3 && modified_with_revision >= 42)) {
501                                         midi_regions_use_bbt_beats = true;
502                                 }
503                         }
504                 }
505         }
506
507         XMLNode* tm_node;
508         bool all_metrum_divisors_are_quarters = true;
509         list<double> divisor_list;
510
511         if ((tm_node = find_named_node (root, "TempoMap")) != 0) {
512                 XMLNodeList metrum;
513                 XMLNodeConstIterator niter;
514                 metrum = tm_node->children();
515                 for (niter = metrum.begin(); niter != metrum.end(); ++niter) {
516                         XMLNode* child = *niter;
517
518                         if (child->name() == MeterSection::xml_state_node_name && (prop = child->property ("note-type")) != 0) {
519                                 double note_type;
520
521                                 if (sscanf (prop->value().c_str(), "%lf", &note_type) ==1) {
522
523                                         if (note_type != 4.0) {
524                                                 all_metrum_divisors_are_quarters = false;
525                                         }
526
527                                         divisor_list.push_back (note_type);
528                                 }
529                         }
530                 }
531         } else {
532                 cout << UTILNAME << ": Session file " <<  xmlpath << " has no TempoMap node. exiting." << endl;
533                 ::exit (EXIT_FAILURE);
534         }
535
536         if (all_metrum_divisors_are_quarters && !force) {
537                 cout << UTILNAME << ": The session " << snapshot_name << " is clear for use in 5.4 (all divisors are quarters). Use -f to override." << endl;
538                 ::exit (EXIT_FAILURE);
539         }
540
541         /* check for multiple note divisors. if there is only one, we can create one file per source. */
542         bool one_source_file_per_source = false;
543         divisor_list.unique();
544
545         if (divisor_list.size() == 1) {
546                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " will be converted using one new file per source." << endl;
547                 cout  << "To continue with per-source conversion enter s. q to quit." << endl;
548
549                 while (1) {
550                         cout  << "[s/q]" << endl;
551
552                         string input;
553                         getline (cin, input);
554
555                         if (input == "s") {
556                                 break;
557                         }
558
559                         if (input == "q") {
560                                 exit (EXIT_SUCCESS);
561                                 break;
562                         }
563                 }
564
565                 one_source_file_per_source = true;
566         } else {
567
568                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " contains multiple meter note divisors." << endl;
569                 cout  << "per-region source conversion ensures that the output snapshot will be identical to the original," << endl;
570                 cout  << "however regions in the new snapshot will no longer share sources." << endl;
571                 cout  << "In many (but not all) cases per-source conversion will work equally well." << endl;
572                 cout  << "It is recommended that you test a snapshot created with the per-source method before using per-region conversion." << endl;
573                 cout  << "To continue with per-region conversion enter r. For per-source conversion, enter s. q to quit." << endl;
574
575                 while (1) {
576                         cout  << "[r/s/q]" << endl;
577
578                         string input;
579                         getline (cin, input);
580
581                         if (input == "s") {
582                                 one_source_file_per_source = true;
583                                 break;
584                         }
585
586                         if (input == "r") {
587                                 break;
588                         }
589
590                         if (input == "q") {
591                                 exit (EXIT_SUCCESS);
592                                 break;
593                         }
594                 }
595         }
596
597         if (midi_regions_use_bbt_beats || force) {
598
599                 if (force) {
600                         cout << UTILNAME << ": Forced update of snapshot : " << snapshot_name << endl;
601                 }
602
603                 SessionUtils::init();
604                 Session* s = 0;
605
606                 cout << UTILNAME << ": Loading snapshot." << endl;
607
608                 s = SessionUtils::load_session (argv[optind], argv[optind+1]);
609
610                 /* save new snapshot and prevent alteration of the original by switching to it.
611                    we know these files don't yet exist.
612                 */
613                 if (s->save_state (out_snapshot_name, false, true)) {
614                         cout << UTILNAME << ": Could not save new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
615
616                         session_fail (s);
617                 }
618
619                 cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
620
621                 if (one_source_file_per_source) {
622                         cout << UTILNAME << ": Will create one MIDI file per source." << endl;
623
624                         if (!apply_one_source_per_source_fix (s)) {
625                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting." << endl;
626                                 session_fail (s);
627                         }
628                 } else {
629                         cout << UTILNAME << ": Will create one MIDI file per midi region." << endl;
630
631                         if (!apply_one_source_per_region_fix (s)) {
632                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting."  << endl;
633                                 session_fail (s);
634                         }
635
636                         if (s->save_state (out_snapshot_name, false, true)) {
637                                 cout << UTILNAME << ": Could not save snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
638                                 session_fail (s);
639                         }
640                         cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
641                 }
642
643                 SessionUtils::unload_session(s);
644                 SessionUtils::cleanup();
645                 cout << UTILNAME << ": Snapshot " << out_snapshot_name << " is ready for use in 5.4" << endl;
646         } else {
647                 cout << UTILNAME << ": The snapshot " << snapshot_name << " doesn't require any change for use in 5.4. Use -f to override." << endl;
648                 ::exit (EXIT_FAILURE);
649         }
650
651         return 0;
652 }