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