Audacity 3.2.0
WaveTrackUtilities.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 WaveTrackUtilities.cpp
7
8 Paul Licameli
9
10**********************************************************************/
11#include "WaveTrackUtilities.h"
12#include "SampleBlock.h"
13#include "Sequence.h"
14#include "WaveClip.h"
15#include <algorithm>
16
18 : mpTrack(&track)
19{
20 if (mpTrack) {
21 auto &&clips = mpTrack->Intervals();
22 Push({ clips.begin(), clips.end() });
23 }
24}
25
26
28{
29 if (mStack.empty())
30 return nullptr;
31 else {
32 auto &[intervals, ii] = mStack.back();
33 return intervals[ii];
34 }
35}
36
38{
39 // The unspecified sequence is a post-order, but there is no
40 // promise whether sister nodes are ordered in time.
41 if (mpTrack && !mStack.empty()) {
42 auto &[intervals, ii] = mStack.back();
43 if (++ii == intervals.size())
44 mStack.pop_back();
45 else
46 Push(intervals[ii]->GetCutLines());
47 }
48
49 return *this;
50}
51
53{
54 if (!mpTrack)
55 return;
56
57 // Go depth first while there are cutlines
58 while (!clips.empty()) {
59 auto nextClips = clips[0]->GetCutLines();
60 mStack.push_back({ move(clips), 0 });
61 clips = move(nextClips);
62 }
63}
64
65namespace {
67 sampleCount start, sampleCount len,
68 sampleCount originalStart, sampleCount originalEnd,
70{
71 bool rc = true;
72 // keep track of two blocks whose data we will swap
73 auto first = start;
74
75 auto blockSize = track.GetMaxBlockSize();
76 const auto width = track.NChannels();
77 Floats buffers0[2]{
78 Floats(blockSize), width > 1 ? Floats(blockSize) : Floats{} };
79 float *pointers0[2]{ buffers0[0].get(),
80 width > 1 ? buffers0[1].get() : nullptr };
81 Floats buffers1[2]{
82 Floats(blockSize), width > 1 ? Floats(blockSize) : Floats{} };
83 float *pointers1[2]{ buffers1[0].get(),
84 width > 1 ? buffers1[1].get() : nullptr };
85 constexpr auto reverseBuffers =
86 [](float *const (&pointers)[2], size_t size){
87 for (const auto pointer : pointers)
88 if (pointer)
89 std::reverse(pointer, pointer + size);
90 };
91
92 auto originalLen = originalEnd - originalStart;
93
94 while (len > 1) {
95 auto block =
96 limitSampleBufferSize(track.GetBestBlockSize(first), len / 2);
97 auto second = first + (len - block);
98
99 track.GetFloats(0, width, pointers0, first, block);
100 reverseBuffers(pointers0, block);
101 track.GetFloats(0, width, pointers1, second, block);
102 reverseBuffers(pointers1, block);
103 // Don't dither on later rendering if only reversing samples
104 const bool success =
105 track.SetFloats(pointers1, first, block, narrowestSampleFormat)
106 &&
107 track.SetFloats(pointers0, second, block, narrowestSampleFormat);
108 if (!success)
109 return false;
110
111 len -= 2 * block;
112 first += block;
113
114 if (!report(
115 2 * (first - originalStart).as_double() / originalLen.as_double()
116 )) {
117 rc = false;
118 break;
119 }
120 }
121
122 return rc;
123}
124}
125
127 sampleCount start, sampleCount len, const ProgressReport &progress)
128{
129 bool rValue = true; // return value
130
131 // start, end, len refer to the selected reverse region
132 auto end = start + len;
133
134 auto clipArray = track.SortedIntervalArray();
135 const auto invariant = [&]{
136 return std::is_sorted(clipArray.begin(), clipArray.end(),
137 [](const auto &pA, const auto &pB){
138 return pA->GetPlayStartTime() < pB->GetPlayEndTime();
139 });
140 };
141 assert(invariant());
142
143 // STEP 1:
144 // If a reverse selection begins and/or ends at the inside of a clip
145 // perform a split at the start and/or end of the reverse selection
146 // Beware, the array grows as we loop over it, so don't use range-for
147 for (size_t ii = 0; ii < clipArray.size(); ++ii) {
148 const auto &clip = *clipArray[ii];
149 auto clipStart = clip.GetPlayStartSample();
150 auto clipEnd = clip.GetPlayEndSample();
151 const auto splitAt = [&](double splitTime){
152 auto [_, second] = track.SplitAt(splitTime);
153 if (second)
154 clipArray.insert(clipArray.begin() + ii + 1, second);
155 };
156 if (clipStart < start && clipEnd > start && clipEnd <= end) {
157 // the reverse selection begins at the inside of a clip
158 double splitTime = track.LongSamplesToTime(start);
159 splitAt(splitTime);
160 }
161 else if (clipStart >= start && clipStart < end && clipEnd > end) {
162 // the reverse selection ends at the inside of a clip
163 double splitTime = track.LongSamplesToTime(end);
164 splitAt(splitTime);
165 }
166 else if (clipStart < start && clipEnd > end) {
167 // the selection begins AND ends at the inside of a clip
168 double splitTime = track.LongSamplesToTime(end);
169 splitAt(splitTime);
170 splitTime = track.LongSamplesToTime(start);
171 splitAt(splitTime);
172 }
173 assert(invariant());
174 }
175
176 //STEP 2:
177 // Individually reverse each clip inside the selected region
178 // and apply the appropriate offset after detaching them from the track
179
180 bool checkedFirstClip = false;
181
182 // used in calculating the offset of clips to rearrange
183 // holds the new end position of the current clip
184 auto currentEnd = end;
185
186 // holds the reversed clips
188 IntervalHolders revClips;
189 // holds the clips that appear after the reverse selection region
190 IntervalHolders otherClips;
191 // Unlike in the previous iteration, clipArray is not inserted or
192 // erased
193 size_t i = 0;
194 for (const auto &clip : clipArray) {
195 Finally Do([&]{ ++i; });
196 auto clipStart = clip->GetPlayStartSample();
197 auto clipEnd = clip->GetPlayEndSample();
198
199 if (clipStart >= start && clipEnd <= end) {
200 // if the clip is inside the selected region
201 // this is used to check if the selected region begins with a
202 // whitespace. If yes then clipStart (of the first clip) and start are
203 // not the same. Adjust currentEnd accordingly and set endMerge to
204 // false
205 if (!checkedFirstClip && clipStart > start) {
206 checkedFirstClip = true;
207 if (i > 0) {
208 if (clipArray[i - 1]->GetPlayEndSample() <= start)
209 currentEnd -= (clipStart - start);
210 }
211 else
212 currentEnd -= (clipStart - start);
213 }
214
215 auto revStart = std::max(clipStart, start);
216 auto revEnd = std::min(end, clipEnd);
217 auto revLen = revEnd - revStart;
218 if (revEnd >= revStart) {
219 // reverse the clip
220 if (!ReverseOneClip(track, revStart, revLen, start, end, progress))
221 {
222 rValue = false;
223 break;
224 }
225
226 // calculate the offset required
227 auto clipOffsetStart = currentEnd - (clipEnd - clipStart);
228 double offsetStartTime = track.LongSamplesToTime(clipOffsetStart);
229 if (i + 1 < clipArray.size()) {
230 // update currentEnd if there is a clip to process next
231 auto nextClipStart = clipArray[i + 1]->GetPlayStartSample();
232 currentEnd = currentEnd -
233 (clipEnd - clipStart) - (nextClipStart - clipEnd);
234 }
235
236 // detach the clip from track
237 revClips.push_back(clip);
238 track.RemoveInterval(clip);
239 // align time to a sample and set offset
240 revClips.back()->SetPlayStartTime(
241 track.SnapToSample(offsetStartTime));
242 }
243 }
244 else if (clipStart >= end) {
245 // clip is after the selection region
246 // simply remove and append to otherClips
247 otherClips.push_back(clip);
248 track.RemoveInterval(clip);
249 }
250 }
251
252 // STEP 3: Append the clips from
253 // revClips and otherClips back to the track
254 // the last clip of revClips is appended to the track first
255 // PRL: I don't think that matters, the sequence of storage of clips in the
256 // track is not elsewhere assumed to be by time
257 for (auto it = revClips.rbegin(), revEnd = revClips.rend();
258 it != revEnd; ++it)
259 track.InsertInterval(*it, false);
260
261 if (!rValue)
262 return false;
263
264 for (auto &clip : otherClips)
265 track.InsertInterval(clip, false);
266
267 return rValue;
268}
269
271{
272 sampleCount result{ 0 };
273 for (const auto &pInterval : track.Intervals())
274 result += pInterval->GetSequenceSamplesCount();
275 return result;
276}
277
279{
280 size_t result{};
281 for (const auto &pInterval : track.Intervals())
282 result += pInterval->CountBlocks();
283 return result;
284}
285
287{
288 for (const auto &pClip : track.Intervals())
289 pClip->CloseLock();
290}
291
292bool WaveTrackUtilities::RemoveCutLine(WaveTrack &track, double cutLinePosition)
293{
294 bool removed = false;
295 for (const auto &pClip : track.Intervals())
296 if (pClip->RemoveCutLine(cutLinePosition)) {
297 removed = true;
298 break;
299 }
300 return removed;
301}
302
303// Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
304// Can't yet promise strong exception safety for a pair of channels together
306 double cutLinePosition, double* cutlineStart,
307 double* cutlineEnd)
308{
309 const bool editClipCanMove = GetEditClipsCanMove();
310
311 // Find clip which contains this cut line
312 double start = 0, end = 0;
313 const auto &clips = track.Intervals();
314 const auto pEnd = clips.end();
315 const auto pClip = std::find_if(clips.begin(), pEnd,
316 [&](const auto &clip) {
317 return clip->FindCutLine(cutLinePosition, &start, &end); });
318 if (pClip != pEnd) {
319 auto &&clip = *pClip;
320 if (!editClipCanMove) {
321 // We are not allowed to move the other clips, so see if there
322 // is enough room to expand the cut line
323 for (const auto &clip2: clips)
324 if (clip2->GetPlayStartTime() > clip->GetPlayStartTime() &&
325 clip->GetPlayEndTime() + end - start > clip2->GetPlayStartTime())
326 // Strong-guarantee in case of this path
329 XO("There is not enough room available to expand the cut line"),
330 XO("Warning"),
331 "Error:_Insufficient_space_in_track"
332 };
333 }
334
335 clip->ExpandCutLine(cutLinePosition);
336
337 // Strong-guarantee provided that the following gives No-fail-guarantee
338
339 if (cutlineStart)
340 *cutlineStart = start;
341 if (cutlineEnd)
342 *cutlineEnd = end;
343
344 // Move clips which are to the right of the cut line
345 if (editClipCanMove)
346 for (const auto &clip2 : clips)
347 if (clip2->GetPlayStartTime() > clip->GetPlayStartTime())
348 clip2->ShiftBy(end - start);
349 }
350}
351
353{
354 const auto &clips = track.Intervals();
355 return std::any_of(clips.begin(), clips.end(), [](const auto &pClip){
356 return pClip->GetTrimLeft() != 0 || pClip->GetTrimRight() != 0;
357 });
358}
359
361{
362 for (const auto &pClip : track.Intervals()) {
363 if (pClip->GetTrimLeft() != 0) {
364 auto t0 = pClip->GetPlayStartTime();
365 pClip->SetTrimLeft(0);
366 pClip->ClearLeft(t0);
367 }
368 if (pClip->GetTrimRight() != 0) {
369 auto t1 = pClip->GetPlayEndTime();
370 pClip->SetTrimRight(0);
371 pClip->ClearRight(t1);
372 }
373 }
374}
375
377 SampleBlockIDSet *pIDs)
378{
379 for (auto wt : tracks.Any<WaveTrack>())
380 // Scan all clips within current track
381 for (const auto &pClip : GetAllClips(*wt))
382 // Scan all sample blocks within current clip
383 for (const auto &pChannel : pClip->Channels()) {
384 auto blocks = pChannel->GetSequenceBlockArray();
385 for (const auto &block : *blocks) {
386 auto &pBlock = block.sb;
387 if (pBlock) {
388 if (pIDs && !pIDs->insert(pBlock->GetBlockID()).second)
389 continue;
390 if (visitor)
391 visitor(pBlock);
392 }
393 }
394 }
395}
396
398 BlockInspector inspector, SampleBlockIDSet *pIDs)
399{
400 VisitBlocks(const_cast<TrackList &>(tracks), move(inspector), pIDs);
401}
402
405 double t0, double t1)
406{
407 assert(t0 <= t1);
409 const auto &intervals = track.Intervals();
410 copy_if(intervals.begin(), intervals.end(), back_inserter(result),
411 [&](const auto &pClip){
412 return pClip->IntersectsPlayRegion(t0, t1); });
413 return result;
414}
415
417
418namespace {
419using namespace WaveTrackUtilities;
420// If any clips have hidden data, don't allow older versions to open the
421// project. Otherwise overlapping clips might result.
424 const TrackList& trackList = TrackList::Get(project);
425 for (auto wt : trackList.Any<const WaveTrack>())
426 for (const auto& clip : GetAllClips(*wt))
427 if (clip->GetTrimLeft() > 0.0 || clip->GetTrimRight() > 0.0)
428 return { 3, 1, 0, 0 };
430 }
431);
432
433// If any clips have any stretch, don't allow older versions to open the
434// project. Otherwise overlapping clips might result.
437 const TrackList& trackList = TrackList::Get(project);
438 for (auto wt : trackList.Any<const WaveTrack>())
439 for (const auto& clip : GetAllClips(*wt))
440 if (clip->GetStretchRatio() != 1.0)
441 return { 3, 4, 0, 0 };
443 }
444);
445}
446
448 const WaveTrack& track, WaveTrack::Interval& interval)
449{
450 if (
451 const auto nextClip =
453 {
454 interval.StretchRightTo(nextClip->GetPlayStartTime());
455 }
456}
@ BadUserAction
Indicates that the user performed an action that is not allowed.
int min(int a, int b)
ArrayOf< float > Floats
Definition: Echo.h:21
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
const ProjectFormatVersion BaseProjectFormatVersion
This is a helper constant for the "most compatible" project version with the value (3,...
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
constexpr sampleFormat narrowestSampleFormat
Definition: SampleFormat.h:46
const auto tracks
const auto project
bool GetEditClipsCanMove()
Definition: WaveTrack.cpp:3381
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
bool GetFloats(size_t iChannel, size_t nBuffers, float *const buffers[], sampleCount start, size_t len, bool backwards=false, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
A MessageBoxException that shows a given, unvarying string.
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:238
void StretchRightTo(double to)
Sets from the right to the absolute time (if in expected range)
Definition: WaveClip.cpp:588
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void InsertInterval(const IntervalHolder &interval, bool newClip, bool allowEmpty=false)
Definition: WaveTrack.cpp:3208
IntervalConstHolder GetNextInterval(const Interval &interval, PlaybackDirection searchDirection) const
Definition: WaveTrack.cpp:175
bool SetFloats(const float *const *buffers, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.cpp:3246
std::pair< IntervalHolder, IntervalHolder > SplitAt(double t)
Definition: WaveTrack.cpp:3138
IntervalHolders SortedIntervalArray()
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3270
std::vector< IntervalHolder > IntervalHolders
Definition: WaveTrack.h:210
auto Intervals()
Definition: WaveTrack.h:670
std::vector< IntervalConstHolder > IntervalConstHolders
Definition: WaveTrack.h:212
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:532
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:2258
size_t GetBestBlockSize(sampleCount t) const
Definition: WaveTrack.cpp:2239
void RemoveInterval(const IntervalHolder &interval)
Definition: WaveTrack.cpp:3219
double LongSamplesToTime(sampleCount pos) const
double SnapToSample(double t) const
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
WAVE_TRACK_API void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs=nullptr)
WAVE_TRACK_API size_t CountBlocks(const WaveTrack &track)
WAVE_TRACK_API sampleCount GetSequenceSamplesCount(const WaveTrack &track)
std::function< void(std::shared_ptr< const SampleBlock >)> BlockInspector
WAVE_TRACK_API bool Reverse(WaveTrack &track, sampleCount start, sampleCount len, const ProgressReport &report={})
WAVE_TRACK_API void ExpandCutLine(WaveTrack &track, double cutLinePosition, double *cutlineStart=nullptr, double *cutlineEnd=nullptr)
WAVE_TRACK_API void CloseLock(WaveTrack &track) noexcept
Should be called upon project close. Not balanced by unlocking calls.
std::vector< IntervalHolder > IntervalHolders
std::unordered_set< SampleBlockID > SampleBlockIDSet
IteratorRange< AllClipsIterator > GetAllClips(WaveTrack &track)
WAVE_TRACK_API bool RemoveCutLine(WaveTrack &track, double cutLinePosition)
Remove cut line, without expanding the audio in it.
std::function< bool(double)> ProgressReport
WAVE_TRACK_API void ExpandClipTillNextOne(const WaveTrack &track, WaveTrack::Interval &interval)
WAVE_TRACK_API bool HasHiddenData(const WaveTrack &track)
Whether any clips have hidden audio.
WAVE_TRACK_API void DiscardTrimmed(WaveTrack &track)
Remove hidden audio from all clips.
std::function< void(const std::shared_ptr< SampleBlock > &)> BlockVisitor
WAVE_TRACK_API void VisitBlocks(TrackList &tracks, BlockVisitor visitor, SampleBlockIDSet *pIDs=nullptr)
WAVE_TRACK_API WaveTrack::IntervalConstHolders GetClipsIntersecting(const WaveTrack &track, double t0, double t1)
bool ReverseOneClip(WaveTrack &track, sampleCount start, sampleCount len, sampleCount originalStart, sampleCount originalEnd, const WaveTrackUtilities::ProgressReport &report)
ProjectFormatExtensionsRegistry::Extension smartClipsExtension([](const AudacityProject &project) -> ProjectFormatVersion { const TrackList &trackList=TrackList::Get(project);for(auto wt :trackList.Any< const WaveTrack >()) for(const auto &clip :GetAllClips(*wt)) if(clip->GetTrimLeft() > 0.0||clip->GetTrimRight() > 0.0) return { 3, 1, 0, 0 };return BaseProjectFormatVersion;})
ProjectFormatExtensionsRegistry::Extension stretchedClipsExtension([](const AudacityProject &project) -> ProjectFormatVersion { const TrackList &trackList=TrackList::Get(project);for(auto wt :trackList.Any< const WaveTrack >()) for(const auto &clip :GetAllClips(*wt)) if(clip->GetStretchRatio() !=1.0) return { 3, 4, 0, 0 };return BaseProjectFormatVersion;})
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
"finally" as in The C++ Programming Language, 4th ed., p. 358 Useful for defining ad-hoc RAII actions...
Definition: MemoryX.h:175
A struct to register the project extension that requires a different project version.
A structure that holds the project version.