@@ -221,9 +221,12 @@ class MeteringAudioSource : public yup::PositionableAudioSource
221221class TimeStretchAudioSource : public yup ::PositionableAudioSource
222222{
223223public:
224- TimeStretchAudioSource (yup::PositionableAudioSource* sourceToWrap, int numChannelsToUse)
224+ TimeStretchAudioSource (yup::PositionableAudioSource* sourceToWrap,
225+ int numChannelsToUse,
226+ yup::TimeStretchProcessor::Backend backendToUse)
225227 : source (sourceToWrap)
226228 , numChannels (numChannelsToUse)
229+ , preferredBackend (backendToUse)
227230 {
228231 }
229232
@@ -243,7 +246,7 @@ class TimeStretchAudioSource : public yup::PositionableAudioSource
243246 spec.maximumBlockSize = maxInputBlockSize;
244247 spec.numChannels = numChannels;
245248
246- const auto result = timeStretchProcessor.prepare (spec);
249+ const auto result = timeStretchProcessor.prepare (spec, preferredBackend );
247250 timeStretchAvailable = result.wasOk ();
248251
249252 if (timeStretchAvailable)
@@ -420,9 +423,11 @@ class TimeStretchAudioSource : public yup::PositionableAudioSource
420423 muteHead = static_cast <int > (clampedBegin - beginFrame);
421424 muteTail = static_cast <int > ((beginFrame + numFrames) - clampedEnd);
422425 const int validFrames = static_cast <int > (clampedEnd - clampedBegin);
426+ const int framesToRead = yup::jmin (validFrames, tempBuffer.getNumSamples ());
427+ muteTail = yup::jlimit (0 , numFrames - muteHead, muteTail + validFrames - framesToRead);
423428
424429 source->setNextReadPosition (clampedBegin);
425- yup::AudioSourceChannelInfo info (&tempBuffer, 0 , validFrames );
430+ yup::AudioSourceChannelInfo info (&tempBuffer, 0 , framesToRead );
426431 source->getNextAudioBlock (info);
427432
428433 for (int ch = 0 ; ch < numChannels; ++ch)
@@ -431,11 +436,11 @@ class TimeStretchAudioSource : public yup::PositionableAudioSource
431436 std::fill (destChannels[ch], destChannels[ch] + muteHead, 0 .0f );
432437
433438 std::copy (tempBuffer.getReadPointer (ch),
434- tempBuffer.getReadPointer (ch) + validFrames ,
439+ tempBuffer.getReadPointer (ch) + framesToRead ,
435440 destChannels[ch] + muteHead);
436441
437442 if (muteTail > 0 )
438- std::fill (destChannels[ch] + muteHead + validFrames ,
443+ std::fill (destChannels[ch] + numFrames - muteTail ,
439444 destChannels[ch] + numFrames,
440445 0 .0f );
441446 }
@@ -468,6 +473,7 @@ class TimeStretchAudioSource : public yup::PositionableAudioSource
468473 double sampleRate = 0.0 ;
469474 yup::int64 outputPosition = 0 ;
470475 bool timeStretchAvailable = false ;
476+ yup::TimeStretchProcessor::Backend preferredBackend = yup::TimeStretchProcessor::Backend::automatic;
471477
472478 std::atomic<double > timeRatio { 1.0 };
473479 std::atomic<double > pitchRatio { 1.0 };
@@ -593,7 +599,8 @@ class AudioFileDemo : public yup::Component
593599 header.removeFromTop (4 );
594600 auto stretchRow = header.removeFromTop (buttonHeight);
595601 const int sliderLabelWidth = 70 ;
596- const int sliderWidth = 180 ;
602+ const int sliderWidth = 165 ;
603+ const int backendWidth = 150 ;
597604
598605 timeLabel.setBounds (stretchRow.removeFromLeft (sliderLabelWidth));
599606 stretchRow.removeFromLeft (buttonMargin);
@@ -602,6 +609,10 @@ class AudioFileDemo : public yup::Component
602609 pitchLabel.setBounds (stretchRow.removeFromLeft (sliderLabelWidth));
603610 stretchRow.removeFromLeft (buttonMargin);
604611 pitchShiftSlider.setBounds (stretchRow.removeFromLeft (sliderWidth));
612+ stretchRow.removeFromLeft (buttonMargin * 2 );
613+ backendLabel.setBounds (stretchRow.removeFromLeft (sliderLabelWidth));
614+ stretchRow.removeFromLeft (buttonMargin);
615+ backendComboBox.setBounds (stretchRow.removeFromLeft (backendWidth));
605616
606617 bounds.removeFromTop (6 );
607618 infoLabel.setBounds (bounds.removeFromTop (22 ));
@@ -850,10 +861,31 @@ class AudioFileDemo : public yup::Component
850861 timeStretchSource->setPitchRatio (pitchShiftRatio);
851862 };
852863
864+ addAndMakeVisible (backendLabel);
865+ backendLabel.setText (" Backend" , yup::NotificationType::dontSendNotification);
866+ backendLabel.setColor (yup::Label::Style::textFillColorId, yup::Colors::white);
867+
868+ addAndMakeVisible (backendComboBox);
869+ backendComboBox.addItem (" Auto" , backendAutomaticId);
870+ backendComboBox.addItem (" Time Domain" , backendTimeDomainId);
871+
872+ if (yup::TimeStretchProcessor::isBackendAvailable (yup::TimeStretchProcessor::Backend::bungee))
873+ backendComboBox.addItem (" Bungee" , backendBungeeId);
874+
875+ backendComboBox.setSelectedId (getBackendId (selectedTimeStretchBackend),
876+ yup::NotificationType::dontSendNotification);
877+ backendComboBox.onSelectedItemChanged = [this ]
878+ {
879+ const auto backend = getBackendForId (backendComboBox.getSelectedId ());
880+ setTimeStretchBackend (backend);
881+ };
882+
853883 timeStretchSlider.setEnabled (timeStretchSupported);
854884 pitchShiftSlider.setEnabled (timeStretchSupported);
885+ backendComboBox.setEnabled (timeStretchSupported);
855886 timeLabel.setEnabled (timeStretchSupported);
856887 pitchLabel.setEnabled (timeStretchSupported);
888+ backendLabel.setEnabled (timeStretchSupported);
857889
858890 addAndMakeVisible (waveformDisplay);
859891 waveformDisplay.setSelectable (true );
@@ -877,6 +909,40 @@ class AudioFileDemo : public yup::Component
877909 infoLabel.setText (currentFileName + " | " + positionText + " | Stopped" , yup::NotificationType::dontSendNotification);
878910 }
879911
912+ void setTimeStretchBackend (yup::TimeStretchProcessor::Backend backend)
913+ {
914+ if (selectedTimeStretchBackend == backend)
915+ return ;
916+
917+ selectedTimeStretchBackend = backend;
918+
919+ if (hasLoadedAudio && memorySource != nullptr )
920+ rebuildTimeStretchSource ();
921+
922+ updateStatus (" Time stretch backend: " + getBackendName (selectedTimeStretchBackend));
923+ updatePlaybackStatus ();
924+ }
925+
926+ void rebuildTimeStretchSource ()
927+ {
928+ const bool wasPlaying = transportSource.isPlaying ();
929+ const double previousPosition = transportSource.getCurrentPosition ();
930+
931+ transportSource.stop ();
932+ transportSource.setSource (nullptr );
933+
934+ timeStretchSource = std::make_unique<TimeStretchAudioSource> (memorySource.get (),
935+ audioBuffer.getNumChannels (),
936+ selectedTimeStretchBackend);
937+ timeStretchSource->setTimeRatio (timeStretchRatio);
938+ timeStretchSource->setPitchRatio (pitchShiftRatio);
939+ transportSource.setSource (timeStretchSource.get (), 0 , nullptr , loadedSampleRate, audioBuffer.getNumChannels ());
940+ transportSource.setPosition (previousPosition);
941+
942+ if (wasPlaying)
943+ transportSource.start ();
944+ }
945+
880946 void togglePlayback ()
881947 {
882948 if (! hasLoadedAudio)
@@ -939,7 +1005,9 @@ class AudioFileDemo : public yup::Component
9391005 transportSource.stop ();
9401006 transportSource.setSource (nullptr );
9411007 memorySource = std::make_unique<yup::MemoryAudioSource> (audioBuffer, false , loopEnabled);
942- timeStretchSource = std::make_unique<TimeStretchAudioSource> (memorySource.get (), numChannels);
1008+ timeStretchSource = std::make_unique<TimeStretchAudioSource> (memorySource.get (),
1009+ numChannels,
1010+ selectedTimeStretchBackend);
9431011 timeStretchSource->setTimeRatio (timeStretchRatio);
9441012 timeStretchSource->setPitchRatio (pitchShiftRatio);
9451013 transportSource.setSource (timeStretchSource.get (), 0 , nullptr , loadedSampleRate, numChannels);
@@ -1022,6 +1090,56 @@ class AudioFileDemo : public yup::Component
10221090 return " *.wav;*.aiff;*.aif;*.flac;*.mp3;*.opus;*.m4a;*.wma;*.ogg" ;
10231091 }
10241092
1093+ static int getBackendId (yup::TimeStretchProcessor::Backend backend)
1094+ {
1095+ switch (backend)
1096+ {
1097+ case yup::TimeStretchProcessor::Backend::automatic:
1098+ return backendAutomaticId;
1099+
1100+ case yup::TimeStretchProcessor::Backend::timeDomain:
1101+ return backendTimeDomainId;
1102+
1103+ case yup::TimeStretchProcessor::Backend::bungee:
1104+ return backendBungeeId;
1105+ }
1106+
1107+ return backendAutomaticId;
1108+ }
1109+
1110+ static yup::TimeStretchProcessor::Backend getBackendForId (int backendId)
1111+ {
1112+ switch (backendId)
1113+ {
1114+ case backendTimeDomainId:
1115+ return yup::TimeStretchProcessor::Backend::timeDomain;
1116+
1117+ case backendBungeeId:
1118+ return yup::TimeStretchProcessor::Backend::bungee;
1119+
1120+ case backendAutomaticId:
1121+ default :
1122+ return yup::TimeStretchProcessor::Backend::automatic;
1123+ }
1124+ }
1125+
1126+ static yup::String getBackendName (yup::TimeStretchProcessor::Backend backend)
1127+ {
1128+ switch (backend)
1129+ {
1130+ case yup::TimeStretchProcessor::Backend::automatic:
1131+ return " Auto" ;
1132+
1133+ case yup::TimeStretchProcessor::Backend::timeDomain:
1134+ return " Time Domain" ;
1135+
1136+ case yup::TimeStretchProcessor::Backend::bungee:
1137+ return " Bungee" ;
1138+ }
1139+
1140+ return " Auto" ;
1141+ }
1142+
10251143 yup::AudioFormatManager formatManager;
10261144 yup::AudioDeviceManager deviceManager;
10271145 yup::AudioSourcePlayer sourcePlayer;
@@ -1050,8 +1168,10 @@ class AudioFileDemo : public yup::Component
10501168
10511169 yup::Label timeLabel;
10521170 yup::Label pitchLabel;
1171+ yup::Label backendLabel;
10531172 yup::Slider timeStretchSlider { yup::Slider::LinearHorizontal, " Time Stretch" };
10541173 yup::Slider pitchShiftSlider { yup::Slider::LinearHorizontal, " Pitch Shift" };
1174+ yup::ComboBox backendComboBox { " TimeStretchBackend" };
10551175
10561176 yup::Label infoLabel;
10571177 yup::Label statusLabel;
@@ -1068,7 +1188,12 @@ class AudioFileDemo : public yup::Component
10681188 double audioLengthSeconds = 0.0 ;
10691189 double timeStretchRatio = 1.0 ;
10701190 double pitchShiftRatio = 1.0 ;
1191+ yup::TimeStretchProcessor::Backend selectedTimeStretchBackend = yup::TimeStretchProcessor::Backend::automatic;
10711192 bool hasLoadedAudio = false ;
10721193 bool loopEnabled = false ;
10731194 bool timeStretchSupported = false ;
1195+
1196+ static constexpr int backendAutomaticId = 1 ;
1197+ static constexpr int backendTimeDomainId = 2 ;
1198+ static constexpr int backendBungeeId = 3 ;
10741199};
0 commit comments