Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions pedalboard/RubberbandPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,7 @@ class RubberbandPlugin : public Plugin {
if (!rbPtr)
return 0;

initialSamplesRequired =
std::max(initialSamplesRequired,
(int)(rbPtr->getSamplesRequired() + rbPtr->getLatency() +
lastSpec.maximumBlockSize));

return initialSamplesRequired;
return (int)(rbPtr->getLatency() + lastSpec.maximumBlockSize);
}

private:
Expand Down Expand Up @@ -132,6 +127,5 @@ class RubberbandPlugin : public Plugin {

protected:
std::unique_ptr<RubberBandStretcher> rbPtr;
int initialSamplesRequired = 0;
};
}; // namespace Pedalboard
4 changes: 3 additions & 1 deletion pedalboard/plugin_templates/PrimeWithSilence.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class PrimeWithSilence
JucePlugin<juce::dsp::DelayLine<
SampleType,
juce::dsp::DelayLineInterpolationTypes::None>>::prepare(spec);
this->getDSP().setMaximumDelayInSamples(silenceLengthSamples);
if (this->getDSP().getMaximumDelayInSamples() != silenceLengthSamples) {
this->getDSP().setMaximumDelayInSamples(silenceLengthSamples);
}
this->getDSP().setDelay(silenceLengthSamples);
plugin.prepare(spec);
}
Expand Down
9 changes: 8 additions & 1 deletion pedalboard/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,14 @@ processFloat32(const py::array_t<float, py::array::c_style> inputArray,

// Actually run the process method of all plugins.
int samplesReturned = process(ioBuffer, spec, plugins, reset);
totalOutputLatencySamples = ioBuffer.getNumSamples() - samplesReturned;
if (reset) {
totalOutputLatencySamples = ioBuffer.getNumSamples() - samplesReturned;
} else {
// In streaming mode, return the full buffer including any leading
// silence from plugin priming. Trimming here would discard all output
// when a plugin's startup latency exceeds the chunk size.
totalOutputLatencySamples = 0;
}
}

return copyJuceBufferIntoPyArray(ioBuffer, inputChannelLayout,
Expand Down
45 changes: 45 additions & 0 deletions tests/test_pitch_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,48 @@ def test_pitch_shift_latency_compensation(fundamental_hz, sample_rate, buffer_si
plugin = Pedalboard([PitchShift(0)])
output = plugin.process(sine_wave, sample_rate, buffer_size=buffer_size)
np.testing.assert_allclose(sine_wave, output, atol=1e-6)


@pytest.mark.parametrize("chunk_size", [512, 4096, 8192])
@pytest.mark.parametrize("sample_rate", [22050, 44100, 48000])
def test_pitch_shift_streaming_produces_output(chunk_size, sample_rate):
"""PitchShift with reset=False must produce non-zero output after priming."""
num_seconds = 3.0
signal = np.sin(
2 * np.pi * 440 * np.arange(num_seconds * sample_rate) / sample_rate
).astype(np.float32)

plugin = PitchShift(semitones=5)
output_chunks = []
for i in range(0, len(signal), chunk_size):
chunk = signal[i : i + chunk_size]
out = plugin.process(chunk, sample_rate, reset=False)
assert len(out) == len(chunk), (
f"Chunk {i // chunk_size}: expected {len(chunk)} samples, got {len(out)}"
)
output_chunks.append(out)

concatenated = np.concatenate(output_chunks)
assert np.any(np.abs(concatenated) > 1e-6), (
"Streaming produced all-zero output; PitchShift with reset=False is broken"
)


def test_pitch_shift_streaming_multiple_sessions():
"""Reusing a PitchShift across reset=True then streaming must work each time."""
sample_rate = 44100
chunk_size = 4096
signal = np.sin(
2 * np.pi * 440 * np.arange(3 * sample_rate) / sample_rate
).astype(np.float32)

plugin = PitchShift(semitones=5)
for session in range(3):
chunks = []
for i in range(0, len(signal), chunk_size):
chunk = signal[i : i + chunk_size]
chunks.append(plugin.process(chunk, sample_rate, reset=(i == 0)))
concatenated = np.concatenate(chunks)
assert np.any(np.abs(concatenated) > 1e-6), (
f"Session {session}: streaming after reset produced silence"
)