mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-08 14:13:57 +01:00
... A non-narrowing conversion out to long long is a necessity, but the conversions to float and double are simply conveniences. Conversion from floating is explicit, to avoid unintended consequences with arithmetic operators, when later sampleCount ceases to be an alias for an integral type. Some conversions are not made explicit, where I expect to change the type of the variable later to have mere size_t width.
260 lines
8.0 KiB
C++
260 lines
8.0 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Reverse.cpp
|
|
|
|
Mark Phillips
|
|
|
|
*******************************************************************//**
|
|
|
|
\class EffectReverse
|
|
\brief An Effect that reverses the selected audio.
|
|
|
|
*//********************************************************************/
|
|
|
|
|
|
#include "../Audacity.h"
|
|
#include "Reverse.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <wx/intl.h>
|
|
|
|
#include "../LabelTrack.h"
|
|
#include "../WaveTrack.h"
|
|
|
|
//
|
|
// EffectReverse
|
|
//
|
|
|
|
EffectReverse::EffectReverse()
|
|
{
|
|
}
|
|
|
|
EffectReverse::~EffectReverse()
|
|
{
|
|
}
|
|
|
|
// IdentInterface implementation
|
|
|
|
wxString EffectReverse::GetSymbol()
|
|
{
|
|
return REVERSE_PLUGIN_SYMBOL;
|
|
}
|
|
|
|
wxString EffectReverse::GetDescription()
|
|
{
|
|
return XO("Reverses the selected audio");
|
|
}
|
|
|
|
// EffectIdentInterface implementation
|
|
|
|
EffectType EffectReverse::GetType()
|
|
{
|
|
return EffectTypeProcess;
|
|
}
|
|
|
|
bool EffectReverse::IsInteractive()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Effect implementation
|
|
|
|
bool EffectReverse::Process()
|
|
{
|
|
//Track::All is needed because Reverse should move the labels too
|
|
this->CopyInputTracks(Track::All); // Set up mOutputTracks.
|
|
bool bGoodResult = true;
|
|
|
|
TrackListIterator iter(mOutputTracks.get());
|
|
Track *t = iter.First();
|
|
int count = 0;
|
|
while (t) {
|
|
if (t->GetKind() == Track::Wave &&
|
|
(t->GetSelected() || t->IsSyncLockSelected()))
|
|
{
|
|
WaveTrack *track = (WaveTrack*)t;
|
|
|
|
if (mT1 > mT0) {
|
|
auto start = track->TimeToLongSamples(mT0);
|
|
auto end = track->TimeToLongSamples(mT1);
|
|
auto len = end - start;
|
|
|
|
if (!ProcessOneWave(count, track, start, len))
|
|
{
|
|
bGoodResult = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (t->GetKind() == Track::Label &&
|
|
(t->GetSelected() || t->IsSyncLockSelected()))
|
|
{
|
|
LabelTrack *track = (LabelTrack*)t;
|
|
track->ChangeLabelsOnReverse(mT0, mT1);
|
|
}
|
|
t = iter.Next();
|
|
count++;
|
|
}
|
|
|
|
this->ReplaceProcessedTracks(bGoodResult);
|
|
return bGoodResult;
|
|
}
|
|
|
|
bool EffectReverse::ProcessOneWave(int count, WaveTrack * track, sampleCount start, sampleCount len)
|
|
{
|
|
bool rValue = true; // return value
|
|
|
|
auto end = start + len; // start, end, len refer to the selected reverse region
|
|
|
|
// STEP 1:
|
|
// If a reverse selection begins and/or ends at the inside of a clip
|
|
// perform a split at the start and/or end of the reverse selection
|
|
const auto &clips = track->GetClips();
|
|
// Beware, the array grows as we loop over it. Use integer subscripts, not iterators.
|
|
for (int ii = 0; ii < clips.size(); ++ii) {
|
|
const auto &clip = clips[ii].get();
|
|
auto clipStart = clip->GetStartSample();
|
|
auto clipEnd = clip->GetEndSample();
|
|
if (clipStart < start && clipEnd > start && clipEnd <= end) { // the reverse selection begins at the inside of a clip
|
|
double splitTime = track->LongSamplesToTime(start);
|
|
track->SplitAt(splitTime);
|
|
}
|
|
else if (clipStart >= start && clipStart < end && clipEnd > end) { // the reverse selection ends at the inside of a clip
|
|
double splitTime = track->LongSamplesToTime(end);
|
|
track->SplitAt(splitTime);
|
|
}
|
|
else if (clipStart < start && clipEnd > end) { // the selection begins AND ends at the inside of a clip
|
|
double splitTime = track->LongSamplesToTime(start);
|
|
track->SplitAt(splitTime);
|
|
splitTime = track->LongSamplesToTime(end);
|
|
track->SplitAt(splitTime);
|
|
}
|
|
}
|
|
|
|
//STEP 2:
|
|
// Individually reverse each clip inside the selected region
|
|
// and apply the appropriate offset after detaching them from the track
|
|
|
|
bool checkedFirstClip = false;
|
|
|
|
// used in calculating the offset of clips to rearrange
|
|
// holds the NEW end position of the current clip
|
|
auto currentEnd = end;
|
|
|
|
WaveClipHolders revClips; // holds the reversed clips
|
|
WaveClipHolders otherClips; // holds the clips that appear after the reverse selection region
|
|
auto clipArray = track->SortedClipArray();
|
|
size_t i;
|
|
for (i=0; i < clipArray.size(); i++) {
|
|
|
|
WaveClip *clip = clipArray[i];
|
|
auto clipStart = clip->GetStartSample();
|
|
auto clipEnd = clip->GetEndSample();
|
|
|
|
if (clipStart >= start && clipEnd <= end) { // if the clip is inside the selected region
|
|
|
|
// this is used to check if the selected region begins with a whitespace.
|
|
// if yes then clipStart (of the first clip) and start are not the same.
|
|
// adjust currentEnd accordingly and set endMerge to false
|
|
if(checkedFirstClip == false && clipStart > start) {
|
|
checkedFirstClip = true;
|
|
if(i > 0) {
|
|
if (clipArray[i-1]->GetEndSample() <= start) {
|
|
currentEnd -= (clipStart - start);
|
|
}
|
|
}
|
|
else {
|
|
currentEnd -= (clipStart - start);
|
|
}
|
|
}
|
|
|
|
auto revStart = (clipStart >= start)? clipStart: start;
|
|
auto revEnd = (clipEnd >= end)? end: clipEnd;
|
|
auto revLen = revEnd - revStart;
|
|
if (revEnd >= revStart) {
|
|
if(!ProcessOneClip(count, track, revStart, revLen, start, end)) // reverse the clip
|
|
{
|
|
rValue = false;
|
|
break;
|
|
}
|
|
|
|
auto clipOffsetStart = currentEnd - (clipEnd - clipStart); // calculate the offset required
|
|
double offsetStartTime = track->LongSamplesToTime(clipOffsetStart);
|
|
if(i+1 < clipArray.size()) // update currentEnd if there is a clip to process next
|
|
{
|
|
auto nextClipStart = clipArray[i+1]->GetStartSample();
|
|
currentEnd = currentEnd - (clipEnd - clipStart) - (nextClipStart - clipEnd);
|
|
}
|
|
|
|
revClips.push_back(track->RemoveAndReturnClip(clip)); // detach the clip from track
|
|
revClips.back()->SetOffset(track->LongSamplesToTime(track->TimeToLongSamples(offsetStartTime))); // align time to a sample and set offset
|
|
}
|
|
}
|
|
else if (clipStart >= end) { // clip is after the selection region
|
|
otherClips.push_back(track->RemoveAndReturnClip(clip)); // simply remove and append to otherClips
|
|
}
|
|
}
|
|
|
|
// STEP 3: Append the clips from
|
|
// revClips and otherClips back to the track
|
|
// the last clip of revClips is appended to the track first
|
|
// PRL: I don't think that matters, the sequence of storage of clips in the track
|
|
// is not elsewhere assumed to be by time
|
|
for (auto it = revClips.rbegin(), end = revClips.rend(); it != end; ++it)
|
|
track->AddClip(std::move(*it));
|
|
|
|
for (auto &clip : otherClips)
|
|
track->AddClip(std::move(clip));
|
|
|
|
return rValue;
|
|
}
|
|
|
|
bool EffectReverse::ProcessOneClip(int count, WaveTrack *track,
|
|
sampleCount start, sampleCount len,
|
|
sampleCount originalStart, sampleCount originalEnd)
|
|
{
|
|
bool rc = true;
|
|
// keep track of two blocks whose data we will swap
|
|
auto first = start;
|
|
|
|
auto blockSize = track->GetMaxBlockSize();
|
|
float tmp;
|
|
float *buffer1 = new float[blockSize];
|
|
float *buffer2 = new float[blockSize];
|
|
|
|
auto originalLen = originalEnd - originalStart;
|
|
|
|
while (len > 1) {
|
|
auto block =
|
|
limitSampleBufferSize( track->GetBestBlockSize(first), len / 2 );
|
|
auto second = first + (len - block);
|
|
|
|
track->Get((samplePtr)buffer1, floatSample, first, block);
|
|
track->Get((samplePtr)buffer2, floatSample, second, block);
|
|
for (decltype(block) i = 0; i < block; i++) {
|
|
tmp = buffer1[i];
|
|
buffer1[i] = buffer2[block-i-1];
|
|
buffer2[block-i-1] = tmp;
|
|
}
|
|
track->Set((samplePtr)buffer1, floatSample, first, block);
|
|
track->Set((samplePtr)buffer2, floatSample, second, block);
|
|
|
|
len -= 2 * block;
|
|
first += block;
|
|
|
|
if( TrackProgress(count, 2 * ( first - originalStart ).as_double() /
|
|
originalLen.as_double() ) ) {
|
|
rc = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete[] buffer1;
|
|
delete[] buffer2;
|
|
|
|
return rc;
|
|
}
|