File: importmidi_tempo.cpp

package info (click to toggle)
musescore3 3.2.3%2Bdfsg2-19
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 218,192 kB
  • sloc: cpp: 291,369; xml: 200,226; sh: 3,779; ansic: 1,447; python: 393; makefile: 249; perl: 82; pascal: 79
file content (152 lines) | stat: -rw-r--r-- 6,262 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include "importmidi_tempo.h"

#include "importmidi_inner.h"
#include "importmidi_beat.h"
#include "libmscore/score.h"
#include "libmscore/measure.h"
#include "libmscore/tempo.h"
#include "libmscore/tempotext.h"
#include "importmidi_operations.h"


namespace Ms {
namespace MidiTempo {

ReducedFraction time2Tick(double time, double ticksPerSec)
      {
      return ReducedFraction::fromTicks(int(ticksPerSec * time));
      }

// tempo in beats per second

double findBasicTempo(const std::multimap<int, MTrack> &tracks, bool isHumanPerformance)
      {
      for (const auto &track: tracks) {
                        // don't read tempo from tempo track for human performed files
                        // because very often the tempo in such track is completely erroneous
            if (isHumanPerformance && track.second.chords.empty())
                  continue;
            for (const auto &ie : track.second.mtrack->events()) {
                  const MidiEvent &e = ie.second;
                  if (e.type() == ME_META && e.metaType() == META_TEMPO) {
                        const uchar* data = (uchar*)e.edata();
                        const unsigned tempo = data[2] + (data[1] << 8) + (data[0] << 16);
                        return 1000000.0 / double(tempo);
                        }
                  }
            }

      return 2;   // default beats per second = 120 beats per minute
      }

void setTempoToScore(Score *score, int tick, double beatsPerSecond)
      {
      if (score->tempomap()->find(tick) != score->tempomap()->end())
            return;
                  // don't repeat tempo, always set only tempo for tick 0
      if (tick > 0 && score->tempo(Fraction::fromTicks(tick)) == beatsPerSecond)
            return;

      score->setTempo(Fraction::fromTicks(tick), beatsPerSecond);

      auto *data = midiImportOperations.data();
      if (data->trackOpers.showTempoText.value()) {
            const int tempoInBpm = qRound(beatsPerSecond * 60.0);

            TempoText *tempoText = new TempoText(score);
            tempoText->setTempo(beatsPerSecond);
            tempoText->setXmlText(QString("<sym>metNoteQuarterUp</sym> = %1").arg(tempoInBpm));
            tempoText->setTrack(0);

            Measure *measure = score->tick2measure(Fraction::fromTicks(tick));
            if (!measure) {
                  qDebug("MidiTempo::setTempoToScore: no measure for tick %d", tick);
                  return;
                  }
            Segment *segment = measure->getSegment(SegmentType::ChordRest, Fraction::fromTicks(tick));
            if (!segment) {
                  qDebug("MidiTempo::setTempoToScore: no chord/rest segment for tempo at %d", tick);
                  return;
                  }
            segment->add(tempoText);
            data->hasTempoText = true;      // to show tempo text column in the MIDI import panel
            }
      }

double roundToBpm(double beatsPerSecond)
      {
      return qRound(beatsPerSecond * 60.0) / 60.0;
      }

void applyAllTempoEvents(const std::multimap<int, MTrack> &tracks, Score *score)
      {
      for (const auto &track: tracks) {
            if (track.second.isDivisionInTps) {     // ticks per second
                  const double ticksPerBeat = MScore::division;
                  const double beatsPerSecond = roundToBpm(track.second.division / ticksPerBeat);
                  setTempoToScore(score, 0, beatsPerSecond);
                  }
            else {      // beats per second
                  for (const auto &ie : track.second.mtrack->events()) {
                        const MidiEvent &e = ie.second;
                        if (e.type() == ME_META && e.metaType() == META_TEMPO) {
                              const auto tick = toMuseScoreTicks(
                                                ie.first, track.second.division, false);
                              const uchar* data = (uchar*)e.edata();
                              const unsigned tempo = data[2] + (data[1] << 8) + (data[0] << 16);
                              const double beatsPerSecond = roundToBpm(1000000.0 / tempo);
                              setTempoToScore(score, tick.ticks(), beatsPerSecond);
                              }
                        }
                  }
            }
      }

void setTempo(const std::multimap<int, MTrack> &tracks, Score *score)
      {
      score->tempomap()->clear();
      auto *midiData = midiImportOperations.data();
      std::set<ReducedFraction> beats = midiData->humanBeatData.beatSet;    // copy

      if (beats.empty()) {
                        // it's most likely not a human performance;
                        // we find all tempo events and set tempo changes to score
            applyAllTempoEvents(tracks, score);
            }
      else {            // calculate and set tempo from adjusted beat locations
            if (midiData->trackOpers.measureCount2xLess.value())
                  MidiBeat::removeEvery2ndBeat(beats);

            Q_ASSERT_X(beats.size() > 1, "MidiBeat::setTempo", "Human beat count < 2");

            double averageTempoFactor = 0.0;
            int counter = 0;
            auto it = beats.begin();
            auto beatStart = *it;
            const auto newBeatLen = ReducedFraction::fromTicks(MScore::division);

            for (++it; it != beats.end(); ++it) {
                  const auto &beatEnd = *it;

                  Q_ASSERT_X(beatEnd > beatStart, "MidiBeat::detectTempoChanges",
                             "Beat end <= beat start that is incorrect");

                  averageTempoFactor += (newBeatLen / (beatEnd - beatStart)).toDouble();
                  ++counter;
                  beatStart = beatEnd;
                  }
            averageTempoFactor /= counter;

            const double basicTempo = MidiTempo::findBasicTempo(tracks, true);
            const double tempo = roundToBpm(basicTempo * averageTempoFactor);

            score->tempomap()->clear();         // use only one tempo marking for all score
            setTempoToScore(score, 0, tempo);
            }

      if (score->tempomap()->empty())
            score->tempomap()->setTempo(0, 2.0);      // default tempo
      }

} // namespace MidiTempo
} // namespace Ms