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 "[MupExporter]"
19 // Turn off RG_DEBUG output.
20 //#define RG_NO_DEBUG_PRINT
21 
22 #include "MupExporter.h"
23 
24 #include "misc/Debug.h"
25 #include "base/BaseProperties.h"
26 #include "base/Composition.h"
27 #include "base/Event.h"
28 #include "base/Exception.h"
29 #include "base/NotationQuantizer.h"
30 #include "base/NotationTypes.h"
31 #include "base/Segment.h"
32 #include "base/SegmentNotationHelper.h"
33 #include "base/Sets.h"
34 #include "base/Track.h"
35 
36 #include <QCoreApplication>
37 #include <QObject>
38 
39 using std::string;
40 
41 namespace Rosegarden
42 {
43 using namespace BaseProperties;
44 
MupExporter(QObject *,Composition * composition,string fileName)45 MupExporter::MupExporter(QObject * /*parent*/,
46                          Composition *composition,
47                          string fileName) :
48         m_composition(composition),
49         m_fileName(fileName)
50 {
51     // nothing else
52 }
53 
~MupExporter()54 MupExporter::~MupExporter()
55 {
56     // nothing
57 }
58 
59 bool
write()60 MupExporter::write()
61 {
62     Composition *c = m_composition;
63 
64     std::ofstream str(m_fileName.c_str(), std::ios::out);
65     if (!str) {
66         RG_WARNING << "MupExporter::write() - can't write file " << m_fileName;
67         return false;
68     }
69 
70     str << "score\n";
71     str << "\tstaffs=" << c->getNbTracks() << "\n";
72 
73     int ts = c->getTimeSignatureCount();
74     std::pair<timeT, TimeSignature> tspair;
75     if (ts > 0)
76         tspair = c->getTimeSignatureChange(0);
77     str << "\ttime="
78     << tspair.second.getNumerator() << "/"
79     << tspair.second.getDenominator() << "\n";
80 
81     // For each bar
82     for (int barNo = -1; barNo < c->getNbBars(); ++barNo) {
83 
84         // For each Track
85         for (TrackId trackNo = c->getMinTrackId();
86                 trackNo <= c->getMaxTrackId(); ++trackNo) {
87 
88             if (barNo < 0) {
89                 writeClefAndKey(str, trackNo);
90                 continue;
91             }
92 
93             if (barNo == 0 && trackNo == 0) {
94                 str << "\nmusic\n";
95             }
96 
97             str << "\t" << trackNo + 1 << ":";
98 
99             Segment *s = nullptr;
100             timeT barStart = c->getBarStart(barNo);
101             timeT barEnd = c->getBarEnd(barNo);
102 
103             // For each Segment
104             for (Composition::iterator ci = c->begin(); ci != c->end(); ++ci) {
105                 qApp->processEvents();
106 
107                 // If this segment is on the current Track
108                 if ((*ci)->getTrack() == trackNo &&
109                         (*ci)->getStartTime() < barEnd &&
110                         (*ci)->getEndMarkerTime() > barStart) {
111                     s = *ci;
112                     break;
113                 }
114             }
115 
116             TimeSignature timeSig(c->getTimeSignatureAt(barStart));
117 
118             if (!s) {
119                 // write empty bar
120                 writeInventedRests(str, timeSig, 0, barEnd - barStart);
121                 continue;
122             }
123 
124             if (s->getStartTime() > barStart) {
125                 writeInventedRests(str, timeSig,
126                                    0, s->getStartTime() - barStart);
127             }
128 
129             // Mup insists that every bar has the correct duration, and won't
130             // recover if one goes wrong.  Keep careful tabs on this: it means
131             // that for example we have to round chord durations down where
132             // the next chord starts too soon
133             //!!! we _really_ can't cope with time sig changes yet!
134 
135             timeT writtenDuration = writeBar(str, c, s, barStart, barEnd,
136                                              timeSig, trackNo);
137 
138             if (writtenDuration < timeSig.getBarDuration()) {
139                 RG_DEBUG << "writtenDuration: " << writtenDuration
140                 << ", bar duration " << timeSig.getBarDuration();
141                 writeInventedRests(str, timeSig, writtenDuration,
142                                    timeSig.getBarDuration() - writtenDuration);
143 
144             } else if (writtenDuration > timeSig.getBarDuration()) {
145                 RG_WARNING << "WARNING: overfull bar in Mup export: duration " << writtenDuration
146                 << " into bar of duration " << timeSig.getBarDuration();
147                 //!!! warn user
148             }
149 
150             str << "\n";
151         }
152 
153         if (barNo >= 0)
154             str << "bar" << std::endl;
155     }
156 
157     str << "\n" << std::endl;
158     str.close();
159     return true;
160 }
161 
162 timeT
writeBar(std::ofstream & str,Composition * c,Segment * s,timeT barStart,timeT barEnd,TimeSignature & timeSig,TrackId trackNo)163 MupExporter::writeBar(std::ofstream &str,
164                       Composition *c,
165                       Segment *s,
166                       timeT barStart, timeT barEnd,
167                       TimeSignature &timeSig,
168                       TrackId trackNo)
169 {
170     timeT writtenDuration = 0;
171     SegmentNotationHelper helper(*s);
172     helper.setNotationProperties();
173 
174     long currentGroupId = -1;
175     string currentGroupType = "";
176     long currentTupletCount = 3;
177     bool first = true;
178     bool openBeamWaiting = false;
179 
180     for (Segment::iterator si =
181              SegmentNotationHelper(*s).findNotationAbsoluteTime(barStart);
182             s->isBeforeEndMarker(si) &&
183             (*si)->getNotationAbsoluteTime() < barEnd; ++si) {
184 
185         if ((*si)->isa(Note::EventType)) {
186 
187             Chord chord(*s, si, c->getNotationQuantizer());
188             Event *e = *chord.getInitialNote();
189 
190             timeT absTime = e->getNotationAbsoluteTime();
191             timeT duration = e->getNotationDuration();
192             try {
193                 // tuplet compensation, etc
194                 Note::Type type = e->get<Int>(NOTE_TYPE);
195                 int dots = e->get
196                            <Int>(NOTE_DOTS);
197                 duration = Note(type, dots).getDuration();
198             } catch (const Exception &e) { // no properties
199                 RG_WARNING << "WARNING: MupExporter::writeBar: incomplete note properties: " << e.getMessage();
200             }
201 
202             timeT toNext = duration;
203             Segment::iterator nextElt = chord.getFinalElement();
204             if (s->isBeforeEndMarker(++nextElt)) {
205                 toNext = (*nextElt)->getNotationAbsoluteTime() - absTime;
206                 if (toNext < duration)
207                     duration = toNext;
208             }
209 
210             bool enteringGroup = false;
211 
212             if (e->has(BEAMED_GROUP_ID) && e->has(BEAMED_GROUP_TYPE)) {
213 
214                 long id = e->get
215                           <Int>(BEAMED_GROUP_ID);
216                 string type = e->get
217                               <String>(BEAMED_GROUP_TYPE);
218 
219                 if (id != currentGroupId) {
220 
221                     // leave previous group first
222                     if (currentGroupId >= 0) {
223                         if (!openBeamWaiting)
224                             str << " ebm";
225                         openBeamWaiting = false;
226 
227                         if (currentGroupType == GROUP_TYPE_TUPLED) {
228                             str << "; }" << currentTupletCount;
229                         }
230                     }
231 
232                     currentGroupId = id;
233                     currentGroupType = type;
234                     enteringGroup = true;
235                 }
236             } else {
237 
238                 if (currentGroupId >= 0) {
239                     if (!openBeamWaiting)
240                         str << " ebm";
241                     openBeamWaiting = false;
242 
243                     if (currentGroupType == GROUP_TYPE_TUPLED) {
244                         str << "; }" << currentTupletCount;
245                     }
246 
247                     currentGroupId = -1;
248                     currentGroupType = "";
249                 }
250             }
251 
252             if (openBeamWaiting)
253                 str << " bm";
254             if (!first)
255                 str << ";";
256             str << " ";
257 
258             if (currentGroupType == GROUP_TYPE_TUPLED) {
259                 e->get
260                 <Int>(BEAMED_GROUP_UNTUPLED_COUNT, currentTupletCount);
261                 if (enteringGroup)
262                     str << "{ ";
263                 //!!!		duration = helper.getCompensatedNotationDuration(e);
264 
265             }
266 
267             writeDuration(str, duration);
268 
269             if (toNext > duration && currentGroupType != GROUP_TYPE_TUPLED) {
270                 writeInventedRests
271                 (str, timeSig,
272                  absTime + duration - barStart, toNext - duration);
273             }
274 
275             writtenDuration += toNext;
276 
277             for (Chord::iterator chi = chord.begin();
278                     chi != chord.end(); ++chi) {
279                 writePitch(str, trackNo, **chi);
280             }
281 
282             openBeamWaiting = false;
283             if (currentGroupType == GROUP_TYPE_BEAMED ||
284                     currentGroupType == GROUP_TYPE_TUPLED) {
285                 if (enteringGroup)
286                     openBeamWaiting = true;
287             }
288 
289             si = chord.getFinalElement();
290 
291             first = false;
292 
293         } else if ((*si)->isa(Note::EventRestType)) {
294 
295             if (currentGroupId >= 0) {
296 
297                 if (!openBeamWaiting)
298                     str << " ebm";
299                 openBeamWaiting = false;
300 
301                 if (currentGroupType == GROUP_TYPE_TUPLED) {
302                     str << "; }" << currentTupletCount;
303                 }
304 
305                 currentGroupId = -1;
306                 currentGroupType = "";
307             }
308 
309             if (openBeamWaiting)
310                 str << " bm";
311             if (!first)
312                 str << ";";
313             str << " ";
314 
315             writeDuration(str, (*si)->getNotationDuration());
316             writtenDuration += (*si)->getNotationDuration();
317             str << "r";
318 
319             first = false;
320             openBeamWaiting = false;
321 
322         } // ignore all other sorts of events for now
323     }
324 
325     if (currentGroupId >= 0) {
326         if (!openBeamWaiting)
327             str << " ebm";
328         openBeamWaiting = false;
329 
330         if (currentGroupType == GROUP_TYPE_TUPLED) {
331             str << "; }" << currentTupletCount;
332         }
333     }
334 
335     if (openBeamWaiting)
336         str << " bm";
337     if (!first)
338         str << ";";
339 
340     return writtenDuration;
341 }
342 
343 void
writeClefAndKey(std::ofstream & str,TrackId trackNo)344 MupExporter::writeClefAndKey(std::ofstream &str, TrackId trackNo)
345 {
346     Composition *c = m_composition;
347 
348     for (Composition::iterator i = c->begin(); i != c->end(); ++i) {
349         if ((*i)->getTrack() == trackNo) {
350 
351             Clef clef((*i)->getClefAtTime((*i)->getStartTime()));
352             Rosegarden::Key key((*i)->getKeyAtTime((*i)->getStartTime()));
353 
354 
355             str << "staff " << trackNo + 1 << "\n";
356 
357             if (clef.getClefType() == Clef::Treble) {
358                 str << "\tclef=treble\n";
359             } else if (clef.getClefType() == Clef::Alto) {
360                 str << "\tclef=alto\n";
361             } else if (clef.getClefType() == Clef::Tenor) {
362                 str << "\tclef=tenor\n";
363             } else if (clef.getClefType() == Clef::Bass) {
364                 str << "\tclef=bass\n";
365             }
366 
367             str << "\tkey=" << key.getAccidentalCount()
368             << (key.isSharp() ? "#" : "&")
369             << (key.isMinor() ? "minor" : "major") << std::endl;
370 
371             m_clefKeyMap[trackNo] = ClefKeyPair(clef, key);
372 
373             return ;
374         }
375     }
376 }
377 
378 void
writeInventedRests(std::ofstream & str,TimeSignature & timeSig,timeT offset,timeT duration)379 MupExporter::writeInventedRests(std::ofstream &str,
380                                 TimeSignature &timeSig,
381                                 timeT offset,
382                                 timeT duration)
383 {
384     str << " ";
385     DurationList dlist;
386     timeSig.getDurationListForInterval(dlist, duration, offset);
387     for (DurationList::iterator i = dlist.begin();
388             i != dlist.end(); ++i) {
389         writeDuration(str, *i);
390         str << "r;";
391     }
392 }
393 
394 void
writePitch(std::ofstream & str,TrackId trackNo,Event * event)395 MupExporter::writePitch(std::ofstream &str, TrackId trackNo,
396                         Event *event)
397 {
398     long pitch = 0;
399     if (!event->get
400             <Int>(PITCH, pitch)) {
401         str << "c"; // have to write something, or it won't parse
402         return ;
403     }
404 
405     Accidental accidental = Accidentals::NoAccidental;
406     (void)event->get
407     <String>(ACCIDENTAL, accidental);
408 
409     // mup octave: treble clef is in octave 4?
410 
411     ClefKeyPair ck;
412     ClefKeyMap::iterator ckmi = m_clefKeyMap.find(trackNo);
413     if (ckmi != m_clefKeyMap.end())
414         ck = ckmi->second;
415 
416     Pitch p(pitch, accidental);
417     Accidental acc(p.getDisplayAccidental(ck.second));
418     char note(p.getNoteName(ck.second));
419     int octave(p.getOctaveAccidental(-2, acc));
420 
421     // just to avoid assuming that the note names returned by Pitch are in
422     // the same set as those expected by Mup -- in practice they are the same
423     // letters but this changes the case
424     str << "cdefgab"[Pitch::getIndexForNote(note)];
425 
426     if (acc == Accidentals::DoubleFlat)
427         str << "&&";
428     else if (acc == Accidentals::Flat)
429         str << "&";
430     else if (acc == Accidentals::Sharp)
431         str << "#";
432     else if (acc == Accidentals::DoubleSharp)
433         str << "##";
434     else if (acc == Accidentals::Natural)
435         str << "n";
436 
437     str << octave + 1;
438 }
439 
440 void
writeDuration(std::ofstream & str,timeT duration)441 MupExporter::writeDuration(std::ofstream &str, timeT duration)
442 {
443     Note note(Note::getNearestNote(duration, 2));
444     int n = Note::Semibreve - note.getNoteType();
445     if (n < 0)
446         str << "1/" << (1 << ( -n));
447     else
448         str << (1 << n);
449     for (int d = 0; d < note.getDots(); ++d)
450         str << ".";
451 }
452 
453 }
454