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; Floats buffer1{ blockSize }; Floats buffer2{ 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.get(), floatSample, first, block); track->Get((samplePtr)buffer2.get(), 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.get(), floatSample, first, block); track->Set((samplePtr)buffer2.get(), floatSample, second, block); len -= 2 * block; first += block; if( TrackProgress(count, 2 * ( first - originalStart ).as_double() / originalLen.as_double() ) ) { rc = false; break; } } return rc; }
size_t EffectDtmf::ProcessBlock(float **WXUNUSED(inbuf), float **outbuf, size_t size) { float *buffer = outbuf[0]; decltype(size) processed = 0; // for the whole dtmf sequence, we will be generating either tone or silence // according to a bool value, and this might be done in small chunks of size // 'block', as a single tone might sometimes be larger than the block // tone and silence generally have different duration, thus two generation blocks // // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs. // A problem might be if the tone duration is very short (<10ms)... (?) // // One more problem is to deal with the approximations done when calculating the duration // of both tone and silence: in some cases the final sum might not be same as the initial // duration. So, to overcome this, we had a redistribution block up, and now we will spread // the remaining samples in every bin in order to achieve the full duration: test case was // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation // you ended up with 3.999s or in other units: 3 seconds and 44097 samples. // while (size) { if (numRemaining == 0) { isTone = !isTone; if (isTone) { curSeqPos++; numRemaining = numSamplesTone; curTonePos = 0; } else { numRemaining = numSamplesSilence; } // the statement takes care of extracting one sample from the diff bin and // adding it into the current block until depletion numRemaining += (diff-- > 0 ? 1 : 0); } const auto len = limitSampleBufferSize( size, numRemaining ); if (isTone) { // generate the tone and append MakeDtmfTone(buffer, len, mSampleRate, dtmfSequence[curSeqPos], curTonePos, numSamplesTone, dtmfAmplitude); curTonePos += len; } else { memset(buffer, 0, sizeof(float) * len); } numRemaining -= len; buffer += len; size -= len; processed += len; } return processed; }
// ProcessOne() takes a track, transforms it to bunch of buffer-blocks, // and calls libsamplerate code on these blocks. bool EffectChangeSpeed::ProcessOne(WaveTrack * track, sampleCount start, sampleCount end) { if (track == NULL) return false; // initialization, per examples of Mixer::Mixer and // EffectSoundTouch::ProcessOne auto outputTrack = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); //Get the length of the selection (as double). len is //used simple to calculate a progress meter, so it is easier //to make it a double now than it is to do it later auto len = (end - start).as_double(); // Initiate processing buffers, most likely shorter than // the length of the selection being processed. auto inBufferSize = track->GetMaxBlockSize(); Floats inBuffer{ inBufferSize }; // mFactor is at most 100-fold so this shouldn't overflow size_t auto outBufferSize = size_t( mFactor * inBufferSize + 10 ); Floats outBuffer{ outBufferSize }; // Set up the resampling stuff for this track. Resample resample(true, mFactor, mFactor); // constant rate resampling //Go through the track one buffer at a time. samplePos counts which //sample the current buffer starts at. bool bResult = true; auto samplePos = start; while (samplePos < end) { //Get a blockSize of samples (smaller than the size of the buffer) auto blockSize = limitSampleBufferSize( track->GetBestBlockSize(samplePos), end - samplePos ); //Get the samples from the track and put them in the buffer track->Get((samplePtr) inBuffer.get(), floatSample, samplePos, blockSize); const auto results = resample.Process(mFactor, inBuffer.get(), blockSize, ((samplePos + blockSize) >= end), outBuffer.get(), outBufferSize); const auto outgen = results.second; if (outgen > 0) outputTrack->Append((samplePtr)outBuffer.get(), floatSample, outgen); // Increment samplePos samplePos += results.first; // Update the Progress meter if (TrackProgress(mCurTrackNum, (samplePos - start).as_double() / len)) { bResult = false; break; } } // Flush the output WaveTrack (since it's buffered, too) outputTrack->Flush(); // Take the output track and insert it in place of the original // sample data double newLength = outputTrack->GetEndTime(); if (bResult) { LinearTimeWarper warper { mCurT0, mCurT0, mCurT1, mCurT0 + newLength }; bResult = track->ClearAndPaste( mCurT0, mCurT1, outputTrack.get(), true, false, &warper); } if (newLength > mMaxNewLength) mMaxNewLength = newLength; return bResult; }
bool EffectTruncSilence::Analyze(RegionList& silenceList, RegionList& trackSilences, const WaveTrack *wt, sampleCount* silentFrame, sampleCount* index, int whichTrack, double* inputLength /*= NULL*/, double* minInputLength /*= NULL*/) { // Smallest silent region to detect in frames auto minSilenceFrames = sampleCount(std::max( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate()); double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; auto blockLen = wt->GetMaxBlockSize(); auto start = wt->TimeToLongSamples(mT0); auto end = wt->TimeToLongSamples(mT1); sampleCount outLength = 0; double previewLength; gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0); // Minimum required length in samples. const sampleCount previewLen( previewLength * wt->GetRate() ); // Keep position in overall silences list for optimization RegionList::iterator rit(silenceList.begin()); // Allocate buffer Floats buffer{ blockLen }; // Loop through current track while (*index < end) { if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) { *inputLength = std::min<double>(*inputLength, *minInputLength); if (outLength >= previewLen) { *minInputLength = *inputLength; } return true; } if (!inputLength) { // Show progress dialog, test for cancellation bool cancelled = TotalProgress( detectFrac * (whichTrack + (*index - start).as_double() / (end - start).as_double()) / (double)GetNumWaveTracks()); if (cancelled) return false; } // Optimization: if not in a silent region skip ahead to the next one double curTime = wt->LongSamplesToTime(*index); for ( ; rit != silenceList.end(); ++rit) { // Find the first silent region ending after current time if (rit->end >= curTime) { break; } } if (rit == silenceList.end()) { // No more regions -- no need to process the rest of the track if (inputLength) { // Add available samples up to previewLength. auto remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index; auto requiredTrackSamples = previewLen - outLength; outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples; } break; } else if (rit->start > curTime) { // End current silent region, skip ahead if (*silentFrame >= minSilenceFrames) { trackSilences.push_back(Region( wt->LongSamplesToTime(*index - *silentFrame), wt->LongSamplesToTime(*index) )); } *silentFrame = 0; auto newIndex = wt->TimeToLongSamples(rit->start); if (inputLength) { auto requiredTrackSamples = previewLen - outLength; // Add non-silent sample to outLength outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index; } *index = newIndex; } // End of optimization // Limit size of current block if we've reached the end auto count = limitSampleBufferSize( blockLen, end - *index ); // Fill buffer wt->Get((samplePtr)(buffer.get()), floatSample, *index, count); // Look for silenceList in current block for (decltype(count) i = 0; i < count; ++i) { if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) { *inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start); break; } if (fabs(buffer[i]) < truncDbSilenceThreshold) { (*silentFrame)++; } else { sampleCount allowed = 0; if (*silentFrame >= minSilenceFrames) { if (inputLength) { switch (mActionIndex) { case kTruncate: outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence); break; case kCompress: allowed = wt->TimeToLongSamples(mInitialAllowedSilence); outLength += sampleCount( allowed.as_double() + (*silentFrame - allowed).as_double() * mSilenceCompressPercent / 100.0 ); break; // default: // Not currently used. } } // Record the silent region trackSilences.push_back(Region( wt->LongSamplesToTime(*index + i - *silentFrame), wt->LongSamplesToTime(*index + i) )); } else if (inputLength) { // included as part of non-silence outLength += *silentFrame; } *silentFrame = 0; if (inputLength) { ++outLength; // Add non-silent sample to outLength } } } // Next block *index += count; } if (inputLength) { *inputLength = std::min<double>(*inputLength, *minInputLength); if (outLength >= previewLen) { *minInputLength = *inputLength; } } return true; }
//ProcessOne() takes a track, transforms it to bunch of buffer-blocks, //and executes TwoBufferProcessPass1 or TwoBufferProcessPass2 on these blocks bool EffectTwoPassSimpleMono::ProcessOne(WaveTrack * track, sampleCount start, sampleCount end) { bool ret; float *tmpfloat; //Get the length of the buffer (as double). len is //used simple to calculate a progress meter, so it is easier //to make it a double now than it is to do it later auto len = (end - start).as_double(); auto maxblock = track->GetMaxBlockSize(); //Initiate a processing buffer. This buffer will (most likely) //be shorter than the length of the track being processed. float *buffer1 = new float[maxblock]; float *buffer2 = new float[maxblock]; auto samples1 = limitSampleBufferSize( std::min( maxblock, track->GetBestBlockSize(start) ), end - start ); //Get the samples from the track and put them in the buffer track->Get((samplePtr) buffer1, floatSample, start, samples1); // Process the first buffer with a NULL previous buffer if (mPass == 0) ret = TwoBufferProcessPass1(NULL, 0, buffer1, samples1); else ret = TwoBufferProcessPass2(NULL, 0, buffer1, samples1); if (!ret) { delete[]buffer1; delete[]buffer2; //Return false because the effect failed. return false; } //Go through the track one buffer at a time. s counts which //sample the current buffer starts at. auto s = start + samples1; while (s < end) { //Get a block of samples (smaller than the size of the buffer) //Adjust the block size if it is the final block in the track auto samples2 = limitSampleBufferSize( std::min( track->GetBestBlockSize(s), maxblock ), end - s ); //Get the samples from the track and put them in the buffer track->Get((samplePtr) buffer2, floatSample, s, samples2); //Process the buffer. If it fails, clean up and exit. if (mPass == 0) ret = TwoBufferProcessPass1(buffer1, samples1, buffer2, samples2); else ret = TwoBufferProcessPass2(buffer1, samples1, buffer2, samples2); if (!ret) { delete[]buffer1; delete[]buffer2; //Return false because the effect failed. return false; } //Processing succeeded. copy the newly-changed samples back //onto the track. track->Set((samplePtr) buffer1, floatSample, s-samples1, samples1); //Increment s one blockfull of samples s += samples2; //Update the Progress meter if (mSecondPassDisabled) ret = TotalProgress( (mCurTrackNum + (s-start).as_double()/len) / GetNumWaveTracks()); else ret = TotalProgress( (mCurTrackNum + (s-start).as_double()/len + GetNumWaveTracks()*mPass) / (GetNumWaveTracks()*2)); if (ret) { delete[]buffer1; delete[]buffer2; //Return false because the effect failed. return false; } // Rotate the buffers tmpfloat = buffer1; buffer1 = buffer2; buffer2 = tmpfloat; std::swap(samples1, samples2); } // Send the last buffer with a NULL pointer for the current buffer if (mPass == 0) ret = TwoBufferProcessPass1(buffer1, samples1, NULL, 0); else ret = TwoBufferProcessPass2(buffer1, samples1, NULL, 0); if (!ret) { delete[]buffer1; delete[]buffer2; //Return false because the effect failed. return false; } //Processing succeeded. copy the newly-changed samples back //onto the track. track->Set((samplePtr) buffer1, floatSample, s-samples1, samples1); //Clean up the buffer delete[]buffer1; delete[]buffer2; //Return true because the effect processing succeeded. return true; }
bool VampEffect::Process() { if (!mPlugin) { return false; } int count = 0; bool multiple = false; unsigned prevTrackChannels = 0; if (GetNumWaveGroups() > 1) { // 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; } std::vector<std::shared_ptr<Effect::AddedAnalysisTrack>> addedTracks; for (auto leader : inputTracks()->Leaders<const WaveTrack>()) { auto channelGroup = TrackList::Channels(leader); auto left = *channelGroup.first++; sampleCount lstart, rstart = 0; sampleCount len; GetSamples(left, &lstart, &len); unsigned channels = 1; // channelGroup now contains all but the first channel const WaveTrack *right = channelGroup.size() ? *channelGroup.first++ : nullptr; if (right) { channels = 2; GetSamples(right, &rstart, &len); } // TODO: more-than-two-channels 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)) { Effect::MessageBox(_("Sorry, Vamp Plug-in failed to initialize.")); return false; } } const auto effectName = GetSymbol().Translation(); addedTracks.push_back(AddAnalysisTrack( multiple ? wxString::Format( _("%s: %s"), left->GetName(), effectName ) : effectName )); LabelTrack *ltrack = addedTracks.back()->get(); FloatBuffers data{ channels, block }; auto originalLen = len; auto ls = lstart; auto rs = rstart; while (len != 0) { const auto request = limitSampleBufferSize( block, len ); if (left) { left->Get((samplePtr)data[0].get(), floatSample, ls, request); } if (right) { right->Get((samplePtr)data[1].get(), floatSample, rs, request); } if (request < block) { for (unsigned int c = 0; c < channels; ++c) { for (decltype(block) i = request; i < block; ++i) { data[c][i] = 0.f; } } } // UNSAFE_SAMPLE_COUNT_TRUNCATION // Truncation in case of very long tracks! Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime( long( ls.as_long_long() ), (int)(mRate + 0.5) ); Vamp::Plugin::FeatureSet features = mPlugin->process( reinterpret_cast< float** >( data.get() ), 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).as_double() / originalLen.as_double() )) { return false; } } else { if (TrackProgress(count, (ls - lstart).as_double() / originalLen.as_double() )) { return false; } } } Vamp::Plugin::FeatureSet features = mPlugin->getRemainingFeatures(); AddFeatures(ltrack, features); prevTrackChannels = channels; } // All completed without cancellation, so commit the addition of tracks now for (auto &addedTrack : addedTracks) addedTrack->Commit(); return true; }
// this currently does an exponential fade bool EffectAutoDuck::ApplyDuckFade(int trackNum, WaveTrack* t, double t0, double t1) { bool cancel = false; auto start = t->TimeToLongSamples(t0); auto end = t->TimeToLongSamples(t1); Floats buf{ kBufSize }; auto pos = start; auto fadeDownSamples = t->TimeToLongSamples( mOuterFadeDownLen + mInnerFadeDownLen); if (fadeDownSamples < 1) fadeDownSamples = 1; auto fadeUpSamples = t->TimeToLongSamples( mOuterFadeUpLen + mInnerFadeUpLen); if (fadeUpSamples < 1) fadeUpSamples = 1; float fadeDownStep = mDuckAmountDb / fadeDownSamples.as_double(); float fadeUpStep = mDuckAmountDb / fadeUpSamples.as_double(); while (pos < end) { const auto len = limitSampleBufferSize( kBufSize, end - pos ); t->Get((samplePtr)buf.get(), floatSample, pos, len); for (auto i = pos; i < pos + len; i++) { float gainDown = fadeDownStep * (i - start).as_float(); float gainUp = fadeUpStep * (end - i).as_float(); float gain; if (gainDown > gainUp) gain = gainDown; else gain = gainUp; if (gain < mDuckAmountDb) gain = mDuckAmountDb; // i - pos is bounded by len: buf[ ( i - pos ).as_size_t() ] *= DB_TO_LINEAR(gain); } t->Set((samplePtr)buf.get(), floatSample, pos, len); pos += len; float curTime = t->LongSamplesToTime(pos); float fractionFinished = (curTime - mT0) / (mT1 - mT0); if (TotalProgress( (trackNum + 1 + fractionFinished) / (GetNumWaveTracks() + 1) )) { cancel = true; break; } } return cancel; }
bool EffectAutoDuck::Process() { if (GetNumWaveTracks() == 0 || !mControlTrack) return false; bool cancel = false; auto start = mControlTrack->TimeToLongSamples(mT0 + mOuterFadeDownLen); auto end = mControlTrack->TimeToLongSamples(mT1 - mOuterFadeUpLen); if (end <= start) return false; // the minimum number of samples we have to wait until the maximum // pause has been exceeded double maxPause = mMaximumPause; // We don't fade in until we have time enough to actually fade out again if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen) maxPause = mOuterFadeDownLen + mOuterFadeUpLen; auto minSamplesPause = mControlTrack->TimeToLongSamples(maxPause); double threshold = DB_TO_LINEAR(mThresholdDb); // adjust the threshold so we can compare it to the rmsSum value threshold = threshold * threshold * kRMSWindowSize; int rmsPos = 0; float rmsSum = 0; // to make the progress bar appear more natural, we first look for all // duck regions and apply them all at once afterwards std::vector<AutoDuckRegion> regions; bool inDuckRegion = false; { Floats rmsWindow{ kRMSWindowSize, true }; Floats buf{ kBufSize }; // initialize the following two variables to prevent compiler warning double duckRegionStart = 0; sampleCount curSamplesPause = 0; auto pos = start; while (pos < end) { const auto len = limitSampleBufferSize( kBufSize, end - pos ); mControlTrack->Get((samplePtr)buf.get(), floatSample, pos, len); for (auto i = pos; i < pos + len; i++) { rmsSum -= rmsWindow[rmsPos]; // i - pos is bounded by len: auto index = ( i - pos ).as_size_t(); rmsWindow[rmsPos] = buf[ index ] * buf[ index ]; rmsSum += rmsWindow[rmsPos]; rmsPos = (rmsPos + 1) % kRMSWindowSize; bool thresholdExceeded = rmsSum > threshold; if (thresholdExceeded) { // everytime the threshold is exceeded, reset our count for // the number of pause samples curSamplesPause = 0; if (!inDuckRegion) { // the threshold has been exceeded for the first time, so // let the duck region begin here inDuckRegion = true; duckRegionStart = mControlTrack->LongSamplesToTime(i); } } if (!thresholdExceeded && inDuckRegion) { // the threshold has not been exceeded and we are in a duck // region, but only fade in if the maximum pause has been // exceeded curSamplesPause += 1; if (curSamplesPause >= minSamplesPause) { // do the actual duck fade and reset all values double duckRegionEnd = mControlTrack->LongSamplesToTime(i - curSamplesPause); regions.push_back(AutoDuckRegion( duckRegionStart - mOuterFadeDownLen, duckRegionEnd + mOuterFadeUpLen)); inDuckRegion = false; } } } pos += len; if (TotalProgress( (pos - start).as_double() / (end - start).as_double() / (GetNumWaveTracks() + 1) )) { cancel = true; break; } } // apply last duck fade, if any if (inDuckRegion) { double duckRegionEnd = mControlTrack->LongSamplesToTime(end - curSamplesPause); regions.push_back(AutoDuckRegion( duckRegionStart - mOuterFadeDownLen, duckRegionEnd + mOuterFadeUpLen)); } } if (!cancel) { CopyInputTracks(); // Set up mOutputTracks. SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks.get()); Track *iterTrack = iter.First(); int trackNum = 0; while (iterTrack) { WaveTrack* t = (WaveTrack*)iterTrack; for (size_t i = 0; i < regions.size(); i++) { const AutoDuckRegion& region = regions[i]; if (ApplyDuckFade(trackNum, t, region.t0, region.t1)) { cancel = true; break; } } if (cancel) break; iterTrack = iter.Next(); trackNum++; } } ReplaceProcessedTracks(!cancel); return !cancel; }