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