mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 12:20:48 +02:00
init
This commit is contained in:
commit
d21bc831e1
178 changed files with 24136 additions and 0 deletions
161
Source/Sampler/Stretcher.h
Normal file
161
Source/Sampler/Stretcher.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
Stretcher.h
|
||||
Created: 27 May 2024 4:09:49pm
|
||||
Author: binya
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <Bungee.h>
|
||||
|
||||
// Bungee sets a hard limit on the pitch ratio to simplify memory management. We can increase this limit before building
|
||||
// and use a resampling hack when necessary (the hack is not great because it requires reallocation of the stretcher).
|
||||
// This must be set to the value in Timing.cpp (internal to Bungee)
|
||||
static constexpr int bungeeMaxPitchOctaves = BUNGEE_MAX_OCTAVES;
|
||||
static constexpr float bungeeMinimumRatio = 1.f / (1 << bungeeMaxPitchOctaves);
|
||||
|
||||
class BungeeStretcher
|
||||
{
|
||||
public:
|
||||
explicit BungeeStretcher(const juce::AudioBuffer<float>& sampleBuffer, int sampleRate) : buffer(&sampleBuffer),
|
||||
bufferSampleRate(sampleRate)
|
||||
{
|
||||
}
|
||||
|
||||
/** Allocates a new stretcher and the input buffer. */
|
||||
void preallocateStretcher(int appSampleRate)
|
||||
{
|
||||
if (appSampleRate == 0 || appSampleRate == applicationSampleRate)
|
||||
return;
|
||||
|
||||
bungee = std::make_unique<Bungee::Stretcher<Bungee::Basic>>(Bungee::SampleRates{ bufferSampleRate, appSampleRate }, buffer->getNumChannels());
|
||||
inputData.setSize(1, buffer->getNumChannels() * bungee->maxInputFrameCount(), false, false, true); // Note, maxInputFrameCount has a reported overflow issue
|
||||
previousInputRate = bufferSampleRate;
|
||||
applicationSampleRate = appSampleRate;
|
||||
}
|
||||
|
||||
void initialize(long double sampleStart, float initialRatio = 1, float initialSpeed = 1)
|
||||
{
|
||||
setPitchAndSpeed(initialRatio, initialSpeed);
|
||||
|
||||
// We only reallocate when necessary (if resamplingHack requires it, as it's not good for real-time performance)
|
||||
int inputRate = int(bufferSampleRate / resamplingHack);
|
||||
if (inputRate != previousInputRate)
|
||||
{
|
||||
bungee = std::make_unique<Bungee::Stretcher<Bungee::Basic>>(Bungee::SampleRates{ inputRate, applicationSampleRate }, buffer->getNumChannels());
|
||||
inputData.setSize(1, buffer->getNumChannels() * bungee->maxInputFrameCount(), false, false, true); // maxInputFrameCount has a reported overflow issue
|
||||
previousInputRate = inputRate;
|
||||
}
|
||||
|
||||
output = Bungee::OutputChunk{};
|
||||
outputIndex = 0;
|
||||
|
||||
preroll(double(sampleStart));
|
||||
}
|
||||
|
||||
/** Pre-rolls the stretcher to a new position. Use this before you plan to move the position. */
|
||||
void preroll(double newPosition)
|
||||
{
|
||||
request = Bungee::Request{ newPosition, speedFactor * resamplingHack, pitchRatio, true };
|
||||
bungee->preroll(request);
|
||||
|
||||
while (!output.data || std::isnan(output.request[Bungee::OutputChunk::begin]->position) ||
|
||||
newPosition > output.request[Bungee::OutputChunk::end]->position || newPosition < output.request[Bungee::OutputChunk::begin]->position)
|
||||
{
|
||||
auto input = bungee->specifyGrain(request);
|
||||
|
||||
if (buffer->getNumChannels() * (input.end - input.begin) >= inputData.getNumSamples()) // Can happen with extreme ratios
|
||||
inputData.setSize(1, buffer->getNumChannels() * (input.end - input.begin));
|
||||
|
||||
int begin = juce::jlimit<int>(int(std::ceil(newPosition)), buffer->getNumSamples(), input.begin);
|
||||
int end = juce::jlimit<int>(int(std::ceil(newPosition)), buffer->getNumSamples(), input.end);
|
||||
|
||||
inputData.clear();
|
||||
if (begin < end)
|
||||
{
|
||||
for (int ch = 0; ch < buffer->getNumChannels(); ch++)
|
||||
inputData.copyFrom(0, ch * (input.end - input.begin) + begin - input.begin, *buffer, ch, begin, end - begin);
|
||||
}
|
||||
|
||||
bungee->analyseGrain(inputData.getReadPointer(0), (input.end - input.begin));
|
||||
bungee->synthesiseGrain(output);
|
||||
bungee->next(request);
|
||||
|
||||
outputIndex = int(std::round((newPosition - output.request[Bungee::OutputChunk::begin]->position) / positionSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches the next sample. Call this for each channel before advancing. */
|
||||
float nextSample(int channel, bool advance = true)
|
||||
{
|
||||
if (outputIndex >= output.frameCount)
|
||||
{
|
||||
request.pitch = pitchRatio;
|
||||
request.speed = speedFactor * resamplingHack;
|
||||
auto [begin, end] = bungee->specifyGrain(request);
|
||||
|
||||
begin = juce::jlimit<int>(0, buffer->getNumSamples(), begin);
|
||||
end = juce::jlimit<int>(0, buffer->getNumSamples(), end);
|
||||
|
||||
inputData.clear(); // Preferring simplicity over maximum efficiency
|
||||
for (int ch = 0; ch < buffer->getNumChannels(); ch++)
|
||||
inputData.copyFrom(0, ch * (end - begin), *buffer, ch, begin, end - begin);
|
||||
|
||||
bungee->analyseGrain(inputData.getReadPointer(0), end - begin);
|
||||
bungee->synthesiseGrain(output);
|
||||
bungee->next(request);
|
||||
|
||||
outputIndex = 0;
|
||||
}
|
||||
|
||||
float result = output.data[channel * output.channelStride + outputIndex];
|
||||
if (advance)
|
||||
outputIndex++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void setPitchAndSpeed(float newPitchRatio, float newSpeedFactor)
|
||||
{
|
||||
pitchRatio = newPitchRatio;
|
||||
speedFactor = newSpeedFactor;
|
||||
|
||||
if (newPitchRatio < bungeeMinimumRatio)
|
||||
{
|
||||
pitchRatio = bungeeMinimumRatio;
|
||||
resamplingHack = bungeeMinimumRatio / newPitchRatio;
|
||||
}
|
||||
else
|
||||
{
|
||||
resamplingHack = 1.f;
|
||||
}
|
||||
|
||||
positionSpeed = speedFactor * bufferSampleRate / applicationSampleRate;
|
||||
}
|
||||
|
||||
/** Returns the speed at which the stretcher advances through the buffer, relative to the buffer's sample rate. */
|
||||
float getPositionSpeed() const { return positionSpeed; }
|
||||
|
||||
private:
|
||||
const juce::AudioBuffer<float>* buffer{ nullptr };
|
||||
int bufferSampleRate{ 0 };
|
||||
int applicationSampleRate{ 0 };
|
||||
|
||||
float pitchRatio{ 1. };
|
||||
float speedFactor{ 1. };
|
||||
float positionSpeed{ 0. }; // speedFactor * bufferSampleRate / applicationSampleRate
|
||||
|
||||
// We use a little hack to ignore Bungee's maxPitchOctaves limit
|
||||
float resamplingHack{ 1.f };
|
||||
int previousInputRate{ 0 };
|
||||
|
||||
std::unique_ptr<Bungee::Stretcher<Bungee::Basic>> bungee;
|
||||
Bungee::Request request{};
|
||||
Bungee::OutputChunk output{};
|
||||
|
||||
juce::AudioBuffer<float> inputData;
|
||||
int outputIndex{ 0 };
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue