tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TimeStretch.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11#if JUCE_WINDOWS && TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
12 #pragma comment (lib, "libelastiquePro.lib")
13 #pragma comment (lib, "libelastiqueEfficient.lib")
14 #pragma comment (lib, "libelastiqueSOLOIST.lib")
15 #pragma comment (lib, "libResample.lib")
16 #pragma comment (lib, "libzplVecLib.lib")
17#endif
18
19#if JUCE_WINDOWS && TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE_IPP
20 #pragma comment (lib, "ippcore_l.lib")
21 #pragma comment (lib, "ipps_l.lib")
22 #pragma comment (lib, "ippvm_l.lib")
23#endif
24
25#if JUCE_WINDOWS
26 #define NOMINMAX
27#endif
28
29namespace tracktion { inline namespace engine
30{
31
33{
34 if (string.isEmpty())
35 return;
36
37 if (string.startsWith ("1/"))
38 {
39 juce::StringArray tokens;
40 tokens.addTokens (string, "/", {});
41
42 if (tokens.size() == 5)
43 {
44 midSideStereo = tokens[1].getIntValue() != 0;
45 syncTimeStrPitchShft = tokens[2].getIntValue() != 0;
46 preserveFormants = tokens[3].getIntValue() != 0;
47 envelopeOrder = tokens[4].getIntValue();
48 return;
49 }
50 }
51
52 jassertfalse; //unknown string format
53}
54
56{
57 // version / midside / sync / preserve / order
58 return juce::String::formatted ("1/%d/%d/%d/%d",
59 midSideStereo ? 1 : 0,
60 syncTimeStrPitchShft ? 1 : 0,
61 preserveFormants ? 1 : 0,
62 envelopeOrder);
63}
64
66{
67 return midSideStereo == other.midSideStereo
68 && syncTimeStrPitchShft == other.syncTimeStrPitchShft
69 && preserveFormants == other.preserveFormants
70 && envelopeOrder == other.envelopeOrder;
71}
72
74{
75 return ! operator== (other);
76}
77
78//==============================================================================
80{
81 virtual ~Stretcher() {}
82
83 virtual bool isOk() const = 0;
84 virtual void reset() = 0;
85 virtual bool setSpeedAndPitch (float speedRatio, float semitonesUp) = 0;
86 virtual int getFramesNeeded() const = 0;
87 virtual int getMaxFramesNeeded() const = 0;
88 virtual int processData (const float* const* inChannels, int numSamples, float* const* outChannels) = 0;
89 virtual int flush (float* const* outChannels) = 0;
90};
91
92//==============================================================================
93#if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
94
95struct ElastiqueStretcher : public TimeStretcher::Stretcher
96{
97 ElastiqueStretcher (double sourceSampleRate, int samplesPerBlock, int numChannels,
98 TimeStretcher::Mode mode, TimeStretcher::ElastiqueProOptions options, float minFactor)
99 : elastiqueMode (mode),
100 elastiqueProOptions (options),
101 samplesPerOutputBuffer (samplesPerBlock)
102 {
104 jassert (sourceSampleRate > 0.0);
105 int res = CElastiqueProV3If::CreateInstance (elastique, samplesPerBlock, numChannels, (float) sourceSampleRate,
106 getElastiqueMode (mode), minFactor);
107 jassert (res == 0); juce::ignoreUnused (res);
108
109 if (elastique == nullptr)
110 {
112 TRACKTION_LOG_ERROR ("Cannot create Elastique");
113 }
114 else
115 {
116 // This must be called first, before any other functions and can not
117 // be called again
118 maxFramesNeeded = elastique->GetMaxFramesNeeded();
119
120 if (mode == TimeStretcher::elastiquePro)
121 elastique->SetStereoInputMode (elastiqueProOptions.midSideStereo ? CElastiqueProV3If::kMSMode : CElastiqueProV3If::kPlainStereoMode);
122 }
123 }
124
125 ~ElastiqueStretcher() override
126 {
127 if (elastique != nullptr)
128 CElastiqueProV3If::DestroyInstance (elastique);
129 }
130
131 bool isOk() const override { return elastique != nullptr; }
132 void reset() override { elastique->Reset(); }
133
134 bool setSpeedAndPitch (float speedRatio, float semitonesUp) override
135 {
136 float pitch = juce::jlimit (0.25f, 4.0f, Pitch::semitonesToRatio (semitonesUp));
137 bool sync = (elastiqueMode == TimeStretcher::elastiquePro) ? elastiqueProOptions.syncTimeStrPitchShft : false;
138 int r = elastique->SetStretchPitchQFactor (speedRatio, pitch, sync);
139 jassert (r == 0); juce::ignoreUnused (r);
140
141 if (elastiqueMode == TimeStretcher::elastiquePro && elastiqueProOptions.preserveFormants)
142 {
143 elastique->SetEnvelopeFactor (pitch);
144 elastique->SetEnvelopeOrder (elastiqueProOptions.envelopeOrder);
145 }
146
147 return r == 0;
148 }
149
150 int getFramesNeeded() const override
151 {
152 const int framesNeeded = elastique->GetFramesNeeded();
153 jassert (framesNeeded <= maxFramesNeeded);
154 return framesNeeded;
155 }
156
157 int getMaxFramesNeeded() const override
158 {
159 return maxFramesNeeded;
160 }
161
162 int processData (const float* const* inChannels, int numSamples, float* const* outChannels) override
163 {
165 int err = elastique->ProcessData ((float**) inChannels, numSamples, (float**) outChannels);
166 jassert (err == 0); juce::ignoreUnused (err);
167 return samplesPerOutputBuffer;
168 }
169
170 int flush (float* const* outChannels) override
171 {
172 return elastique->FlushBuffer ((float**) outChannels);
173 }
174
175private:
176 CElastiqueProV3If* elastique = nullptr;
177 TimeStretcher::Mode elastiqueMode;
178 TimeStretcher::ElastiqueProOptions elastiqueProOptions;
179 const int samplesPerOutputBuffer;
180 int maxFramesNeeded;
181
182 static CElastiqueProV3If::ElastiqueMode_t getElastiqueMode (TimeStretcher::Mode mode)
183 {
184 switch (mode)
185 {
186 case TimeStretcher::elastiquePro: return CElastiqueProV3If::kV3Pro;
187 case TimeStretcher::elastiqueEfficient: return CElastiqueProV3If::kV3Eff;
188 case TimeStretcher::elastiqueMobile: return CElastiqueProV3If::kV3mobile;
189 case TimeStretcher::elastiqueMonophonic: return CElastiqueProV3If::kV3Monophonic;
190 case TimeStretcher::elastiqueDirectPro: [[ fallthrough ]];
191 case TimeStretcher::elastiqueDirectEfficient: [[ fallthrough ]];
192 case TimeStretcher::elastiqueDirectMobile: [[ fallthrough ]];
193 case TimeStretcher::disabled: [[ fallthrough ]];
194 case TimeStretcher::elastiqueTransient: [[ fallthrough ]];
195 case TimeStretcher::elastiqueTonal: [[ fallthrough ]];
196 case TimeStretcher::soundtouchNormal: [[ fallthrough ]];
197 case TimeStretcher::soundtouchBetter: [[ fallthrough ]];
198 case TimeStretcher::melodyne: [[ fallthrough ]];
199 case TimeStretcher::rubberbandMelodic: [[ fallthrough ]];
200 case TimeStretcher::rubberbandPercussive: [[ fallthrough ]];
201 default:
203 return CElastiqueProV3If::kV3Pro;
204 }
205 }
206
208};
209
210
211struct ElastiqueDirectStretcher : public TimeStretcher::Stretcher
212{
213 ElastiqueDirectStretcher (double sourceSampleRate, int samplesPerBlock, int numChannels_,
214 TimeStretcher::Mode mode, TimeStretcher::ElastiqueProOptions options, float minFactor)
215 : elastiqueMode (mode),
216 elastiqueProOptions (options),
217 samplesPerOutputBuffer (samplesPerBlock),
218 numChannels (numChannels_),
219 outputFifo (numChannels, samplesPerBlock)
220 {
222 jassert (sourceSampleRate > 0.0);
223 [[maybe_unused]] int res = CElastiqueProV3DirectIf::CreateInstance (elastique, numChannels, (float) sourceSampleRate,
224 getElastiqueMode (mode), minFactor);
225 jassert (res == 0);
226
227 if (elastique == nullptr)
228 {
230 TRACKTION_LOG_ERROR ("Cannot create Elastique Direct");
231 }
232 else
233 {
234 // This must be called first, before any other functions and can not
235 // be called again
236 maxFramesNeeded = elastique->GetMaxFramesNeeded();
237 outputFifo.setSize (numChannels, maxFramesNeeded * 2);
238
239 if (mode == TimeStretcher::elastiquePro)
240 elastique->SetStereoInputMode (elastiqueProOptions.midSideStereo ? CElastiqueProV3DirectIf::kMSMode
241 : CElastiqueProV3DirectIf::kPlainStereoMode);
242 }
243 }
244
245 ~ElastiqueDirectStretcher() override
246 {
247 if (elastique != nullptr)
248 CElastiqueProV3DirectIf::DestroyInstance (elastique);
249 }
250
251 bool isOk() const override
252 {
253 return elastique != nullptr;
254 }
255
256 void reset() override
257 {
258 hasBeenReset = true;
259 elastique->Reset();
260 outputFifo.reset();
261
262 newSpeedAndPitchPending = true;
263 }
264
265 bool setSpeedAndPitch (float speedRatio, float semitonesUp) override
266 {
267 newSpeedRatio = speedRatio;
268 newSemitonesUp = semitonesUp;
269 newSpeedAndPitchPending = true;
270 tryToSetNewSpeedAndPitch();
271
272 return true;
273 }
274
275 int getFramesNeeded() const override
276 {
277 tryToSetNewSpeedAndPitch();
278
279 if (outputFifo.getNumReady() > samplesPerOutputBuffer)
280 return 0;
281
282 const int framesNeeded = hasBeenReset ? elastique->GetPreFramesNeeded()
283 : elastique->GetFramesNeeded();
284 jassert (framesNeeded <= maxFramesNeeded);
285
286 return framesNeeded;
287 }
288
289 int getMaxFramesNeeded() const override
290 {
291 return maxFramesNeeded;
292 }
293
294 int processData (const float* const* inChannels, int numSamples, float* const* outChannels) override
295 {
297 using namespace choc::buffer;
298
299 if (numSamples > 0)
300 {
301 const auto numFrames = static_cast<FrameCount> (numSamples);
302 const auto inputView = createChannelArrayView (inChannels,
303 static_cast<ChannelCount> (numChannels),
304 numFrames);
305
306 if (std::exchange (hasBeenReset, false))
307 {
308 assert (numFrames == static_cast<FrameCount> (elastique->GetPreFramesNeeded()));
309 processPreFrames (inputView);
310 }
311 else
312 {
313 assert (numSamples == elastique->GetFramesNeeded());
314 processFrames (inputView);
315 }
316 }
317 else
318 {
319 // This should only happen if there are enough frames in the fifo to return
320 assert (outputFifo.getNumReady() >= samplesPerOutputBuffer);
321 }
322
323 // If enough samples are ready, output these now
324 if (const int numReady = outputFifo.getNumReady(); numReady > 0)
325 {
326 const int numToReturn = std::min (numReady, samplesPerOutputBuffer);
327 juce::AudioBuffer<float> outputView (outChannels, numChannels, numToReturn);
328
329 if (! outputFifo.read (outputView, 0))
330 {
331 assert (false);
332 return 0;
333 }
334
335 return outputView.getNumSamples();
336 }
337
339 return 0;
340 }
341
342 int flush (float* const* outChannels) override
343 {
344 {
345 AudioScratchBuffer scratchBuffer (numChannels, maxFramesNeeded);
346 const int numFramesReturned = elastique->FlushBuffer ((float **) scratchBuffer.buffer.getArrayOfWritePointers());
347 assert (numFramesReturned <= scratchBuffer.buffer.getNumSamples());
348 assert (outputFifo.getFreeSpace() >= numFramesReturned);
349 outputFifo.write (scratchBuffer.buffer, 0, numFramesReturned);
350 }
351
352 // If enough samples are ready, output these now
353 if (const int numReady = outputFifo.getNumReady(); numReady > 0)
354 {
355 const int numToReturn = std::min (numReady, samplesPerOutputBuffer);
356 juce::AudioBuffer<float> outputView (outChannels, numChannels, numToReturn);
357 [[maybe_unused]] const bool success = outputFifo.read (outputView, 0);
358 jassert (success);
359
360 return outputView.getNumSamples();
361 }
362
363 return 0;
364 }
365
366private:
367 CElastiqueProV3DirectIf* elastique = nullptr;
368 TimeStretcher::Mode elastiqueMode;
369 TimeStretcher::ElastiqueProOptions elastiqueProOptions;
370 const int samplesPerOutputBuffer, numChannels;
371 int maxFramesNeeded = 0;
372 AudioFifo outputFifo;
373 bool hasBeenReset = true;
374 float newSpeedRatio, newSemitonesUp;
375 mutable bool newSpeedAndPitchPending = false;
376
377 static CElastiqueProV3DirectIf::ElastiqueVersion_t getElastiqueMode (TimeStretcher::Mode mode)
378 {
379 switch (mode)
380 {
381 case TimeStretcher::elastiqueDirectPro: return CElastiqueProV3DirectIf::kV3Pro;
382 case TimeStretcher::elastiqueDirectEfficient: return CElastiqueProV3DirectIf::kV3Eff;
383 case TimeStretcher::elastiqueDirectMobile: return CElastiqueProV3DirectIf::kV3mobile;
384 case TimeStretcher::elastiquePro: [[ fallthrough ]];
385 case TimeStretcher::elastiqueEfficient: [[ fallthrough ]];
386 case TimeStretcher::elastiqueMobile: [[ fallthrough ]];
387 case TimeStretcher::elastiqueMonophonic: [[ fallthrough ]];
388 case TimeStretcher::disabled: [[ fallthrough ]];
389 case TimeStretcher::elastiqueTransient: [[ fallthrough ]];
390 case TimeStretcher::elastiqueTonal: [[ fallthrough ]];
391 case TimeStretcher::soundtouchNormal: [[ fallthrough ]];
392 case TimeStretcher::soundtouchBetter: [[ fallthrough ]];
393 case TimeStretcher::melodyne: [[ fallthrough ]];
394 case TimeStretcher::rubberbandMelodic: [[ fallthrough ]];
395 case TimeStretcher::rubberbandPercussive: [[ fallthrough ]];
396 default:
398 return CElastiqueProV3DirectIf::kV3Pro;
399 }
400 }
401
402 void tryToSetNewSpeedAndPitch() const
403 {
404 if (! newSpeedAndPitchPending)
405 return;
406
407 float pitch = juce::jlimit (0.25f, 4.0f, Pitch::semitonesToRatio (newSemitonesUp));
408 const bool sync = elastiqueMode == TimeStretcher::elastiqueDirectPro ? elastiqueProOptions.syncTimeStrPitchShft : false;
409 const int res = elastique->SetStretchPitchQFactor (newSpeedRatio, pitch, sync);
410 newSpeedAndPitchPending = res != 0;
411
412 if (elastiqueMode == TimeStretcher::elastiqueDirectPro && elastiqueProOptions.preserveFormants)
413 {
414 elastique->SetEnvelopeFactor (pitch);
415 elastique->SetEnvelopeOrder (elastiqueProOptions.envelopeOrder);
416 }
417 }
418
419 int processPreFrames (const choc::buffer::ChannelArrayView<const float>& inFrames)
420 {
421 const int numInputFrames = static_cast<int> (inFrames.size.numFrames);
422 assert (numInputFrames == elastique->GetPreFramesNeeded());
423
424 AudioScratchBuffer scratchBuffer (numChannels, maxFramesNeeded);
425 const int numPreProcessFrames = elastique->PreProcessData ((float **) inFrames.data.channels, numInputFrames,
426 (float **) scratchBuffer.buffer.getArrayOfWritePointers(),
427 samplesPerOutputBuffer >= 512 ? CElastiqueProV3DirectIf::kFastStartup
428 : CElastiqueProV3DirectIf::kNormalStartup);
429
430 assert (numPreProcessFrames <= scratchBuffer.buffer.getNumSamples());
431 assert (outputFifo.getFreeSpace() >= numPreProcessFrames);
432 outputFifo.write (scratchBuffer.buffer, 0, numPreProcessFrames);
433
434 return numPreProcessFrames;
435 }
436
437 int processFrames (const choc::buffer::ChannelArrayView<const float>& inFrames)
438 {
439 const int numInputFrames = static_cast<int> (inFrames.size.numFrames);
440
441 if (numInputFrames == 0)
442 return 0;
443
444 {
445 assert (numInputFrames == elastique->GetFramesNeeded());
446 [[maybe_unused]] const int err = elastique->ProcessData ((float **) inFrames.data.channels, numInputFrames);
447 assert (err == 0);
448 }
449
450 for (int numProcessCalls = elastique->GetNumOfProcessCalls(); --numProcessCalls >= 0;)
451 {
452 [[maybe_unused]] const int err = elastique->ProcessData();
453 assert (err == 0);
454 }
455
456 // N.B. GetFramesProcessed always returns a positive number until new data is supplied
457 const int numFramesProcessed = elastique->GetFramesProcessed();
458 AudioScratchBuffer scratchBuffer (numChannels, numFramesProcessed);
459 const int numFramesReturned = elastique->GetProcessedData ((float **) scratchBuffer.buffer.getArrayOfWritePointers());
460 assert (numFramesProcessed == numFramesReturned);
461 assert (numFramesReturned <= scratchBuffer.buffer.getNumSamples());
462 assert (outputFifo.getFreeSpace() >= numFramesReturned);
463 outputFifo.write (scratchBuffer.buffer, 0, numFramesReturned);
464
465 return numFramesReturned;
466 }
467
468 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ElastiqueDirectStretcher)
469};
470
471
472#endif
473
474//==============================================================================
475#if TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
476
477#include "soundtouch/include/SoundTouch.h"
478
479struct SoundTouchStretcher : public TimeStretcher::Stretcher,
480 private soundtouch::SoundTouch
481{
482 SoundTouchStretcher (double sourceSampleRate, int samplesPerBlock,
483 int chans, bool betterQuality)
484 : numChannels (chans), samplesPerOutputBuffer (samplesPerBlock)
485 {
487 setChannels (static_cast<unsigned int> (numChannels));
488 setSampleRate (static_cast<unsigned int> (sourceSampleRate));
489
490 if (betterQuality)
491 {
492 setSetting (SETTING_USE_AA_FILTER, 1);
493 setSetting (SETTING_AA_FILTER_LENGTH, 64);
494 setSetting (SETTING_USE_QUICKSEEK, 0);
495 setSetting (SETTING_SEQUENCE_MS, 60);
496 setSetting (SETTING_SEEKWINDOW_MS, 25);
497 }
498 }
499
500 bool isOk() const override { return true; }
501 void reset() override { clear(); }
502
503 bool setSpeedAndPitch (float speedRatio, float semitonesUp) override
504 {
505 setTempo (1.0f / speedRatio);
506 setPitchSemiTones (semitonesUp);
507 inputOutputSampleRatio = getInputOutputSampleRatio();
508
509 return true;
510 }
511
512 int getFramesNeeded() const override
513 {
514 const int numAvailable = (int) numSamples();
515 const int numRequiredForOneBlock = juce::roundToInt (samplesPerOutputBuffer * inputOutputSampleRatio);
516
517 return std::max (0, numRequiredForOneBlock - numAvailable);
518 }
519
520 int getMaxFramesNeeded() const override
521 {
522 // This was derived by experimentation
523 return 8192;
524 }
525
526 int processData (const float* const* inChannels, int numSamples, float* const* outChannels) override
527 {
529 jassert (numSamples <= getFramesNeeded());
530 writeInput (inChannels, numSamples);
531
532 const int numAvailable = (int) soundtouch::SoundTouch::numSamples();
533 jassert (numAvailable >= 0);
534
535 const int numToRead = std::min (numAvailable, samplesPerOutputBuffer);
536
537 if (numToRead > 0)
538 return readOutput (outChannels, 0, numToRead);
539
540 return 0;
541 }
542
543 int flush (float* const* outChannels) override
544 {
546 if (! hasDoneFinalBlock)
547 {
548 soundtouch::SoundTouch::flush();
549 hasDoneFinalBlock = true;
550 }
551
552 const int numAvailable = (int) numSamples();
553 const int numToRead = std::min (numAvailable, samplesPerOutputBuffer);
554
555 return readOutput (outChannels, 0, numToRead);
556 }
557
558private:
559 int numChannels = 0, samplesPerOutputBuffer = 0;
560 bool hasDoneFinalBlock = false;
561 double inputOutputSampleRatio = 1.0;
562
563 int readOutput (float* const* outChannels, int offset, int numNeeded)
564 {
565 float* interleaved = ptrBegin();
566 auto num = std::min (numNeeded, (int) numSamples());
567
568 for (int chan = 0; chan < numChannels; ++chan)
569 {
570 const float* src = interleaved + chan;
571 float* dest = outChannels[chan] + offset;
572
573 for (int i = 0; i < num; ++i)
574 {
575 dest[i] = *src;
576 src += numChannels;
577 }
578 }
579
580 receiveSamples (static_cast<unsigned int> (num));
581 return num;
582 }
583
584 void writeInput (const float* const* inChannels, int numSamples)
585 {
586 if (numChannels > 1)
587 {
588 AudioScratchBuffer scratch (1, numSamples * numChannels);
589 float* interleaved = scratch.buffer.getWritePointer(0);
590
591 for (int chan = 0; chan < numChannels; ++chan)
592 {
593 float* dest = interleaved + chan;
594 const float* src = inChannels[chan];
595
596 for (int i = 0; i < numSamples; ++i)
597 {
598 *dest = src[i];
599 dest += numChannels;
600 }
601 }
602
603 putSamples (interleaved, static_cast<unsigned int> (numSamples));
604 }
605 else
606 {
607 putSamples (inChannels[0], static_cast<unsigned int> (numSamples));
608 }
609 }
610
612};
613#endif
614
615//==============================================================================
616#if TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
617
618#ifdef __GNUC__
619 #pragma GCC diagnostic push
620 #pragma GCC diagnostic ignored "-Wsign-conversion"
621 #pragma GCC diagnostic ignored "-Wconversion"
622 #pragma GCC diagnostic ignored "-Wextra-semi"
623 #pragma GCC diagnostic ignored "-Wshadow"
624 #pragma GCC diagnostic ignored "-Wsign-compare"
625 #pragma GCC diagnostic ignored "-Wcast-align"
626 #if ! __clang__
627 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
628 #endif
629 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
630 #pragma GCC diagnostic ignored "-Wunused-variable"
631 #pragma GCC diagnostic ignored "-Wunused-parameter"
632 #pragma GCC diagnostic ignored "-Wpedantic"
633 #pragma GCC diagnostic ignored "-Wunknown-pragmas"
634 #pragma GCC diagnostic ignored "-Wswitch-enum"
635 #pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-dtor"
636 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
637#endif
638
639#define WIN32_LEAN_AND_MEAN 1
640#define Point CarbonDummyPointName
641#define Component CarbonDummyCompName
642
643}} // namespace tracktion { inline namespace engine
644#if TRACKTION_BUILD_RUBBERBAND
645 #if __has_include(<rubberband/single/RubberBandSingle.cpp>)
646 #include <rubberband/single/RubberBandSingle.cpp>
647 #elif __has_include("../3rd_party/rubberband/single/RubberBandSingle.cpp")
648 #include "../3rd_party/rubberband/single/RubberBandSingle.cpp"
649 #else
650 #error "TRACKTION_BUILD_RUBBERBAND enabled but not found in the search path!"
651 #endif
652#else
653 #if __has_include(<rubberband/rubberband/RubberBandStretcher.h>)
654 #include <rubberband/rubberband/RubberBandStretcher.h>
655 #elif __has_include("../3rd_party/rubberband/rubberband/RubberBandStretcher.h")
656 #include "../3rd_party/rubberband/rubberband/RubberBandStretcher.h"
657 #else
658 #error "TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND enabled but not found in the search path!"
659 #endif
660#endif
661namespace tracktion { inline namespace engine {
662
663#undef WIN32_LEAN_AND_MEAN
664#undef Point
665#undef Component
666
667#ifdef __GNUC__
668 #pragma GCC diagnostic pop
669#endif
670
671struct RubberBandStretcher : public TimeStretcher::Stretcher
672{
673 static int getOptionFlags (bool percussive)
674 {
675 if (percussive)
676 return RubberBand::RubberBandStretcher::OptionProcessRealTime
677 | RubberBand::RubberBandStretcher::OptionPitchHighConsistency
678 | RubberBand::RubberBandStretcher::PercussiveOptions;
679
680 return RubberBand::RubberBandStretcher::OptionProcessRealTime
681 | RubberBand::RubberBandStretcher::OptionPitchHighConsistency
682 | RubberBand::RubberBandStretcher::OptionWindowShort;
683 }
684
685 RubberBandStretcher (double sourceSampleRate, int samplesPerBlock, int numChannels, bool percussive)
686 : rubberBandStretcher ((size_t) sourceSampleRate, (size_t) numChannels,
687 getOptionFlags (percussive)),
688 samplesPerOutputBuffer (samplesPerBlock)
689 {
690 }
691
692 bool isOk() const override
693 {
694 return true;
695 }
696
697 void reset() override
698 {
699 rubberBandStretcher.reset();
700 }
701
702 bool setSpeedAndPitch (float speedRatio, float semitonesUp) override
703 {
704 const float pitch = juce::jlimit (0.25f, 4.0f, tracktion::engine::Pitch::semitonesToRatio (semitonesUp));
705
706 rubberBandStretcher.setPitchScale (pitch);
707 rubberBandStretcher.setTimeRatio (speedRatio);
708
709 if (numSamplesToDrop == -1)
710 {
711 // This is the first speed and pitch change so set up the padding and dropping
712 numSamplesToDrop = int (rubberBandStretcher.getLatency());
713 int numSamplesToPad = juce::roundToInt (numSamplesToDrop * pitch);
714
715 if (numSamplesToPad > 0)
716 {
717 AudioScratchBuffer scratch (int (rubberBandStretcher.getChannelCount()), samplesPerOutputBuffer);
718 scratch.buffer.clear();
719
720 while (numSamplesToPad > 0)
721 {
722 const int numThisTime = std::min (numSamplesToPad, samplesPerOutputBuffer);
723 rubberBandStretcher.process (scratch.buffer.getArrayOfReadPointers(), numThisTime, false);
724 numSamplesToPad -= numThisTime;
725 }
726 }
727
728 jassert (numSamplesToPad == 0);
729 }
730
731 const bool r = rubberBandStretcher.getPitchScale() == pitch
732 && rubberBandStretcher.getTimeRatio() == speedRatio;
733 jassert (r);
735
736 return r;
737 }
738
739 int getFramesNeeded() const override
740 {
741 return (int) rubberBandStretcher.getSamplesRequired();
742 }
743
744 int getMaxFramesNeeded() const override
745 {
746 return maxFramesNeeded;
747 }
748
749 int processData (const float* const* inChannels, int numSamples, float* const* outChannels) override
750 {
751 jassert (numSamples <= getFramesNeeded());
752 rubberBandStretcher.process (inChannels, (size_t) numSamples, false);
753
754 // Once there is output, read this in to the output buffer
755 int numAvailable = rubberBandStretcher.available();
756 jassert (numAvailable >= 0);
757
758 if (numSamplesToDrop > 0)
759 {
760 auto numToDropThisTime = std::min (numSamplesToDrop, std::min (numAvailable, samplesPerOutputBuffer));
761 rubberBandStretcher.retrieve (outChannels, (size_t) numToDropThisTime);
762 numSamplesToDrop -= numToDropThisTime;
763 jassert (numSamplesToDrop >= 0);
764
765 numAvailable -= numToDropThisTime;
766 }
767
768 if (numAvailable > 0)
769 return (int) rubberBandStretcher.retrieve (outChannels, (size_t) numAvailable);
770
771 return 0;
772 }
773
774 int flush (float* const* outChannels) override
775 {
776 // Empty the FIFO in to the stretcher and mark the last block as final
777 if (! hasDoneFinalBlock)
778 {
779 float* inChannels[32] = { nullptr };
780 rubberBandStretcher.process (inChannels, 0, true);
781 hasDoneFinalBlock = true;
782 }
783
784 // Then get the rest of the data out of the stretcher
785 const int numAvailable = rubberBandStretcher.available();
786 const int numThisBlock = std::min (numAvailable, samplesPerOutputBuffer);
787
788 if (numThisBlock > 0)
789 return (int) rubberBandStretcher.retrieve (outChannels, (size_t) numThisBlock);
790
791 return 0;
792 }
793
794private:
795 RubberBand::RubberBandStretcher rubberBandStretcher;
796 const int maxFramesNeeded = 8192;
797 const int samplesPerOutputBuffer = 0;
798 int numSamplesToDrop = -1;
799 bool hasDoneFinalBlock = false;
800
802};
803
804#endif // TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
805
806//==============================================================================
809
810static juce::String getMelodyne() { return "Melodyne"; }
811static juce::String getElastiquePro() { return "Elastique (" + TRANS("Pro") + ")"; }
812static juce::String getElastiqueEfficeint() { return "Elastique (" + TRANS("Efficient") + ")"; }
813static juce::String getElastiqueMobile() { return "Elastique (" + TRANS("Mobile") + ")"; }
814static juce::String getElastiqueMono() { return "Elastique (" + TRANS("Monophonic") + ")"; }
815static juce::String getSoundTouchNormal() { return "SoundTouch (" + TRANS("Normal") + ")"; }
816static juce::String getSoundTouchBetter() { return "SoundTouch (" + TRANS("Better") + ")"; }
817static juce::String getRubberBandMelodic() { return "RubberBand (" + TRANS("Melodic") + ")"; }
818static juce::String getRubberBandPercussive() { return "RubberBand (" + TRANS("Percussive") + ")"; }
819static juce::String getElastiqueDirectPro() { return "Elastique Direct (" + TRANS("Pro") + ")"; }
820static juce::String getElastiqueDirectEfficeint() { return "Elastique Direct (" + TRANS("Efficient") + ")"; }
821static juce::String getElastiqueDirectMobile() { return "Elastique Direct (" + TRANS("Mobile") + ")"; }
822
824{
825 switch (m)
826 {
828 case elastiqueTonal:
829 #if ! TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
830 case soundtouchNormal:
831 case soundtouchBetter:
832 #endif
833 #if ! TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
834 case elastiquePro:
836 case elastiqueMobile:
841 #endif
842 #if ! TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
845 #endif
846 return defaultMode;
847 #if TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
848 case soundtouchNormal:
849 case soundtouchBetter:
850 return m;
851 #endif
852 #if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
853 case elastiquePro:
855 case elastiqueMobile:
860 return m;
861 #endif
862 #if TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
865 return m;
866 #endif
867 case disabled:
868 case melodyne:
869 default:
870 return m;
871 }
872}
873
875{
877
878 #if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
879 s.add (getElastiquePro());
880 s.add (getElastiqueEfficeint());
881 s.add (getElastiqueMobile());
882 s.add (getElastiqueMono());
883 s.add (getElastiqueDirectPro());
884 s.add (getElastiqueDirectEfficeint());
885 s.add (getElastiqueDirectMobile());
886 #endif
887
888 #if TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
889 s.add (getSoundTouchNormal());
890 s.add (getSoundTouchBetter());
891 #endif
892
893 #if TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
894 s.add (getRubberBandMelodic());
895 s.add (getRubberBandPercussive());
896 #endif
897
898 #if TRACKTION_ENABLE_ARA
899 if (! excludeMelodyne && e.getPluginManager().getARACompatiblePlugDescriptions().size() > 0)
900 s.add (getMelodyne());
901 #else
902 juce::ignoreUnused (e, excludeMelodyne);
903 #endif
904
905 return s;
906}
907
909{
910 #if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
911 if (name == getElastiquePro()) return elastiquePro;
912 if (name == getElastiqueEfficeint()) return elastiqueEfficient;
913 if (name == getElastiqueMobile()) return elastiqueMobile;
914 if (name == getElastiqueMono()) return elastiqueMonophonic;
915 if (name == getElastiqueDirectPro()) return elastiqueDirectPro;
916 if (name == getElastiqueDirectEfficeint()) return elastiqueDirectEfficient;
917 if (name == getElastiqueDirectMobile()) return elastiqueDirectMobile;
918 #endif
919
920 #if TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
921 if (name == getRubberBandMelodic()) return rubberbandMelodic;
922 if (name == getRubberBandPercussive()) return rubberbandPercussive;
923 #endif
924
925 #if TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
926 if (name == getSoundTouchNormal()) return soundtouchNormal;
927 if (name == getSoundTouchBetter()) return soundtouchBetter;
928 #endif
929
930 #if TRACKTION_ENABLE_ARA
931 if (name == getMelodyne()) return melodyne;
932 #endif
933
934 return getPossibleModes (e, false).contains (name) ? defaultMode
935 : disabled;
936}
937
939{
940 switch (mode)
941 {
942 case elastiquePro: return getElastiquePro();
943 case elastiqueEfficient: return getElastiqueEfficeint();
944 case elastiqueMobile: return getElastiqueMobile();
945 case elastiqueMonophonic: return getElastiqueMono();
946 case elastiqueDirectPro: return getElastiqueDirectPro();
947 case elastiqueDirectEfficient: return getElastiqueDirectEfficeint();
948 case elastiqueDirectMobile: return getElastiqueDirectMobile();
949 case soundtouchNormal: return getSoundTouchNormal();
950 case soundtouchBetter: return getSoundTouchBetter();
951 case rubberbandMelodic: return getRubberBandMelodic();
952 case rubberbandPercussive: return getRubberBandPercussive();
953 case melodyne: return getMelodyne();
954 case disabled:
956 case elastiqueTonal:
957 default: jassertfalse; break;
958 }
959
960 return {};
961}
962
964{
965 #if TRACKTION_ENABLE_ARA
966 return mode == melodyne;
967 #else
968 juce::ignoreUnused (mode);
969 return false;
970 #endif
971}
972
974{
975 return stretcher != nullptr;
976}
977
978void TimeStretcher::initialise (double sourceSampleRate, int samplesPerBlock,
979 int numChannels, Mode mode, ElastiqueProOptions options, bool realtime)
980{
981 juce::ignoreUnused (sourceSampleRate, numChannels, mode, options, realtime);
982 jassert (! isMelodyne (mode));
983
984 samplesPerBlockRequested = samplesPerBlock;
985
987 jassert (stretcher == nullptr);
988
989 #if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE || TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND || TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
990 switch (mode)
991 {
992 #if TRACKTION_ENABLE_TIMESTRETCH_ELASTIQUE
993 case elastiquePro:
995 case elastiqueMobile:
997 stretcher = std::make_unique<ElastiqueStretcher> (sourceSampleRate, samplesPerBlock, numChannels,
998 mode, options, realtime ? 0.25f : 0.1f);
999 break;
1000 case elastiqueDirectPro:
1003 stretcher = std::make_unique<ElastiqueDirectStretcher> (sourceSampleRate, samplesPerBlock, numChannels,
1004 mode, options, realtime ? 0.25f : 0.1f);
1005 break;
1006 #else
1007 case elastiquePro: [[fallthrough]];
1008 case elastiqueEfficient: [[fallthrough]];
1009 case elastiqueMobile: [[fallthrough]];
1010 case elastiqueMonophonic: [[fallthrough]];
1011 case elastiqueDirectPro: [[fallthrough]];
1012 case elastiqueDirectEfficient: [[fallthrough]];
1014 break;
1015 #endif
1016
1017 #if TRACKTION_ENABLE_TIMESTRETCH_SOUNDTOUCH
1018 case soundtouchNormal:
1019 case soundtouchBetter:
1020 juce::ignoreUnused (options, realtime);
1021 stretcher = std::make_unique<SoundTouchStretcher> (sourceSampleRate, samplesPerBlock, numChannels,
1022 mode == soundtouchBetter);
1023 break;
1024 #else
1025 case soundtouchNormal: [[fallthrough]];
1026 case soundtouchBetter:
1027 break;
1028 #endif
1029
1030 #if TRACKTION_ENABLE_TIMESTRETCH_RUBBERBAND
1031 case rubberbandMelodic:
1033 juce::ignoreUnused (options, realtime);
1034 stretcher = std::make_unique<tracktion::engine::RubberBandStretcher> (sourceSampleRate, samplesPerBlock, numChannels,
1035 mode == rubberbandPercussive);
1036 break;
1037 #else
1038 case rubberbandMelodic: [[fallthrough]];
1040 break;
1041 #endif
1042
1043 case disabled: [[fallthrough]];
1044 case melodyne: [[fallthrough]];
1045 case elastiqueTransient: [[fallthrough]];
1046 case elastiqueTonal: [[fallthrough]];
1047 default:
1048 break;
1049 }
1050 #endif
1051
1052 if (stretcher != nullptr)
1053 if (! stretcher->isOk())
1054 stretcher.reset();
1055}
1056
1058{
1059 return mode != TimeStretcher::disabled;
1060}
1061
1063{
1064 if (stretcher != nullptr)
1065 stretcher->reset();
1066}
1067
1068bool TimeStretcher::setSpeedAndPitch (float speedRatio, float semitonesUp)
1069{
1070 if (stretcher != nullptr)
1071 return stretcher->setSpeedAndPitch (speedRatio, semitonesUp);
1072
1073 return false;
1074}
1075
1077{
1078 if (stretcher != nullptr)
1079 return stretcher->getFramesNeeded();
1080
1081 return 0;
1082}
1083
1085{
1086 if (stretcher != nullptr)
1087 return stretcher->getMaxFramesNeeded();
1088
1089 return 0;
1090}
1091
1092int TimeStretcher::processData (const float* const* inChannels, int numSamples, float* const* outChannels)
1093{
1094 if (stretcher != nullptr)
1095 return stretcher->processData (inChannels, numSamples, outChannels);
1096
1097 return 0;
1098}
1099
1100int TimeStretcher::processData (AudioFifo& inFifo, int numSamples, AudioFifo& outFifo)
1101{
1102 if (stretcher == nullptr)
1103 return 0;
1104
1105 jassert (numSamples == stretcher->getFramesNeeded());
1106 AudioScratchBuffer inBuffer (inFifo.getNumChannels(), numSamples);
1107 AudioScratchBuffer outBuffer (outFifo.getNumChannels(), samplesPerBlockRequested);
1108
1109 auto inChannels = inBuffer.buffer.getArrayOfReadPointers();
1110 auto outChannels = outBuffer.buffer.getArrayOfWritePointers();
1111
1112 inFifo.read (inBuffer.buffer, 0, numSamples);
1113 const int numOutputFrames = stretcher->processData (inChannels, numSamples, outChannels);
1114 outFifo.write (outBuffer.buffer, 0, numOutputFrames);
1115
1116 return numOutputFrames;
1117}
1118
1119int TimeStretcher::flush (float* const* outChannels)
1120{
1121 if (stretcher != nullptr)
1122 return stretcher->flush (outChannels);
1123
1124 return 0;
1125}
1126
1127}} // namespace tracktion { inline namespace engine
assert
const Type *const * getArrayOfReadPointers() const noexcept
Type *const * getArrayOfWritePointers() noexcept
bool contains(StringRef stringToLookFor, bool ignoreCase=false) const
int size() const noexcept
void add(String stringToAdd)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
static String formatted(const String &formatStr, Args... args)
An audio scratch buffer that has pooled storage.
juce::AudioBuffer< float > & buffer
The buffer to use.
The Engine is the central class for all tracktion sessions.
PluginManager & getPluginManager() const
Returns the PluginManager instance.
void initialise(double sourceSampleRate, int samplesPerBlock, int numChannels, Mode, ElastiqueProOptions, bool realtime)
Initialises the TimeStretcher ready to perform timestretching.
int getFramesNeeded() const
Returns the expected number of frames required to generate some output.
bool isInitialised() const
Returns true if this has been fully initialised.
void reset()
Resets the TimeStretcher ready for a new set of audio data, maintains mode, speed and pitch ratios.
static bool isMelodyne(Mode)
Returns true if the given Mode is a Melodyne mode.
static bool canProcessFor(Mode)
Checks that the given Mode is a valid mode and not disabled.
static juce::String getNameOfMode(Mode)
Returns the name of a given Mode for display purposes.
int getMaxFramesNeeded() const
Returns the maximum number of frames that will ever be returned by getFramesNeeded.
int processData(const float *const *inChannels, int numSamples, float *const *outChannels)
Processes some input frames and fills some output frames with the applied speed ratio and pitch shift...
static Mode getModeFromName(Engine &, const juce::String &name)
Returns the Mode for a given name.
int flush(float *const *outChannels)
Flushes the end of the stream when input data is exhausted but there is still output data available.
TimeStretcher()
Creates an TimeStretcher using the default mode.
static Mode checkModeIsAvailable(Mode)
Checks if the given mode is available for use.
bool setSpeedAndPitch(float speedRatio, float semitones)
Sets the timestretch speed ratio and semitones pitch shift.
Mode
Holds the various algorithms to which can be used (if enabled).
@ elastiqueDirectPro
Elastique Direct Pro good all round (.
@ rubberbandMelodic
RubberBand tailored to melodic sounds prioritising pitch accuracy.
@ soundtouchNormal
SoundTouch normal quality, lower CPU use.
@ elastiquePro
Elastique Pro good all round (.
@ elastiqueMonophonic
Elastique which can sound better for monophonic sounds.
@ elastiqueDirectEfficient
Elastique Direct lower quality and lower CPU usage.
@ elastiqueDirectMobile
Elastique Direct lower quality and lower CPU usage, optimised for mobile.
@ rubberbandPercussive
RubberBand tailored to percussive sounds prioritising transient accuracy.
@ melodyne
Melodyne, only used for clip timestretching.
@ elastiqueEfficient
Elastique lower quality and lower CPU usage.
@ elastiqueMobile
Elastique lower quality and lower CPU usage, optimised for mobile.
@ soundtouchBetter
SoundTouch better quality, higher CPU use.
static juce::StringArray getPossibleModes(Engine &, bool excludeMelodyne)
Returns the names of the availabel Modes.
T exchange(T... args)
T flush(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef int
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
void ignoreUnused(Types &&...) noexcept
int roundToInt(const FloatType value) noexcept
A set of options that can be used in conjunction with the elastiquePro Mode to fine tune the algorith...
int envelopeOrder
Sets a spectral envelope shift factor.
bool operator!=(const ElastiqueProOptions &) const
Compare these options with another set.
juce::String toString() const
Save the current options as a string.
bool midSideStereo
Optomise algorthim for mid/side channel layouts.
bool operator==(const ElastiqueProOptions &) const
Compare these options with another set.
bool syncTimeStrPitchShft
Synchronises timestretch and pitchshifting.
ElastiqueProOptions()=default
Create a default set of options.
sync
typedef size_t
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.