void FreqWindow::GetAudio() { int selcount = 0; int i; bool warning = false; //wxLogDebug(wxT("Entering FreqWindow::GetAudio()")); TrackListIterator iter(p->GetTracks()); Track *t = iter.First(); while (t) { if (t->GetSelected() && t->GetKind() == Track::Wave) { WaveTrack *track = (WaveTrack *)t; if (selcount==0) { mRate = track->GetRate(); sampleCount start, end; start = track->TimeToLongSamples(p->mViewInfo.sel0); end = track->TimeToLongSamples(p->mViewInfo.sel1); mDataLen = (sampleCount)(end - start); if (mDataLen > 10485760) { warning = true; mDataLen = 10485760; } if (mBuffer) { delete [] mBuffer; } mBuffer = new float[mDataLen]; track->Get((samplePtr)mBuffer, floatSample, start, mDataLen); } else { if (track->GetRate() != mRate) { wxMessageBox(_("To plot the spectrum, all selected tracks must be the same sample rate.")); delete[] mBuffer; mBuffer = NULL; return; } sampleCount start; start = track->TimeToLongSamples(p->mViewInfo.sel0); float *buffer2 = new float[mDataLen]; track->Get((samplePtr)buffer2, floatSample, start, mDataLen); for(i=0; i<mDataLen; i++) mBuffer[i] += buffer2[i]; delete[] buffer2; } selcount++; } t = iter.Next(); } if (selcount == 0) return; if (warning) { wxString msg; msg.Printf(_("Too much audio was selected. Only the first %.1f seconds of audio will be analyzed."), (mDataLen / mRate)); //wxLogDebug(wxT("About to show length warning message")); wxMessageBox(msg); //wxLogDebug(wxT("Length warning message done")); } //wxLogDebug(wxT("Leaving FreqWindow::GetAudio()")); }
void FreqWindow::GetAudio() { mData.reset(); mDataLen = 0; int selcount = 0; bool warning = false; TrackListIterator iter(p->GetTracks()); Track *t = iter.First(); while (t) { if (t->GetSelected() && t->GetKind() == Track::Wave) { WaveTrack *track = (WaveTrack *)t; if (selcount==0) { mRate = track->GetRate(); auto start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0()); auto end = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t1()); auto dataLen = end - start; if (dataLen > 10485760) { warning = true; mDataLen = 10485760; } else // dataLen is not more than 10 * 2 ^ 20 mDataLen = dataLen.as_size_t(); mData = Floats{ mDataLen }; // Don't allow throw for bad reads track->Get((samplePtr)mData.get(), floatSample, start, mDataLen, fillZero, false); } else { if (track->GetRate() != mRate) { AudacityMessageBox(_("To plot the spectrum, all selected tracks must be the same sample rate.")); mData.reset(); mDataLen = 0; return; } auto start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0()); Floats buffer2{ mDataLen }; // Again, stop exceptions track->Get((samplePtr)buffer2.get(), floatSample, start, mDataLen, fillZero, false); for (size_t i = 0; i < mDataLen; i++) mData[i] += buffer2[i]; } selcount++; } t = iter.Next(); } if (selcount == 0) return; if (warning) { wxString msg; msg.Printf(_("Too much audio was selected. Only the first %.1f seconds of audio will be analyzed."), (mDataLen / mRate)); AudacityMessageBox(msg); } }
//This might continue over a number of blocks. double VoiceKey::TestEnergy (WaveTrack & t, sampleCount start, sampleCount len) { double sum = 1; sampleCount s = start; //Keep track of start sampleCount originalLen = len; //Keep track of the length of block to process (its not the length of t) sampleCount blockSize = t.GetMaxBlockSize(); //Determine size of sampling buffer if( blockSize > len) blockSize = len; float *buffer = new float[blockSize]; //Get a sampling buffer while(len > 0) { sampleCount block = t.GetBestBlockSize(s); //Figure out how much to grab if(block > len) block = len; //Don't grab too much! t.Get((samplePtr)buffer,floatSample, s,block); //grab the block; //Now, go through the block and calculate energy for(int i = 0; i< block; i++) { sum += buffer[i]*buffer[i]; } len -= block; s += block; } delete [] buffer; return sum / originalLen; }
//This might continue over a number of blocks. double VoiceKey::TestEnergy (WaveTrack & t, sampleCount start, sampleCount len) { double sum = 1; auto s = start; //Keep track of start auto originalLen = len; //Keep track of the length of block to process (its not the length of t) const auto blockSize = limitSampleBufferSize( t.GetMaxBlockSize(), len); //Determine size of sampling buffer float *buffer = new float[blockSize]; //Get a sampling buffer while(len > 0) { //Figure out how much to grab auto block = limitSampleBufferSize ( t.GetBestBlockSize(s), len ); t.Get((samplePtr)buffer,floatSample, s,block); //grab the block; //Now, go through the block and calculate energy for(decltype(block) i = 0; i< block; i++) { sum += buffer[i]*buffer[i]; } len -= block; s += block; } delete [] buffer; return sum / originalLen.as_double(); }
// Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. void EffectChangePitch::DeduceFrequencies() { // As a neat trick, attempt to get the frequency of the note at the // beginning of the selection. SelectedTrackListOfKindIterator iter(Track::Wave, inputTracks()); WaveTrack *track = (WaveTrack *) iter.First(); if (track) { double rate = track->GetRate(); // Auto-size window -- high sample rates require larger windowSize. // Aim for around 2048 samples at 44.1 kHz (good down to about 100 Hz). // To detect single notes, analysis period should be about 0.2 seconds. // windowSize must be a power of 2. const size_t windowSize = // windowSize < 256 too inaccurate std::max(256, wxRound(pow(2.0, floor((log(rate / 20.0)/log(2.0)) + 0.5)))); // we want about 0.2 seconds to catch the first note. // number of windows rounded to nearest integer >= 1. const unsigned numWindows = std::max(1, wxRound((double)(rate / (5.0f * windowSize)))); double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; auto start = track->TimeToLongSamples(t0); auto analyzeSize = windowSize * numWindows; Floats buffer{ analyzeSize }; Floats freq{ windowSize / 2 }; Floats freqa{ windowSize / 2, true }; track->Get((samplePtr) buffer.get(), floatSample, start, analyzeSize); for(unsigned i = 0; i < numWindows; i++) { ComputeSpectrum(buffer.get() + i * windowSize, windowSize, windowSize, rate, freq.get(), true); for(size_t j = 0; j < windowSize / 2; j++) freqa[j] += freq[j]; } size_t argmax = 0; for(size_t j = 1; j < windowSize / 2; j++) if (freqa[j] > freqa[argmax]) argmax = j; auto lag = (windowSize / 2 - 1) - argmax; m_dStartFrequency = rate / lag; } double dFromMIDInote = FreqToMIDInote(m_dStartFrequency); double dToMIDInote = dFromMIDInote + m_dSemitonesChange; m_nFromPitch = PitchIndex(dFromMIDInote); m_nFromOctave = PitchOctave(dFromMIDInote); m_nToPitch = PitchIndex(dToMIDInote); m_nToOctave = PitchOctave(dToMIDInote); m_FromFrequency = m_dStartFrequency; Calc_PercentChange(); Calc_ToFrequency(); }
void AudacityProject::OnPlotSpectrum(wxCommandEvent & event) { int selcount = 0; WaveTrack *selt = NULL; TrackListIterator iter(mTracks); VTrack *t = iter.First(); while (t) { if (t->GetSelected()) selcount++; if (t->GetKind() == VTrack::Wave) selt = (WaveTrack *) t; t = iter.Next(); } if (selcount != 1) { wxMessageBox(_("Please select a single track first.\n")); return; } /* This shouldn't be possible, since the menu is grayed out. * But we'll check just in case it does happen, to prevent * the crash that would result. */ if (!selt) { wxMessageBox(_("Please select a track first.\n")); return; } sampleCount s0 = (sampleCount) ((mViewInfo.sel0 - selt->GetOffset()) * selt->GetRate()); sampleCount s1 = (sampleCount) ((mViewInfo.sel1 - selt->GetOffset()) * selt->GetRate()); sampleCount slen = s1 - s0; if (slen > 1048576) slen = 1048576; float *data = new float[slen]; sampleType *data_sample = new sampleType[slen]; if (s0 >= selt->GetNumSamples() || s0 + slen > selt->GetNumSamples()) { wxMessageBox(_("Not enough samples selected.\n")); delete[]data; delete[]data_sample; return; } selt->Get(data_sample, s0, slen); for (sampleCount i = 0; i < slen; i++) data[i] = data_sample[i] / 32767.; gFreqWindow->Plot(slen, data, selt->GetRate()); gFreqWindow->Show(true); gFreqWindow->Raise(); delete[]data; delete[]data_sample; }
double VoiceKey::TestDirectionChanges(WaveTrack & t, sampleCount start, sampleCount len) { sampleCount s = start; //Keep track of start sampleCount originalLen = len; //Keep track of the length of block to process (its not the length of t) sampleCount blockSize = t.GetMaxBlockSize(); //Determine size of sampling buffer unsigned long directionchanges = 1; sampleFormat lastval=sampleFormat(0); int lastdirection=1; if( blockSize > len) blockSize = len; sampleFormat *buffer = new sampleFormat[blockSize]; //Get a sampling buffer while(len > 0) { sampleCount block = t.GetBestBlockSize(s); //Figure out how much to grab if(block > len) block = len; //Don't grab too much! t.Get((samplePtr)buffer,floatSample, s,block); //grab the block; if (len == originalLen) { //The first time through, set stuff up special. lastval = buffer[0]; } //Now, go through the block and calculate zero crossings for(int i = 0; i< block; i++) { if( sgn(buffer[i]-lastval) != lastdirection) { directionchanges++; lastdirection = sgn(buffer[i] - lastval); } lastval = buffer[i]; } len -= block; s += block; } delete [] buffer; return (double)directionchanges/originalLen; }
// DeduceFrequencies is Dominic's extremely cool trick (Vaughan sez so!) // to set deduce m_FromFrequency from the samples at the beginning of // the selection. Then we set some other params accordingly. void EffectChangePitch::DeduceFrequencies() { // As a neat trick, attempt to get the frequency of the note at the // beginning of the selection. SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *track = (WaveTrack *) iter.First(); if (track) { const int windowSize = 1024; const int analyzeSize = 8192; const int numWindows = analyzeSize / windowSize; double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; sampleCount start = track->TimeToLongSamples(t0); double rate = track->GetRate(); float buffer[analyzeSize]; float freq[windowSize/2]; float freqa[windowSize/2]; int i, j, argmax; int lag; for(j=0; j<windowSize/2; j++) freqa[j] = 0; track->Get((samplePtr) buffer, floatSample, start, analyzeSize); for(i=0; i<numWindows; i++) { ComputeSpectrum(buffer+i*windowSize, windowSize, windowSize, rate, freq, true); for(j=0; j<windowSize/2; j++) freqa[j] += freq[j]; } argmax=0; for(j=1; j<windowSize/2; j++) if (freqa[j] > freqa[argmax]) argmax = j; lag = (windowSize/2 - 1) - argmax; m_FromFrequency = rate / lag; m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0; // Now we can set the pitch control values. m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_ToPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_ToFrequency)); } }
double VoiceKey::TestDirectionChanges( const WaveTrack & t, sampleCount start, sampleCount len) { auto s = start; //Keep track of start auto originalLen = len; //Keep track of the length of block to process (its not the length of t) const auto blockSize = limitSampleBufferSize( t.GetMaxBlockSize(), len); //Determine size of sampling buffer unsigned long directionchanges = 1; float lastval=float(0); int lastdirection=1; Floats buffer{ blockSize }; //Get a sampling buffer while(len > 0) { //Figure out how much to grab auto block = limitSampleBufferSize ( t.GetBestBlockSize(s), len ); t.Get((samplePtr)buffer.get(), floatSample, s, block); //grab the block; if (len == originalLen) { //The first time through, set stuff up special. lastval = buffer[0]; } //Now, go through the block and calculate zero crossings for(decltype(block) i = 0; i< block; i++){ if( sgn(buffer[i]-lastval) != lastdirection) { directionchanges++; lastdirection = sgn(buffer[i] - lastval); } lastval = buffer[i]; } len -= block; s += block; } return (double)directionchanges/originalLen.as_double(); }
double VoiceKey::TestSignChanges(WaveTrack & t, sampleCount start, sampleCount len) { auto s = start; //Keep track of start auto originalLen = len; //Keep track of the length of block to process (its not the length of t) const auto blockSize = limitSampleBufferSize( t.GetMaxBlockSize(), len); //Determine size of sampling buffer unsigned long signchanges = 1; int currentsign=0; float *buffer = new float[blockSize]; //Get a sampling buffer while(len > 0) { //Figure out how much to grab auto block = limitSampleBufferSize ( t.GetBestBlockSize(s), len ); t.Get((samplePtr)buffer,floatSample, s,block); //grab the block; if (len == originalLen) { //The first time through, set stuff up special. currentsign = sgn(buffer[0]); } //Now, go through the block and calculate zero crossings for(decltype(block) i = 0; i< block; i++) { if( sgn(buffer[i]) != currentsign) { currentsign = sgn(buffer[i]); signchanges++; } } len -= block; s += block; } delete [] buffer; return (double)signchanges / originalLen.as_double(); }
bool VampEffect::Process() { if (!mPlugin) return false; TrackListIterator iter(mWaveTracks); int count = 0; WaveTrack *left = (WaveTrack *)iter.First(); bool multiple = false; int prevTrackChannels = 0; TrackListIterator scooter(iter); if (left->GetLinked()) scooter.Next(); if (scooter.Next()) { // if there is another track beyond this one and any linked one, // then we're processing more than one track. That means we // should use the originating track name in each new label // track's name, to make clear which is which multiple = true; } while (left) { sampleCount lstart, rstart; sampleCount len; GetSamples(left, &lstart, &len); WaveTrack *right = NULL; int channels = 1; if (left->GetLinked()) { right = (WaveTrack *)iter.Next(); channels = 2; GetSamples(right, &rstart, &len); } size_t step = mPlugin->getPreferredStepSize(); size_t block = mPlugin->getPreferredBlockSize(); bool initialiseRequired = true; if (block == 0) { if (step != 0) block = step; else block = 1024; } if (step == 0) { step = block; } if (prevTrackChannels > 0) { // Plugin has already been initialised, so if the number of // channels remains the same, we only need to do a reset. // Otherwise we need to re-construct the whole plugin, // because a Vamp plugin can't be re-initialised. if (prevTrackChannels == channels) { mPlugin->reset(); initialiseRequired = false; } else { //!!! todo: retain parameters previously set Init(); } } if (initialiseRequired) { if (!mPlugin->initialise(channels, step, block)) { wxMessageBox(_("Sorry, Vamp Plug-in failed to initialize.")); return false; } } LabelTrack *ltrack = mFactory->NewLabelTrack(); if (!multiple) { ltrack->SetName(GetEffectName()); } else { ltrack->SetName(wxString::Format(wxT("%s: %s"), left->GetName().c_str(), GetEffectName().c_str())); } mTracks->Add(ltrack); float **data = new float*[channels]; for (int c = 0; c < channels; ++c) data[c] = new float[block]; sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; while (len) { int request = block; if (request > len) request = len; if (left) left->Get((samplePtr)data[0], floatSample, ls, request); if (right) right->Get((samplePtr)data[1], floatSample, rs, request); if (request < (int)block) { for (int c = 0; c < channels; ++c) { for (int i = request; i < (int)block; ++i) { data[c][i] = 0.f; } } } Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime (ls, (int)(mRate + 0.5)); Vamp::Plugin::FeatureSet features = mPlugin->process(data, timestamp); AddFeatures(ltrack, features); if (len > (int)step) len -= step; else len = 0; ls += step; rs += step; if (channels > 1) { if (TrackGroupProgress(count, (ls - lstart) / double(originalLen))) return false; } else { if (TrackProgress(count, (ls - lstart) / double(originalLen))) return false; } } Vamp::Plugin::FeatureSet features = mPlugin->getRemainingFeatures(); AddFeatures(ltrack, features); prevTrackChannels = channels; left = (WaveTrack *)iter.Next(); } return true; }
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; }
//Move forward to find an ON region. sampleCount VoiceKey::OnForward (WaveTrack & t, sampleCount start, sampleCount len) { if((mWindowSize) >= (len + 10).as_double() ){ /* i18n-hint: Voice key is an experimental/incomplete feature that is used to navigate in vocal recordings, to move forwards and backwards by words. So 'key' is being used in the sense of an index. This error message means that you've selected too short a region of audio to be able to use this feature.*/ wxMessageBox(_("Selection is too small to use voice key.")); return start; } else { //Change the millisecond-based parameters into sample-based parameters double rate = t.GetRate(); //Translates seconds to samples size_t WindowSizeInt = (rate * mWindowSize); //Size of window to examine size_t SignalWindowSizeInt = (rate * mSignalWindowSize); //This much signal is necessary to trip key auto samplesleft = len - WindowSizeInt; //Indexes the number of samples remaining in the selection auto lastsubthresholdsample = start; //start this off at the selection start // keeps track of the sample number of the last sample to not exceed the threshold int blockruns=0; //keeps track of the number of consecutive above-threshold blocks //This loop goes through the selection a block at a time. If a long enough run //of above-threshold blocks occur, we return to the last sub-threshold block and //go through one sample at a time. //If there are fewer than 10 samples leftover, don't bother. for(auto i = start; samplesleft >= 10; i += (WindowSizeInt - 1) , samplesleft -= (WindowSizeInt - 1)) { //Set blocksize so that it is the right size const auto blocksize = limitSampleBufferSize( WindowSizeInt, samplesleft); //Test whether we are above threshold (the number of stats) if(AboveThreshold(t,i,blocksize)) { blockruns++; //Hit } else { blockruns=0; //Miss--start over lastsubthresholdsample = i; } //If the blockrun is long enough, break out of the loop early: if(blockruns > mSignalWindowSize/mWindowSize) break; } //Now, if we broke out early (samplesleft > 10), go back to the lastsubthresholdsample and look more carefully if(samplesleft > 10) { //Calculate how many to scan through--we only have to go through (at most) //the first window + 1 samples--but we need another window samples to draw from. size_t remaining = 2*WindowSizeInt+1; //To speed things up, create a local buffer to store things in, to avoid the costly t.Get(); //Only go through the first SignalWindowSizeInt samples, and choose the first that trips the key. float *buffer = new float[remaining]; t.Get((samplePtr)buffer, floatSample, lastsubthresholdsample, remaining); //Initialize these trend markers atrend and ztrend. They keep track of the //up/down trends at the start and end of the evaluation window. int atrend = sgn(buffer[1]-buffer[0]); int ztrend = sgn(buffer[WindowSizeInt+1]-buffer[WindowSizeInt]); double erg=0; double sc=0; double dc=0; //Get initial test statistic values. if(mUseEnergy) erg = TestEnergy(t, lastsubthresholdsample, WindowSizeInt); if(mUseSignChangesLow || mUseSignChangesHigh) sc = TestSignChanges(t,lastsubthresholdsample, WindowSizeInt); if(mUseDirectionChangesLow || mUseDirectionChangesHigh) dc = TestDirectionChanges(t,lastsubthresholdsample,WindowSizeInt); //Now, go through the sound again, sample by sample. wxASSERT(WindowSizeInt < SignalWindowSizeInt); size_t i; for(i = 0; i + WindowSizeInt < SignalWindowSizeInt; i++) { int tests = 0; int testThreshold = 0; //Update the test statistics if(mUseEnergy) { TestEnergyUpdate(erg, WindowSizeInt,buffer[i],buffer[i+WindowSizeInt+1]); tests += (int)(erg>mThresholdEnergy); testThreshold++; } if(mUseSignChangesLow) { TestSignChangesUpdate(sc,WindowSizeInt,buffer[i],buffer[i+1],buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(sc < mThresholdSignChangesLower); testThreshold++; } if(mUseSignChangesHigh) { TestSignChangesUpdate(sc,WindowSizeInt,buffer[i],buffer[i+1],buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(sc > mThresholdSignChangesUpper); testThreshold++; } if(mUseDirectionChangesLow) { TestDirectionChangesUpdate(dc,WindowSizeInt,atrend,buffer[i],buffer[i+1],ztrend,buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(dc < mThresholdDirectionChangesLower); testThreshold++; } if(mUseDirectionChangesHigh) { TestDirectionChangesUpdate(dc,WindowSizeInt,atrend,buffer[i],buffer[i+1],ztrend,buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(dc > mThresholdDirectionChangesUpper); testThreshold++; } if(tests >= testThreshold) { //Finish off on the first hit break; } } //When we get here, i+lastsubthresholdsample is the best guess for where the word starts delete [] buffer; return i + lastsubthresholdsample; } else { //If we failed to find anything, return the start position return start ; } } }
//Move backward from the end to find an OFF region sampleCount VoiceKey::OffBackward (WaveTrack & t, sampleCount end, sampleCount len) { if((mWindowSize) >= len+10) { wxMessageBox(_("Selection is too small to use voice key.")); return end; } else { sampleCount lastsubthresholdsample; // keeps track of the sample number of the last sample to not exceed the threshold //Change the millisecond-based parameters into sample-based parameters double rate = t.GetRate(); //Translates seconds to samples unsigned int WindowSizeInt = (unsigned int)(rate * mWindowSize); //Size of window to examine //unsigned int SilentWindowSizeInt = (unsigned int)(rate * mSilentWindowSize); //This much signal is necessary to trip key int samplesleft = len - WindowSizeInt; //Indexes the number of samples remaining in the selection lastsubthresholdsample = end; //start this off at the end unsigned int i; //iterates through waveblock int blockruns=0; //keeps track of the number of consecutive above-threshold blocks int blocksize; //The final block may be smaller than WindowSizeInt, so use this //This loop goes through the selection a block at a time in reverse order. If a long enough run //of above-threshold blocks occur, we return to the last sub-threshold block and //go through one sample at a time. //If there are fewer than 10 samples leftover, don't bother. for(i = end - WindowSizeInt; samplesleft >=10; i-=(WindowSizeInt-1) , samplesleft -= (WindowSizeInt -1)) { //Set blocksize so that it is the right size if(samplesleft < (int)WindowSizeInt) { blocksize = samplesleft; } else { blocksize = WindowSizeInt; } if(!AboveThreshold(t,i,blocksize)) { blockruns++; //Hit } else { blockruns=0; //Miss--start over lastsubthresholdsample = i+WindowSizeInt; } //If the blockrun is long enough, break out of the loop early: if(blockruns > mSilentWindowSize/mWindowSize) break; } //Now, if we broke out early (samplesleft > 10), go back to the lastsubthresholdsample and look more carefully if(samplesleft > 10) { //Calculate how many to scan through--we only have to go through (at most) //the first window + 1 samples--but we need another window samples to draw from. samplesleft = 2*WindowSizeInt+1; //To speed things up, create a local buffer to store things in, to avoid the costly t.Get(); //Only go through the first SilentWindowSizeInt samples, and choose the first that trips the key. sampleFormat *buffer = new sampleFormat[samplesleft]; t.Get((samplePtr)buffer, floatSample, lastsubthresholdsample-samplesleft,samplesleft); //Initialize these trend markers atrend and ztrend. They keep track of the //up/down trends at the start and end of the evaluation window. int atrend = sgn(buffer[samplesleft - 2]-buffer[samplesleft - 1]); int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-buffer[samplesleft - WindowSizeInt-2]); double erg=0; double sc=0; double dc=0; //Get initial test statistic values. if(mUseEnergy) erg = TestEnergy(t, lastsubthresholdsample, WindowSizeInt); if(mUseSignChangesLow || mUseSignChangesHigh) sc = TestSignChanges(t,lastsubthresholdsample, WindowSizeInt); if(mUseDirectionChangesLow || mUseDirectionChangesHigh) dc = TestDirectionChanges(t,lastsubthresholdsample,WindowSizeInt); //Now, go through the sound again, sample by sample. for(i=samplesleft-1; i>WindowSizeInt; i--) { int tests = 0; int testThreshold = 0; //Update the test statistics if(mUseEnergy) { TestEnergyUpdate(erg, WindowSizeInt,buffer[i],buffer[i+WindowSizeInt+1]); tests += (int)(erg>mThresholdEnergy); testThreshold++; } if(mUseSignChangesLow) { TestSignChangesUpdate(sc,WindowSizeInt,buffer[i],buffer[i+1],buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(sc < mThresholdSignChangesLower); testThreshold++; } if(mUseSignChangesHigh) { TestSignChangesUpdate(sc,WindowSizeInt,buffer[i],buffer[i+1],buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(sc > mThresholdSignChangesUpper); testThreshold++; } if(mUseDirectionChangesLow) { TestDirectionChangesUpdate(dc,WindowSizeInt,atrend,buffer[i],buffer[i+1],ztrend,buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(dc < mThresholdDirectionChangesLower); testThreshold++; } if(mUseDirectionChangesHigh) { TestDirectionChangesUpdate(dc,WindowSizeInt,atrend,buffer[i],buffer[i+1],ztrend,buffer[i+WindowSizeInt],buffer[i+WindowSizeInt+1]); tests += (int)(dc > mThresholdDirectionChangesUpper); testThreshold++; } if(tests < testThreshold) { //Finish off on the first hit break; } } //When we get here, i+lastsubthresholdsample is the best guess for where the word starts delete [] buffer; return lastsubthresholdsample - samplesleft + i; } else { //If we failed to find anything, return the start position return end ; } } }
// Deduce m_FromFrequency from the samples at the beginning of // the selection. Then set some other params accordingly. void EffectChangePitch::DeduceFrequencies() { // As a neat trick, attempt to get the frequency of the note at the // beginning of the selection. SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *track = (WaveTrack *) iter.First(); if (track) { double rate = track->GetRate(); // Auto-size window -- high sample rates require larger windowSize. // Aim for around 2048 samples at 44.1 kHz (good down to about 100 Hz). // To detect single notes, analysis period should be about 0.2 seconds. // windowSize must be a power of 2. int windowSize = wxRound(pow(2.0, floor((log(rate / 20.0)/log(2.0)) + 0.5))); // windowSize < 256 too inaccurate windowSize = (windowSize > 256)? windowSize : 256; // we want about 0.2 seconds to catch the first note. // number of windows rounded to nearest integer >= 1. int numWindows = wxRound((double)(rate / (5.0f * windowSize))); numWindows = (numWindows > 0)? numWindows : 1; double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; sampleCount start = track->TimeToLongSamples(t0); int analyzeSize = windowSize * numWindows; float * buffer; buffer = new float[analyzeSize]; float * freq; freq = new float[windowSize/2]; float * freqa; freqa = new float[windowSize/2]; int i, j, argmax; int lag; for(j=0; j<windowSize/2; j++) freqa[j] = 0; track->Get((samplePtr) buffer, floatSample, start, analyzeSize); for(i=0; i<numWindows; i++) { ComputeSpectrum(buffer+i*windowSize, windowSize, windowSize, rate, freq, true); for(j=0; j<windowSize/2; j++) freqa[j] += freq[j]; } argmax=0; for(j=1; j<windowSize/2; j++) if (freqa[j] > freqa[argmax]) argmax = j; delete [] freq; delete [] freqa; delete [] buffer; lag = (windowSize/2 - 1) - argmax; m_dStartFrequency = rate / lag; } double dFromMIDInote = FreqToMIDInote(m_dStartFrequency); double dToMIDInote = dFromMIDInote + m_dSemitonesChange; m_nFromPitch = PitchIndex(dFromMIDInote); m_nFromOctave = PitchOctave(dFromMIDInote); m_nToPitch = PitchIndex(dToMIDInote); m_nToOctave = PitchOctave(dToMIDInote); m_FromFrequency = m_dStartFrequency; Calc_PercentChange(); Calc_ToFrequency(); }
bool EffectTruncSilence::Process() { TrackListIterator iter(mWaveTracks); WaveTrack *t; double t0 = mT0; double t1 = mT1; int tndx; int tcount = 0; // 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(); } // Transform the marker timepoints to samples t = (WaveTrack *) iter.First(); longSampleCount start = t->TimeToLongSamples(t0); longSampleCount end = t->TimeToLongSamples(t1); // Bigger buffers reduce 'reset' blockLen *= 8; // Allocate buffers float **buffer = new float*[tcount]; for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx] = new float[blockLen]; } // 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 truncLongestAllowedSilentSamples = int((wxMax( mTruncLongestAllowedSilentMs, minTruncMs) * rate) / 1000.0); // Figure out number of frames for ramping int quarterSecondFrames = int((rate * QUARTER_SECOND_MS) / 1000.0); int rampInFrames = (truncLongestAllowedSilentSamples / 4); if (rampInFrames > quarterSecondFrames) { rampInFrames = quarterSecondFrames; } // Start processing this->CopyInputWaveTracks(); // Set up mOutputWaveTracks. TrackListIterator iterOut(mOutputWaveTracks); longSampleCount index = start; longSampleCount outTrackOffset = start; bool cancelled = false; while (index < end) { // Limit size of current block if we've reached the end sampleCount limit = blockLen; if ((index + blockLen) > end) { limit = end - index; } // Fill the buffers tndx = 0; t = (WaveTrack *) iter.First(); while (t) { t->Get((samplePtr)buffer[tndx++], floatSample, index, blockLen); t = (WaveTrack *) iter.Next(); } // Reset bool ignoringFrames = false; sampleCount consecutiveSilentFrames = 0; sampleCount truncIndex = 0; // Look for silences in current block for (sampleCount i = 0; 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; } } // 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 > truncLongestAllowedSilentSamples) { ignoringFrames = true; continue; } } else { if (ignoringFrames == true) { sampleCount curOffset = i - rampInFrames; truncIndex -= rampInFrames; // backup into ignored frames for (tndx = 0; tndx < tcount; tndx++) { sampleCount trunci = truncIndex; for (int fr = 0; fr < rampInFrames; fr++) { buffer[tndx][trunci++] = buffer[tndx][curOffset + fr]; } if(((trunci - rampInFrames) - mBlendFrameCount) >= 0) { BlendFrames(buffer[tndx], mBlendFrameCount, ((trunci - rampInFrames) - mBlendFrameCount), ((i - rampInFrames) - mBlendFrameCount)); } } truncIndex += rampInFrames; } consecutiveSilentFrames = 0; ignoringFrames = 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 if (truncIndex < 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(); } } // Maintain output index outTrackOffset += truncIndex; // Update progress and bail if user cancelled cancelled = TrackProgress(0, ((double)index / (double)end)); if (cancelled) { break; } // Bump to next block index += limit; } // Remove stale data at end of output tracks. if (!cancelled && (outTrackOffset < end)) { t = (WaveTrack *) iterOut.First(); while (t) { t->Clear(outTrackOffset / rate, t1); 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->ReplaceProcessedWaveTracks(!cancelled); return !cancelled; }
bool EffectTruncSilence::Process() { // Typical fraction of total time taken by detection (better to guess low) const double detectFrac = .4; // Copy tracks this->CopyInputTracks(Track::All); // Lower bound on the amount of silence to find at a time -- this avoids // detecting silence repeatedly in low-frequency sounds. const double minTruncMs = 0.001; double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; // Master list of silent regions; it is responsible for deleting them. // This list should always be kept in order. RegionList silences; silences.DeleteContents(true); // Start with the whole selection silent Region *sel = new Region; sel->start = mT0; sel->end = mT1; silences.push_back(sel); // Remove non-silent regions in each track SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); int whichTrack = 0; for (Track *t = iter.First(); t; t = iter.Next()) { WaveTrack *wt = (WaveTrack *)t; // Smallest silent region to detect in frames sampleCount minSilenceFrames = sampleCount(wxMax( mInitialAllowedSilence, minTruncMs) * wt->GetRate()); // // Scan the track for silences // RegionList trackSilences; trackSilences.DeleteContents(true); sampleCount blockLen = wt->GetMaxBlockSize(); sampleCount start = wt->TimeToLongSamples(mT0); sampleCount end = wt->TimeToLongSamples(mT1); // Allocate buffer float *buffer = new float[blockLen]; sampleCount index = start; sampleCount silentFrames = 0; bool cancelled = false; // Keep position in overall silences list for optimization RegionList::iterator rit(silences.begin()); while (index < end) { // Show progress dialog, test for cancellation cancelled = TotalProgress( detectFrac * (whichTrack + index / (double)end) / (double)GetNumWaveTracks()); if (cancelled) break; // // Optimization: if not in a silent region skip ahead to the next one // double curTime = wt->LongSamplesToTime(index); for ( ; rit != silences.end(); ++rit) { // Find the first silent region ending after current time if ((*rit)->end >= curTime) break; } if (rit == silences.end()) { // No more regions -- no need to process the rest of the track break; } else if ((*rit)->start > curTime) { // End current silent region, skip ahead if (silentFrames >= minSilenceFrames) { Region *r = new Region; r->start = wt->LongSamplesToTime(index - silentFrames); r->end = wt->LongSamplesToTime(index); trackSilences.push_back(r); } silentFrames = 0; index = wt->TimeToLongSamples((*rit)->start); } // // End of optimization // // Limit size of current block if we've reached the end sampleCount count = blockLen; if ((index + count) > end) { count = end - index; } // Fill buffer wt->Get((samplePtr)(buffer), floatSample, index, count); // Look for silences in current block for (sampleCount i = 0; i < count; ++i) { if (fabs(buffer[i]) < truncDbSilenceThreshold) { ++silentFrames; } else { if (silentFrames >= minSilenceFrames) { // Record the silent region Region *r = new Region; r->start = wt->LongSamplesToTime(index + i - silentFrames); r->end = wt->LongSamplesToTime(index + i); trackSilences.push_back(r); } silentFrames = 0; } } // Next block index += count; } delete [] buffer; // Buffer has been freed, so we're OK to return if cancelled if (cancelled) { ReplaceProcessedTracks(false); return false; } if (silentFrames >= minSilenceFrames) { // Track ended in silence -- record region Region *r = new Region; r->start = wt->LongSamplesToTime(index - silentFrames); r->end = wt->LongSamplesToTime(index); trackSilences.push_back(r); } // Intersect with the overall silent region list Intersect(silences, trackSilences); whichTrack++; } // // Now remove the silent regions from all selected / sync-lock selected tracks. // // Loop over detected regions in reverse (so cuts don't change time values // down the line) int whichReg = 0; RegionList::reverse_iterator rit; double totalCutLen = 0.0; // For cutting selection at the end for (rit = silences.rbegin(); rit != silences.rend(); ++rit) { Region *r = *rit; // Progress dialog and cancellation. Do additional cleanup before return. if (TotalProgress(detectFrac + (1 - detectFrac) * whichReg / (double)silences.size())) { ReplaceProcessedTracks(false); return false; } // Intersection may create regions smaller than allowed; ignore them. // Allow one nanosecond extra for consistent results with exact milliseconds of allowed silence. if ((r->end - r->start) < (mInitialAllowedSilence - 0.000000001)) continue; // Find new silence length as requested double inLength = r->end - r->start; double outLength; switch (mProcessIndex) { case 0: outLength = wxMin(mTruncLongestAllowedSilence, inLength); break; case 1: outLength = mInitialAllowedSilence + (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0; break; default: // Not currently used. outLength = wxMin(mInitialAllowedSilence + (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0, mTruncLongestAllowedSilence); } double cutLen = inLength - outLength; totalCutLen += cutLen; TrackListIterator iterOut(mOutputTracks); for (Track *t = iterOut.First(); t; t = iterOut.Next()) { // Don't waste time past the end of a track if (t->GetEndTime() < r->start) continue; if (t->GetKind() == Track::Wave && ( t->GetSelected() || t->IsSyncLockSelected())) { // In WaveTracks, clear with a cross-fade WaveTrack *wt = (WaveTrack *)t; sampleCount blendFrames = mBlendFrameCount; double cutStart = (r->start + r->end - cutLen) / 2; double cutEnd = cutStart + cutLen; // Round start/end times to frame boundaries cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart)); cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd)); // Make sure the cross-fade does not affect non-silent frames if (wt->LongSamplesToTime(blendFrames) > inLength) { blendFrames = wt->TimeToLongSamples(inLength); } // Perform cross-fade in memory float *buf1 = new float[blendFrames]; float *buf2 = new float[blendFrames]; sampleCount t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2; sampleCount t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2; wt->Get((samplePtr)buf1, floatSample, t1, blendFrames); wt->Get((samplePtr)buf2, floatSample, t2, blendFrames); for (sampleCount i = 0; i < blendFrames; ++i) { buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) / (double)blendFrames; } // Perform the cut wt->Clear(cutStart, cutEnd); // Write cross-faded data wt->Set((samplePtr)buf1, floatSample, t1, blendFrames); delete [] buf1; delete [] buf2; } else if (t->GetSelected() || t->IsSyncLockSelected()) { // Non-wave tracks: just do a sync-lock adjust double cutStart = (r->start + r->end - cutLen) / 2; double cutEnd = cutStart + cutLen; t->SyncLockAdjust(cutEnd, cutStart); } } ++whichReg; } mT1 -= totalCutLen; ReplaceProcessedTracks(true); return true; }