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