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 "[HydrogenXMLHandler]"
19 
20 #include "HydrogenXMLHandler.h"
21 
22 #include "base/Event.h"
23 #include "base/BaseProperties.h"
24 #include "misc/Debug.h"
25 #include "misc/Strings.h"
26 #include "base/Composition.h"
27 #include "base/Instrument.h"
28 #include "base/MidiProgram.h"
29 #include "base/NotationTypes.h"
30 #include "base/Segment.h"
31 #include "base/Track.h"
32 #include <QString>
33 
34 
35 namespace Rosegarden
36 {
37 
HydrogenXMLHandler(Composition * composition,InstrumentId drumIns)38 HydrogenXMLHandler::HydrogenXMLHandler(Composition *composition,
39                                        InstrumentId drumIns):
40         m_composition(composition),
41         m_drumInstrument(drumIns),
42         m_inNote(false),
43         m_inInstrument(false),
44         m_inPattern(false),
45         m_inSequence(false),
46         m_patternName(""),
47         m_patternSize(0),
48         m_sequenceName(""),
49         m_position(0),
50         m_velocity(0.0),
51         m_panL(0.0),
52         m_panR(0.0),
53         m_pitch(0.0),
54         m_instrument(0),
55         m_id(0),
56         m_muted(false),
57         m_fileName(""),
58         m_bpm(0),
59         m_volume(0.0),
60         m_name(""),
61         m_author(""),
62         m_notes(""),
63         m_songMode(false),
64         m_version(""),
65         m_currentProperty(""),
66         m_segment(nullptr),
67         m_currentTrackNb(0),
68         m_segmentAdded(false),
69         m_currentBar(0),
70         m_newSegment(false)
71 {}
72 
73 bool
startDocument()74 HydrogenXMLHandler::startDocument()
75 {
76     RG_DEBUG << "HydrogenXMLHandler::startDocument";
77 
78     m_inNote = false;
79     m_inInstrument = false;
80     m_inPattern = false;
81     m_inSequence = false;
82 
83     // Pattern attributes
84     //
85     m_patternName = "";
86     m_patternSize = 0;
87 
88     // Sequence attributes
89     //
90     m_sequenceName = "";
91 
92     // Note attributes
93     //
94     m_position = 0;
95     m_velocity = 0.0;
96     m_panL = 0.0;
97     m_panR = 0.0;
98     m_pitch = 0.0;
99     m_instrument = 0;
100 
101     // Instrument attributes
102     //
103     m_id = 0;
104     m_muted = false;
105     m_instrumentVolumes.clear();
106     m_fileName = "";
107 
108     // Global attributes
109     //
110     m_bpm = 0;
111     m_volume = 0.0;
112     m_name = "";
113     m_author = "";
114     m_notes = "";
115     m_songMode = false;
116     m_version = "";
117 
118     m_currentProperty = "";
119 
120     m_segment = nullptr;
121     m_currentTrackNb = 0;
122     m_segmentAdded = 0;
123     m_currentBar = 0;
124     m_newSegment = false;
125 
126     return true;
127 }
128 
129 
startElement_093(const QString &,const QString &,const QString & qName,const QXmlStreamAttributes &)130 bool HydrogenXMLHandler::startElement_093(const QString& /*namespaceURI*/,
131                                           const QString& /*localName*/,
132                                           const QString& qName,
133                                           const QXmlStreamAttributes& /*atts*/)
134 {
135     QString lcName = qName.toLower();
136 
137     RG_DEBUG << "HydrogenXMLHandler::startElement - " << lcName;
138 
139     if (lcName == "note") {
140 
141         if (m_inInstrument)
142             return false;
143 
144         m_inNote = true;
145 
146     } else if (lcName == "instrument") {
147 
148         // Beware instrument attributes inside Notes
149         if (!m_inNote)
150             m_inInstrument = true;
151     } else if (lcName == "pattern") {
152         m_inPattern = true;
153         m_segmentAdded = false; // flag the segments being added
154     } else if (lcName == "sequence") {
155 
156         // Create a new segment and set some flags
157         //
158         m_segment = new Segment();
159         m_newSegment = true;
160         m_inSequence = true;
161     }
162 
163     m_currentProperty = lcName;
164 
165     return true;
166 }
167 
168 bool
startElement(const QString &,const QString &,const QString & qName,const QXmlStreamAttributes &)169 HydrogenXMLHandler::startElement(const QString& /*namespaceURI*/,
170                                  const QString& /*localName*/,
171                                  const QString& qName,
172                                  const QXmlStreamAttributes& /*atts*/)
173 {
174  bool rc=false;
175  QXmlStreamAttributes DummyAttr;
176  QString DummyQString;
177 
178  if (m_version=="") {
179    /* no version yet, use 093 */
180    rc=startElement_093(DummyQString, DummyQString, qName, DummyAttr);
181  }
182  else {
183    /* select version dependant function */
184    rc=startElement_093(DummyQString, DummyQString, qName, DummyAttr);
185  }
186  return rc;
187 }
188 
189 bool
endElement_093(const QString &,const QString &,const QString & qName)190 HydrogenXMLHandler::endElement_093(const QString& /*namespaceURI*/,
191                                    const QString& /*localName*/,
192                                    const QString& qName)
193 {
194     QString lcName = qName.toLower();
195 
196     if (lcName == "note") {
197 
198         RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Note : position = " << m_position
199         << ", velocity = " << m_velocity
200         << ", panL = " << m_panL
201         << ", panR = " << m_panR
202         << ", pitch = " << m_pitch
203         << ", instrument = " << m_instrument;
204 
205         timeT barLength = m_composition->getBarEnd(m_currentBar) -
206                           m_composition->getBarStart(m_currentBar);
207 
208         timeT pos = m_composition->getBarStart(m_currentBar) +
209                     timeT(
210                         double(m_position) / double(m_patternSize) * double(barLength));
211 
212         // Insert a rest if we've got a new segment
213         //
214         if (m_newSegment) {
215             Event *restEvent = new Event(Note::EventRestType,
216                                          m_composition->getBarStart(m_currentBar),
217                                          pos - m_composition->getBarStart(m_currentBar),
218                                          Note::EventRestSubOrdering);
219             m_segment->insert(restEvent);
220             m_newSegment = false;
221         }
222 
223         // Create and insert this event
224         //
225         Event *noteEvent = new Event(Note::EventType,
226                                      pos, Note(Note::Semiquaver).getDuration());
227 
228         // get drum mapping from instrument and calculate velocity
229         noteEvent->set
230         <Int>(
231             BaseProperties::PITCH, 36 + m_instrument);
232         noteEvent->set
233         <Int>(BaseProperties::VELOCITY,
234               int(127.0 * m_velocity * m_volume *
235                   m_instrumentVolumes[m_instrument]));
236         m_segment->insert(noteEvent);
237 
238         m_inNote = false;
239 
240     } else if (lcName == "instrument" && m_inInstrument) {
241 
242         RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Instrument : id = " << m_id
243         << ", muted = " << m_muted
244         << ", volume = " << m_instrumentVolumes[m_instrument]
245         << ", filename = \"" << m_fileName << "\"";
246 
247         m_inInstrument = false;
248 
249     } else if (lcName == "pattern") {
250         m_inPattern = false;
251 
252         if (m_segmentAdded) {
253 
254             // Add a blank track to demarcate patterns
255             //
256             Track *track = new Track
257                            (m_currentTrackNb, m_drumInstrument, m_currentTrackNb,
258                             "<blank spacer>", false);
259             m_currentTrackNb++;
260             m_composition->addTrack(track);
261 
262             std::vector<TrackId> trackIds;
263             trackIds.push_back(track->getId());
264             m_composition->notifyTracksAdded(trackIds);
265 
266             m_segmentAdded = false;
267 
268             // Each pattern has it's own bar so that the imported
269             // song shows off each pattern a bar at a time.
270             //
271             m_currentBar++;
272         }
273 
274     } else if (lcName == "sequence") {
275 
276         // If we're closing out a sequencer tab and we have a m_segment then
277         // we should close up and add that segment.  Only create if we have
278         // some Events in it
279         //
280         if (m_segment->size() > 0) {
281 
282             m_segment->setTrack(m_currentTrackNb);
283 
284             Track *track = new Track
285                            (m_currentTrackNb, m_drumInstrument, m_currentTrackNb,
286                             m_patternName, false);
287             m_currentTrackNb++;
288 
289             // Enforce start and end markers for this bar so that we have a
290             // whole bar unit segment.
291             //
292             m_segment->setEndMarkerTime(m_composition->getBarEnd(m_currentBar));
293             QString label = QString("%1 - %2 %3 %4").arg(strtoqstr(m_patternName))
294                             .arg(strtoqstr(m_sequenceName))
295                             .arg(tr(" imported from Hydrogen ")).arg(strtoqstr(m_version));
296             m_segment->setLabel(qstrtostr(label));
297 
298             m_composition->addTrack(track);
299 
300             std::vector<TrackId> trackIds;
301             trackIds.push_back(track->getId());
302             m_composition->notifyTracksAdded(trackIds);
303 
304             m_composition->addSegment(m_segment);
305             m_segment = nullptr;
306 
307             m_segmentAdded = true;
308         }
309 
310         m_inSequence = false;
311 
312     } else if (lcName == "version") {
313         // up to now we can only read files of version 0.9.3 and earlier
314         // there was an xml format change  in 0.9.4
315         Version canHandleVersion, versionInFile;
316 
317         canHandleVersion.qstrtoversion("0.9.3");
318         versionInFile.qstrtoversion(strtoqstr(m_version));
319 
320         RG_DEBUG << "HydrogenXMLHandler::endElement version " << m_version;
321         RG_DEBUG << "ch_major: " << canHandleVersion.Major() <<
322                     "  ch_minor: " << canHandleVersion.Minor() <<
323                     "  ch_micro: " << canHandleVersion.Micro();
324         RG_DEBUG << "if_major: " << versionInFile.Major() <<
325                     "  if_minor: " << versionInFile.Minor() <<
326                     "  if_micro: " << versionInFile.Micro();
327 
328         bool bCanHandleFile=(versionInFile<=canHandleVersion);
329 
330         if (bCanHandleFile==true) {
331           // go on, this is a good version
332           RG_DEBUG << "HydrogenXMLHandler::endElement version: version ok ";
333         }
334         else {
335           // error
336           RG_DEBUG << "HydrogenXMLHandler::endElement version: bad version (file created with hydrogen version " << m_version << " can not be parsed)";
337           return false;
338         }
339     }
340 
341     return true;
342 }
343 
344 bool
endElement(const QString &,const QString &,const QString & qName)345 HydrogenXMLHandler::endElement(const QString& /*namespaceURI*/,
346                                const QString& /*localName*/,
347                                const QString& qName)
348 {
349  bool rc=false;
350  QString DummyQString;
351 
352  if (m_version=="") {
353    /* no version yet, use 093 */
354    rc=endElement_093(DummyQString, DummyQString, qName);
355  }
356  else {
357    /* select version dependant function */
358    rc=endElement_093(DummyQString, DummyQString, qName);
359  }
360  return rc;
361 }
362 
363 bool
characters_093(const QString & chars)364 HydrogenXMLHandler::characters_093(const QString& chars)
365 {
366     QString ch = chars.trimmed();
367     if (ch == "")
368         return true;
369 
370     if (m_inNote) {
371         if (m_currentProperty == "position") {
372             m_position = ch.toInt();
373         } else if (m_currentProperty == "velocity") {
374             m_velocity = qstrtodouble(ch);
375         } else if (m_currentProperty == "pan_L") {
376             m_panL = qstrtodouble(ch);
377         } else if (m_currentProperty == "pan_R") {
378             m_panR = qstrtodouble(ch);
379         } else if (m_currentProperty == "pitch") {
380             m_pitch = qstrtodouble(ch);
381         } else if (m_currentProperty == "instrument") {
382             m_instrument = ch.toInt();
383 
384             // Standard kit conversion - hardcoded conversion for Hyrdogen's default
385             // drum kit.  The m_instrument mapping for low values maps well onto the
386             // kick drum GM kit starting point (MIDI pitch = 36).
387             //
388             switch (m_instrument) {
389             case 11:  // Cowbell
390                 m_instrument = 20;
391                 break;
392             case 12:  // Ride Jazz
393                 m_instrument = 15;
394                 break;
395             case 14:  // Ride Rock
396                 m_instrument = 17;
397                 break;
398             case 15:  // Crash Jazz
399                 m_instrument = 16;
400                 break;
401 
402             default:
403                 break;
404             }
405 
406         }
407     } else if (m_inInstrument) {
408         if (m_currentProperty == "id") {
409             m_id = ch.toInt();
410         } else if (m_currentProperty == "ismuted") {
411             if (ch.toLower() == "true")
412                 m_muted = true;
413             else
414                 m_muted = false;
415         } else if (m_currentProperty == "filename") {
416             m_fileName = qstrtostr(chars); // don't strip whitespace from the filename
417         } else if (m_currentProperty == "volume") {
418             m_instrumentVolumes.push_back(qstrtodouble(ch));
419         }
420 
421 
422     } else if (m_inPattern) {
423 
424         // Pattern attributes
425 
426         if (m_currentProperty == "name") {
427             if (m_inSequence)
428                 m_sequenceName = qstrtostr(chars);
429             else
430                 m_patternName = qstrtostr(chars);
431         } else if (m_currentProperty == "size") {
432             m_patternSize = ch.toInt();
433         }
434 
435     } else {
436 
437         // Global attributes
438         if (m_currentProperty == "version") {
439             m_version = qstrtostr(chars);
440         } else if (m_currentProperty == "bpm") {
441 
442             m_bpm = qstrtodouble(ch);
443             m_composition->addTempoAtTime
444             (0, Composition::getTempoForQpm(m_bpm));
445 
446         } else if (m_currentProperty == "volume") {
447             m_volume = qstrtodouble(ch);
448         } else if (m_currentProperty == "name") {
449             m_name = qstrtostr(chars);
450         } else if (m_currentProperty == "author") {
451             m_author = qstrtostr(chars);
452         } else if (m_currentProperty == "notes") {
453             m_notes = qstrtostr(chars);
454         } else if (m_currentProperty == "mode") {
455             if (ch.toLower() == "song")
456                 m_songMode = true;
457             else
458                 m_songMode = false;
459         }
460     }
461 
462     return true;
463 }
464 
465 bool
characters(const QString & chars)466 HydrogenXMLHandler::characters(const QString& chars)
467 {
468  bool rc=false;
469 
470  if (m_version=="") {
471    /* no version yet, use 093 */
472    rc=characters_093(chars);
473  }
474  else {
475    /* select version dependant function */
476    rc=characters_093(chars);
477  }
478  return rc;
479 }
480 
481 bool
endDocument()482 HydrogenXMLHandler::endDocument()
483 {
484     RG_DEBUG << "HydrogenXMLHandler::endDocument";
485     return true;
486 }
487 
488 }
489