1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[RoseXmlHandler]"
19 
20 #define RG_NO_DEBUG_PRINT 1
21 
22 #include "RoseXmlHandler.h"
23 
24 #include "sound/Midi.h"
25 #include "misc/Debug.h"
26 #include "misc/Strings.h"
27 #include "base/AudioLevel.h"
28 #include "base/AudioPluginInstance.h"
29 #include "base/BaseProperties.h"
30 #include "base/ColourMap.h"
31 #include "base/Composition.h"
32 #include "base/ControlParameter.h"
33 #include "base/Device.h"
34 #include "base/Instrument.h"
35 #include "base/Marker.h"
36 #include "base/MidiDevice.h"
37 #include "base/SoftSynthDevice.h"
38 #include "base/MidiProgram.h"
39 #include "base/MidiTypes.h"
40 #include "base/NotationTypes.h"
41 #include "base/RealTime.h"
42 #include "base/RecordIn.h"
43 #include "base/Segment.h"
44 #include "base/SegmentLinker.h"
45 #include "base/Studio.h"
46 #include "base/Track.h"
47 #include "base/TriggerSegment.h"
48 #include "base/Profiler.h"
49 #include "gui/application/RosegardenMainWindow.h"
50 #include "sequencer/RosegardenSequencer.h"
51 #include "gui/dialogs/FileLocateDialog.h"
52 #include "gui/widgets/StartupLogo.h"
53 #include "gui/studio/AudioPlugin.h"
54 #include "gui/studio/AudioPluginManager.h"
55 #include "RosegardenDocument.h"
56 #include "sound/AudioFileManager.h"
57 #include "XmlStorableEvent.h"
58 #include "XmlSubHandler.h"
59 
60 #include <QMessageBox>
61 #include <QByteArray>
62 #include <QColor>
63 #include <QDataStream>
64 #include <QDialog>
65 #include <QFileInfo>
66 #include <QString>
67 #include <QStringList>
68 
69 namespace Rosegarden
70 {
71 
72 using namespace BaseProperties;
73 
74 class ConfigurationXmlSubHandler : public XmlSubHandler
75 {
76 public:
77     ConfigurationXmlSubHandler(const QString &elementName,
78                    Rosegarden::Configuration *configuration);
79 
80     bool startElement(const QString& namespaceURI,
81                               const QString& localName,
82                               const QString& qName,
83                               const QXmlStreamAttributes& atts) override;
84 
85     bool endElement(const QString& namespaceURI,
86                             const QString& localName,
87                             const QString& qName,
88                             bool& finished) override;
89 
90     bool characters(const QString& ch) override;
91 
92     //--------------- Data members ---------------------------------
93 
94     Rosegarden::Configuration *m_configuration;
95 
96     QString m_elementName;
97     QString m_propertyName;
98     QString m_propertyType;
99 };
100 
ConfigurationXmlSubHandler(const QString & elementName,Rosegarden::Configuration * configuration)101 ConfigurationXmlSubHandler::ConfigurationXmlSubHandler(const QString &elementName,
102                                Rosegarden::Configuration *configuration)
103     : m_configuration(configuration),
104       m_elementName(elementName)
105 {
106 }
107 
startElement(const QString &,const QString &,const QString & lcName,const QXmlStreamAttributes & atts)108 bool ConfigurationXmlSubHandler::startElement(const QString&, const QString&,
109                                               const QString& lcName,
110                                               const QXmlStreamAttributes& atts)
111 {
112     m_propertyName = lcName;
113     m_propertyType = atts.value("type").toString();
114 
115     if (m_propertyName == "property") {
116         // handle alternative encoding for properties with arbitrary names
117         m_propertyName = atts.value("name").toString();
118         QString value = atts.value("value").toString();
119         if (!value.isEmpty()) {
120             m_propertyType = "String";
121             m_configuration->set<String>(qstrtostr(m_propertyName),
122                                          qstrtostr(value));
123         }
124     }
125 
126     return true;
127 }
128 
characters(const QString & chars)129 bool ConfigurationXmlSubHandler::characters(const QString& chars)
130 {
131     //RG_DEBUG << "ConfigurationXmlSubHandler::characters()";
132 
133     QString ch = chars.trimmed();
134     // this method is also called on newlines - skip these cases
135     if (ch.isEmpty()) return true;
136 
137 
138     if (m_propertyType == "Int") {
139         long i = ch.toInt();
140         //RG_DEBUG << "  setting (int) " << m_propertyName << "=" << i;
141         m_configuration->set<Int>(qstrtostr(m_propertyName), i);
142 
143         return true;
144     }
145 
146     if (m_propertyType == "RealTime") {
147         Rosegarden::RealTime rt;
148         int sepIdx = ch.indexOf(',');
149 
150         rt.sec = ch.left(sepIdx).toInt();
151         rt.nsec = ch.mid(sepIdx + 1).toInt();
152 
153         //RG_DEBUG << "  setting (RealTimeT) " << m_propertyName << "=" << rt.sec << "(sec) " << rt.nsec << "(nsec)";
154 
155         m_configuration->set<Rosegarden::RealTimeT>(qstrtostr(m_propertyName), rt);
156 
157         return true;
158     }
159 
160     if (m_propertyType == "Bool") {
161         QString chLc = ch.toLower();
162 
163         bool b = (chLc == "true" ||
164                   chLc == "1"    ||
165                   chLc == "on");
166 
167         //RG_DEBUG << "  setting (Bool) " << m_propertyName << "=" << b;
168 
169         m_configuration->set<Rosegarden::Bool>(qstrtostr(m_propertyName), b);
170 
171         return true;
172     }
173 
174     if (m_propertyType.isEmpty() ||
175     m_propertyType == "String") {
176 
177         //RG_DEBUG << "  setting (String) " << m_propertyName << "=" << ch;
178 
179         m_configuration->set<Rosegarden::String>(qstrtostr(m_propertyName),
180                          qstrtostr(ch));
181 
182         return true;
183     }
184 
185 
186     return true;
187 }
188 
189 bool
endElement(const QString &,const QString &,const QString & lcName,bool & finished)190 ConfigurationXmlSubHandler::endElement(const QString&,
191                                        const QString&,
192                                        const QString& lcName,
193                                        bool& finished)
194 {
195     m_propertyName = "";
196     m_propertyType = "";
197     finished = (lcName == m_elementName);
198     return true;
199 }
200 
201 
202 //----------------------------------------
203 
204 
205 
RoseXmlHandler(RosegardenDocument * doc,unsigned int elementCount,QPointer<QProgressDialog> progressDialog,bool createNewDevicesWhenNeeded)206 RoseXmlHandler::RoseXmlHandler(RosegardenDocument *doc,
207                                unsigned int elementCount,
208                                QPointer<QProgressDialog> progressDialog,
209                                bool createNewDevicesWhenNeeded) :
210     m_doc(doc),
211     m_currentSegment(nullptr),
212     m_currentEvent(nullptr),
213     m_currentTime(0),
214     m_chordDuration(0),
215     m_segmentEndMarkerTime(nullptr),
216     m_inChord(false),
217     m_inGroup(false),
218     m_inComposition(false),
219     m_inColourMap(false),
220     m_inMatrix(false),
221     m_inNotation(false),
222     m_groupId(0),
223     m_groupTupletBase(0),
224     m_groupTupledCount(0),
225     m_groupUntupledCount(0),
226     m_foundTempo(false),
227     m_section(NoSection),
228     m_device(nullptr),
229     m_deviceRunningId(Device::NO_DEVICE),
230     m_deviceInstrumentBase(MidiInstrumentBase),
231     m_deviceReadInstrumentBase(0),
232     m_percussion(false),
233     m_sendBankSelect(false),
234     m_msb(0),
235     m_lsb(0),
236     m_instrument(nullptr),
237     m_controlChangeEncountered(false),
238     m_volumeEncountered(false),
239     m_volume(100),
240     m_panEncountered(false),
241     m_pan(64),
242     m_buss(nullptr),
243     m_plugin(nullptr),
244     m_pluginInBuss(false),
245     m_colourMap(nullptr),
246     m_keyMapping(),
247     m_pluginId(0),
248     m_totalElements(elementCount),
249     m_elementsSoFar(0),
250     m_subHandler(nullptr),
251     m_deprecation(false),
252     m_createDevices(createNewDevicesWhenNeeded),
253     m_haveControls(false),
254     m_skipAllAudio(false),
255     m_hasActiveAudio(false),
256     m_oldSolo(false),
257     m_progressDialog(progressDialog)
258 {}
259 
~RoseXmlHandler()260 RoseXmlHandler::~RoseXmlHandler()
261 {
262     delete m_subHandler;
263 }
264 
265 Composition &
getComposition()266 RoseXmlHandler::getComposition()
267 {
268     return m_doc->getComposition();
269 }
270 
271 Studio &
getStudio()272 RoseXmlHandler::getStudio()
273 {
274     return m_doc->getStudio();
275 }
276 
277 AudioFileManager &
getAudioFileManager()278 RoseXmlHandler::getAudioFileManager()
279 {
280     return m_doc->getAudioFileManager();
281 }
282 
283 QSharedPointer<AudioPluginManager>
getAudioPluginManager()284 RoseXmlHandler::getAudioPluginManager()
285 {
286     return m_doc->getPluginManager();
287 }
288 
289 bool
startDocument()290 RoseXmlHandler::startDocument()
291 {
292     if (m_progressDialog) {
293         m_progressDialog->setLabelText(tr("Reading file..."));
294         m_progressDialog->setRange(0, 100);
295     }
296 
297     // Clear tracks
298     //
299     getComposition().clearTracks();
300 
301     // And the loop
302     //
303     getComposition().setLoopStart(0);
304     getComposition().setLoopEnd(0);
305 
306     // All plugins
307     //
308     m_doc->clearAllPlugins();
309 
310     // reset state
311     return true;
312 }
313 
314 bool
startElement(const QString & namespaceURI,const QString & localName,const QString & qName,const QXmlStreamAttributes & atts)315 RoseXmlHandler::startElement(const QString& namespaceURI,
316                              const QString& localName,
317                              const QString& qName,
318                              const QXmlStreamAttributes& atts)
319 {
320     // If the user cancelled, bail.
321     if (m_progressDialog  &&  m_progressDialog->wasCanceled())
322         return false;
323 
324     QString lcName = qName.toLower();
325 
326     if (getSubHandler()) {
327         return getSubHandler()->startElement(namespaceURI, localName, lcName, atts);
328     }
329 
330     if (lcName == "event") {
331 
332         //        RG_DEBUG << "RoseXmlHandler::startElement: found event, current time is " << m_currentTime;
333 
334         if (m_currentEvent) {
335             RG_DEBUG << "RoseXmlHandler::startElement: Warning: new event found at time " << m_currentTime << " before previous event has ended; previous event will be lost";
336             delete m_currentEvent;
337         }
338 
339         m_currentEvent = new XmlStorableEvent(atts, m_currentTime);
340 
341         if (m_currentEvent->has(BEAMED_GROUP_ID)) {
342 
343             // remap -- we want to ensure that the segment's nextId
344             // is always used (and incremented) in preference to the
345             // stored id
346 
347             if (!m_currentSegment) {
348                 m_errorString = "Got grouped event outside of a segment";
349                 return false;
350             }
351 
352             long storedId = m_currentEvent->get
353                             <Int>(BEAMED_GROUP_ID);
354 
355             if (m_groupIdMap.find(storedId) == m_groupIdMap.end()) {
356                 m_groupIdMap[storedId] = m_currentSegment->getNextId();
357             }
358 
359             m_currentEvent->set
360             <Int>(BEAMED_GROUP_ID, m_groupIdMap[storedId]);
361 
362         } else if (m_inGroup) {
363             m_currentEvent->set
364             <Int>(BEAMED_GROUP_ID, m_groupId);
365             m_currentEvent->set
366             <String>(BEAMED_GROUP_TYPE, m_groupType);
367             if (m_groupType == GROUP_TYPE_TUPLED) {
368                 m_currentEvent->set
369                 <Int>
370                 (BEAMED_GROUP_TUPLET_BASE, m_groupTupletBase);
371                 m_currentEvent->set
372                 <Int>
373                 (BEAMED_GROUP_TUPLED_COUNT, m_groupTupledCount);
374                 m_currentEvent->set
375                 <Int>
376                 (BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount);
377             }
378         }
379 
380         timeT duration = m_currentEvent->getDuration();
381 
382         if (!m_inChord) {
383 
384             m_currentTime = m_currentEvent->getAbsoluteTime() + duration;
385 
386             //            RG_DEBUG << "RoseXmlHandler::startElement: (we're not in a chord) ";
387 
388         } else if (duration != 0) {
389 
390             // set chord duration to the duration of the shortest
391             // element with a non-null duration (if no such elements,
392             // leave it as 0).
393 
394             if (m_chordDuration == 0 || duration < m_chordDuration) {
395                 m_chordDuration = duration;
396             }
397         }
398 
399     } else if (lcName == "property") {
400 
401         if (!m_currentEvent) {
402             RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found property outside of event at time " << m_currentTime << ", ignoring";
403         } else {
404             m_currentEvent->setPropertyFromAttributes(atts, true);
405         }
406 
407     } else if (lcName == "nproperty") {
408 
409         if (!m_currentEvent) {
410             RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found nproperty outside of event at time " << m_currentTime << ", ignoring";
411         } else {
412             m_currentEvent->setPropertyFromAttributes(atts, false);
413         }
414 
415     } else if (lcName == "chord") {
416 
417         m_inChord = true;
418 
419     } else if (lcName == "group") {
420 
421         if (!m_currentSegment) {
422             m_errorString = "Got group outside of a segment";
423             return false;
424         }
425 
426         if (!m_deprecation)
427             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"group\".  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
428         m_deprecation = true;
429 
430         m_inGroup = true;
431         m_groupId = m_currentSegment->getNextId();
432         m_groupType = qstrtostr(atts.value("type").toString());
433 
434         if (m_groupType == GROUP_TYPE_TUPLED) {
435             m_groupTupletBase = atts.value("base").toInt();
436             m_groupTupledCount = atts.value("tupled").toInt();
437             m_groupUntupledCount = atts.value("untupled").toInt();
438         }
439 
440     } else if (lcName == "rosegarden-data") {
441 
442         // FILE FORMAT VERSIONING -- see comments in
443         // RosegardenDocument.cpp.  We only care about major and minor
444         // here, not point.
445 
446         QString version = atts.value("version").toString();
447         QString smajor = atts.value("format-version-major").toString();
448         QString sminor = atts.value("format-version-minor").toString();
449 
450 //        RG_WARNING << "\n\n\nRosegarden file version = \"" << version << "\"\n";
451 
452         if (!smajor.isEmpty()) {
453 
454             int major = smajor.toInt();
455             int minor = sminor.toInt();
456 
457             if (major > RosegardenDocument::FILE_FORMAT_VERSION_MAJOR) {
458                 m_errorString = tr("This file was written by Rosegarden %1, and it uses\na different file format that cannot be read by this version.").arg(version);
459                 return false;
460             }
461 
462             if (major == RosegardenDocument::FILE_FORMAT_VERSION_MAJOR &&
463                     minor > RosegardenDocument::FILE_FORMAT_VERSION_MINOR) {
464 
465                 StartupLogo::hideIfStillThere();
466 
467                 QMessageBox::information(nullptr, tr("Rosegarden"), tr("This file was written by Rosegarden %1, which is more recent than this version.\nThere may be some incompatibilities with the file format.").arg(version));
468 
469             }
470         }
471 
472     } else if (lcName == "studio") {
473 
474         if (m_section != NoSection) {
475             m_errorString = "Found Studio in another section";
476             return false;
477         }
478 
479         Studio &studio = m_doc->getStudio();
480 
481         // In the Studio we clear down everything apart from Devices and
482         // Instruments before we reload.  Instruments are derived from
483         // the Sequencer, the bank/program information is loaded from
484         // the file we're currently examining.
485         //
486         studio.clearMidiBanksAndPrograms();
487         studio.clearBusses();
488         studio.clearRecordIns();
489 
490         m_section = InStudio; // set top level section
491 
492         // Get and set MIDI filters
493         //
494         QString thruStr = atts.value("thrufilter").toString();
495 
496         if (!thruStr.isEmpty())
497             studio.setMIDIThruFilter(thruStr.toInt());
498 
499         QString recordStr = atts.value("recordfilter").toString();
500 
501         if (!recordStr.isEmpty())
502             studio.setMIDIRecordFilter(recordStr.toInt());
503 
504         QString inputStr = atts.value("audioinputpairs").toString();
505 
506         if (!inputStr.isEmpty()) {
507             int inputs = inputStr.toInt();
508             if (inputs < 1)
509                 inputs = 1; // we simply don't permit no inputs
510             while (int(studio.getRecordIns().size()) < inputs) {
511                 studio.addRecordIn(new RecordIn());
512             }
513         }
514 
515         QString metronomeStr = atts.value("metronomedevice").toString();
516 
517         if (!metronomeStr.isEmpty()) {
518             DeviceId metronome = metronomeStr.toUInt();
519             studio.setMetronomeDevice(metronome);
520         }
521 
522         QString temp;
523 
524         temp = atts.value("amwshowaudiofaders").toString();
525         studio.amwShowAudioFaders =
526                 temp.isEmpty() ? true : (temp.toInt() != 0);
527 
528         temp = atts.value("amwshowsynthfaders").toString();
529         studio.amwShowSynthFaders =
530                 temp.isEmpty() ? true : (temp.toInt() != 0);
531 
532         temp = atts.value("amwshowaudiosubmasters").toString();
533         studio.amwShowAudioSubmasters =
534                 temp.isEmpty() ? true : (temp.toInt() != 0);
535 
536         temp = atts.value("amwshowunassignedfaders").toString();
537         studio.amwShowUnassignedFaders =
538                 temp.isEmpty() ? false : (temp.toInt() != 0);
539 
540     } else if (lcName == "timesignature") {
541 
542         if (m_inComposition == false) {
543             m_errorString = "TimeSignature object found outside Composition";
544             return false;
545         }
546 
547         timeT t = 0;
548         QString timeStr = atts.value("time").toString();
549         if (!timeStr.isEmpty())
550             t = timeStr.toInt();
551 
552         int num = 4;
553         QString numStr = atts.value("numerator").toString();
554         if (!numStr.isEmpty())
555             num = numStr.toInt();
556 
557         int denom = 4;
558         QString denomStr = atts.value("denominator").toString();
559         if (!denomStr.isEmpty())
560             denom = denomStr.toInt();
561 
562         bool common = false;
563         QString commonStr = atts.value("common").toString();
564         if (!commonStr.isEmpty())
565             common = (commonStr == "true");
566 
567         bool hidden = false;
568         QString hiddenStr = atts.value("hidden").toString();
569         if (!hiddenStr.isEmpty())
570             hidden = (hiddenStr == "true");
571 
572         bool hiddenBars = false;
573         QString hiddenBarsStr = atts.value("hiddenbars").toString();
574         if (!hiddenBarsStr.isEmpty())
575             hiddenBars = (hiddenBarsStr == "true");
576 
577         getComposition().addTimeSignature
578         (t, TimeSignature(num, denom, common, hidden, hiddenBars));
579 
580     } else if (lcName == "tempo") {
581 
582         timeT t = 0;
583         QString timeStr = atts.value("time").toString();
584         if (!timeStr.isEmpty())
585             t = timeStr.toInt();
586 
587         tempoT tempo = Composition::getTempoForQpm(120.0);
588         QString tempoStr = atts.value("tempo").toString();
589         QString targetStr = atts.value("target").toString();
590         QString bphStr = atts.value("bph").toString();
591         if (!tempoStr.isEmpty()) {
592             tempo = tempoStr.toInt();
593         } else if (!bphStr.isEmpty()) {
594             tempo = Composition::getTempoForQpm
595                     (double(bphStr.toInt()) / 60.0);
596         }
597 
598         if (!targetStr.isEmpty()) {
599             getComposition().addTempoAtTime(t, tempo, targetStr.toInt());
600         } else {
601             getComposition().addTempoAtTime(t, tempo);
602         }
603 
604     } else if (lcName == "composition") {
605 
606         if (m_section != NoSection) {
607             m_errorString = "Found Composition in another section";
608             return false;
609         }
610 
611         // set Segment
612         m_section = InComposition;
613 
614         // Get and set the record track
615         //
616         QString recordStr = atts.value("recordtrack").toString();
617         if (!recordStr.isEmpty()) {
618             getComposition().setTrackRecording(recordStr.toInt(), true);
619         }
620 
621         QString recordPlStr = atts.value("recordtracks").toString();
622         if (!recordPlStr.isEmpty()) {
623             RG_DEBUG << "Record tracks: " << recordPlStr;
624             QStringList recordList = recordPlStr.split(',');
625             for (QStringList::iterator i = recordList.begin();
626                     i != recordList.end(); ++i) {
627                 RG_DEBUG << "Record track: " << (*i).toInt();
628                 getComposition().setTrackRecording((*i).toInt(), true);
629             }
630         }
631 
632         // Get and set the position pointer
633         //
634         int position = 0;
635         QString positionStr = atts.value("pointer").toString();
636         if (!positionStr.isEmpty()) {
637             position = positionStr.toInt();
638         }
639 
640         getComposition().setPosition(position);
641 
642 
643         // Get and (eventually) set the default tempo.
644         // We prefer the new compositionDefaultTempo over the
645         // older defaultTempo.
646         //
647         QString tempoStr = atts.value("compositionDefaultTempo").toString();
648         if (!tempoStr.isEmpty()) {
649             tempoT tempo = tempoT(tempoStr.toInt());
650             getComposition().setCompositionDefaultTempo(tempo);
651         } else {
652             tempoStr = atts.value("defaultTempo").toString();
653             if (!tempoStr.isEmpty()) {
654                 double tempo = qstrtodouble(tempoStr);
655                 getComposition().setCompositionDefaultTempo
656                 (Composition::getTempoForQpm(tempo));
657             }
658         }
659 
660         // set the composition flag
661         m_inComposition = true;
662 
663 
664         // Set the loop
665         //
666         QString loopStartStr = atts.value("loopstart").toString();
667         QString loopEndStr = atts.value("loopend").toString();
668 
669         if (!loopStartStr.isEmpty() && !loopEndStr.isEmpty()) {
670             int loopStart = loopStartStr.toInt();
671             int loopEnd = loopEndStr.toInt();
672 
673             getComposition().setLoopStart(loopStart);
674             getComposition().setLoopEnd(loopEnd);
675         }
676 
677         QString selectedTrackStr = atts.value("selected").toString();
678 
679         if (!selectedTrackStr.isEmpty()) {
680             TrackId selectedTrack =
681                 (TrackId)selectedTrackStr.toInt();
682 
683             getComposition().setSelectedTrack(selectedTrack);
684         }
685 
686         QString soloTrackStr = atts.value("solo").toString();
687         m_oldSolo = false;
688         if (!soloTrackStr.isEmpty())
689             if (soloTrackStr.toInt() == 1)
690                 m_oldSolo = true;
691 
692         QString playMetStr = atts.value("playmetronome").toString();
693         if (!playMetStr.isEmpty()) {
694             if (playMetStr.toInt())
695                 getComposition().setPlayMetronome(true);
696             else
697                 getComposition().setPlayMetronome(false);
698         }
699 
700         QString recMetStr = atts.value("recordmetronome").toString();
701         if (!recMetStr.isEmpty()) {
702             if (recMetStr.toInt())
703                 getComposition().setRecordMetronome(true);
704             else
705                 getComposition().setRecordMetronome(false);
706         }
707 
708         QString nextTriggerIdStr = atts.value("nexttriggerid").toString();
709         if (!nextTriggerIdStr.isEmpty()) {
710             getComposition().setNextTriggerSegmentId(nextTriggerIdStr.toInt());
711         }
712 
713         QString copyrightStr = atts.value("copyright").toString();
714         if (!copyrightStr.isEmpty()) {
715             getComposition().setCopyrightNote(qstrtostr(copyrightStr));
716         }
717 
718         QString startMarkerStr = atts.value("startMarker").toString();
719         QString endMarkerStr = atts.value("endMarker").toString();
720 
721         if (!startMarkerStr.isEmpty()) {
722             getComposition().setStartMarker(startMarkerStr.toInt());
723         }
724 
725         if (!endMarkerStr.isEmpty()) {
726             getComposition().setEndMarker(endMarkerStr.toInt());
727         }
728 
729         QString autoExpand = atts.value("autoExpand").toString();
730         if (!autoExpand.isEmpty()) {
731             if (autoExpand.toInt() == 1)
732                 getComposition().setAutoExpand(true);
733             else
734                 getComposition().setAutoExpand(false);
735         }
736 
737         QString panLawStr = atts.value("panlaw").toString();
738         if (!panLawStr.isEmpty()) {
739             int panLaw = panLawStr.toInt();
740             AudioLevel::setPanLaw(panLaw);
741         } else {
742             // Since no "panlaw" was found in this tag, apply the default.
743             AudioLevel::setPanLaw(0);
744         }
745 
746         QString notationSpacingStr = atts.value("notationspacing").toString();
747         if (!notationSpacingStr.isEmpty()) {
748             getComposition().m_notationSpacing = notationSpacingStr.toInt();
749         } else {
750             // Default to 100.
751             getComposition().m_notationSpacing = 100;
752         }
753 
754     } else if (lcName == "track") {
755 
756         if (m_section != InComposition) {
757             m_errorString = "Track object found outside Composition";
758             return false;
759         }
760 
761         int id = -1;
762         int position = -1;
763         int instrument = -1;
764         std::string label;
765         bool muted = false;
766 
767         QString trackNbStr = atts.value("id").toString();
768         if (!trackNbStr.isEmpty()) {
769             id = trackNbStr.toInt();
770         }
771 
772         QString labelStr = atts.value("label").toString();
773         if (!labelStr.isEmpty()) {
774             label = qstrtostr(labelStr);
775         }
776 
777         QString mutedStr = atts.value("muted").toString();
778         if (!mutedStr.isEmpty()) {
779             if (mutedStr == "true")
780                 muted = true;
781             else
782                 muted = false;
783         }
784 
785         QString positionStr = atts.value("position").toString();
786         if (!positionStr.isEmpty()) {
787             position = positionStr.toInt();
788         }
789 
790         QString instrumentStr = atts.value("instrument").toString();
791         if (!instrumentStr.isEmpty()) {
792             instrument = instrumentStr.toInt();
793         }
794 
795         Track *track = new Track(id,
796                                  instrument,
797                                  position,
798                                  label,
799                                  muted);
800 
801         if (m_oldSolo) {
802             // if this is the selected track
803             if (static_cast<TrackId>(id) ==
804                     getComposition().getSelectedTrack()) {
805                 track->setSolo(true);
806             }
807         }
808 
809         QString shortLabelStr = atts.value("shortLabel").toString();
810         if (!shortLabelStr.isEmpty()) {
811             track->setShortLabel(shortLabelStr.toStdString());
812         }
813 
814         // track properties affecting newly created segments are initialized
815         // to default values in the ctor, so they don't need to be initialized
816         // here
817 
818         QString presetLabelStr = atts.value("defaultLabel").toString();
819         if (!presetLabelStr.isEmpty()) {
820             track->setPresetLabel( qstrtostr(presetLabelStr) );
821         }
822 
823         QString clefStr = atts.value("defaultClef").toString();
824         if (!clefStr.isEmpty()) {
825             track->setClef(clefStr.toInt());
826         }
827 
828         QString transposeStr = atts.value("defaultTranspose").toString();
829         if (!transposeStr.isEmpty()) {
830             track->setTranspose(transposeStr.toInt());
831         }
832 
833         QString colorStr = atts.value("defaultColour").toString();
834         if (!colorStr.isEmpty()) {
835             track->setColor(colorStr.toInt());
836         }
837 
838         QString highplayStr = atts.value("defaultHighestPlayable").toString();
839         if (!highplayStr.isEmpty()) {
840             track->setHighestPlayable(highplayStr.toInt());
841         }
842 
843         QString lowplayStr = atts.value("defaultLowestPlayable").toString();
844         if (!lowplayStr.isEmpty()) {
845             track->setLowestPlayable(lowplayStr.toInt());
846         }
847 
848         QString staffSizeStr = atts.value("staffSize").toString();
849         if (!staffSizeStr.isEmpty()) {
850             track->setStaffSize(staffSizeStr.toInt());
851         }
852 
853         QString staffBracketStr = atts.value("staffBracket").toString();
854         if (!staffBracketStr.isEmpty()) {
855             track->setStaffBracket(staffBracketStr.toInt());
856         }
857 
858         QString inputDeviceStr = atts.value("inputDevice").toString();
859         if (!inputDeviceStr.isEmpty()) {
860             track->setMidiInputDevice(inputDeviceStr.toUInt());
861         }
862 
863         QString inputChannelStr = atts.value("inputChannel").toString();
864         if (!inputChannelStr.isEmpty()) {
865             track->setMidiInputChannel(inputChannelStr.toInt());
866         }
867 
868         QString thruRoutingStr = atts.value("thruRouting").toString();
869         if (!thruRoutingStr.isEmpty()) {
870             track->setThruRouting(
871                     static_cast<Track::ThruRouting>(thruRoutingStr.toInt()));
872         }
873 
874         QString soloStr = atts.value("solo").toString();
875         if (soloStr == "true")
876             track->setSolo(true);
877 
878         QString archivedStr = atts.value("archived").toString();
879         if (archivedStr == "true")
880             track->setArchived(true);
881 
882         // If the composition tag had this track set to record, make sure
883         // it is armed.
884         if (getComposition().isTrackRecording(id)) {
885             track->setArmed(true);
886         }
887 
888         getComposition().addTrack(track);
889 
890         std::vector<TrackId> trackIds;
891         trackIds.push_back(track->getId());
892         getComposition().notifyTracksAdded(trackIds);
893 
894 
895     } else if (lcName == "segment") {
896 
897         if (m_section != NoSection) {
898             m_errorString = "Found Segment in another section";
899             return false;
900         }
901 
902         // set Segment
903         if(lcName == "segment") {
904             m_section = InSegment;
905         }
906 
907         int track = -1, startTime = 0;
908         unsigned int colourindex = 0;
909         QString trackNbStr = atts.value("track").toString();
910         if (!trackNbStr.isEmpty()) {
911             track = trackNbStr.toInt();
912         }
913 
914         QString startIdxStr = atts.value("start").toString();
915         if (!startIdxStr.isEmpty()) {
916             startTime = startIdxStr.toInt();
917         }
918 
919         QString segmentType = (atts.value("type")).toString().toLower();
920         if (!segmentType.isEmpty()) {
921             if (segmentType == "audio") {
922                 int audioFileId = atts.value("file").toInt();
923 
924                 // check this file id exists on the AudioFileManager
925 
926                 if (getAudioFileManager().fileExists(audioFileId) == false) {
927                     // We don't report an error as this audio file might've
928                     // been excluded deliberately as we could't actually
929                     // find the audio file itself.
930                     //
931                     return true;
932                 }
933 
934                 // Create an Audio segment and add its reference
935                 //
936                 m_currentSegment = new Segment(Segment::Audio);
937                 m_currentSegment->setAudioFileId(audioFileId);
938                 m_currentSegment->setStartTime(startTime);
939             } else {
940                 // Create a (normal) internal Segment
941                 m_currentSegment = new Segment(Segment::Internal);
942             }
943 
944         } else {
945             // for the moment we default
946             m_currentSegment = new Segment(Segment::Internal);
947         }
948 
949         QString repeatStr = atts.value("repeat").toString();
950         if (repeatStr.toLower() == "true") {
951             m_currentSegment->setRepeating(true);
952         }
953 
954         QString delayStr = atts.value("delay").toString();
955         if (!delayStr.isEmpty()) {
956             RG_DEBUG << "Delay string is \"" << delayStr << "\"";
957             long delay = delayStr.toLong();
958             RG_DEBUG << "Delay is " << delay;
959             m_currentSegment->setDelay(delay);
960         }
961 
962         QString rtDelaynSec = atts.value("rtdelaynsec").toString();
963         QString rtDelayuSec = atts.value("rtdelayusec").toString();
964         QString rtDelaySec = atts.value("rtdelaysec").toString();
965         if ( (!rtDelaySec.isEmpty()) && ((!rtDelaynSec.isEmpty()) || (!rtDelayuSec.isEmpty())) ){
966             if (!rtDelaynSec.isEmpty()) {
967                 m_currentSegment->setRealTimeDelay(
968                             RealTime(rtDelaySec.toInt(),
969                                           rtDelaynSec.toInt()));
970             } else {
971                 m_currentSegment->setRealTimeDelay
972                 (RealTime(rtDelaySec.toInt(),
973                           rtDelayuSec.toInt() * 1000));
974             }
975         }
976 
977         QString transposeStr = atts.value("transpose").toString();
978         if (!transposeStr.isEmpty())
979             m_currentSegment->setTranspose(transposeStr.toInt());
980 
981         // fill in the label
982         QString labelStr = atts.value("label").toString();
983         if (!labelStr.isEmpty())
984             m_currentSegment->setLabel(qstrtostr(labelStr));
985 
986         m_currentSegment->setTrack(track);
987         //m_currentSegment->setStartTime(startTime);
988 
989         QString colourIndStr = atts.value("colourindex").toString();
990         if (!colourIndStr.isEmpty()) {
991             colourindex = colourIndStr.toInt();
992         }
993 
994         m_currentSegment->setColourIndex(colourindex);
995 
996         QString snapGridSizeStr = atts.value("snapgridsize").toString();
997         if (!snapGridSizeStr.isEmpty()) {
998             m_currentSegment->setSnapGridSize(snapGridSizeStr.toInt());
999         }
1000 
1001         QString viewFeaturesStr = atts.value("viewfeatures").toString();
1002         if (!viewFeaturesStr.isEmpty()) {
1003             m_currentSegment->setViewFeatures(viewFeaturesStr.toInt());
1004         }
1005 
1006         QString forNotationStr = atts.value("fornotation").toString();
1007         if (!forNotationStr.isEmpty()) {
1008             bool forNotation = (forNotationStr.toUpper() == "TRUE");
1009             m_currentSegment->setForNotation(forNotation);
1010         }
1011 
1012         m_currentTime = startTime;
1013 
1014         QString triggerIdStr = atts.value("triggerid").toString();
1015         QString triggerPitchStr = atts.value("triggerbasepitch").toString();
1016         QString triggerVelocityStr = atts.value("triggerbasevelocity").toString();
1017         QString triggerRetuneStr = atts.value("triggerretune").toString();
1018         QString triggerAdjustTimeStr = atts.value("triggeradjusttimes").toString();
1019 
1020         QString linkerIdStr = atts.value("linkerid").toString();
1021 
1022         QString linkerChgKeyStr = atts.value("linkertransposechangekey").toString();
1023         QString linkerStepsStr = atts.value("linkertransposesteps").toString();
1024         QString linkerSemitonesStr = atts.value("linkertransposesemitones").toString();
1025         QString linkerTransBackStr =
1026                              atts.value("linkertransposesegmentback").toString();
1027 
1028         if (!triggerIdStr.isEmpty()) {
1029             int pitch = -1;
1030             if (!triggerPitchStr.isEmpty())
1031                 pitch = triggerPitchStr.toInt();
1032             int velocity = -1;
1033             if (!triggerVelocityStr.isEmpty())
1034                 velocity = triggerVelocityStr.toInt();
1035             TriggerSegmentRec *rec =
1036                 getComposition().addTriggerSegment(m_currentSegment,
1037                                                    triggerIdStr.toInt(),
1038                                                    pitch, velocity);
1039             if (rec) {
1040                 if (!triggerRetuneStr.isEmpty())
1041                     rec->setDefaultRetune(triggerRetuneStr.toLower() == "true");
1042                 if (!triggerAdjustTimeStr.isEmpty())
1043                     rec->setDefaultTimeAdjust(qstrtostr(triggerAdjustTimeStr));
1044             }
1045             m_currentSegment->setStartTimeDataMember(startTime);
1046         } else {
1047             if (!linkerIdStr.isEmpty()) {
1048                 SegmentLinker *linker = nullptr;
1049                 int linkId = linkerIdStr.toInt();
1050                 SegmentLinkerMap::iterator linkerItr =
1051                                                   m_segmentLinkers.find(linkId);
1052                 if (linkerItr == m_segmentLinkers.end()) {
1053                     //need to create a SegmentLinker with this id
1054                     linker = new SegmentLinker(linkId);
1055                     m_segmentLinkers[linkId] = linker;
1056                 } else {
1057                     linker = linkerItr->second;
1058                 }
1059                 Segment::LinkTransposeParams params(
1060                                         linkerChgKeyStr.toLower()=="true",
1061                                         linkerStepsStr.toInt(),
1062                                         linkerSemitonesStr.toInt(),
1063                                         linkerTransBackStr.toLower()=="true");
1064                 linker->addLinkedSegment(m_currentSegment);
1065                 m_currentSegment->setLinkTransposeParams(params);
1066             }
1067             getComposition().addSegment(m_currentSegment);
1068             getComposition().setSegmentStartTime(m_currentSegment, startTime);
1069         }
1070 
1071         QString endMarkerStr = atts.value("endmarker").toString();
1072         if (!endMarkerStr.isEmpty()) {
1073             delete m_segmentEndMarkerTime;
1074             m_segmentEndMarkerTime = new timeT(endMarkerStr.toInt());
1075         }
1076 
1077         m_groupIdMap.clear();
1078 
1079     } else if (lcName == "matrix") {  // <matrix>
1080 
1081         // If we're in a <segment>, <matrix> is valid.
1082         if (m_currentSegment)
1083             m_inMatrix = true;
1084 
1085     } else if (lcName == "notation") {  // <notation>
1086 
1087         // If we're in a <segment>, <notation> is valid.
1088         if (m_currentSegment)
1089             m_inNotation = true;
1090 
1091     } else if (lcName == "hzoom") {  // <hzoom>
1092 
1093         if (m_currentSegment && m_inMatrix)
1094             m_currentSegment->matrixHZoomFactor = atts.value("factor").toDouble();
1095 
1096     } else if (lcName == "vzoom") {  // <vzoom>
1097 
1098         if (m_currentSegment && m_inMatrix)
1099             m_currentSegment->matrixVZoomFactor = atts.value("factor").toDouble();
1100 
1101     } else if (lcName == "ruler") {  // <ruler>
1102 
1103         if (m_currentSegment && m_inMatrix) {
1104             Segment::Ruler segmentRuler;
1105             segmentRuler.type = atts.value("type").toString().toStdString();
1106             segmentRuler.ccNumber = atts.value("ccnumber").toInt();
1107             m_currentSegment->matrixRulers->insert(segmentRuler);
1108         }
1109 
1110         if (m_currentSegment && m_inNotation) {
1111             Segment::Ruler segmentRuler;
1112             segmentRuler.type = atts.value("type").toString().toStdString();
1113             segmentRuler.ccNumber = atts.value("ccnumber").toInt();
1114             m_currentSegment->notationRulers->insert(segmentRuler);
1115         }
1116 
1117     } else if (lcName == "gui") {  // <gui>
1118 
1119         // This element is no longer supported.  But please don't reuse
1120         // the name in case it pops up in an old file.
1121 
1122         // <gui> elements used to be found in <segment> elements.
1123         // <gui> elements contained <controller> elements.
1124         // The example file bogus-surf-jam.rg still has this.
1125         // However, they never did anything.
1126 
1127     } else if (lcName == "controller") {  // <controller>
1128 
1129         // This element is no longer supported.  But please don't reuse
1130         // the name in case it pops up in an old file.
1131 
1132         // <controller> elements used to be found in <gui> elements.
1133         // The example file bogus-surf-jam.rg still has this.
1134         // However, they never did anything.
1135 
1136     } else if (lcName == "resync") {
1137 
1138         if (!m_deprecation)
1139             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"resync\".  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1140         m_deprecation = true;
1141 
1142         QString time(atts.value("time").toString());
1143         bool isNumeric;
1144         int numTime = time.toInt(&isNumeric);
1145         if (isNumeric)
1146             m_currentTime = numTime;
1147 
1148     } else if (lcName == "audio") {
1149 
1150         if (m_section != InAudioFiles) {
1151             m_errorString = "Audio object found outside Audio section";
1152             return false;
1153         }
1154 
1155         if (m_skipAllAudio) {
1156             RG_DEBUG << "SKIPPING audio file";
1157             return true;
1158         }
1159 
1160         QString id(atts.value("id").toString());
1161         QString file(atts.value("file").toString());
1162         QString label(atts.value("label").toString());
1163 
1164         if (id.isEmpty() || file.isEmpty() || label.isEmpty()) {
1165             m_errorString = "Audio object has empty parameters";
1166             return false;
1167         }
1168 
1169         m_hasActiveAudio = true;
1170 
1171         // attempt to insert file into AudioFileManager
1172         // (this checks the integrity of the file at the
1173         // same time)
1174         //
1175         if (getAudioFileManager().insertFile(qstrtostr(label),
1176                                              file,
1177                                              id.toInt()) == false) {
1178 
1179             // We failed to find the audio file.  First we'll try to
1180             // set the audio file path to "a sensible default", and if
1181             // this still doesn't work, we show a locate-file dialog.
1182             // In earlier times, RG used to use the last file path
1183             // that the KDE file dialog had associated with an audio
1184             // file for its sensible default, but we no longer have
1185             // that facility.  More recent code dropped the sensible
1186             // default and went straight to showing a directory
1187             // location dialog to ask the user to set the audio file
1188             // path -- but without any explanation of what the dialog
1189             // is for, this behaviour is just baffling.  And we're
1190             // about to show a file-locate dialog if this fails
1191             // anyway.  So instead let's just start by looking in the
1192             // same place as the .rg file.
1193 
1194             QString docPath = m_doc->getAbsFilePath();
1195             QString dirPath = QFileInfo(docPath).path();
1196             getAudioFileManager().setAudioPath(dirPath);
1197 
1198             RG_DEBUG << "Attempting to find audio file " << file
1199                      << " in path " << dirPath;
1200 
1201             if (getAudioFileManager().insertFile(qstrtostr(label),
1202                                                  file, id.toInt()) == false) {
1203 
1204                 // Hide splash screen if present on startup
1205                 StartupLogo::hideIfStillThere();
1206 
1207                 // Create a locate file dialog - give it the file name
1208                 // and the AudioFileManager path that we've already
1209                 // tried.  If we manually locate the file then we reset
1210                 // the audiofilepath to the new value and see if this
1211                 // helps us locate the rest of the files.
1212                 //
1213 
1214                 QString newFilename = "";
1215                 QString newPath = "";
1216 
1217                 do {
1218 
1219                     FileLocateDialog fL((QWidget *)m_doc->parent(),
1220                                         file,
1221                                         getAudioFileManager().getAudioPath());
1222                     int result = fL.exec();
1223 
1224                     if (result == QDialog::Accepted) {
1225                         newFilename = fL.getFilename();
1226                         newPath = fL.getDirectory();
1227                     } else if (result == QDialog::Rejected) {
1228                         // just skip the file
1229                         break;
1230                     } else {
1231                         // don't process any more audio files
1232                         m_skipAllAudio = true;
1233                         return true;
1234                     }
1235 
1236 
1237                 } while (getAudioFileManager().insertFile(qstrtostr(label),
1238                          newFilename,
1239                          id.toInt()) == false);
1240 
1241                 if (newPath != "") {
1242                     getAudioFileManager().setAudioPath(newPath);
1243                     // Set a document post-modify flag
1244                     //m_doc->setModified(true);
1245                 }
1246 
1247                 getAudioFileManager().print();
1248 
1249             } else {
1250                 // AudioPath is modified so set a document post modify flag
1251                 //
1252                 //m_doc->setModified(true);
1253             }
1254 
1255         }
1256 
1257     } else if (lcName == "audiopath") {
1258 
1259         if (m_section != InAudioFiles) {
1260             m_errorString = "Audiopath object found outside AudioFiles section";
1261             return false;
1262         }
1263 
1264         QString search(atts.value("value").toString());
1265 
1266         if (search.isEmpty()) {
1267             m_errorString = "Audiopath has no value";
1268             return false;
1269         }
1270 
1271         if (!search.startsWith("/") && !search.startsWith("~")) {
1272             QString docPath = m_doc->getAbsFilePath();
1273             QString dirPath = QFileInfo(docPath).path();
1274             if (QFileInfo(dirPath).exists()) {
1275                 search = dirPath + "/" + search;
1276             }
1277         }
1278 
1279         getAudioFileManager().setAudioPath(search);
1280 
1281     } else if (lcName == "begin") {
1282 
1283         double marker = qstrtodouble(atts.value("index").toString());
1284 
1285         if (!m_currentSegment) {
1286             // Don't fail - as this segment could be defunct if we
1287             // skipped loading the audio file
1288             //
1289             return true;
1290         }
1291 
1292         if (m_currentSegment->getType() != Segment::Audio) {
1293             m_errorString = "Found audio begin index in non audio segment";
1294             return false;
1295         }
1296 
1297         // convert to RealTime from float
1298         int sec = (int)marker;
1299         int usec = (int)((marker - ((double)sec)) * 1000000.0);
1300         m_currentSegment->setAudioStartTime(RealTime(sec, usec * 1000));
1301 
1302 
1303     } else if (lcName == "end") {
1304 
1305         double marker = qstrtodouble(atts.value("index").toString());
1306 
1307         if (!m_currentSegment) {
1308             // Don't fail - as this segment could be defunct if we
1309             // skipped loading the audio file
1310             //
1311             return true;
1312         }
1313 
1314         if (m_currentSegment->getType() != Segment::Audio) {
1315             m_errorString = "found audio end index in non audio segment";
1316             return false;
1317         }
1318 
1319         int sec = (int)marker;
1320         int usec = (int)((marker - ((double)sec)) * 1000000.0);
1321         RealTime markerTime(sec, usec * 1000);
1322 
1323         if (markerTime < m_currentSegment->getAudioStartTime()) {
1324             m_errorString = "Audio end index before audio start marker";
1325             return false;
1326         }
1327 
1328         m_currentSegment->setAudioEndTime(markerTime);
1329 
1330         // Ensure we set end time according to correct RealTime end of Segment
1331         //
1332         RealTime realEndTime = getComposition().
1333                                getElapsedRealTime(m_currentSegment->getStartTime()) +
1334                                m_currentSegment->getAudioEndTime() -
1335                                m_currentSegment->getAudioStartTime();
1336 
1337         timeT absEnd = getComposition().getElapsedTimeForRealTime(realEndTime);
1338         m_currentSegment->setEndTime(absEnd);
1339 
1340     } else if (lcName == "fadein") {
1341 
1342         if (!m_currentSegment) {
1343             // Don't fail - as this segment could be defunct if we
1344             // skipped loading the audio file
1345             //
1346             return true;
1347         }
1348 
1349         if (m_currentSegment->getType() != Segment::Audio) {
1350             m_errorString = "found fade in time in non audio segment";
1351             return false;
1352         }
1353 
1354         double marker = qstrtodouble(atts.value("time").toString());
1355         int sec = (int)marker;
1356         int usec = (int)((marker - ((double)sec)) * 1000000.0);
1357         RealTime markerTime(sec, usec * 1000);
1358 
1359         m_currentSegment->setFadeInTime(markerTime);
1360         m_currentSegment->setAutoFade(true);
1361 
1362 
1363     } else if (lcName == "fadeout") {
1364 
1365         if (!m_currentSegment) {
1366             // Don't fail - as this segment could be defunct if we
1367             // skipped loading the audio file
1368             //
1369             return true;
1370         }
1371 
1372         if (m_currentSegment->getType() != Segment::Audio) {
1373             m_errorString = "found fade out time in non audio segment";
1374             return false;
1375         }
1376 
1377         double marker = qstrtodouble(atts.value("time").toString());
1378         int sec = (int)marker;
1379         int usec = (int)((marker - ((double)sec)) * 1000000.0);
1380         RealTime markerTime(sec, usec * 1000);
1381 
1382         m_currentSegment->setFadeOutTime(markerTime);
1383         m_currentSegment->setAutoFade(true);
1384 
1385     } else if (lcName == "device") {
1386 
1387         if (m_section != InStudio) {
1388             m_errorString = "Found Device outside Studio";
1389             return false;
1390         }
1391 
1392         m_haveControls = false;
1393 
1394         QString type = (atts.value("type")).toString().toLower();
1395         QString idString = atts.value("id").toString();
1396         QString nameStr = atts.value("name").toString();
1397 
1398         if (idString.isNull()) {
1399             m_errorString = "No ID on Device tag";
1400             return false;
1401         }
1402 
1403         //int id = idString.toInt();
1404 
1405         if (type == "midi") {
1406             QString direction = atts.value("direction").toString().toLower();
1407 
1408             if (direction.isNull() ||
1409                 direction == "" ||
1410                 direction == "play") { // ignore inputs
1411 
1412                 if (!nameStr.isEmpty()) {
1413                     addMIDIDevice(nameStr, m_createDevices, "play"); // also sets m_device
1414                 }
1415             }
1416 
1417 
1418             if (direction == "record") {
1419                 if (m_device) {
1420                     if (!nameStr.isEmpty()) {
1421                         m_device->setName(qstrtostr(nameStr));
1422                     }
1423                 } else if (!nameStr.isEmpty()) {
1424                     addMIDIDevice(nameStr, m_createDevices, "record"); // also sets m_device
1425                 }
1426             }
1427 
1428             QString connection = atts.value("connection").toString();
1429             if ((m_createDevices) && (m_device) &&
1430                 !connection.isNull() && (!connection.isEmpty()) ) {
1431                 setMIDIDeviceConnection(connection);
1432             }
1433 
1434             if (m_createDevices)
1435                 setMIDIDeviceName(nameStr);
1436 
1437             QString vstr = atts.value("variation").toString().toLower();
1438             MidiDevice::VariationType variation =
1439                 MidiDevice::NoVariations;
1440             if (!vstr.isNull()) {
1441                 if (vstr == "lsb") {
1442                     variation = MidiDevice::VariationFromLSB;
1443                 } else if (vstr == "msb") {
1444                     variation = MidiDevice::VariationFromMSB;
1445                 } else if (vstr == "") {
1446                     variation = MidiDevice::NoVariations;
1447                 }
1448             }
1449             MidiDevice *md = dynamic_cast<MidiDevice *>(m_device);
1450             if (md) {
1451                 md->setVariationType(variation);
1452             }
1453         } else if (type == "softsynth") {
1454             m_device = getStudio().getSoftSynthDevice();
1455             if (m_device && m_device->getType() == Device::SoftSynth) {
1456                 m_device->setName(qstrtostr(nameStr));
1457                 m_deviceRunningId = m_device->getId();
1458                 m_deviceInstrumentBase = SoftSynthInstrumentBase;
1459                 m_deviceReadInstrumentBase = 0;
1460             }
1461         } else if (type == "audio") {
1462             m_device = getStudio().getAudioDevice();
1463             if (m_device && m_device->getType() == Device::Audio) {
1464                 m_device->setName(qstrtostr(nameStr));
1465                 m_deviceRunningId = m_device->getId();
1466                 m_deviceInstrumentBase = AudioInstrumentBase;
1467                 m_deviceReadInstrumentBase = 0;
1468             }
1469         } else {
1470             m_errorString = "Found unknown Device type";
1471             return false;
1472         }
1473 
1474     } else if (lcName == "librarian") {
1475 
1476         // The contact details for the maintainer of the banks/programs
1477         // information.
1478         //
1479         if (m_device && m_device->getType() == Device::Midi) {
1480             QString name = atts.value("name").toString();
1481             QString email = atts.value("email").toString();
1482 
1483             dynamic_cast<MidiDevice*>(m_device)->
1484             setLibrarian(qstrtostr(name), qstrtostr(email));
1485         }
1486 
1487     } else if (lcName == "bank") {
1488 
1489         if (m_device) // only if we have a device
1490         {
1491             if (m_section != InStudio && m_section != InInstrument)
1492             {
1493                 m_errorString = "Found Bank outside Studio or Instrument";
1494                 return false;
1495             }
1496 
1497             QString nameStr = atts.value("name").toString();
1498             m_percussion = (atts.value("percussion").toString().toLower() == "true");
1499             m_msb = (atts.value("msb")).toInt();
1500             m_lsb = (atts.value("lsb")).toInt();
1501 
1502             // Must account for file format <1.6.0
1503             // which would return an empty string.
1504             //
1505             // File formats <1.6.0 assume the existence of bank tag implies
1506             // send bank select should be set to true.
1507             m_sendBankSelect = !(atts.value("send").toString().toLower() == "false");
1508 
1509             // To actually create a bank
1510             //
1511             if (m_section == InStudio)
1512             {
1513                 // Create a new bank
1514                 MidiBank bank(m_percussion,
1515                               m_msb,
1516                               m_lsb,
1517                               qstrtostr(nameStr));
1518 
1519                 if (m_device->getType() == Device::Midi) {
1520                     // Insert the bank
1521                     //
1522                     dynamic_cast<MidiDevice*>(m_device)->addBank(bank);
1523                 }
1524             } else // otherwise we're referencing it in an instrument
1525                 if (m_section == InInstrument)
1526                 {
1527                     if (m_instrument) {
1528                         m_instrument->setPercussion(m_percussion);
1529                         m_instrument->setMSB(m_msb);
1530                         m_instrument->setLSB(m_lsb);
1531                         m_instrument->setSendBankSelect(m_sendBankSelect);
1532                     }
1533                 }
1534         }
1535 
1536     } else if (lcName == "program") {
1537 
1538         if (m_device) // only if we have a device
1539         {
1540             if (m_section == InStudio)
1541             {
1542                 QString nameStr = (atts.value("name").toString());
1543                 MidiByte pc = atts.value("id").toInt();
1544                 QString keyMappingStr = (atts.value("keymapping").toString());
1545 
1546                 // Create a new program
1547                 bool k = !keyMappingStr.isEmpty();
1548 
1549                 MidiProgram program
1550                 (MidiBank(m_percussion,
1551                           m_msb,
1552                           m_lsb),
1553                  pc,
1554                  qstrtostr(nameStr),
1555                  k ? qstrtostr(keyMappingStr) : "");
1556 
1557                 if (m_device->getType() == Device::Midi) {
1558                     // Insert the program
1559                     //
1560                     dynamic_cast<MidiDevice*>(m_device)->
1561                     addProgram(program);
1562                 }
1563 
1564             } else if (m_section == InInstrument)
1565             {
1566                 if (m_instrument) {
1567                     // Must account for file format <1.6.0
1568                     // which would return an empty string.
1569                     //
1570                     // File formats <1.6.0 assume the existence of bank tag implies
1571                     // send program change should be set to true.
1572                     bool sendProgramChange = !(atts.value("send").toString().toLower() == "false");
1573 
1574                     MidiByte id = atts.value("id").toInt();
1575                     m_instrument->setProgramChange(id);
1576                     m_instrument->setSendProgramChange(sendProgramChange);
1577                 }
1578             } else
1579             {
1580                 m_errorString = "Found Program outside Studio and Instrument";
1581                 return false;
1582             }
1583         }
1584 
1585     } else if (lcName == "keymapping") {
1586 
1587         if (m_section == InInstrument) {
1588             RG_DEBUG << "Old-style keymapping in instrument found, ignoring";
1589         } else {
1590 
1591             if (m_section != InStudio) {
1592                 m_errorString = "Found Keymapping outside Studio";
1593                 return false;
1594             }
1595 
1596             if (m_device && (m_device->getType() == Device::Midi)) {
1597                 QString name = atts.value("name").toString();
1598                 m_keyMapping.reset(new MidiKeyMapping(qstrtostr(name)));
1599                 m_keyNameMap.clear();
1600             }
1601         }
1602 
1603     } else if (lcName == "key") {
1604 
1605         if (m_keyMapping) {
1606             QString numStr = atts.value("number").toString();
1607             QString namStr = atts.value("name").toString();
1608             if (!numStr.isEmpty() && !namStr.isEmpty()) {
1609                 m_keyNameMap[numStr.toInt()] = qstrtostr(namStr);
1610             }
1611         }
1612 
1613     } else if (lcName == "controls") {
1614 
1615         // Only clear down the controllers list if we have found some controllers in the RG file
1616         //
1617         if (m_device) {
1618             dynamic_cast<MidiDevice*>(m_device)->clearControlList();
1619         }
1620 
1621         m_haveControls = true;
1622 
1623     } else if (lcName == "control") {
1624 
1625         if (m_section != InStudio) {
1626             m_errorString = "Found ControlParameter outside Studio";
1627             return false;
1628         }
1629 
1630         if (!m_device) {
1631             //!!! ach no, we can't give this warning -- we might be in a <device> elt
1632             // but have no sequencer support, for example.  we need a separate m_inDevice
1633             // flag
1634             //        m_deprecation = true;
1635             //        RG_WARNING << "WARNING: This Rosegarden file uses a deprecated control parameter structure.  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1636 
1637         } else if (m_device->getType() == Device::Midi) {
1638 
1639             if (!m_haveControls) {
1640                 m_errorString = "Found ControlParameter outside Controls block";
1641                 return false;
1642             }
1643 
1644             QString name = atts.value("name").toString();
1645             QString type = atts.value("type").toString();
1646             QString descr = atts.value("description").toString();
1647             QString min = atts.value("min").toString();
1648             QString max = atts.value("max").toString();
1649             QString def = atts.value("default").toString();
1650             QString controllerNumber = atts.value("controllervalue").toString();
1651             QString colour = atts.value("colourindex").toString();
1652             QString ipbPosition = atts.value("ipbposition").toString();
1653 
1654             ControlParameter con(qstrtostr(name),
1655                                  qstrtostr(type),
1656                                  qstrtostr(descr),
1657                                  min.toInt(),
1658                                  max.toInt(),
1659                                  def.toInt(),
1660                                  MidiByte(controllerNumber.toInt()),
1661                                  colour.toInt(),
1662                                  ipbPosition.toInt());
1663 
1664             // !!! Not clear whether this should propagate to
1665             // instruments.  Conservatively keeping the original
1666             // semantics, which may not be right.
1667             dynamic_cast<MidiDevice*>(m_device)->
1668                 addControlParameter(con, true);
1669         }
1670 
1671     } else if (lcName == "reverb") { // deprecated but we still read 'em
1672 
1673         if (!m_deprecation)
1674             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"reverb\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1675         m_deprecation = true;
1676 
1677         if (m_section != InInstrument) {
1678             m_errorString = "Found Reverb outside Instrument";
1679             return false;
1680         }
1681 
1682         MidiByte value = atts.value("value").toInt();
1683 
1684         if (m_instrument)
1685             m_instrument->setControllerValue(MIDI_CONTROLLER_REVERB, value);
1686 
1687 
1688     } else if (lcName == "chorus") { // deprecated but we still read 'em
1689 
1690         if (!m_deprecation)
1691             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"chorus\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1692         m_deprecation = true;
1693 
1694         if (m_section != InInstrument) {
1695             m_errorString = "Found Chorus outside Instrument";
1696             return false;
1697         }
1698 
1699         MidiByte value = atts.value("value").toInt();
1700 
1701         if (m_instrument)
1702             m_instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, value);
1703 
1704     } else if (lcName == "filter") { // deprecated but we still read 'em
1705 
1706         if (!m_deprecation)
1707             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"filter\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1708         m_deprecation = true;
1709 
1710         if (m_section != InInstrument) {
1711             m_errorString = "Found Filter outside Instrument";
1712             return false;
1713         }
1714 
1715         MidiByte value = atts.value("value").toInt();
1716 
1717         if (m_instrument)
1718             m_instrument->setControllerValue(MIDI_CONTROLLER_FILTER, value);
1719 
1720 
1721     } else if (lcName == "resonance") { // deprecated but we still read 'em
1722 
1723         if (!m_deprecation)
1724             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"resonance\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1725         m_deprecation = true;
1726 
1727         if (m_section != InInstrument) {
1728             m_errorString = "Found Resonance outside Instrument";
1729             return false;
1730         }
1731 
1732         MidiByte value = atts.value("value").toInt();
1733 
1734         if (m_instrument)
1735             m_instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, value);
1736 
1737 
1738     } else if (lcName == "attack") { // deprecated but we still read 'em
1739 
1740         if (!m_deprecation)
1741             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"attack\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1742         m_deprecation = true;
1743 
1744         if (m_section != InInstrument) {
1745             m_errorString = "Found Attack outside Instrument";
1746             return false;
1747         }
1748 
1749         MidiByte value = atts.value("value").toInt();
1750 
1751         if (m_instrument)
1752             m_instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, value);
1753 
1754     } else if (lcName == "release") { // deprecated but we still read 'em
1755 
1756         if (!m_deprecation)
1757             RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"release\" (now replaced by a control parameter).  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1758         m_deprecation = true;
1759 
1760         if (m_section != InInstrument) {
1761             m_errorString = "Found Release outside Instrument";
1762             return false;
1763         }
1764 
1765         MidiByte value = atts.value("value").toInt();
1766 
1767         if (m_instrument)
1768             m_instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, value);
1769 
1770     } else if (lcName == "pan") {
1771 
1772         if (m_section != InInstrument && m_section != InBuss) {
1773             m_errorString = "Found Pan outside Instrument or Buss";
1774             return false;
1775         }
1776 
1777         MidiByte value = atts.value("value").toInt();
1778 
1779         if (m_section == InInstrument) {
1780             if (m_instrument) {
1781                 if (m_instrument->getType() == Instrument::Midi) {
1782                     // For MIDI Instruments, hold onto this in case we don't
1783                     // run into any <controlchange> tags.
1784                     m_pan = value;
1785                     m_panEncountered = true;
1786                 } else {
1787                     // For softsynth and audio Instruments, just set the
1788                     // pan as usual.
1789                     m_instrument->setControllerValue(MIDI_CONTROLLER_PAN, value);
1790                 }
1791             }
1792         } else if (m_section == InBuss) {
1793             if (m_buss) {
1794                 m_buss->setPan(value);
1795             }
1796         }
1797 
1798         // keep "velocity" so we're backwards compatible
1799     } else if (lcName == "velocity" || lcName == "volume") {
1800 
1801         if (lcName == "velocity") {
1802             if (!m_deprecation)
1803                 RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"velocity\" for an overall MIDI instrument level (now replaced by \"volume\").  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1804             m_deprecation = true;
1805         }
1806 
1807         if (m_section != InInstrument) {
1808             m_errorString = "Found Volume outside Instrument";
1809             return false;
1810         }
1811 
1812         MidiByte value = atts.value("value").toInt();
1813 
1814         if (m_instrument) {
1815             if (m_instrument->getType() == Instrument::Midi) {
1816                 // For MIDI Instruments, hold onto this in case we don't
1817                 // run into any <controlchange> tags.
1818                 m_volume = value;
1819                 m_volumeEncountered = true;
1820             } else {
1821                 // For Audio and SoftSynth Instruments, translate into level.
1822                 // Backward compatibility: "volume" was in a 0-127
1823                 // range and we now store "level" (float dB) instead.
1824                 // Note that we have no such compatibility for
1825                 // "recordLevel", whose range has changed silently.
1826                 if (!m_deprecation)
1827                     RG_WARNING << "WARNING: This Rosegarden file uses the deprecated element \"volume\" for an audio instrument (now replaced by \"level\").  We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions";
1828                 m_deprecation = true;
1829                 m_instrument->setLevel
1830                     (AudioLevel::multiplier_to_dB(float(value) / 100.0));
1831             }
1832         }
1833 
1834     } else if (lcName == "level") {
1835 
1836         if (m_section != InBuss &&
1837                 (m_section != InInstrument ||
1838                  (m_instrument &&
1839                   m_instrument->getType() != Instrument::Audio &&
1840                   m_instrument->getType() != Instrument::SoftSynth))) {
1841             m_errorString = "Found Level outside (audio) Instrument or Buss";
1842             return false;
1843         }
1844 
1845         double value = qstrtodouble(atts.value("value").toString());
1846 
1847         if (m_section == InBuss) {
1848             if (m_buss)
1849                 m_buss->setLevel(value);
1850         } else {
1851             if (m_instrument)
1852                 m_instrument->setLevel(value);
1853         }
1854 
1855     } else if (lcName == "controlchange") {
1856 
1857         if (m_section != InInstrument) {
1858             m_errorString = "Found ControlChange outside Instrument";
1859             return false;
1860         }
1861 
1862         MidiByte type = atts.value("type").toInt();
1863         MidiByte value = atts.value("value").toInt();
1864 
1865         if (m_instrument) {
1866             m_instrument->setControllerValue(type, value);
1867         }
1868 
1869         // No need to fall back on the old <volume> and <pan> tags.
1870         m_controlChangeEncountered = true;
1871 
1872     } else if (lcName == "plugin" || lcName == "synth") {
1873 
1874         PluginContainer *container = nullptr;
1875 
1876         if (m_section == InInstrument) {
1877 //            RG_WARNING << "Found plugin in instrument";
1878             container = m_instrument;
1879             m_pluginInBuss = false;
1880         } else if (m_section == InBuss) {
1881 //            RG_WARNING << "Found plugin in buss";
1882             container = m_buss;
1883             m_pluginInBuss = true;
1884         } else {
1885             m_errorString = "Found Plugin outside Instrument or Buss";
1886             return false;
1887         }
1888 
1889         // Despite being InInstrument or InBuss we might not actually
1890         // have a valid one.
1891         //
1892         if (container) {
1893 
1894 //            RG_WARNING << "Have container";
1895 
1896             if (m_progressDialog)
1897                 m_progressDialog->setLabelText(tr("Loading plugins..."));
1898 
1899             qApp->processEvents(QEventLoop::AllEvents, 100);
1900 
1901             // Get the details
1902             int position;
1903             if (lcName == "synth") {
1904                 position = Instrument::SYNTH_PLUGIN_POSITION;
1905             } else {
1906                 position = atts.value("position").toInt();
1907             }
1908 
1909             bool bypassed = false;
1910             QString bpStr = atts.value("bypassed").toString();
1911             if (bpStr.toLower() == "true")
1912                 bypassed = true;
1913 
1914             std::string program = "";
1915             QString progStr = atts.value("program").toString();
1916             if (!progStr.isEmpty()) {
1917                 program = qstrtostr(progStr);
1918             }
1919 
1920             // Plugins are identified by a structured identifier
1921             // string, but we will accept a LADSPA UniqueId if there's
1922             // no identifier, for backward compatibility
1923 
1924             QString identifier = atts.value("identifier").toString();
1925 
1926             QSharedPointer<AudioPlugin> plugin;
1927             QSharedPointer<AudioPluginManager> apm = getAudioPluginManager();
1928 
1929             if ( identifier.isEmpty() ) {
1930                 QString q = atts.value("id").toString();
1931                 if (!q.isEmpty()) {
1932                     unsigned long id = atts.value("id").toULong();
1933                     if (apm)
1934                         plugin = apm->getPluginByUniqueId(id);
1935                 }
1936             } else {
1937                 if (apm)
1938                     plugin = apm->getPluginByIdentifier(identifier);
1939             }
1940 
1941             RG_DEBUG << "Plugin identifier " << identifier << " -> plugin " << plugin;
1942 
1943             // If we find the plugin all is well and good but if
1944             // we don't we just skip it.
1945             //
1946             if (plugin) {
1947                 m_plugin = container->getPlugin(position);
1948                 if (!m_plugin) {
1949                     RG_DEBUG << "WARNING: RoseXmlHandler: instrument/buss "
1950                     << container->getId() << " has no plugin position "
1951                     << position;
1952                 } else {
1953                     m_plugin->setAssigned(true);
1954                     m_plugin->setBypass(bypassed);
1955                     m_plugin->setIdentifier( qstrtostr( plugin->getIdentifier() ) );
1956                     RG_DEBUG << "set identifier to plugin at position " << position << " of container " << container->getId();
1957                     if (program != "") {
1958                         m_plugin->setProgram(program);
1959                     }
1960                 }
1961             } else {
1962                 // we shouldn't be halting import of the RG file just because
1963                 // we can't match a plugin
1964                 //
1965                 QString q = atts.value("id").toString();
1966 
1967                 if (!identifier.isEmpty()) {
1968                     RG_DEBUG << "WARNING: RoseXmlHandler: plugin " << identifier << " not found";
1969                     m_pluginsNotFound.insert(identifier);
1970                 } else if (!q.isEmpty()) {
1971                     RG_DEBUG << "WARNING: RoseXmlHandler: plugin uid " << atts.value("id") << " not found";
1972                 } else {
1973                     m_errorString = "No plugin identifier or uid specified";
1974                     return false;
1975                 }
1976             }
1977         } else { // no instrument
1978 
1979             if (lcName == "synth") {
1980                 QString identifier = atts.value("identifier").toString();
1981                 if (!identifier.isEmpty()) {
1982                     RG_DEBUG << "WARNING: RoseXmlHandler: no instrument for plugin " << identifier;
1983                     m_pluginsNotFound.insert(identifier);
1984                 }
1985             }
1986         }
1987 
1988         m_section = InPlugin;
1989 
1990     } else if (lcName == "port") {
1991 
1992         if (m_section != InPlugin) {
1993             m_errorString = "Found Port outside Plugin";
1994             return false;
1995         }
1996         unsigned long portId = atts.value("id").toULong();
1997         double value = qstrtodouble(atts.value("value").toString());
1998 
1999         QString changed = atts.value("changed").toString();
2000         bool changedSinceProgram = (changed == "true");
2001 
2002         if (m_plugin) {
2003             m_plugin->addPort(portId, value);
2004             if (changedSinceProgram) {
2005                 PluginPortInstance *ppi = m_plugin->getPort(portId);
2006                 if (ppi)
2007                     ppi->changedSinceProgramChange = true;
2008             }
2009         }
2010 
2011     } else if (lcName == "configure") {
2012 
2013         if (m_section != InPlugin) {
2014             m_errorString = "Found Configure outside Plugin";
2015             return false;
2016         }
2017 
2018         QString key = atts.value("key").toString();
2019         QString value = atts.value("value").toString();
2020 
2021         if (m_plugin) {
2022             m_plugin->setConfigurationValue(qstrtostr(key), qstrtostr(value));
2023         }
2024 
2025     } else if (lcName == "metronome") {
2026 
2027         if (m_section != InStudio) {
2028             m_errorString = "Found Metronome outside Studio";
2029             return false;
2030         }
2031 
2032         // Only create if we have a device
2033         //
2034         if (m_device &&
2035             (m_device->getType() == Device::Midi ||
2036              m_device->getType() == Device::SoftSynth)) {
2037 
2038             // We will map this to an "actual" instrument ID at the
2039             // end, when we do the same for the track->instrument
2040             // references
2041             InstrumentId instrument = atts.value("instrument").toInt();
2042 
2043             MidiMetronome metronome(instrument);
2044 
2045             QString q = atts.value("barpitch").toString();
2046             if (!q.isEmpty())
2047                 metronome.setBarPitch(atts.value("barpitch").toInt());
2048 
2049             q = atts.value("beatpitch").toString();
2050             if (!q.isEmpty())
2051                 metronome.setBeatPitch(atts.value("beatpitch").toInt());
2052 
2053             q = atts.value("subbeatpitch").toString();
2054             if (!q.isEmpty())
2055                 metronome.setSubBeatPitch(atts.value("subbeatpitch").toInt());
2056 
2057             q = atts.value("depth").toString();
2058             if (!q.isEmpty())
2059                 metronome.setDepth(atts.value("depth").toInt());
2060 
2061             q = atts.value("barvelocity").toString();
2062             if (!q.isEmpty())
2063                 metronome.setBarVelocity(atts.value("barvelocity").toInt());
2064 
2065             q = atts.value("beatvelocity").toString();
2066             if (!q.isEmpty())
2067                 metronome.setBeatVelocity(atts.value("beatvelocity").toInt());
2068 
2069             q = atts.value("subbeatvelocity").toString();
2070             if (!q.isEmpty())
2071                 metronome.setSubBeatVelocity(atts.value("subbeatvelocity").toInt());
2072 
2073             MidiDevice *md = dynamic_cast<MidiDevice *>(m_device);
2074             if (md) md->setMetronome(metronome);
2075 
2076             SoftSynthDevice *ssd = dynamic_cast<SoftSynthDevice *>(m_device);
2077             if (ssd) ssd->setMetronome(metronome);
2078         }
2079 
2080     } else if (lcName == "instrument") {
2081 
2082         if (m_section != InStudio) {
2083             m_errorString = "Found Instrument outside Studio";
2084             return false;
2085         }
2086 
2087         m_section = InInstrument;
2088 
2089         m_controlChangeEncountered = false;
2090         m_volumeEncountered = false;
2091         m_volume = 100;  // A nominal default.
2092         m_panEncountered = false;
2093         m_pan = 64;  // A nominal default.
2094 
2095         InstrumentId id = mapToActualInstrument(atts.value("id").toInt());
2096 
2097         std::string stringType = qstrtostr(atts.value("type").toString());
2098         Instrument::InstrumentType type;
2099 
2100         if (stringType == "midi")
2101             type = Instrument::Midi;
2102         else if (stringType == "audio")
2103             type = Instrument::Audio;
2104         else if (stringType == "softsynth")
2105             type = Instrument::SoftSynth;
2106         else {
2107             m_errorString = "Found unknown Instrument type";
2108             return false;
2109         }
2110 
2111         // Try and match an Instrument in the file with one in
2112         // our studio
2113         //
2114         Instrument *instrument = getStudio().getInstrumentById(id);
2115 
2116         RG_DEBUG << "Found Instrument in document: mapped actual id " << id << " to instrument " << instrument;
2117 
2118         // If we've got an instrument and the types match then
2119         // we use it from now on.
2120         //
2121         if (instrument && instrument->getType() == type) {
2122             m_instrument = instrument;
2123 
2124             // Synth and Audio instruments always have the channel set to 2.
2125             // Preserve this.
2126             MidiByte channel = (MidiByte)atts.value("channel").toInt();
2127 
2128             m_instrument->setNaturalChannel(channel);
2129 
2130             if (type == Instrument::Midi) {
2131                 if (atts.value("fixed").toString() == "false")
2132                     m_instrument->releaseFixedChannel();
2133                 else
2134                     m_instrument->setFixedChannel();
2135             }
2136         }
2137 
2138     } else if (lcName == "buss") {
2139 
2140         if (m_section != InStudio) {
2141             m_errorString = "Found Buss outside Studio";
2142             return false;
2143         }
2144 
2145         m_section = InBuss;
2146 
2147         BussId id = atts.value("id").toInt();
2148         Buss *buss = getStudio().getBussById(id);
2149 
2150         // If we've got a buss then we use it from now on.
2151         //
2152         if (buss) {
2153             m_buss = buss;
2154         } else {
2155             m_buss = new Buss(id);
2156             getStudio().addBuss(m_buss);
2157         }
2158 
2159     } else if (lcName == "audiofiles") {
2160 
2161         if (m_section != NoSection) {
2162             m_errorString = "Found AudioFiles inside another section";
2163             return false;
2164         }
2165 
2166         m_section = InAudioFiles;
2167 
2168         int rate = atts.value("expectedRate").toInt();
2169         if (rate) {
2170             getAudioFileManager().setExpectedSampleRate(rate);
2171         }
2172 
2173     } else if (lcName == "configuration") {
2174 
2175         setSubHandler(new ConfigurationXmlSubHandler
2176                       (lcName, &m_doc->getConfiguration()));
2177 
2178     } else if (lcName == "metadata") {
2179 
2180         if (m_section != InComposition) {
2181             m_errorString = "Found Metadata outside Composition";
2182             return false;
2183         }
2184 
2185         setSubHandler(new ConfigurationXmlSubHandler
2186                       (lcName, &getComposition().getMetadata()));
2187 
2188     } else if (lcName == "recordlevel") {
2189 
2190         if (m_section != InInstrument) {
2191             m_errorString = "Found recordLevel outside Instrument";
2192             return false;
2193         }
2194 
2195         double value = qstrtodouble(atts.value("value").toString());
2196 
2197         // if the value retrieved is greater than (say) 15 then we
2198         // must have an old-style 0-127 value instead of a shiny new
2199         // dB value, so convert it
2200         if (value > 15.0) {
2201             value = AudioLevel::multiplier_to_dB(value / 100);
2202         }
2203 
2204         if (m_instrument)
2205             m_instrument->setRecordLevel(value);
2206 
2207     } else if (lcName == "alias") {
2208 
2209         if (m_section != InInstrument) {
2210             m_errorString = "Found alias outside Instrument";
2211             return false;
2212         }
2213         if (m_instrument) {
2214             QString alias = atts.value("value").toString();
2215             m_instrument->setAlias(alias.toStdString());
2216         }
2217 
2218     } else if (lcName == "audioinput") {
2219 
2220         if (m_section != InInstrument) {
2221             m_errorString = "Found audioInput outside Instrument";
2222             return false;
2223         }
2224 
2225         int value = atts.value("value").toInt();
2226         int channel = atts.value("channel").toInt();
2227 
2228         QString type = atts.value("type").toString();
2229         if (!type.isEmpty()) {
2230             if (type.toLower() == "buss") {
2231                 if (m_instrument)
2232                     m_instrument->setAudioInputToBuss(value, channel);
2233             } else if (type.toLower() == "record") {
2234                 if (m_instrument)
2235                     m_instrument->setAudioInputToRecord(value, channel);
2236             }
2237         }
2238 
2239     } else if (lcName == "audiooutput") {
2240 
2241         if (m_section != InInstrument) {
2242             m_errorString = "Found audioOutput outside Instrument";
2243             return false;
2244         }
2245 
2246         int value = atts.value("value").toInt();
2247         if (m_instrument)
2248             m_instrument->setAudioOutput(value);
2249 
2250     } else if (lcName == "appearance") {
2251 
2252         m_section = InAppearance;
2253 
2254     } else if (lcName == "colourmap") {
2255 
2256         if (m_section == InAppearance) {
2257             QString mapName = atts.value("name").toString();
2258             m_inColourMap = true;
2259             if (mapName == "segmentmap") {
2260                 m_colourMap = &m_doc->getComposition().getSegmentColourMap();
2261             } else
2262                 if (mapName == "generalmap") {
2263                     m_colourMap = &m_doc->getComposition().getGeneralColourMap();
2264                 } else { // This will change later once we get more of the Appearance code sorted out
2265                     RG_DEBUG << "RoseXmlHandler::startElement : Found colourmap with unknown name\n";
2266                 }
2267         } else {
2268             m_errorString = "Found colourmap outside Appearance";
2269             return false;
2270         }
2271 
2272     } else if (lcName == "colourpair") {
2273 
2274         if (m_inColourMap && m_colourMap) {
2275             unsigned int id = atts.value("id").toInt();
2276             QString name = atts.value("name").toString();
2277             unsigned int red = atts.value("red").toInt();
2278             unsigned int blue = atts.value("blue").toInt();
2279             unsigned int green = atts.value("green").toInt();
2280             QColor colour(red, green, blue);
2281             m_colourMap->colours[id] =
2282                     ColourMap::Entry(colour, qstrtostr(name));
2283         } else {
2284             m_errorString = "Found colourpair outside ColourMap";
2285             return false;
2286         }
2287 
2288     } else if (lcName == "markers") {
2289 
2290         if (!m_inComposition) {
2291             m_errorString = "Found Markers outside Composition";
2292             return false;
2293         }
2294 
2295         // clear down any markers
2296         getComposition().clearMarkers();
2297 
2298     } else if (lcName == "marker") {
2299         if (!m_inComposition) {
2300             m_errorString = "Found Marker outside Composition";
2301             return false;
2302         }
2303         int time = atts.value("time").toInt();
2304         QString name = atts.value("name").toString();
2305         QString descr = atts.value("description").toString();
2306 
2307         Marker *marker =
2308             new Marker(time,
2309                        qstrtostr(name),
2310                        qstrtostr(descr));
2311 
2312         getComposition().addMarker(marker);
2313     } else {
2314         RG_DEBUG << "RoseXmlHandler::startElement : Don't know how to parse this : " << qName;
2315     }
2316 
2317     return true;
2318 }
2319 
2320 bool
endElement(const QString & namespaceURI,const QString & localName,const QString & qName)2321 RoseXmlHandler::endElement(const QString& namespaceURI,
2322                            const QString& localName,
2323                            const QString& qName)
2324 {
2325     if (getSubHandler()) {
2326         bool finished;
2327         bool res = getSubHandler()->endElement(namespaceURI, localName, qName.toLower(), finished);
2328         if (finished)
2329             setSubHandler(nullptr);
2330         return res;
2331     }
2332 
2333     // Set percentage done
2334     //
2335     if ((m_totalElements > m_elementsSoFar) &&
2336         (++m_elementsSoFar % 300 == 0)) {
2337 
2338         if (m_progressDialog) {
2339             // If the user cancelled, bail.
2340             if (m_progressDialog->wasCanceled())
2341                 return false;
2342 
2343             m_progressDialog->setValue(static_cast<int>(
2344                     static_cast<double>(m_elementsSoFar) /
2345                     static_cast<double>(m_totalElements) * 100.0));
2346         }
2347 
2348         // Kick the event loop so that we don't appear to be in
2349         // an endless loop.
2350         qApp->processEvents(QEventLoop::AllEvents, 100);
2351     }
2352 
2353     QString lcName = qName.toLower();
2354 
2355     if (lcName == "rosegarden-data") {
2356 
2357         Composition &comp = getComposition();
2358 
2359         // Remap all the instrument IDs in track and metronome objects
2360         // from "file" to "actual" IDs.  See discussion in
2361         // mapToActualInstrument() below.
2362 
2363         for (Composition::trackcontainer::iterator i = comp.getTracks().begin();
2364              i != comp.getTracks().end(); ++i) {
2365             InstrumentId iid = i->second->getInstrument();
2366             InstrumentId aid = mapToActualInstrument(iid);
2367             RG_DEBUG << "RoseXmlHandler: mapping instrument " << iid
2368                      << " to " << aid << " for track " << i->first;
2369             i->second->setInstrument(aid);
2370         }
2371 
2372         Studio &studio = getStudio();
2373         for (DeviceList::iterator i = studio.getDevices()->begin();
2374              i != studio.getDevices()->end(); ++i) {
2375             MidiMetronome mm(0);
2376             MidiDevice *md = dynamic_cast<MidiDevice *>(*i);
2377             SoftSynthDevice *sd = dynamic_cast<SoftSynthDevice *>(*i);
2378             if (md && md->getMetronome()) mm = *md->getMetronome();
2379             else if (sd && sd->getMetronome()) mm = *sd->getMetronome();
2380             else continue;
2381             InstrumentId iid = mm.getInstrument();
2382             InstrumentId aid = mapToActualInstrument(iid);
2383             RG_DEBUG << "RoseXmlHandler: mapping instrument " << iid
2384                      << " to " << aid << " for metronome";
2385             if (md) md->setMetronome(mm);
2386             else if (sd) sd->setMetronome(mm);
2387         }
2388 
2389         comp.updateTriggerSegmentReferences();
2390 
2391     } else if (lcName == "event") {
2392 
2393         if (m_currentSegment && m_currentEvent) {
2394             m_currentSegment->insert(m_currentEvent);
2395             m_currentEvent = nullptr;
2396         } else if (!m_currentSegment && m_currentEvent) {
2397             m_errorString = "Got event outside of a Segment";
2398             return false;
2399         }
2400 
2401     } else if (lcName == "chord") {
2402 
2403         m_currentTime += m_chordDuration;
2404         m_inChord = false;
2405         m_chordDuration = 0;
2406 
2407     } else if (lcName == "group") {
2408 
2409         m_inGroup = false;
2410 
2411     } else if (lcName == "segment") {
2412 
2413         if (m_currentSegment && m_segmentEndMarkerTime) {
2414             m_currentSegment->setEndMarkerTime(*m_segmentEndMarkerTime);
2415 
2416             // If the segment is zero or negative duration
2417             if (m_currentSegment->getEndMarkerTime() <=
2418                     m_currentSegment->getStartTime()) {
2419                 // Make it stick out so the user can take care of it.
2420                 m_currentSegment->setEndMarkerTime(
2421                     m_currentSegment->getStartTime() +
2422                         Note(Note::Shortest).getDuration());
2423             }
2424 
2425             delete m_segmentEndMarkerTime;
2426             m_segmentEndMarkerTime = nullptr;
2427         }
2428 
2429         m_currentSegment = nullptr;
2430         m_section = NoSection;
2431 
2432     } else if (lcName == "bar-segment" || lcName == "tempo-segment") {
2433 
2434         m_currentSegment = nullptr;
2435 
2436     } else if (lcName == "composition") {
2437         m_inComposition = false;
2438         m_section = NoSection;
2439 
2440     } else if (lcName == "studio") {
2441 
2442         m_section = NoSection;
2443 
2444     } else if (lcName == "buss") {
2445 
2446         m_section = InStudio;
2447         m_buss = nullptr;
2448 
2449     } else if (lcName == "instrument") {
2450 
2451         // If there were no <controlchange> tags within the <instrument>.
2452         if (!m_controlChangeEncountered) {
2453             // Fall back on the deprecated <volume> and <pan> tags if they
2454             // are available.
2455 
2456             // This only happens with MIDI instruments.  Audio instruments
2457             // are taken care of immediately when their <pan> and <level>
2458             // tags are encountered.
2459 
2460             if (m_volumeEncountered) {
2461                 MidiDevice *midiDevice = dynamic_cast<MidiDevice *>(m_device);
2462                 if (midiDevice) {
2463                     // If volume has a knob in the MIPP, go ahead and set the
2464                     // CC value in the Instrument.  (If we do this when there
2465                     // is no knob, CCs go out when they shouldn't.)
2466                     if (midiDevice->isVisibleControlParameter(
2467                             MIDI_CONTROLLER_VOLUME)) {
2468                         m_instrument->setControllerValue(
2469                                 MIDI_CONTROLLER_VOLUME, m_volume);
2470                     }
2471                 }
2472             }
2473 
2474             if (m_panEncountered) {
2475                 MidiDevice *midiDevice = dynamic_cast<MidiDevice *>(m_device);
2476                 if (midiDevice) {
2477                     // If pan has a knob in the MIPP, go ahead and set the
2478                     // CC value in the Instrument.  (If we do this when there
2479                     // is no knob, CCs go out when they shouldn't.)
2480                     if (midiDevice->isVisibleControlParameter(
2481                             MIDI_CONTROLLER_PAN)) {
2482                         m_instrument->setControllerValue(
2483                                 MIDI_CONTROLLER_PAN, m_pan);
2484                     }
2485                 }
2486             }
2487         }
2488 
2489         // Exit the <instrument> section.
2490         m_section = InStudio;
2491         m_instrument = nullptr;
2492 
2493     } else if (lcName == "plugin") {
2494 
2495         if (m_pluginInBuss) {
2496             m_section = InBuss;
2497         } else {
2498             m_section = InInstrument;
2499         }
2500         m_plugin = nullptr;
2501         m_pluginId = 0;
2502 
2503     } else if (lcName == "device") {
2504 
2505         m_device = nullptr;
2506 
2507     } else if (lcName == "keymapping") {
2508 
2509         if (m_section == InStudio) {
2510             if (m_keyMapping) {
2511                 if (!m_keyNameMap.empty()) {
2512                     MidiDevice *md = dynamic_cast<MidiDevice *>
2513                                      (m_device);
2514                     if (md) {
2515                         m_keyMapping->setMap(m_keyNameMap);
2516                         md->addKeyMapping(*m_keyMapping);
2517                     }
2518                 }
2519 
2520                 m_keyMapping.reset();
2521             }
2522         }
2523 
2524     } else if (lcName == "audiofiles") {
2525 
2526         m_section = NoSection;
2527 
2528     } else if (lcName == "appearance") {
2529 
2530         m_section = NoSection;
2531 
2532     } else if (lcName == "colourmap") {
2533         m_inColourMap = false;
2534         m_colourMap = nullptr;
2535     } else if (lcName == "matrix") {
2536         m_inMatrix = false;
2537     } else if (lcName == "notation") {
2538         m_inNotation = false;
2539     }
2540 
2541     return true;
2542 }
2543 
2544 bool
characters(const QString & s)2545 RoseXmlHandler::characters(const QString& s)
2546 {
2547     if (m_subHandler)
2548         return m_subHandler->characters(s);
2549 
2550     return true;
2551 }
2552 
2553 QString
errorString() const2554 RoseXmlHandler::errorString() const
2555 {
2556     return m_errorString;
2557 }
2558 
2559 bool
fatalError(int lineNumber,int columnNumber,const QString & msg)2560 RoseXmlHandler::fatalError(int lineNumber, int columnNumber,
2561                            const QString& msg)
2562 {
2563     m_errorString = QString("%1 at line %2, column %3")
2564                     .arg(msg)
2565                     .arg(lineNumber)
2566                     .arg(columnNumber);
2567     return false;
2568 }
2569 
2570 bool
endDocument()2571 RoseXmlHandler::endDocument()
2572 {
2573     if (m_foundTempo == false) {
2574         getComposition().setCompositionDefaultTempo
2575         (Composition::getTempoForQpm(120.0));
2576     }
2577 
2578     return true;
2579 }
2580 
2581 void
setSubHandler(XmlSubHandler * sh)2582 RoseXmlHandler::setSubHandler(XmlSubHandler* sh)
2583 {
2584     delete m_subHandler;
2585     m_subHandler = sh;
2586 }
2587 
2588 void
addMIDIDevice(QString name,bool createAtSequencer,QString dir)2589 RoseXmlHandler::addMIDIDevice(QString name, bool createAtSequencer, QString dir)
2590 {
2591     /**
2592     *   params:
2593     *   QString name           : device name
2594     *   bool createAtSequencer : normally true
2595     *   QString dir            : direction "play" or "record"
2596     **/
2597 
2598     unsigned int deviceId = 0;
2599 
2600     MidiDevice::DeviceDirection devDir;
2601 
2602     if (dir == "play") {
2603         devDir = MidiDevice::Play;
2604     } else if (dir == "record") {
2605         devDir = MidiDevice::Record;
2606     } else {
2607         RG_WARNING << "Error: Device direction \"" << dir
2608                   << "\" invalid in RoseXmlHandler::addMIDIDevice()";
2609         return;
2610     }
2611 
2612     InstrumentId instrumentBase;
2613     deviceId = getStudio().getSpareDeviceId(instrumentBase);
2614 
2615     if (createAtSequencer) {
2616         if (!RosegardenSequencer::getInstance()->
2617             addDevice(Device::Midi, deviceId, instrumentBase, devDir)) {
2618             RG_DEBUG << "addMIDIDevice() - sequencer addDevice failed";
2619             return;
2620         }
2621 
2622         RG_DEBUG << "addMIDIDevice() - "
2623                      << " added device " << deviceId
2624                      << " with instrument base " << instrumentBase
2625                      << " at sequencer";
2626     }
2627 
2628     getStudio().addDevice(qstrtostr(name), deviceId,
2629                           instrumentBase, Device::Midi);
2630     m_device = getStudio().getDevice(deviceId);
2631     if (m_device) {
2632         MidiDevice *md = dynamic_cast<MidiDevice *>(m_device);
2633         if (md) md->setDirection(devDir);
2634     }
2635 
2636     RG_DEBUG << "addMIDIDevice() - "
2637                  << " added device " << deviceId
2638                  << " with instrument base " << instrumentBase
2639                  << " in studio";
2640 
2641     m_deviceRunningId = deviceId;
2642     m_deviceInstrumentBase = instrumentBase;
2643     m_deviceReadInstrumentBase = 0;
2644 }
2645 
2646 InstrumentId
mapToActualInstrument(InstrumentId oldId)2647 RoseXmlHandler::mapToActualInstrument(InstrumentId oldId)
2648 {
2649     /*
2650       When we read a device from the file, we want to be able to add
2651       it to the studio and the sequencer immediately.  But to do so,
2652       we (now) need to be able to provide a base instrument number for
2653       the device.  We can make up a plausible one (we know what type
2654       of device it is, and that's the main determining factor) but we
2655       can't know for sure whether it's the same one as used in the
2656       file until we have continued and read the first instrument
2657       definition in the device.
2658 
2659       Device and instrument numbers in the file have always been
2660       problematic in other ways as well.  Rosegarden actually never
2661       used the device numbers loaded from the file (we always made up
2662       a new device number for each device as we read it) and it was
2663       theoretically possible for the instrument definitions to end up
2664       attached to different devices from the ones they were hung onto
2665       in the file (because of the way we sometimes re-used instruments
2666       that already existed in the studio).
2667 
2668       Our new approach is to assume that we will _never_ believe the
2669       device or instrument numbers found in the file, except for the
2670       purposes of resolving internal references within the file.  (The
2671       main examples of these are track -> instrument mappings outside
2672       of the studio definition, and metronome -> instrument mappings
2673       within a device.)  Instead, we assign a new device ID to each
2674       device based on its type and the available spare IDs; we assign
2675       a new instrument ID to each instrument based on the device type;
2676       and we maintain a map from "file" to "actual" instrument IDs as
2677       we go along, using this to resolve the track -> instrument and
2678       metronome -> instrument references at the end of the file (see
2679       the endElement method for rosegarden-data element).
2680     */
2681 
2682     /*
2683       This function is first called for any given instrument when
2684       first reading that instrument's definition in the context of a
2685       device definition.  So we know that if the instrument is not in
2686       the map already, then m_deviceInstrumentBase must contain a
2687       valid instrument base ID that was set when we added the device.
2688 
2689       To map from the number we just read, we subtract
2690       m_deviceReadInstrumentBase (if it exists; otherwise we set it as
2691       this is the first instrument for this device) and add
2692       m_deviceInstrumentBase.
2693     */
2694 
2695     if (m_actualInstrumentIdMap.find(oldId) != m_actualInstrumentIdMap.end()) {
2696         return m_actualInstrumentIdMap[oldId];
2697     }
2698 
2699     InstrumentId id = oldId;
2700 
2701     // here be dark wartortles
2702 
2703     if (m_deviceReadInstrumentBase == 0 || id < m_deviceReadInstrumentBase) {
2704         m_deviceReadInstrumentBase = id;
2705     }
2706     id = id - m_deviceReadInstrumentBase;
2707     id = id + m_deviceInstrumentBase;
2708 
2709     RG_DEBUG << "RoseXmlHandler::mapToActualInstrument: instrument " << oldId
2710              << ", dev read base " << m_deviceReadInstrumentBase
2711              << ", dev base " << m_deviceInstrumentBase << " -> " << id;
2712 
2713     m_actualInstrumentIdMap[oldId] = id;
2714 
2715     return id;
2716 }
2717 
2718 void
skipToNextPlayDevice()2719 RoseXmlHandler::skipToNextPlayDevice()
2720 {
2721     RG_DEBUG << "skipToNextPlayDevice(): m_deviceRunningId is " << m_deviceRunningId;
2722 
2723     for (DeviceList::iterator i = getStudio().getDevices()->begin();
2724             i != getStudio().getDevices()->end(); ++i) {
2725 
2726         MidiDevice *md = dynamic_cast<MidiDevice *>(*i);
2727 
2728         if (md && md->getDirection() == MidiDevice::Play) {
2729             if (m_deviceRunningId == Device::NO_DEVICE ||
2730                 md->getId() > m_deviceRunningId) {
2731 
2732                 RG_DEBUG << "skipToNextPlayDevice(): found next device: id " << md->getId();
2733 
2734                 m_device = md;
2735                 m_deviceRunningId = md->getId();
2736                 return ;
2737             }
2738         }
2739     }
2740 
2741     RG_DEBUG << "skipToNextPlayDevice(): fresh out of devices";
2742 
2743     m_device = nullptr;
2744 }
2745 
2746 void
setMIDIDeviceConnection(QString connection)2747 RoseXmlHandler::setMIDIDeviceConnection(QString connection)
2748 {
2749     RG_DEBUG << "setMIDIDeviceConnection(" << connection << ")";
2750 
2751     MidiDevice *midiDevice = dynamic_cast<MidiDevice *>(m_device);
2752     if (!midiDevice)
2753         return;
2754 
2755     RosegardenSequencer::getInstance()->setPlausibleConnection(
2756             midiDevice->getId(), connection);
2757 
2758     midiDevice->setUserConnection(qstrtostr(connection));
2759 
2760     // ??? This might not be the actual connection.  I'm guessing
2761     //     Studio::resyncDeviceConnections() is called at some point
2762     //     and this is corrected.
2763     midiDevice->setCurrentConnection(qstrtostr(connection));
2764 }
2765 
2766 void
setMIDIDeviceName(QString name)2767 RoseXmlHandler::setMIDIDeviceName(QString name)
2768 {
2769     RG_DEBUG << "setMIDIDeviceName(" << name << ")";
2770 
2771     MidiDevice *md = dynamic_cast<MidiDevice *>(m_device);
2772     if (!md) return;
2773 
2774     RosegardenSequencer::getInstance()->renameDevice
2775         (md->getId(), name);
2776 }
2777 
2778 }
2779