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