Skip to content

Commit b7eee0c

Browse files
authored
Time domain stretching (#104)
1 parent c64d1c2 commit b7eee0c

10 files changed

Lines changed: 1563 additions & 253 deletions

File tree

examples/graphics/source/examples/AudioFileDemo.h

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ class MeteringAudioSource : public yup::PositionableAudioSource
221221
class TimeStretchAudioSource : public yup::PositionableAudioSource
222222
{
223223
public:
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

Comments
 (0)