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_NO_DEBUG_PRINT 1
19 #define RG_MODULE_STRING "[FitToBeatsCommand]"
20
21 #include "FitToBeatsCommand.h"
22 #include "misc/Debug.h"
23 #include "base/Event.h"
24 #include "base/CompositionTimeSliceAdapter.h"
25 #include "base/NotationTypes.h"
26
27 namespace Rosegarden
28 {
29
30 // Construct FitToBeatsCommand
31 // @author Tom Breton (Tehom)
FitToBeatsCommand(Segment * grooveSegment)32 FitToBeatsCommand::FitToBeatsCommand(Segment *grooveSegment)
33 :
34 NamedCommand(getGlobalName()),
35 m_composition(grooveSegment->getComposition()),
36 m_executed(false)
37 {
38 initialise(grooveSegment);
39 }
40
41 // Destruct FitToBeatsCommand
42 // @author Tom Breton (Tehom)
~FitToBeatsCommand()43 FitToBeatsCommand::~FitToBeatsCommand()
44 {}
45
46 // Change to newTempi, removing all old tempi
47 // @author Tom Breton (Tehom)
48 void
changeAllTempi(TempoMap newTempi)49 FitToBeatsCommand::changeAllTempi(TempoMap newTempi)
50 {
51 const unsigned int Nb = m_composition->getTempoChangeCount();
52 for (unsigned int i = 0; i < Nb; i++)
53 { m_composition->removeTempoChange(0); }
54
55 for (TempoMap::iterator i = newTempi.begin(); i != newTempi.end(); ++i) {
56 m_composition->addTempoAtTime(i->first, i->second);
57 }
58 }
59
60 // @remarks Change from using oldSegments to newSegments
61 // @author Tom Breton (Tehom)
62 void
changeSegments(SegmentMultiSet oldSegments,SegmentMultiSet newSegments)63 FitToBeatsCommand::changeSegments(SegmentMultiSet oldSegments,
64 SegmentMultiSet newSegments)
65 {
66 for (SegmentMultiSet::iterator i = oldSegments.begin();
67 i != oldSegments.end();
68 ++i) {
69 m_composition->detachSegment(*i);
70 }
71
72 for (SegmentMultiSet::iterator i = newSegments.begin();
73 i != newSegments.end();
74 ++i) {
75 m_composition->addSegment(*i);
76 }
77 }
78
79 // Initialize FitToBeatsCommand
80 // @author Tom Breton (Tehom)
81 void
initialise(Segment * s)82 FitToBeatsCommand::initialise(Segment *s)
83 {
84 m_oldTempi.clear();
85 m_newTempi.clear();
86 m_oldSegments.clear();
87 m_newSegments.clear();
88
89 // Get the real times from the beat segment
90 vecRealTime beatRealTimes;
91 int success =
92 getBeatRealTimes(s, beatRealTimes);
93 if(!success) { return; }
94
95 // Store the current tempos
96 getCurrentTempi(*m_composition, m_oldTempi);
97 tempoT defaultTempo = m_composition->getCompositionDefaultTempo();
98
99 // A temporary copy of the composition. It is not intended to be
100 // a complete copy, it just provides a place for new segments and
101 // tempi to live until we have fully copied events to their new
102 // state.
103 Composition scratchComposition;
104 scratchComposition.clear();
105 scratchComposition.setCompositionDefaultTempo(defaultTempo);
106
107
108 // Set tempos in scratchComposition such that each observed beat
109 // in beatRealTimes takes one beatTime.
110 {
111 // Starting time is the same for both.
112 timeT firstBeatTime =
113 m_composition->getElapsedTimeForRealTime(beatRealTimes[0]);
114
115 unsigned int numBeats = beatRealTimes.size();
116
117 // Get interval between beats from time signature.
118 // Get time signature
119 TimeSignature timeSig =
120 m_composition->getTimeSignatureAt(firstBeatTime);
121 timeT beatTime = timeSig.getBeatDuration();
122
123 // We're going to visit the beats in reverse order, and always
124 // remembering the next beat (the next beat time-wise, which
125 // the iterator visited last time)
126 vecRealTime::const_reverse_iterator i = beatRealTimes.rbegin();
127
128 // Treat the final beat specially
129 timeT finalBeatTime = firstBeatTime + ((numBeats - 1) * beatTime);
130 RealTime finalRealTime = beatRealTimes.back();
131 scratchComposition.addTempoAtTime(finalBeatTime, defaultTempo, -1);
132 // Step past it
133 ++i;
134
135 // Set up loop variables
136 timeT nextBeatTime = finalBeatTime;
137 RealTime nextRealTime = finalRealTime;
138 // nextTempo is unused, it will be used if we make ramped
139 // tempi.
140 /* tempoT nextTempo = defaultTempo; */
141
142
143 // Treat all the other beats.
144 while (i != beatRealTimes.rend()) {
145 timeT timeNow = nextBeatTime - beatTime;
146 RealTime realTimeNow = *i;
147 RealTime realTimeDelta = nextRealTime - realTimeNow;
148 // Calculate what tempoT will get us to the right real
149 // time. For now, we use unramped tempi.
150 tempoT rampTo = -1;
151 tempoT tempo = Composition::timeRatioToTempo(realTimeDelta,
152 beatTime, rampTo);
153 scratchComposition.addTempoAtTime(timeNow, tempo, rampTo);
154
155 // Step
156 nextBeatTime = timeNow;
157 nextRealTime = realTimeNow;
158 /* nextTempo = tempo; */
159 ++i;
160 }
161 }
162 // We don't try to copy over tempo changes that are outside the
163 // range of the groove segment (before or after). We don't try to
164 // correct for accumulated error.
165
166 // Done setting Tempi
167
168 // Collect tempi
169 getCurrentTempi(scratchComposition, m_newTempi);
170
171
172 // Copy all the events to scratchComposition. The copies will be
173 // at the same realtime but not the same timeT. Even events in
174 // the groove segment get copied.
175 SegmentMultiSet &origSegments = m_composition->getSegments();
176 for (Composition::iterator i = origSegments.begin();
177 i != origSegments.end();
178 ++i) {
179 Segment * const oldSegment = *i;
180
181 // We'd prefer to just make a segment with no events that's
182 // otherwise the same as the old one but we can't.
183 Segment *newSegment = oldSegment->clone(false);
184 newSegment->clear();
185
186 // Add the segments into appropriate containers.
187 // scratchComposition owns the new segments during initialise,
188 // but m_newSegments will own them after initialise returns.
189 m_oldSegments.insert(oldSegment);
190 m_newSegments.insert(newSegment);
191 scratchComposition.addSegment(newSegment);
192
193 //Iterate over notes in the old segment.
194 const timeT earliestTime = 0;
195 for (Segment::iterator j = oldSegment->findTime(earliestTime);
196 oldSegment->isBeforeEndMarker(j);
197 ++j) {
198 // Get the old-timed event times.
199 timeT oldStartTime = (*j)->getAbsoluteTime();
200 timeT duration = (*j)->getDuration();
201
202 // Get the real event times.
203 RealTime RealStartTime =
204 m_composition->getElapsedRealTime(oldStartTime);
205
206 RealTime RealEndTime;
207 if (duration == 0) {
208 RealEndTime = RealStartTime;
209 }
210 else {
211 timeT oldEndTime = oldStartTime + duration;
212 RealEndTime =
213 m_composition->getElapsedRealTime(oldEndTime);
214 }
215
216 // Get the new target times. Use scratchComposition
217 // because its times use the new Tempi.
218 timeT newStartTime =
219 scratchComposition.getElapsedTimeForRealTime(RealStartTime);
220 timeT newDuration;
221 if (duration == 0) {
222 newDuration = 0;
223 }
224 else {
225 timeT newEndTime =
226 scratchComposition.getElapsedTimeForRealTime(RealEndTime);
227 newDuration = newEndTime - newStartTime;
228 }
229
230 // Add a parallel event in the new segment.
231 newSegment->insert(new Event(**j, newStartTime, newDuration));
232 }
233 }
234
235 // Detach the segments before scratchComposition goes out of
236 // scope. m_newSegments contains exactly the segments that need
237 // to be detached.
238 for (SegmentMultiSet::iterator i = m_newSegments.begin();
239 i != m_newSegments.end();
240 ++i) {
241 scratchComposition.weakDetachSegment(*i);
242 }
243
244 // We do the actual swapping of old <-> new in (un)execute.
245 }
246
247 // @param A beat segment
248 // @param Reference to a vector of RealTime, to be filled.
249 // @returns non-zero on success
250 // @author Tom Breton (Tehom)
251 int
getBeatRealTimes(Segment * s,vecRealTime & beatRealTimes)252 FitToBeatsCommand::getBeatRealTimes(Segment *s,
253 vecRealTime &beatRealTimes)
254 {
255 for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) {
256 if ((*i)->isa(Note::EventType)) {
257 RealTime Time =
258 s->getComposition()->getElapsedRealTime
259 ((*i)->getAbsoluteTime());
260 beatRealTimes.push_back(Time);
261 }
262 }
263
264 return (beatRealTimes.size() > 1);
265 }
266
267 // Clone a given tempo by index.
268 // @remarks Currently discards ramping because TempoT doesn't hold it.
269 // @author Tom Breton (Tehom)
270 FitToBeatsCommand::TempoChange
getTempoChange(Composition & composition,int i)271 FitToBeatsCommand::getTempoChange(Composition &composition, int i)
272 {
273 return composition.getTempoChange(i);
274 }
275
276 // Get the current tempos of the composition
277 // @param A composition
278 // @param Reference to a TempoMap, to be filled.
279 // @author Tom Breton (Tehom)
280 void
getCurrentTempi(Composition & composition,TempoMap & Tempos)281 FitToBeatsCommand::getCurrentTempi(Composition &composition, TempoMap &Tempos)
282 {
283 const int numTempoChanges = composition.getTempoChangeCount();
284 for (int i = 0; i < numTempoChanges; ++i) {
285 TempoChange tempoChange =
286 getTempoChange(composition, i);
287 Tempos[tempoChange.first] = tempoChange.second;
288 }
289 }
290
291 // Execute the command
292 // @author Tom Breton (Tehom)
293 void
execute()294 FitToBeatsCommand::execute()
295 {
296 RG_DEBUG << "Executing FitToBeatsCommand";
297 changeAllTempi(m_newTempi);
298 changeSegments(m_oldSegments, m_newSegments);
299 m_executed = true;
300 }
301
302 // Unexecute the command
303 // @author Tom Breton (Tehom)
304 void
unexecute()305 FitToBeatsCommand::unexecute()
306 {
307 changeAllTempi(m_oldTempi);
308 changeSegments(m_newSegments, m_oldSegments);
309 m_executed = false;
310 }
311
312 } //End namespace Rosegarden
313