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 "[CreateTempoMapFromSegmentCommand]"
19 
20 #include "CreateTempoMapFromSegmentCommand.h"
21 
22 #include "misc/Debug.h"
23 #include "base/Composition.h"
24 #include "base/NotationTypes.h"
25 #include "base/RealTime.h"
26 #include "base/Segment.h"
27 
28 
29 namespace Rosegarden
30 {
31 
CreateTempoMapFromSegmentCommand(Segment * groove)32 CreateTempoMapFromSegmentCommand::CreateTempoMapFromSegmentCommand(Segment *groove) :
33         NamedCommand(tr("Set Tempos from Beat Segment")),
34         m_composition(groove->getComposition())
35 {
36     initialise(groove);
37 }
38 
~CreateTempoMapFromSegmentCommand()39 CreateTempoMapFromSegmentCommand::~CreateTempoMapFromSegmentCommand()
40 {
41     // nothing
42 }
43 
44 void
execute()45 CreateTempoMapFromSegmentCommand::execute()
46 {
47     for (TempoMap::iterator i = m_oldTempi.begin(); i != m_oldTempi.end(); ++i) {
48         int n = m_composition->getTempoChangeNumberAt(i->first);
49         if (n < m_composition->getTempoChangeCount()) {
50             m_composition->removeTempoChange(n);
51         }
52     }
53 
54     for (TempoMap::iterator i = m_newTempi.begin(); i != m_newTempi.end(); ++i) {
55         m_composition->addTempoAtTime(i->first, i->second);
56     }
57 }
58 
59 void
unexecute()60 CreateTempoMapFromSegmentCommand::unexecute()
61 {
62     for (TempoMap::iterator i = m_newTempi.begin(); i != m_newTempi.end(); ++i) {
63         int n = m_composition->getTempoChangeNumberAt(i->first);
64         if (n < m_composition->getTempoChangeCount()) {
65             m_composition->removeTempoChange(n);
66         }
67     }
68 
69     for (TempoMap::iterator i = m_oldTempi.begin(); i != m_oldTempi.end(); ++i) {
70         m_composition->addTempoAtTime(i->first, i->second);
71     }
72 }
73 
74 void
initialise(Segment * s)75 CreateTempoMapFromSegmentCommand::initialise(Segment *s)
76 {
77     m_oldTempi.clear();
78     m_newTempi.clear();
79 
80     //!!! need an additional option: per-chord, per-beat, per-bar.
81     // Let's work per-beat for the moment.  Even for this, we should
82     // probably use TimeSignature.getDivisions()
83 
84     std::vector<timeT> beatTimeTs;
85     std::vector<RealTime> beatRealTimes;
86 
87     int startBar = m_composition->getBarNumber(s->getStartTime());
88     int barNo = startBar;
89     int beat = 0;
90 
91     for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) {
92         if ((*i)->isa(Note::EventType)) {
93 
94             bool isNew;
95             TimeSignature sig =
96                 m_composition->getTimeSignatureInBar(barNo, isNew);
97 
98             beatTimeTs.push_back(m_composition->getBarStart(barNo) +
99                                  beat * sig.getBeatDuration());
100 
101             if (++beat >= sig.getBeatsPerBar()) {
102                 ++barNo;
103                 beat = 0;
104             }
105 
106             beatRealTimes.push_back(s->getComposition()->getElapsedRealTime
107                                     ((*i)->getAbsoluteTime()));
108         }
109     }
110 
111     if (beatTimeTs.size() < 2)
112         return ;
113 
114     tempoT prevTempo = 0;
115 
116     // set up m_oldTempi and prevTempo
117 
118     timeT firstBeatTimeT = *beatTimeTs.begin();
119     timeT lastBeatTimeT = *(beatTimeTs.end() - 1);
120 
121     for (int i = m_composition->getTempoChangeNumberAt(firstBeatTimeT - 1) + 1;
122             i <= m_composition->getTempoChangeNumberAt(lastBeatTimeT - 1); ++i) {
123 
124         std::pair<timeT, tempoT> tempoChange =
125             m_composition->getTempoChange(i);
126         m_oldTempi[tempoChange.first] = tempoChange.second;
127         if (prevTempo == 0)
128             prevTempo = tempoChange.second;
129     }
130 
131     RG_DEBUG << "starting tempo: " << prevTempo;
132 
133     timeT quarter = Note(Note::Crotchet).getDuration();
134 
135     for (size_t beat = 1; beat < beatTimeTs.size(); ++beat) {
136 
137         timeT beatTime = beatTimeTs[beat] - beatTimeTs[beat - 1];
138         RealTime beatRealTime = beatRealTimes[beat] - beatRealTimes[beat - 1];
139 
140         // Calculate tempo to nearest qpm.
141         // This is 60 / {quarter note duration in seconds}
142         // = 60 / ( {beat in seconds} * {quarter in ticks} / { beat in ticks} )
143         // = ( 60 * {beat in ticks} ) / ( {beat in seconds} * {quarter in ticks} )
144         // Precision is deliberately limited to qpm to avoid silly values.
145 
146         double beatSec = double(beatRealTime.sec) +
147                          double(beatRealTime.usec() / 1000000.0);
148         double qpm = (60.0 * beatTime) / (beatSec * quarter);
149         tempoT tempo = Composition::getTempoForQpm(int(qpm + 0.001));
150 
151         RG_DEBUG << "prev beat: " << beatTimeTs[beat] << ", prev beat real time " << beatRealTimes[beat];
152         RG_DEBUG << "time " << beatTime << ", rt " << beatRealTime << ", beatSec " << beatSec << ", tempo " << tempo;
153 
154         if (tempo != prevTempo) {
155             m_newTempi[beatTimeTs[beat - 1]] = tempo;
156             prevTempo = tempo;
157         }
158     }
159 
160 }
161 
162 }
163