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