bool EffectTruncSilence::Process() { SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *t; double t0 = mT0; double t1 = mT1; int tndx; int tcount = 0; int fr; // Init using first track t = (WaveTrack *) iter.First(); double rate = t->GetRate(); sampleCount blockLen = t->GetMaxBlockSize(); // Get the left and right bounds for all tracks while (t) { // Make sure all tracks have the same sample rate if (rate != t->GetRate()) { wxMessageBox(_("All tracks must have the same sample rate"), _("Truncate Silence")); return false; } // Count the tracks tcount++; // Set the current bounds to whichever left marker is // greater and whichever right marker is less t0 = wxMax(mT0, t->GetStartTime()); t1 = wxMin(mT1, t->GetEndTime()); // Use the smallest block size of all the tracks blockLen = wxMin(blockLen, t->GetMaxBlockSize()); // Iterate to the next track t = (WaveTrack*) iter.Next(); } // Just a sanity check, really it should be much higher if(blockLen < 4*mBlendFrameCount) blockLen = 4*mBlendFrameCount; // Transform the marker timepoints to samples t = (WaveTrack *) iter.First(); sampleCount start = t->TimeToLongSamples(t0); sampleCount end = t->TimeToLongSamples(t1); // Bigger buffers reduce 'reset' //blockLen *= 8; // Stress-test the logic for cutting samples through block endpoints //blockLen /= 8; // Set thresholds // We have a lower bound on the amount of silence we chop out at a time // to avoid chopping up low frequency sounds. We're good down to 10Hz // if we use 100ms. const float minTruncMs = 1.0f; double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; int truncInitialAllowedSilentSamples = int((wxMax( mTruncInitialAllowedSilentMs, minTruncMs) * rate) / 1000.0); int truncLongestAllowedSilentSamples = int((wxMax( mTruncLongestAllowedSilentMs, minTruncMs) * rate) / 1000.0); // Require at least 4 samples for lengths if(truncInitialAllowedSilentSamples < 4) truncInitialAllowedSilentSamples = 4; if(truncLongestAllowedSilentSamples < 4) truncLongestAllowedSilentSamples = 4; // If the cross-fade is longer than the minimum length, // then limit the cross-fade length to the minimum length // This allows us to have reasonable cross-fade by default // and still allow for 1ms minimum lengths if(truncInitialAllowedSilentSamples < mBlendFrameCount) mBlendFrameCount = truncInitialAllowedSilentSamples; if(truncLongestAllowedSilentSamples < mBlendFrameCount) mBlendFrameCount = truncLongestAllowedSilentSamples; // For sake of efficiency, don't let blockLen be less than double the longest silent samples // up until a sane limit of 1Meg samples while((blockLen > 0) && (blockLen < truncLongestAllowedSilentSamples*2) && (blockLen < 1048576)) { blockLen *= 2; } // Don't allow either value to be more than half of the block length if(truncLongestAllowedSilentSamples > blockLen/2) truncLongestAllowedSilentSamples = blockLen/2; if(truncInitialAllowedSilentSamples > truncLongestAllowedSilentSamples) truncInitialAllowedSilentSamples = truncLongestAllowedSilentSamples; // We use the 'longest' variable as additive to the 'initial' variable truncLongestAllowedSilentSamples -= truncInitialAllowedSilentSamples; // Perform the crossfade half-way through the minimum removed silence duration int rampInFrames = (truncInitialAllowedSilentSamples + mBlendFrameCount) / 2; if(rampInFrames > truncInitialAllowedSilentSamples) rampInFrames = truncInitialAllowedSilentSamples; // Allocate buffers float **buffer = new float*[tcount]; for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx] = new float[blockLen]; } // Start processing //Track::All is needed because this effect has clear functionality this->CopyInputTracks(Track::All); // Set up mOutputTracks. SelectedTrackListOfKindIterator iterOut(Track::Wave, mOutputTracks); sampleCount index = start; sampleCount outTrackOffset = start; bool cancelled = false; // Reset bool ignoringFrames = false; bool truncToMinimum = true; // Ignore the initial samples until we get above the noise floor sampleCount consecutiveSilentFrames = 0; sampleCount truncIndex = 0; sampleCount i = 0; sampleCount keep; while (index < end) { // Limit size of current block if we've reached the end sampleCount count = blockLen-i; if ((index + count) > end) { count = end - index; } // Fill the buffers tndx = 0; t = (WaveTrack *) iter.First(); while (t) { t->Get((samplePtr)(buffer[tndx++]+i), floatSample, index, count); t = (WaveTrack *) iter.Next(); } // Shift over to account for samples remaining from prior block sampleCount limit = count+i; // Look for silences in current block for ( ; i < limit; i++) { // Is current frame in all tracks below threshold bool below = true; for (tndx = 0; tndx < tcount; tndx++) { if (fabs(buffer[tndx][i]) >= truncDbSilenceThreshold) { below = false; break; } } // Make sure we cross-fade and output the last silence // so we get a smooth transition into whatever follows the selected region // Also set the 'truncToMinimum' flag so that the last silence is truncated to the minimum amount if(below && ((index+i+1) == end)) { below = false; truncToMinimum = true; } // Count frame if it's below threshold if (below) { consecutiveSilentFrames++; // Ignore this frame (equivalent to cutting it) // otherwise, keep sample to be part of allowed silence if (consecutiveSilentFrames > truncInitialAllowedSilentSamples) { ignoringFrames = true; continue; } } else { if (ignoringFrames == true) { // Scale the consectiveSilentFrames so we keep a silence duration // which is proportional to the original silence up to the limit keep = consecutiveSilentFrames - truncInitialAllowedSilentSamples; keep /= mSilenceCompressRatio; // The first and last samples always get truncated to the minimum amount if(truncToMinimum == true) keep = 0; if(keep > truncLongestAllowedSilentSamples) keep = truncLongestAllowedSilentSamples; if(keep < 0) keep = 0; // Compute the location of the cross-fade to be halfway through the silence // with restriction to the samples we still have available to use rampInFrames = (truncInitialAllowedSilentSamples - keep + mBlendFrameCount) / 2; if(rampInFrames > truncInitialAllowedSilentSamples) rampInFrames = truncInitialAllowedSilentSamples; if(rampInFrames < mBlendFrameCount) rampInFrames = mBlendFrameCount; // Include the cross-fade samples in the count to make the loop logic easier keep += rampInFrames; truncIndex -= rampInFrames; // back up for cross-fade sampleCount curOffset = i - keep; if(curOffset < 0) { // This should never happen, but just in case... keep += curOffset - rampInFrames; if(keep < mBlendFrameCount) keep = mBlendFrameCount; curOffset = 0; } if(truncIndex < 0) { // This should never happen, but just in case... truncIndex = 0; } for (tndx = 0; tndx < tcount; tndx++) { // Cross fade the cut point for (fr = 0; fr < mBlendFrameCount; fr++) { buffer[tndx][truncIndex+fr] = ((mBlendFrameCount-fr)*buffer[tndx][truncIndex+fr] + fr*buffer[tndx][curOffset + fr]) / mBlendFrameCount; } // Append the 'keep' samples, if any for ( ; fr < keep; fr++) { buffer[tndx][truncIndex+fr] = buffer[tndx][curOffset + fr]; } } truncIndex += keep; } consecutiveSilentFrames = 0; ignoringFrames = false; truncToMinimum = false; } // Can get here either because > dbThreshold // or silence duration isn't longer than allowed for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx][truncIndex] = buffer[tndx][i]; } truncIndex++; } // Update tracks if any samples were removed, now or before if (outTrackOffset + truncIndex != index + limit) { // Put updated sample back into output tracks. tndx = 0; t = (WaveTrack *) iterOut.First(); while (t) { t->Set((samplePtr)buffer[tndx++], floatSample, outTrackOffset, truncIndex); t = (WaveTrack *) iterOut.Next(); } } // If currently in a silent section, retain samples for the next pass if(ignoringFrames) { keep = consecutiveSilentFrames - truncInitialAllowedSilentSamples; if(keep > (truncLongestAllowedSilentSamples+mBlendFrameCount)) keep = truncLongestAllowedSilentSamples+mBlendFrameCount; for (tndx = 0; tndx < tcount; tndx++) { for(fr = 0; fr < truncInitialAllowedSilentSamples; fr++) { buffer[tndx][fr] = buffer[tndx][truncIndex-truncInitialAllowedSilentSamples+fr]; } for(fr = 0; fr < keep; fr++) { buffer[tndx][truncInitialAllowedSilentSamples+fr] = buffer[tndx][i-keep+fr]; } } // Update the output index, less what we are retaining for next time outTrackOffset += truncIndex - truncInitialAllowedSilentSamples; // Append the following buffer to the existing data i = consecutiveSilentFrames = truncInitialAllowedSilentSamples + keep; truncIndex = truncInitialAllowedSilentSamples; } else { // Maintain output index outTrackOffset += truncIndex; // Reset the buffer pointers to the beginning i = 0; truncIndex = 0; consecutiveSilentFrames = 0; } // Update progress and bail if user cancelled cancelled = TrackProgress(0, ((double)index / (double)end)); if (cancelled) { break; } // Bump to next block index += count; } AudacityProject *p = GetActiveProject(); if (!p) return false; // Remove stale data at end of output tracks. if (!cancelled && (outTrackOffset < end)) { t = (WaveTrack *) iterOut.First(); if( p->IsSticky() ) t->Clear(outTrackOffset / rate, t1, mOutputTracks); else while(t) { t->Clear(outTrackOffset / rate, t1, mOutputTracks); t = (WaveTrack *) iterOut.Next(); } t1 = outTrackOffset / rate; } // Free buffers for (tndx = 0; tndx < tcount; tndx++) { delete [] buffer[tndx]; } delete [] buffer; mT0 = t0; mT1 = t1; this->ReplaceProcessedTracks(!cancelled); return !cancelled; }
bool EffectChangeSpeed::Process() { // Similar to EffectSoundTouch::Process() //Iterate over each track this->CopyInputWaveTracks(); // Set up mOutputWaveTracks. bool bGoodResult = true; TrackListIterator iter(mOutputWaveTracks); WaveTrack* pOutWaveTrack = (WaveTrack*)(iter.First()); mCurTrackNum = 0; m_maxNewLength = 0.0; //Get start and end times from track mCurT0 = pOutWaveTrack->GetStartTime(); mCurT1 = pOutWaveTrack->GetEndTime(); //Set the current bounds to whichever left marker is //greater and whichever right marker is less: mCurT0 = wxMax(mT0, mCurT0); mCurT1 = wxMin(mT1, mCurT1); double len = pOutWaveTrack->GetEndTime() - pOutWaveTrack->GetStartTime(); while (pOutWaveTrack != NULL) { //Get start and end times from track mCurT0 = pOutWaveTrack->GetStartTime(); mCurT1 = pOutWaveTrack->GetEndTime(); //Set the current bounds to whichever left marker is //greater and whichever right marker is less: mCurT0 = wxMax(mT0, mCurT0); mCurT1 = wxMin(mT1, mCurT1); // Process only if the right marker is to the right of the left marker if (mCurT1 > mCurT0) { //Transform the marker timepoints to samples sampleCount start = pOutWaveTrack->TimeToLongSamples(mCurT0); sampleCount end = pOutWaveTrack->TimeToLongSamples(mCurT1); //ProcessOne() (implemented below) processes a single track if (!ProcessOne(pOutWaveTrack, start, end)) { bGoodResult = false; break; } } //Iterate to the next track pOutWaveTrack = (WaveTrack*)(iter.Next()); mCurTrackNum++; } this->ReplaceProcessedWaveTracks(bGoodResult); #ifdef EXPERIMENTAL_FULL_LINKING AudacityProject *p = (AudacityProject*)mParent; if( p && p->IsSticky() ){ pOutWaveTrack = (WaveTrack*)(iter.First()); double newLen = pOutWaveTrack->GetEndTime() - pOutWaveTrack->GetStartTime(); double timeAdded = newLen-len; double sel = mCurT1-mCurT0; double percent = (sel/(timeAdded+sel))*100 - 100; if ( !(HandleGroupChangeSpeed(percent, mCurT0, mCurT1)) ) bGoodResult = false; } #endif // mT1 = mT0 + m_maxNewLength; // Update selection. return bGoodResult; }