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