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