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
init_entries(ring_t * entries)18 #define RG_MODULE_STRING "[MidiInserter]"
19 #define RG_NO_DEBUG_PRINT 1
20
21 #include "MidiInserter.h"
22
23 #include "MidiEvent.h"
24 #include "base/Composition.h"
25 #include "base/MidiTypes.h"
26 #include "misc/Debug.h"
27 #include "sound/MidiFile.h"
28 #include "sound/MappedEvent.h"
29
30 #include <QtGlobal>
31
32 #include <string>
33
34 #define MIDI_DEBUG 1
35
36 namespace Rosegarden
37 {
38 /*** TrackData ***/
39
40 // Insert and take ownership of a MidiEvent. The event's time is
41 // converted from an absolute time to a time delta relative to the
42 // previous time.
43 // @author Tom Breton (Tehom)
44 void
45 MidiInserter::TrackData::
46 insertMidiEvent(MidiEvent *event)
47 {
48 timeT absoluteTime = event->getTime();
49 timeT delta = absoluteTime - m_previousTime;
50 if (delta < 0)
51 { delta = 0; }
52 else
53 { m_previousTime = absoluteTime; }
54 event->setTime(delta);
55 #ifdef MIDI_DEBUG
56 RG_DEBUG << "Converting absoluteTime" << (int)absoluteTime
57 << "to delta" << (int)delta;
58 #endif
59 m_midiTrack.push_back(event);
60 }
61
62 void
63 MidiInserter::TrackData::
64 endTrack(timeT t)
65 {
66 // Safe even if t is too early in timeT because insertMidiEvent
67 // fixes it.
68 insertMidiEvent
69 (new MidiEvent(t, MIDI_FILE_META_EVENT,
70 MIDI_END_OF_TRACK, ""));
71 }
72
73 void
74 MidiInserter::TrackData::
75 insertTempo(timeT t, long tempo)
76 {
77 double qpm = Composition::getTempoQpm(tempo);
78 long tempoValue = long(60000000.0 / qpm + 0.01);
79
80 std::string tempoString;
81 tempoString += (MidiByte) ( tempoValue >> 16 & 0xFF );
82 tempoString += (MidiByte) ( tempoValue >> 8 & 0xFF );
83 tempoString += (MidiByte) ( tempoValue & 0xFF );
84
85
86 insertMidiEvent
87 (new MidiEvent(t,
88 MIDI_FILE_META_EVENT,
89 MIDI_SET_TEMPO,
90 tempoString));
91 }
92
93 /*** MidiInserter ***/
94 const timeT MidiInserter::crotchetDuration =
95 Note(Note::Crotchet).getDuration();
96
97 MidiInserter::
98 MidiInserter(Composition &composition, int timingDivision, RealTime trueEnd) :
99 m_comp(composition),
100 m_timingDivision(timingDivision),
101 m_finished(false),
102 m_trueEnd(trueEnd),
103 m_previousRealTime(RealTime::zeroTime),
104 m_previousTime(0),
105 m_ramping(false)
106 { setup(); }
107
108 // Get the absolute RG time of evt. We don't convert time to a delta
109 // here because if we didn't end up inserting the event, the new
110 // reference time that we made would be wrong.
111 // @author Tom Breton (Tehom)
112 timeT
113 MidiInserter::
114 getAbsoluteTime(RealTime realtime)
115 {
116 timeT time = m_comp.getElapsedTimeForRealTime(realtime);
117 timeT retVal = (time * m_timingDivision) / crotchetDuration;
118 #ifdef MIDI_DEBUG
119 RG_DEBUG << "Converting RealTime" << realtime
120 << "to timeT" << retVal
121 << "intermediate" << time;
122 #endif
123
124 return retVal;
125 }
126
127 // Initialize a normal track (not a conductor track)
128 // @author Tom Breton (Tehom)
129 // Adapted from MidiFile.cpp
130 void
131 MidiInserter::
132 initNormalTrack(TrackData &trackData, TrackId RGTrackPos)
133 {
134 Track *track = m_comp.getTrackById(RGTrackPos);
135 trackData.m_previousTime = 0;
136 trackData.
137 insertMidiEvent
138 (new MidiEvent(0,
139 MIDI_FILE_META_EVENT,
140 MIDI_TRACK_NAME,
141 track->getLabel()));
142 }
143
144 // Return the respective track data, creating it if needed.
145 // @author Tom Breton (Tehom)
146 MidiInserter::TrackData &
147 MidiInserter::
148 getTrackData(TrackId RGTrackPos, int channelNb)
149 {
150 #ifdef MIDI_DEBUG
151 RG_DEBUG << "Getting track " << (int)RGTrackPos;
152 #endif
153 // Some events like TimeSig and Tempo have invalid trackId and
154 // should be written on the conductor track.
155 if (RGTrackPos == NoTrack)
156 { return m_conductorTrack; }
157 // Otherwise we're looking it up.
158 TrackKey key = TrackKey(RGTrackPos, channelNb);
159 // If we are starting a new track, initialize it.
160 if (m_trackPosMap.find(key) == m_trackPosMap.end()) {
161 initNormalTrack(m_trackPosMap[key], RGTrackPos);
162 }
163 return m_trackPosMap[key];
164 }
165
166 // Get ready to receive events. Assumes nothing is written to
167 // tracks yet.
168 // @author Tom Breton (Tehom)
169 // Adapted from MidiFile.cpp
170 void
171 MidiInserter::
172 setup()
173 {
174 m_conductorTrack.m_previousTime = 0;
175
176 // Insert the Rosegarden Signature Track here and any relevant
177 // file META information - this will get written out just like
178 // any other MIDI track.
179 //
180 m_conductorTrack.
181 insertMidiEvent
182 (new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_COPYRIGHT_NOTICE,
183 m_comp.getCopyrightNote()));
184
185 m_conductorTrack.
186 insertMidiEvent
187 (new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
188 "Created by Rosegarden"));
189
190 m_conductorTrack.
191 insertMidiEvent
192 (new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
193 "http://www.rosegardenmusic.com/"));
194 }
195
196 // Done receiving events. Tracks will be complete when this returns.
197 // @author Tom Breton (Tehom)
198 void
199 MidiInserter::
200 finish()
201 {
202 if(m_finished) { return; }
203 timeT endOfComp = getAbsoluteTime(m_trueEnd);
204 m_conductorTrack.endTrack(endOfComp);
205 for (TrackIterator i = m_trackPosMap.begin();
206 i != m_trackPosMap.end();
207 ++i) {
208 i->second.endTrack(endOfComp);
209 }
210 m_finished = true;
211 }
212
213 // Insert a (MidiEvent) copy of evt.
214 // @author Tom Breton (Tehom)
215 // Adapted from MidiFile.cpp
216 void
217 MidiInserter::
218 insertCopy(const MappedEvent &evt)
219 {
220 Q_ASSERT(!m_finished);
221
222 MidiByte midiChannel = evt.getRecordedChannel();
223 TrackData& trackData = getTrackData(evt.getTrackId(), midiChannel);
224 timeT midiEventAbsoluteTime = getAbsoluteTime(evt.getEventTime());
225
226 // If we are ramping, calculate a previous tempo that would get us
227 // to this event at this time and pre-insert it, unless this
228 // event's time is the same as last.
229 if (m_ramping && (midiEventAbsoluteTime != m_previousTime)) {
230 RealTime diffReal = evt.getEventTime() - m_previousRealTime;
231 // We undo the scaling getAbsoluteTime does.
232 timeT diffTime =
233 (midiEventAbsoluteTime - m_previousTime) *
234 crotchetDuration /
235 m_timingDivision;
236
237 tempoT bridgingTempo =
238 Composition::timeRatioToTempo(diffReal, diffTime, -1);
239
240 trackData.insertTempo(m_previousTime, bridgingTempo);
241 m_previousRealTime = evt.getEventTime();
242 m_previousTime = midiEventAbsoluteTime;
243 }
244 #ifdef MIDI_DEBUG
245 RG_DEBUG << "Inserting an event for channel " << (int)midiChannel + 1;
246 #endif
247
248 try {
249 switch (evt.getType())
250 {
251 case MappedEvent::Tempo:
252 {
253 m_ramping = (evt.getData1() > 0) ? true : false;
254 // Yes, we fetch it from "instrument" because
255 // that's what TempoSegmentMapper puts it in.
256 tempoT tempo = evt.getInstrument();
257 trackData.insertTempo(midiEventAbsoluteTime, tempo);
258 break;
259 }
260 case MappedEvent::TimeSignature:
261 {
262 int numerator = evt.getData1();
263 int denominator = evt.getData2();
264 timeT beatDuration =
265 TimeSignature(numerator, denominator).
266 getBeatDuration();
267
268 std::string timeSigString;
269 timeSigString += (MidiByte) numerator;
270 int denPowerOf2 = 0;
271
272 // Work out how many powers of two are in the denominator
273 //
274 {
275 int denominatorCopy = denominator;
276 while (denominatorCopy >>= 1)
277 { denPowerOf2++; }
278 }
279
280 timeSigString += (MidiByte) denPowerOf2;
281
282 // The third byte is the number of MIDI clocks per beat.
283 // There are 24 clocks per quarter-note (the MIDI clock
284 // is tempo-independent and is not related to the timebase).
285 //
286 int cpb = 24 * beatDuration / crotchetDuration;
287 timeSigString += (MidiByte) cpb;
288
289 // And the fourth byte is always 8, for us (it expresses
290 // the number of notated 32nd-notes in a MIDI quarter-note,
291 // for applications that may want to notate and perform
292 // in different units)
293 //
294 timeSigString += (MidiByte) 8;
295
296 trackData.
297 insertMidiEvent
298 (new MidiEvent(midiEventAbsoluteTime,
299 MIDI_FILE_META_EVENT,
300 MIDI_TIME_SIGNATURE,
301 timeSigString));
302
303 break;
304 }
305 case MappedEvent::MidiController:
306 {
307 trackData.
308 insertMidiEvent
309 (new MidiEvent(midiEventAbsoluteTime,
310 MIDI_CTRL_CHANGE | midiChannel,
311 evt.getData1(), evt.getData2()));
312
313 break;
314 }
315 case MappedEvent::MidiProgramChange:
316 {
317 trackData.
318 insertMidiEvent
319 (new MidiEvent(midiEventAbsoluteTime,
320 MIDI_PROG_CHANGE | midiChannel,
321 evt.getData1()));
322 break;
323 }
324
325 case MappedEvent::MidiNote:
326 case MappedEvent::MidiNoteOneShot:
327 {
328 MidiByte pitch = evt.getData1();
329 MidiByte midiVelocity = evt.getData2();
330
331 if ((evt.getType() == MappedEvent::MidiNote) &&
332 (midiVelocity == 0)) {
333 // It's actually a NOTE_OFF.
334 // "MIDI devices that can generate Note Off
335 // messages, but don't implement velocity
336 // features, will transmit Note Off messages
337 // with a preset velocity of 64"
338 trackData.
339 insertMidiEvent
340 (new MidiEvent(midiEventAbsoluteTime,
341 MIDI_NOTE_OFF | midiChannel,
342 pitch,
343 64));
344 } else {
345 // It's a NOTE_ON.
346 trackData.
347 insertMidiEvent
348 (new MidiEvent(midiEventAbsoluteTime,
349 MIDI_NOTE_ON | midiChannel,
350 pitch,
351 midiVelocity));
352 }
353 break;
354 }
355 case MappedEvent::MidiPitchBend:
356 {
357 trackData.
358 insertMidiEvent
359 (new MidiEvent(midiEventAbsoluteTime,
360 MIDI_PITCH_BEND | midiChannel,
361 evt.getData2(), evt.getData1()));
362 break;
363 }
364
365 case MappedEvent::MidiSystemMessage:
366 {
367 std::string data =
368 DataBlockRepository::getInstance()->
369 getDataBlockForEvent(&evt);
370
371 // check for closing EOX and add one if none found
372 //
373 if (MidiByte(data[data.length() - 1]) != MIDI_END_OF_EXCLUSIVE) {
374 data += (char)MIDI_END_OF_EXCLUSIVE;
375 }
376
377 // construct plain SYSEX event
378 //
379 trackData.
380 insertMidiEvent
381 (new MidiEvent(midiEventAbsoluteTime,
382 MIDI_SYSTEM_EXCLUSIVE,
383 data));
384
385 break;
386 }
387
388 case MappedEvent::MidiChannelPressure:
389 {
390 trackData.
391 insertMidiEvent
392 (new MidiEvent(midiEventAbsoluteTime,
393 MIDI_CHNL_AFTERTOUCH | midiChannel,
394 evt.getData1()));
395
396 break;
397 }
398 case MappedEvent::MidiKeyPressure:
399 {
400 trackData.
401 insertMidiEvent
402 (new MidiEvent(midiEventAbsoluteTime,
403 MIDI_POLY_AFTERTOUCH | midiChannel,
404 evt.getData1(), evt.getData2()));
405
406 break;
407 }
408
409 case MappedEvent::Marker:
410 {
411 std::string metaMessage =
412 DataBlockRepository::getInstance()->
413 getDataBlockForEvent(&evt);
414
415 trackData.
416 insertMidiEvent
417 (new MidiEvent(midiEventAbsoluteTime,
418 MIDI_FILE_META_EVENT,
419 MIDI_TEXT_MARKER,
420 metaMessage));
421
422 break;
423 }
424
425 case MappedEvent::Text:
426 {
427 MidiByte midiTextType = evt.getData1();
428
429 std::string metaMessage =
430 DataBlockRepository::getInstance()->
431 getDataBlockForEvent(&evt);
432
433 trackData.
434 insertMidiEvent
435 (new MidiEvent(midiEventAbsoluteTime,
436 MIDI_FILE_META_EVENT,
437 midiTextType,
438 metaMessage));
439 break;
440 }
441
442 case MappedEvent::KeySignature:
443 {
444 std::string metaMessage;
445 metaMessage += MidiByte(evt.getData1());
446 metaMessage += MidiByte(evt.getData2());
447
448 trackData.insertMidiEvent(
449 new MidiEvent(midiEventAbsoluteTime,
450 MIDI_FILE_META_EVENT,
451 MIDI_KEY_SIGNATURE,
452 metaMessage));
453 }
454 // Pacify compiler warnings about missed cases.
455 case MappedEvent::InvalidMappedEvent:
456 case MappedEvent::Audio:
457 case MappedEvent::AudioCancel:
458 case MappedEvent::AudioLevel:
459 case MappedEvent::AudioStopped:
460 case MappedEvent::AudioGeneratePreview:
461 case MappedEvent::SystemUpdateInstruments:
462 case MappedEvent::SystemJackTransport:
463 case MappedEvent::SystemMMCTransport:
464 case MappedEvent::SystemMIDIClock:
465 case MappedEvent::SystemMetronomeDevice:
466 case MappedEvent::SystemAudioPortCounts:
467 case MappedEvent::SystemAudioPorts:
468 case MappedEvent::SystemFailure:
469 case MappedEvent::Panic:
470 case MappedEvent::SystemMTCTransport:
471 case MappedEvent::SystemMIDISyncAuto:
472 case MappedEvent::SystemAudioFileFormat:
473 default:
474 break;
475 }
476
477 } catch (const Event::NoData &d) {
478 #ifdef MIDI_DEBUG
479 RG_DEBUG << "Caught Event::NoData at " << midiEventAbsoluteTime << ", message is:" << d.getMessage();
480 #endif
481
482 } catch (const Event::BadType &b) {
483 #ifdef MIDI_DEBUG
484 RG_DEBUG << "Caught Event::BadType at " << midiEventAbsoluteTime << ", message is:" << b.getMessage();
485 #endif
486
487 } catch (const SystemExclusive::BadEncoding &e) {
488 #ifdef MIDI_DEBUG
489 RG_DEBUG << "Caught bad SysEx encoding at " << midiEventAbsoluteTime;
490 #endif
491
492 }
493 }
494 void
495 MidiInserter::
496 assignToMidiFile(MidiFile &midifile)
497 {
498 finish();
499
500 midifile.clearMidiComposition();
501
502 // We leave out fields that write doesn't look at.
503 //
504 midifile.m_numberOfTracks = m_trackPosMap.size() + 1;
505 midifile.m_timingDivision = m_timingDivision;
506 midifile.m_format = MidiFile::MIDI_SIMULTANEOUS_TRACK_FILE;
507
508 midifile.m_midiComposition[0] = m_conductorTrack.m_midiTrack;
509 unsigned int index = 0;
510 for (TrackIterator i = m_trackPosMap.begin();
511 i != m_trackPosMap.end();
512 ++i, ++index) {
513 midifile.m_midiComposition[index + 1] =
514 i->second.m_midiTrack;
515 }
516 }
517
518 }
519