1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Reverse.cpp
6
7 Mark Phillips
8
9 *******************************************************************//**
10
11 \class EffectReverse
12 \brief An Effect that reverses the selected audio.
13
14 *//********************************************************************/
15
16
17
18 #include "Reverse.h"
19 #include "LoadEffects.h"
20
21 #include <math.h>
22
23 #include <wx/intl.h>
24
25 #include "../LabelTrack.h"
26 #include "../WaveClip.h"
27 #include "../WaveTrack.h"
28
29 //
30 // EffectReverse
31 //
32
33 const ComponentInterfaceSymbol EffectReverse::Symbol
34 { XO("Reverse") };
35
36 namespace{ BuiltinEffectsModule::Registration< EffectReverse > reg; }
37
EffectReverse()38 EffectReverse::EffectReverse()
39 {
40 }
41
~EffectReverse()42 EffectReverse::~EffectReverse()
43 {
44 }
45
46 // ComponentInterface implementation
47
GetSymbol()48 ComponentInterfaceSymbol EffectReverse::GetSymbol()
49 {
50 return Symbol;
51 }
52
GetDescription()53 TranslatableString EffectReverse::GetDescription()
54 {
55 return XO("Reverses the selected audio");
56 }
57
58 // EffectDefinitionInterface implementation
59
GetType()60 EffectType EffectReverse::GetType()
61 {
62 return EffectTypeProcess;
63 }
64
IsInteractive()65 bool EffectReverse::IsInteractive()
66 {
67 return false;
68 }
69
70 // Effect implementation
71
Process()72 bool EffectReverse::Process()
73 {
74 //all needed because Reverse should move the labels too
75 this->CopyInputTracks(true); // Set up mOutputTracks.
76 bool bGoodResult = true;
77 int count = 0;
78
79 auto trackRange =
80 mOutputTracks->Any() + &Track::IsSelectedOrSyncLockSelected;
81 trackRange.VisitWhile( bGoodResult,
82 [&](WaveTrack * track) {
83 if (mT1 > mT0) {
84 auto start = track->TimeToLongSamples(mT0);
85 auto end = track->TimeToLongSamples(mT1);
86 auto len = end - start;
87
88 if (!ProcessOneWave(count, track, start, len))
89 bGoodResult = false;
90 }
91 count++;
92 },
93 [&](LabelTrack * track) {
94 track->ChangeLabelsOnReverse(mT0, mT1);
95 count++;
96 }
97 );
98
99 this->ReplaceProcessedTracks(bGoodResult);
100 return bGoodResult;
101 }
102
ProcessOneWave(int count,WaveTrack * track,sampleCount start,sampleCount len)103 bool EffectReverse::ProcessOneWave(int count, WaveTrack * track, sampleCount start, sampleCount len)
104 {
105 bool rValue = true; // return value
106
107 auto end = start + len; // start, end, len refer to the selected reverse region
108
109 // STEP 1:
110 // If a reverse selection begins and/or ends at the inside of a clip
111 // perform a split at the start and/or end of the reverse selection
112 const auto &clips = track->GetClips();
113 // Beware, the array grows as we loop over it. Use integer subscripts, not iterators.
114 for (size_t ii = 0; ii < clips.size(); ++ii) {
115 const auto &clip = clips[ii].get();
116 auto clipStart = clip->GetPlayStartSample();
117 auto clipEnd = clip->GetPlayEndSample();
118 if (clipStart < start && clipEnd > start && clipEnd <= end) { // the reverse selection begins at the inside of a clip
119 double splitTime = track->LongSamplesToTime(start);
120 track->SplitAt(splitTime);
121 }
122 else if (clipStart >= start && clipStart < end && clipEnd > end) { // the reverse selection ends at the inside of a clip
123 double splitTime = track->LongSamplesToTime(end);
124 track->SplitAt(splitTime);
125 }
126 else if (clipStart < start && clipEnd > end) { // the selection begins AND ends at the inside of a clip
127 double splitTime = track->LongSamplesToTime(start);
128 track->SplitAt(splitTime);
129 splitTime = track->LongSamplesToTime(end);
130 track->SplitAt(splitTime);
131 }
132 }
133
134 //STEP 2:
135 // Individually reverse each clip inside the selected region
136 // and apply the appropriate offset after detaching them from the track
137
138 bool checkedFirstClip = false;
139
140 // used in calculating the offset of clips to rearrange
141 // holds the NEW end position of the current clip
142 auto currentEnd = end;
143
144 WaveClipHolders revClips; // holds the reversed clips
145 WaveClipHolders otherClips; // holds the clips that appear after the reverse selection region
146 auto clipArray = track->SortedClipArray();
147 size_t i;
148 for (i=0; i < clipArray.size(); i++) {
149
150 WaveClip *clip = clipArray[i];
151 auto clipStart = clip->GetPlayStartSample();
152 auto clipEnd = clip->GetPlayEndSample();
153
154 if (clipStart >= start && clipEnd <= end) { // if the clip is inside the selected region
155
156 // this is used to check if the selected region begins with a whitespace.
157 // if yes then clipStart (of the first clip) and start are not the same.
158 // adjust currentEnd accordingly and set endMerge to false
159 if(checkedFirstClip == false && clipStart > start) {
160 checkedFirstClip = true;
161 if(i > 0) {
162 if (clipArray[i-1]->GetPlayEndSample() <= start) {
163 currentEnd -= (clipStart - start);
164 }
165 }
166 else {
167 currentEnd -= (clipStart - start);
168 }
169 }
170
171 auto revStart = (clipStart >= start)? clipStart: start;
172 auto revEnd = (clipEnd >= end)? end: clipEnd;
173 auto revLen = revEnd - revStart;
174 if (revEnd >= revStart) {
175 if(!ProcessOneClip(count, track, revStart, revLen, start, end)) // reverse the clip
176 {
177 rValue = false;
178 break;
179 }
180
181 auto clipOffsetStart = currentEnd - (clipEnd - clipStart); // calculate the offset required
182 double offsetStartTime = track->LongSamplesToTime(clipOffsetStart);
183 if(i+1 < clipArray.size()) // update currentEnd if there is a clip to process next
184 {
185 auto nextClipStart = clipArray[i+1]->GetPlayStartSample();
186 currentEnd = currentEnd - (clipEnd - clipStart) - (nextClipStart - clipEnd);
187 }
188
189 revClips.push_back(track->RemoveAndReturnClip(clip)); // detach the clip from track
190 revClips.back()->SetPlayStartTime(track->LongSamplesToTime(track->TimeToLongSamples(offsetStartTime))); // align time to a sample and set offset
191 }
192 }
193 else if (clipStart >= end) { // clip is after the selection region
194 otherClips.push_back(track->RemoveAndReturnClip(clip)); // simply remove and append to otherClips
195 }
196 }
197
198 // STEP 3: Append the clips from
199 // revClips and otherClips back to the track
200 // the last clip of revClips is appended to the track first
201 // PRL: I don't think that matters, the sequence of storage of clips in the track
202 // is not elsewhere assumed to be by time
203 {
204 for (auto it = revClips.rbegin(), revEnd = revClips.rend(); rValue && it != revEnd; ++it)
205 rValue = track->AddClip(*it);
206 }
207
208 for (auto &clip : otherClips)
209 if (!(rValue = track->AddClip(clip)))
210 break;
211
212 return rValue;
213 }
214
ProcessOneClip(int count,WaveTrack * track,sampleCount start,sampleCount len,sampleCount originalStart,sampleCount originalEnd)215 bool EffectReverse::ProcessOneClip(int count, WaveTrack *track,
216 sampleCount start, sampleCount len,
217 sampleCount originalStart, sampleCount originalEnd)
218 {
219 bool rc = true;
220 // keep track of two blocks whose data we will swap
221 auto first = start;
222
223 auto blockSize = track->GetMaxBlockSize();
224 float tmp;
225 Floats buffer1{ blockSize };
226 Floats buffer2{ blockSize };
227
228 auto originalLen = originalEnd - originalStart;
229
230 while (len > 1) {
231 auto block =
232 limitSampleBufferSize( track->GetBestBlockSize(first), len / 2 );
233 auto second = first + (len - block);
234
235 track->GetFloats(buffer1.get(), first, block);
236 track->GetFloats(buffer2.get(), second, block);
237 for (decltype(block) i = 0; i < block; i++) {
238 tmp = buffer1[i];
239 buffer1[i] = buffer2[block-i-1];
240 buffer2[block-i-1] = tmp;
241 }
242 track->Set((samplePtr)buffer1.get(), floatSample, first, block);
243 track->Set((samplePtr)buffer2.get(), floatSample, second, block);
244
245 len -= 2 * block;
246 first += block;
247
248 if( TrackProgress(count, 2 * ( first - originalStart ).as_double() /
249 originalLen.as_double() ) ) {
250 rc = false;
251 break;
252 }
253 }
254
255 return rc;
256 }
257