diff --git a/pedalboard/RubberbandPlugin.h b/pedalboard/RubberbandPlugin.h index ddb3bbb25..3c0ddda16 100644 --- a/pedalboard/RubberbandPlugin.h +++ b/pedalboard/RubberbandPlugin.h @@ -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: @@ -132,6 +127,5 @@ class RubberbandPlugin : public Plugin { protected: std::unique_ptr rbPtr; - int initialSamplesRequired = 0; }; }; // namespace Pedalboard diff --git a/pedalboard/plugin_templates/PrimeWithSilence.h b/pedalboard/plugin_templates/PrimeWithSilence.h index c8f96e5dc..a7c39eed4 100644 --- a/pedalboard/plugin_templates/PrimeWithSilence.h +++ b/pedalboard/plugin_templates/PrimeWithSilence.h @@ -40,7 +40,9 @@ class PrimeWithSilence JucePlugin>::prepare(spec); - this->getDSP().setMaximumDelayInSamples(silenceLengthSamples); + if (this->getDSP().getMaximumDelayInSamples() != silenceLengthSamples) { + this->getDSP().setMaximumDelayInSamples(silenceLengthSamples); + } this->getDSP().setDelay(silenceLengthSamples); plugin.prepare(spec); } diff --git a/pedalboard/process.h b/pedalboard/process.h index 22e0e2653..61c5c2354 100644 --- a/pedalboard/process.h +++ b/pedalboard/process.h @@ -267,7 +267,14 @@ processFloat32(const py::array_t 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, diff --git a/tests/test_pitch_shift.py b/tests/test_pitch_shift.py index f33e8765b..f67e1005c 100644 --- a/tests/test_pitch_shift.py +++ b/tests/test_pitch_shift.py @@ -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" + )