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