1 //=========================================================
2 // MusE
3 // Linux Music Editor
4 // $Id: songfile.cpp,v 1.25.2.12 2009/11/04 15:06:07 spamatica Exp $
5 //
6 // (C) Copyright 1999/2000 Werner Schweer (ws@seh.de)
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; version 2 of
11 // the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 //
22 //=========================================================
23
24 #include <map>
25
26 #include <QUuid>
27 #include <QProgressDialog>
28 #include <QMessageBox>
29 #include <QCheckBox>
30 #include <QString>
31
32 #include "app.h"
33 #include "song.h"
34 #include "arranger.h"
35 #include "arrangerview.h"
36 #include "cobject.h"
37 #include "drumedit.h"
38 #include "pianoroll.h"
39 #include "scoreedit.h"
40 #include "globals.h"
41 #include "xml.h"
42 #include "drummap.h"
43 #include "drum_ordering.h"
44 #include "event.h"
45 #include "marker/marker.h"
46 #include "midiport.h"
47 #include "audio.h"
48 #include "mitplugin.h"
49 #include "wave.h"
50 #include "midictrl.h"
51 #include "audiodev.h"
52 #include "conf.h"
53 #include "keyevent.h"
54 #include "gconfig.h"
55 #include "config.h"
56
57 namespace MusEGlobal {
58 MusECore::CloneList cloneList;
59 }
60
61 namespace MusECore {
62
63
64 //---------------------------------------------------------
65 // NKey::write
66 //---------------------------------------------------------
67
write(int level,Xml & xml) const68 void NKey::write(int level, Xml& xml) const
69 {
70 xml.intTag(level, "key", val);
71 }
72
73 //---------------------------------------------------------
74 // NKey::read
75 //---------------------------------------------------------
76
read(Xml & xml)77 void NKey::read(Xml& xml)
78 {
79 for (;;) {
80 Xml::Token token = xml.parse();
81 switch (token) {
82 case Xml::Error:
83 case Xml::End:
84 return;
85 case Xml::Text:
86 val = xml.s1().toInt();
87 break;
88 case Xml::TagEnd:
89 if (xml.s1() == "key")
90 return;
91 default:
92 break;
93 }
94 }
95 }
96
97
98 //---------------------------------------------------------
99 // Scale::write
100 //---------------------------------------------------------
101
write(int level,Xml & xml) const102 void Scale::write(int level, Xml& xml) const
103 {
104 xml.intTag(level, "scale", val);
105 }
106
107 //---------------------------------------------------------
108 // Scale::read
109 //---------------------------------------------------------
110
read(Xml & xml)111 void Scale::read(Xml& xml)
112 {
113 for (;;) {
114 Xml::Token token = xml.parse();
115 switch (token) {
116 case Xml::Error:
117 case Xml::End:
118 return;
119 case Xml::Text:
120 val = xml.s1().toInt();
121 break;
122 case Xml::TagEnd:
123 if (xml.s1() == "scale")
124 return;
125 default:
126 break;
127 }
128 }
129 }
130
131 //---------------------------------------------------------
132 // Part::readFromXml
133 //---------------------------------------------------------
134
readFromXml(Xml & xml,Track * track,bool doClone,bool toTrack)135 Part* Part::readFromXml(Xml& xml, Track* track, bool doClone, bool toTrack)
136 {
137 int id = -1;
138 Part* npart = nullptr;
139 QUuid uuid;
140 bool uuidvalid = false;
141 bool clone = true;
142 bool wave = false;
143 bool isclone = false;
144
145 for (;;) {
146 Xml::Token token = xml.parse();
147 const QString& tag = xml.s1();
148 switch (token) {
149 case Xml::Error:
150 case Xml::End:
151 return npart;
152 case Xml::TagStart:
153 if(!npart) // If the part has not been created yet...
154 {
155 if(id != -1) // If an id was found...
156 {
157 for(iClone i = MusEGlobal::cloneList.begin(); i != MusEGlobal::cloneList.end(); ++i)
158 {
159 if(i->is_deleted) // Is the clone item marked as deleted? Ignore it.
160 continue;
161 if(i->id == id) // Is a matching part found in the clone list?
162 {
163 // Create a clone. It must still be added later in a operationgroup
164 npart = track->newPart((Part*)i->cp, true);
165 break;
166 }
167 }
168 }
169 else if(uuidvalid) // If a uuid was found...
170 {
171 for(iClone i = MusEGlobal::cloneList.begin(); i != MusEGlobal::cloneList.end(); ++i)
172 {
173 if(uuid == i->_uuid) // Is a matching part found in the clone list?
174 {
175 Track* cpt = i->cp->track();
176 if(toTrack) // If we want to paste to the given track...
177 {
178 // If the given track type is not the same as the part's
179 // original track type, we can't continue. Just return.
180 if(!track || cpt->type() != track->type())
181 {
182 xml.skip("part");
183 return nullptr;
184 }
185 }
186 else // ...else we want to paste to the part's original track.
187 {
188 // Make sure the track exists (has not been deleted).
189 if((cpt->isMidiTrack() && MusEGlobal::song->midis()->find(cpt) != MusEGlobal::song->midis()->end()) ||
190 (cpt->type() == Track::WAVE && MusEGlobal::song->waves()->find(cpt) != MusEGlobal::song->waves()->end()))
191 track = cpt;
192 else // Track was not found. Try pasting to the given track, as above...
193 {
194 if(!track || cpt->type() != track->type())
195 {
196 // No luck. Just return.
197 xml.skip("part");
198 return nullptr;
199 }
200 }
201 }
202
203 if(i->is_deleted) // Is the clone item marked as deleted? Don't create a clone, create a copy.
204 break;
205
206 // If it's a regular paste (not paste clone), and the original part is
207 // not a clone, defer so that a new copy is created in TagStart above.
208 if(!doClone && !isclone)
209 break;
210
211 // Create a clone. It must still be added later in a operationgroup
212 npart = track->newPart((Part*)i->cp, true);
213 break;
214 }
215 }
216 }
217
218 if(!npart) // If the part still has not been created yet...
219 {
220
221 if(!track) // A clone was not created from any matching
222 { // part. Create a non-clone part now.
223 xml.skip("part");
224 return nullptr;
225 }
226 // If we're pasting to selected track and the 'wave'
227 // variable is valid, check for mismatch...
228 if(toTrack && uuidvalid)
229 {
230 // If both the part and track are not midi or wave...
231 if((wave && track->isMidiTrack()) ||
232 (!wave && track->type() == Track::WAVE))
233 {
234 xml.skip("part");
235 return nullptr;
236 }
237 }
238
239 if (track->isMidiTrack())
240 npart = new MidiPart((MidiTrack*)track);
241 else if (track->type() == Track::WAVE)
242 npart = new MusECore::WavePart((MusECore::WaveTrack*)track);
243 else
244 {
245 xml.skip("part");
246 return nullptr;
247 }
248
249 // Signify a new non-clone part was created.
250 // Even if the original part was itself a clone, clear this because the
251 // attribute section did not create a clone from any matching part.
252 clone = false;
253
254 // If an id or uuid was found, add the part to the clone list
255 // so that subsequent parts can look it up and clone from it...
256 if(id != -1)
257 {
258 ClonePart ncp(npart, id);
259 MusEGlobal::cloneList.push_back(ncp);
260 }
261 else
262 if(uuidvalid)
263 {
264 ClonePart ncp(npart);
265 // New ClonePart creates its own uuid, but we need to replace it.
266 ncp._uuid = uuid; // OK for non-windows?
267 MusEGlobal::cloneList.push_back(ncp);
268 }
269 }
270 }
271
272 if (tag == "name")
273 npart->setName(xml.parse1());
274 else if (tag == "viewState") {
275 npart->viewState().read(xml);
276 }
277 else if (tag == "poslen") {
278 ((PosLen*)npart)->read(xml, "poslen");
279 }
280 else if (tag == "pos") {
281 Pos pos;
282 pos.read(xml, "pos"); // obsolete
283 npart->setTick(pos.tick());
284 }
285 else if (tag == "len") {
286 Pos len;
287 len.read(xml, "len"); // obsolete
288 npart->setLenTick(len.tick());
289 }
290 else if (tag == "selected")
291 npart->setSelected(xml.parseInt());
292 else if (tag == "color")
293 npart->setColorIndex(xml.parseInt());
294 else if (tag == "mute")
295 npart->setMute(xml.parseInt());
296 else if (tag == "event")
297 {
298 // If a new non-clone part was created, accept the events...
299 if(!clone)
300 {
301 EventType type = Wave;
302 if(track->isMidiTrack())
303 type = Note;
304 Event e(type);
305 e.read(xml);
306 // stored pos for event has absolute value. However internally
307 // pos is relative to start of part, we substract part pos.
308 // In case the event and part pos types differ, the event dominates.
309 e.setPosValue(e.posValue() - npart->posValue(e.pos().type()));
310 #ifdef ALLOW_LEFT_HIDDEN_EVENTS
311 npart->addEvent(e);
312 #else
313 const int posval = e.posValue();
314 if(posval < 0)
315 {
316 printf("readClone: warning: event at posval:%d not in part:%s, discarded\n",
317 posval, npart->name().toLatin1().constData());
318 }
319 #endif
320 }
321 else // ...Otherwise a clone was created, so we don't need the events.
322 xml.skip(tag);
323 }
324 else
325 xml.unknown("readXmlPart");
326 break;
327 case Xml::Attribut:
328 if (tag == "type")
329 {
330 if(xml.s2() == "wave")
331 wave = true;
332 }
333 else if (tag == "cloneId")
334 {
335 id = xml.s2().toInt();
336 }
337 else if (tag == "uuid")
338 {
339 uuid = QUuid(xml.s2());
340 if(!uuid.isNull())
341 {
342 uuidvalid = true;
343 }
344 }
345 else if(tag == "isclone")
346 isclone = xml.s2().toInt();
347 break;
348 case Xml::TagEnd:
349 if (tag == "part")
350 return npart;
351 default:
352 break;
353 }
354 }
355 return npart;
356 }
357
358 //---------------------------------------------------------
359 // Part::write
360 // If isCopy is true, write the xml differently so that
361 // we can have 'Paste Clone' feature.
362 //---------------------------------------------------------
363
write(int level,Xml & xml,bool isCopy,bool forceWavePaths) const364 void Part::write(int level, Xml& xml, bool isCopy, bool forceWavePaths) const
365 {
366 int id = -1;
367 QUuid uuid;
368 bool dumpEvents = true;
369 bool wave = _track->type() == Track::WAVE;
370
371 // NOTE ::write() should never be called on a deleted part, so no checking here for cloneList items marked as deleted.
372 // Checking is awkward anyway and doesn't fit well here.
373
374 if(isCopy)
375 {
376 for(iClone i = MusEGlobal::cloneList.begin(); i != MusEGlobal::cloneList.end(); ++i)
377 {
378 if(i->cp->isCloneOf(this))
379 {
380 uuid = i->_uuid;
381 dumpEvents = false;
382 break;
383 }
384 }
385 if(uuid.isNull())
386 {
387 ClonePart cp(this);
388 uuid = cp._uuid;
389 MusEGlobal::cloneList.push_back(cp);
390 }
391 }
392 else
393 {
394 if (this->hasClones())
395 {
396 for (iClone i = MusEGlobal::cloneList.begin(); i != MusEGlobal::cloneList.end(); ++i)
397 {
398 if (i->cp->isCloneOf(this))
399 {
400 id = i->id;
401 dumpEvents = false;
402 break;
403 }
404 }
405 if (id == -1)
406 {
407 id = MusEGlobal::cloneList.size();
408 ClonePart cp(this, id);
409 MusEGlobal::cloneList.push_back(cp);
410 }
411 }
412 }
413
414 // Special markers if this is a copy operation and the
415 // part is a clone.
416 if(isCopy)
417 {
418 if(wave)
419 xml.nput(level, "<part type=\"wave\" uuid=\"%s\"", uuid.toByteArray().constData());
420 else
421 xml.nput(level, "<part uuid=\"%s\"", uuid.toByteArray().constData());
422
423 if(hasClones())
424 xml.nput(" isclone=\"1\"");
425 xml.put(">");
426 level++;
427 }
428 else
429 if (id != -1)
430 {
431 xml.tag(level++, "part cloneId=\"%d\"", id);
432 }
433 else
434 xml.tag(level++, "part");
435
436 xml.strTag(level, "name", _name);
437
438 // This won't bother writing if the state is invalid.
439 viewState().write(level, xml);
440
441 PosLen::write(level, xml, "poslen");
442 xml.intTag(level, "selected", _selected);
443 xml.intTag(level, "color", _colorIndex);
444 if (_mute)
445 xml.intTag(level, "mute", _mute);
446 if (dumpEvents) {
447 for (ciEvent e = events().begin(); e != events().end(); ++e)
448 e->second.write(level, xml, *this, forceWavePaths);
449 }
450 xml.etag(level, "part");
451 }
452
453
454 //---------------------------------------------------------
455 // writeFont
456 //---------------------------------------------------------
457
458 //void Song::writeFont(int level, Xml& xml, const char* name,
459 // const QFont& font) const
460 // {
461 // xml.nput(level, "<%s family=\"%s\" size=\"%d\"",
462 // name, Xml::xmlString(font.family()).toLatin1().constData(), font.pointSize());
463 // if (font.weight() != QFont::Normal)
464 // xml.nput(" weight=\"%d\"", font.weight());
465 // if (font.italic())
466 // xml.nput(" italic=\"1\"");
467 // xml.nput(" />\n");
468 // }
469
470 //---------------------------------------------------------
471 // readFont
472 //---------------------------------------------------------
473
474 //QFont Song::readFont(Xml& xml, const char* name)
475 // {
476 // QFont f;
477 // for (;;) {
478 // Xml::Token token = xml.parse();
479 // switch (token) {
480 // case Xml::Error:
481 // case Xml::End:
482 // return f;
483 // case Xml::TagStart:
484 // xml.unknown("readFont");
485 // break;
486 // case Xml::Attribut:
487 // if (xml.s1() == "family")
488 // f.setFamily(xml.s2());
489 // else if (xml.s1() == "size")
490 // f.setPointSize(xml.s2().toInt());
491 // else if (xml.s1() == "weight")
492 // f.setWeight(xml.s2().toInt());
493 // else if (xml.s1() == "italic")
494 // f.setItalic(xml.s2().toInt());
495 // break;
496 // case Xml::TagEnd:
497 // if (xml.s1() == name)
498 // return f;
499 // default:
500 // break;
501 // }
502 // }
503 // return f;
504 // }
505
506 //---------------------------------------------------------
507 // readMarker
508 //---------------------------------------------------------
509
readMarker(Xml & xml)510 void Song::readMarker(Xml& xml)
511 {
512 Marker m;
513 m.read(xml);
514 _markerList->add(m);
515 }
516
517 //---------------------------------------------------------
518 // checkSongSampleRate
519 // Called by gui thread.
520 //---------------------------------------------------------
521
checkSongSampleRate()522 void Song::checkSongSampleRate()
523 {
524 std::map<int, int> waveRates;
525
526 for(ciWaveTrack iwt = waves()->begin(); iwt != waves()->end(); ++iwt)
527 {
528 WaveTrack* wt = *iwt;
529 for(ciPart ipt = wt->parts()->begin(); ipt != wt->parts()->end(); ++ipt)
530 {
531 Part* pt = ipt->second;
532 for(ciEvent ie = pt->events().begin(); ie != pt->events().end(); ++ie)
533 {
534 const Event e(ie->second);
535 if(e.sndFile().isOpen())
536 {
537 const int sr = e.sndFile().samplerate();
538 std::map<int, int>::iterator iwr = waveRates.find(sr);
539 if(iwr == waveRates.end())
540 {
541 waveRates.insert(std::pair<int, int>(sr, 1));
542 }
543 else
544 {
545 ++iwr->second;
546 }
547 }
548 }
549 }
550 }
551
552 for(std::map<int, int>::const_iterator iwr = waveRates.cbegin(); iwr != waveRates.cend(); ++iwr)
553 {
554
555 }
556 }
557
558 //---------------------------------------------------------
559 // read
560 //---------------------------------------------------------
561
read(Xml & xml,bool)562 void Song::read(Xml& xml, bool /*isTemplate*/)
563 {
564 MusEGlobal::cloneList.clear();
565 for (;;) {
566 if (MusEGlobal::muse->progress) {
567 MusEGlobal::muse->progress->setValue(MusEGlobal::muse->progress->value()+1);
568 }
569
570 Xml::Token token;
571 token = xml.parse();
572 const QString& tag = xml.s1();
573 switch (token) {
574 case Xml::Error:
575 case Xml::End:
576 goto song_read_end;
577 case Xml::TagStart:
578 if (tag == "master")
579 {
580 // Avoid emitting songChanged.
581 // Tick parameter is not used.
582 MusEGlobal::tempomap.setMasterFlag(0, xml.parseInt());
583 }
584 else if (tag == "info")
585 songInfoStr = xml.parse1();
586 else if (tag == "showinfo")
587 showSongInfo = xml.parseInt();
588 else if (tag == "loop")
589 setLoop(xml.parseInt());
590 else if (tag == "punchin")
591 setPunchin(xml.parseInt());
592 else if (tag == "punchout")
593 setPunchout(xml.parseInt());
594 else if (tag == "record")
595 // This doesn't work as there are no tracks yet at this point and this is checked
596 // in setRecord. So better make it clear and explicit.
597 // (Using the default autoRecEnable==true would seem wrong too at this point.)
598 // setRecord(xml.parseInt());
599 setRecord(false);
600 else if (tag == "solo")
601 soloFlag = xml.parseInt();
602 else if (tag == "type") // Obsolete.
603 xml.parseInt();
604 else if (tag == "recmode")
605 _recMode = xml.parseInt();
606 else if (tag == "cycle")
607 _cycleMode = xml.parseInt();
608 else if (tag == "click")
609 setClick(xml.parseInt());
610 else if (tag == "quantize")
611 _quantize = xml.parseInt();
612 else if (tag == "len")
613 _len = xml.parseInt();
614 else if (tag == "follow")
615 _follow = FollowMode(xml.parseInt());
616 else if (tag == "midiDivision") {
617 // TODO: Compare with current global setting and convert the
618 // song if required - similar to how the song vs. global
619 // sample rate ratio is handled. Ignore for now.
620 xml.parseInt();
621 }
622 else if (tag == "sampleRate") {
623 // Ignore. Sample rate setting is handled by the
624 // song discovery mechanism (in MusE::loadProjectFile1()).
625 xml.parseInt();
626 }
627 else if (tag == "tempolist") {
628 MusEGlobal::tempomap.read(xml);
629 }
630 else if (tag == "siglist")
631 MusEGlobal::sigmap.read(xml);
632 else if (tag == "keylist") {
633 MusEGlobal::keymap.read(xml);
634 }
635 else if (tag == "miditrack") {
636 MidiTrack* track = new MidiTrack();
637 track->read(xml);
638 insertTrack0(track, -1);
639 }
640 else if (tag == "drumtrack") { // Old drumtrack is obsolete.
641 MidiTrack* track = new MidiTrack();
642 track->setType(Track::DRUM);
643 track->read(xml);
644 track->convertToType(Track::DRUM); // Convert the notes and controllers.
645 insertTrack0(track, -1);
646 }
647 else if (tag == "newdrumtrack") {
648 MidiTrack* track = new MidiTrack();
649 track->setType(Track::DRUM);
650 track->read(xml);
651 insertTrack0(track, -1);
652 }
653 else if (tag == "wavetrack") {
654 MusECore::WaveTrack* track = new MusECore::WaveTrack();
655 track->read(xml);
656 insertTrack0(track,-1);
657 // Now that the track has been added to the lists in insertTrack2(),
658 // OSC can find the track and its plugins, and start their native guis if required...
659 track->showPendingPluginNativeGuis();
660 }
661 else if (tag == "AudioInput") {
662 AudioInput* track = new AudioInput();
663 track->read(xml);
664 insertTrack0(track,-1);
665 track->showPendingPluginNativeGuis();
666 }
667 else if (tag == "AudioOutput") {
668 AudioOutput* track = new AudioOutput();
669 track->read(xml);
670 insertTrack0(track,-1);
671 track->showPendingPluginNativeGuis();
672 }
673 else if (tag == "AudioGroup") {
674 AudioGroup* track = new AudioGroup();
675 track->read(xml);
676 insertTrack0(track,-1);
677 track->showPendingPluginNativeGuis();
678 }
679 else if (tag == "AudioAux") {
680 AudioAux* track = new AudioAux();
681 track->read(xml);
682 insertTrack0(track,-1);
683 track->showPendingPluginNativeGuis();
684 }
685 else if (tag == "SynthI") {
686 SynthI* track = new SynthI();
687 track->read(xml);
688 // Done in SynthI::read()
689 // insertTrack(track,-1);
690 //track->showPendingPluginNativeGuis();
691 }
692 else if (tag == "Route") {
693 readRoute(xml);
694 }
695 else if (tag == "marker")
696 readMarker(xml);
697 else if (tag == "globalPitchShift")
698 _globalPitchShift = xml.parseInt();
699 // REMOVE Tim. automation. Removed.
700 // Deprecated. MusEGlobal::automation is now fixed TRUE
701 // for now until we decide what to do with it.
702 else if (tag == "automation")
703 // MusEGlobal::automation = xml.parseInt();
704 xml.parseInt();
705 else if (tag == "cpos") {
706 int pos = xml.parseInt();
707 Pos p(pos, true);
708 setPos(Song::CPOS, p, false, false, false);
709 }
710 else if (tag == "lpos") {
711 int pos = xml.parseInt();
712 Pos p(pos, true);
713 setPos(Song::LPOS, p, false, false, false);
714 }
715 else if (tag == "rpos") {
716 int pos = xml.parseInt();
717 Pos p(pos, true);
718 setPos(Song::RPOS, p, false, false, false);
719 }
720 else if (tag == "drummap")
721 readDrumMap(xml, false);
722 else if (tag == "drum_ordering")
723 MusEGlobal::global_drum_ordering.read(xml);
724 else
725 xml.unknown("Song");
726 break;
727 case Xml::Attribut:
728 break;
729 case Xml::TagEnd:
730 if (tag == "song") {
731 goto song_read_end;
732 }
733 default:
734 break;
735 }
736 }
737
738 song_read_end:
739 dirty = false;
740
741 // Since cloneList is also used for copy/paste operations,
742 // clear the copy clone list again.
743 MusEGlobal::cloneList.clear();
744 }
745
746 //---------------------------------------------------------
747 // write
748 //---------------------------------------------------------
749
write(int level,Xml & xml) const750 void Song::write(int level, Xml& xml) const
751 {
752 xml.tag(level++, "song");
753 xml.strTag(level, "info", songInfoStr);
754 xml.intTag(level, "showinfo", showSongInfo);
755 // REMOVE Tim. automation. Removed.
756 // Deprecated. MusEGlobal::automation is now fixed TRUE
757 // for now until we decide what to do with it.
758 // xml.intTag(level, "automation", MusEGlobal::automation);
759 xml.intTag(level, "cpos", MusEGlobal::song->cpos());
760 xml.intTag(level, "rpos", MusEGlobal::song->rpos());
761 xml.intTag(level, "lpos", MusEGlobal::song->lpos());
762 xml.intTag(level, "master", MusEGlobal::tempomap.masterFlag());
763 xml.intTag(level, "loop", loopFlag);
764 xml.intTag(level, "punchin", punchinFlag);
765 xml.intTag(level, "punchout", punchoutFlag);
766 xml.intTag(level, "record", recordFlag);
767 xml.intTag(level, "solo", soloFlag);
768 xml.intTag(level, "recmode", _recMode);
769 xml.intTag(level, "cycle", _cycleMode);
770 xml.intTag(level, "click", _click);
771 xml.intTag(level, "quantize", _quantize);
772 xml.intTag(level, "len", _len);
773 xml.intTag(level, "follow", _follow);
774 // Save the current global midi division as well as current sample rate
775 // so the song can be scaled properly if the values differ on reload.
776 xml.intTag(level, "midiDivision", MusEGlobal::config.division);
777 xml.intTag(level, "sampleRate", MusEGlobal::sampleRate);
778 if (_globalPitchShift)
779 xml.intTag(level, "globalPitchShift", _globalPitchShift);
780
781 // Make a backup of the current clone list, to retain any 'copy' items,
782 // so that pasting works properly after.
783 CloneList copyCloneList = MusEGlobal::cloneList;
784 MusEGlobal::cloneList.clear();
785
786 // write tracks
787 for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i)
788 (*i)->write(level, xml);
789
790 // write routing
791 for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i)
792 (*i)->writeRouting(level, xml);
793
794 // Write midi device routing.
795 for (iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i)
796 (*i)->writeRouting(level, xml);
797
798 // Write midi port routing.
799 for (int i = 0; i < MusECore::MIDI_PORTS; ++i)
800 MusEGlobal::midiPorts[i].writeRouting(level, xml);
801
802 MusEGlobal::tempomap.write(level, xml);
803 MusEGlobal::sigmap.write(level, xml);
804 MusEGlobal::keymap.write(level, xml);
805 _markerList->write(level, xml);
806
807 writeDrumMap(level, xml, false);
808 MusEGlobal::global_drum_ordering.write(level, xml);
809 xml.tag(level, "/song");
810
811 // Restore backup of the clone list, to retain any 'copy' items,
812 // so that pasting works properly after.
813 MusEGlobal::cloneList.clear();
814 MusEGlobal::cloneList = copyCloneList;
815 }
816
817 //--------------------------------
818 // resolveInstrumentReferences
819 //--------------------------------
820
resolveInstrumentReferences()821 static void resolveInstrumentReferences()
822 {
823 for(int i = 0; i < MIDI_PORTS; ++i)
824 {
825 MidiPort* mp = &MusEGlobal::midiPorts[i];
826 const QString& name = mp->tmpInstrRef();
827 const int idx = mp->tmpTrackRef();
828 if(idx >= 0)
829 {
830 Track* track = MusEGlobal::song->tracks()->index(idx);
831 if(track && track->isSynthTrack())
832 {
833 SynthI* si = static_cast<SynthI*>(track);
834 mp->changeInstrument(si);
835 }
836 }
837 else if(!name.isEmpty())
838 {
839 mp->changeInstrument(registerMidiInstrument(name));
840 }
841
842 // Done with temporary file references. Clear them.
843 mp->clearTmpFileRefs();
844 }
845 }
846
847 //--------------------------------
848 // resolveStripReferences
849 //--------------------------------
850
resolveStripReferences(MusEGlobal::MixerConfig * mconfig)851 static void resolveStripReferences(MusEGlobal::MixerConfig* mconfig)
852 {
853 MusECore::TrackList* tl = MusEGlobal::song->tracks();
854 MusECore::Track* track;
855 MusEGlobal::StripConfigList_t& scl = mconfig->stripConfigList;
856 if(!scl.empty())
857 {
858 for(MusEGlobal::iStripConfigList isc = scl.begin(); isc != scl.end(); )
859 {
860 MusEGlobal::StripConfig& sc = *isc;
861
862 // Is it a dud? Delete it.
863 if(sc.isNull() && sc._tmpFileIdx < 0)
864 {
865 isc = scl.erase(isc);
866 continue;
867 }
868
869 // Does it have a temporary track index?
870 if(sc._tmpFileIdx >= 0)
871 {
872 // Find an existing track at that index.
873 track = tl->index(sc._tmpFileIdx);
874 // No corresponding track found? Delete the strip config.
875 if(!track)
876 {
877 isc = scl.erase(isc);
878 continue;
879 }
880 // Link the strip config to the track, via serial number.
881 sc._serial = track->serial();
882 // Done with temporary index. Reset it.
883 sc._tmpFileIdx = -1;
884 }
885
886 // As for other configs with a serial and no _tmpFileIdx,
887 // leave them alone. They may already be valid. If not,
888 // they may need to persist for the undo system to restore from.
889 // Ultimately when saving, the strip config write() function removes duds anyway.
890
891 ++isc;
892 }
893 }
894 }
895
896 //--------------------------------
897 // resolveSongfileReferences
898 //--------------------------------
899
resolveSongfileReferences()900 void Song::resolveSongfileReferences()
901 {
902 //-----------------------------------------------
903 // Resolve instrument references:
904 //-----------------------------------------------
905 resolveInstrumentReferences();
906
907 //-----------------------------------------------
908 // Resolve mixer strip configuration references:
909 //-----------------------------------------------
910 resolveStripReferences(&MusEGlobal::config.mixer1);
911 resolveStripReferences(&MusEGlobal::config.mixer2);
912 }
913
914 // REMOVE Tim. samplerate. Added. TODO
915 #if 0
916 //---------------------------------------------------------
917 // convertProjectSampleRate
918 //---------------------------------------------------------
919
920 void Song::convertProjectSampleRate(int newRate)
921 {
922 double sratio = double(newRate) / double(MusEGlobal::sampleRate);
923
924 for(iTrack i = _tracks.begin(); i != _tracks.end(); ++i)
925 {
926 Track* track = *i;
927 //if(track->isMidiTrack)
928 // continue;
929
930 PartList* parts = track->parts();
931
932 for(iPart ip = parts->begin(); i != parts->end(); ++i)
933 {
934 Part* part = *ip;
935
936 EventList& el = part->nonconst_events();
937
938 for(iEvent ie = el.begin(); ie != el.end(); ++ie)
939 {
940 Event& e = *ie;
941 }
942
943 if(part->type() == Part::FRAMES)
944 {
945 part->setFrame(double(part->frame()) * sratio);
946 part->setLenFrame(double(part->lenFrame()) * sratio);
947 }
948 }
949
950 if(track->type == Track::WAVE)
951 {
952
953 }
954
955
956 }
957 }
958 #endif
959
960 } // namespace MusECore
961
962 namespace MusEGui {
963
964 //---------------------------------------------------------
965 // readPart
966 //---------------------------------------------------------
967
readPart(MusECore::Xml & xml)968 MusECore::Part* MusE::readPart(MusECore::Xml& xml)
969 {
970 MusECore::Part* part = nullptr;
971 for (;;) {
972 MusECore::Xml::Token token = xml.parse();
973 const QString& tag = xml.s1();
974 switch (token) {
975 case MusECore::Xml::Error:
976 case MusECore::Xml::End:
977 return part;
978 case MusECore::Xml::Text:
979 {
980 int trackIdx, partIdx;
981 sscanf(tag.toLatin1().constData(), "%d:%d", &trackIdx, &partIdx);
982 MusECore::Track* track = nullptr;
983 //check if track index is in bounds before getting it (danvd)
984 if(trackIdx < (int)MusEGlobal::song->tracks()->size())
985 {
986 track = MusEGlobal::song->tracks()->index(trackIdx);
987 }
988 if (track)
989 part = track->parts()->find(partIdx);
990 }
991 break;
992 case MusECore::Xml::TagStart:
993 xml.unknown("readPart");
994 break;
995 case MusECore::Xml::TagEnd:
996 if (tag == "part")
997 return part;
998 default:
999 break;
1000 }
1001 }
1002 }
1003
1004 //---------------------------------------------------------
1005 // readToplevels
1006 //---------------------------------------------------------
1007
readToplevels(MusECore::Xml & xml)1008 void MusE::readToplevels(MusECore::Xml& xml)
1009 {
1010 MusECore::PartList* pl = new MusECore::PartList;
1011 for (;;) {
1012 MusECore::Xml::Token token = xml.parse();
1013 const QString& tag = xml.s1();
1014 switch (token) {
1015 case MusECore::Xml::Error:
1016 case MusECore::Xml::End:
1017 delete pl;
1018 return;
1019 case MusECore::Xml::TagStart:
1020 if (tag == "part") {
1021 MusECore::Part* part = readPart(xml);
1022 if (part)
1023 pl->add(part);
1024 }
1025 else if (tag == "pianoroll") {
1026 // p3.3.34
1027 // Do not open if there are no parts.
1028 // Had bogus '-1' part index for list edit in med file,
1029 // causing list edit to segfault on song load.
1030 // Somehow that -1 was put there on write, because the
1031 // current part didn't exist anymore, so no index number
1032 // could be found for it on write. Watching... may be fixed.
1033 // But for now be safe for all the top levels...
1034 if(!pl->empty())
1035 {
1036 startPianoroll(pl);
1037 toplevels.back()->readStatus(xml);
1038 pl = new MusECore::PartList;
1039 }
1040 }
1041 else if (tag == "scoreedit") {
1042 MusEGui::ScoreEdit* score = new MusEGui::ScoreEdit(this, 0, _arranger->cursorValue());
1043 toplevels.push_back(score);
1044 connect(score, SIGNAL(isDeleting(MusEGui::TopWin*)), SLOT(toplevelDeleting(MusEGui::TopWin*)));
1045 connect(score, SIGNAL(name_changed()), arrangerView, SLOT(scoreNamingChanged()));
1046 score->show();
1047 score->readStatus(xml);
1048 }
1049 else if (tag == "drumedit") {
1050 if(!pl->empty())
1051 {
1052 startDrumEditor(pl);
1053 toplevels.back()->readStatus(xml);
1054 pl = new MusECore::PartList;
1055 }
1056 }
1057 else if (tag == "master") {
1058 startMasterEditor();
1059 toplevels.back()->readStatus(xml);
1060 }
1061 else if (tag == "arrangerview") {
1062 TopWin* tw = toplevels.findType(TopWin::ARRANGER);
1063 tw->readStatus(xml);
1064 tw->showMaximized();
1065 }
1066 else if (tag == "waveedit") {
1067 if(!pl->empty())
1068 {
1069 startWaveEditor(pl);
1070 toplevels.back()->readStatus(xml);
1071 pl = new MusECore::PartList;
1072 }
1073 }
1074 else
1075 xml.unknown("MusE");
1076 break;
1077 case MusECore::Xml::Attribut:
1078 break;
1079 case MusECore::Xml::TagEnd:
1080 if (tag == "toplevels") {
1081 delete pl;
1082 return;
1083 }
1084 default:
1085 break;
1086 }
1087 }
1088 }
1089
1090 //---------------------------------------------------------
1091 // read
1092 // read song
1093 //---------------------------------------------------------
1094
read(MusECore::Xml & xml,bool doReadMidiPorts,bool isTemplate)1095 void MusE::read(MusECore::Xml& xml, bool doReadMidiPorts, bool isTemplate)
1096 {
1097 bool skipmode = true;
1098
1099 writeTopwinState=true;
1100
1101 for (;;) {
1102 if (progress)
1103 progress->setValue(progress->value()+1);
1104 MusECore::Xml::Token token = xml.parse();
1105 const QString& tag = xml.s1();
1106 switch (token) {
1107 case MusECore::Xml::Error:
1108 case MusECore::Xml::End:
1109 return;
1110 case MusECore::Xml::TagStart:
1111 if (skipmode && tag == "muse")
1112 skipmode = false;
1113 else if (skipmode)
1114 break;
1115 else if (tag == "configuration")
1116 readConfiguration(xml, doReadMidiPorts, false /* do NOT read global settings, see below */);
1117 /* Explanation for why "do NOT read global settings":
1118 * if you would use true here, then muse would overwrite certain global config stuff
1119 * by the settings stored in the song. but you don't want this. imagine that you
1120 * send a friend a .med file. your friend opens it and baaam, his configuration is
1121 * garbled. why? well, because these readConfigurations here would have overwritten
1122 * parts (but not all) of his global config (like MDI/SDI, toolbar states etc.)
1123 * with the data stored in the song. (it IS stored there. dunny why, i find it pretty
1124 * senseless.)
1125 *
1126 * If you've a problem which seems to be solved by replacing "false" with "true", i've
1127 * a better solution for you: go into conf.cpp, in void readConfiguration(Xml& xml, bool readOnlySequencer, bool doReadGlobalConfig)
1128 * (around line 525), look for a comment like this:
1129 * "Global and/or per-song config stuff ends here" (alternatively just search for
1130 * "----"). Your problem is probably that some non-global setting should be loaded but
1131 * is not. Fix it by either placing the else if (foo)... clause responsible for that
1132 * setting to be loaded into the first part, that is, before "else if (!doReadGlobalConfig)"
1133 * or (if the settings actually IS global and not per-song), ensure that the routine
1134 * which writes the global (and not the song-)configuration really writes that setting.
1135 * (it might happen that it formerly worked because it was written to the song instead
1136 * of the global config by mistake, and now isn't loaded anymore. write it to the
1137 * correct location.)
1138 *
1139 * -- flo93
1140 */
1141 else if (tag == "song")
1142 {
1143 MusEGlobal::song->read(xml, isTemplate);
1144
1145 // Now that the song file has been fully loaded, resolve any references in the file.
1146 MusEGlobal::song->resolveSongfileReferences();
1147
1148 // Now that all track and instrument references have been resolved,
1149 // it is safe to add all the midi controller cache values.
1150 MusEGlobal::song->changeMidiCtrlCacheEvents(true, true, true, true, true);
1151
1152 MusEGlobal::audio->msgUpdateSoloStates();
1153 // Inform the rest of the app that the song (may) have changed, using these flags.
1154 // After this function is called, the caller can do a general Song::update() MINUS these flags,
1155 // like in MusE::loadProjectFile1() - the only place calling so far, as of this writing.
1156 // Some existing windows need this, like arranger, some don't which are dynamically created after this.
1157 MusEGlobal::song->update(SC_TRACK_INSERTED);
1158 }
1159 else if (tag == "toplevels")
1160 readToplevels(xml);
1161 else if (tag == "no_toplevels")
1162 {
1163 if (!isTemplate)
1164 writeTopwinState=false;
1165
1166 xml.skip("no_toplevels");
1167 }
1168
1169 else
1170 xml.unknown("muse");
1171 break;
1172 case MusECore::Xml::Attribut:
1173 if (tag == "version") {
1174 int major = xml.s2().section('.', 0, 0).toInt();
1175 int minor = xml.s2().section('.', 1, 1).toInt();
1176 xml.setVersion(major, minor);
1177 }
1178 break;
1179 case MusECore::Xml::TagEnd:
1180 if(!xml.isVersionEqualToLatest())
1181 {
1182 fprintf(stderr, "\n***WARNING***\nLoaded file version is %d.%d\nCurrent version is %d.%d\n"
1183 "Conversions may be applied if file is saved!\n\n",
1184 xml.majorVersion(), xml.minorVersion(),
1185 xml.latestMajorVersion(), xml.latestMinorVersion());
1186 // Cannot construct QWidgets until QApplication created!
1187 // Check MusEGlobal::muse which is created shortly after the application...
1188 if(MusEGlobal::muse && MusEGlobal::config.warnOnFileVersions)
1189 {
1190 QString txt = tr("File version is %1.%2\nCurrent version is %3.%4\n"
1191 "Conversions may be applied if file is saved!")
1192 .arg(xml.majorVersion()).arg(xml.minorVersion())
1193 .arg(xml.latestMajorVersion()).arg(xml.latestMinorVersion());
1194 QMessageBox* mb = new QMessageBox(QMessageBox::Warning,
1195 tr("Opening file"),
1196 txt,
1197 QMessageBox::Ok, MusEGlobal::muse);
1198 QCheckBox* cb = new QCheckBox(tr("Do not warn again"));
1199 cb->setChecked(!MusEGlobal::config.warnOnFileVersions);
1200 mb->setCheckBox(cb);
1201 mb->exec();
1202 if(!mb->checkBox()->isChecked() != MusEGlobal::config.warnOnFileVersions)
1203 {
1204 MusEGlobal::config.warnOnFileVersions = !mb->checkBox()->isChecked();
1205 // Save settings. Use simple version - do NOT set style or stylesheet, this has nothing to do with that.
1206 //MusEGlobal::muse->changeConfig(true); // Save settings? No, wait till close.
1207 }
1208 delete mb;
1209 }
1210 }
1211 if (!skipmode && tag == "muse")
1212 return;
1213 default:
1214 break;
1215 }
1216 }
1217 }
1218
1219
1220 //---------------------------------------------------------
1221 // write
1222 // write song
1223 //---------------------------------------------------------
1224
write(MusECore::Xml & xml,bool writeTopwins) const1225 void MusE::write(MusECore::Xml& xml, bool writeTopwins) const
1226 {
1227 xml.header();
1228
1229 int level = 0;
1230 xml.nput(level++, "<muse version=\"%d.%d\">\n", xml.latestMajorVersion(), xml.latestMinorVersion());
1231
1232 writeConfiguration(level, xml);
1233
1234 writeStatusMidiInputTransformPlugins(level, xml);
1235
1236 MusEGlobal::song->write(level, xml);
1237
1238 if (writeTopwins && !toplevels.empty()) {
1239 xml.tag(level++, "toplevels");
1240 for (const auto& i : toplevels) {
1241 if (i->isVisible())
1242 i->writeStatus(level, xml);
1243 }
1244 xml.tag(level--, "/toplevels");
1245 }
1246 else if (!writeTopwins)
1247 {
1248 xml.tag(level, "no_toplevels");
1249 xml.etag(level, "no_toplevels");
1250 }
1251
1252 xml.tag(level, "/muse");
1253 }
1254
1255 } // namespace MusEGui
1256
1257