Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report current time in samples to external plugins. #301

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 55 additions & 2 deletions pedalboard/ExternalPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ class AbstractExternalPlugin : public Plugin {
};

template <typename ExternalPluginType>
class ExternalPlugin : public AbstractExternalPlugin {
class ExternalPlugin : public AbstractExternalPlugin, juce::AudioPlayHead {
public:
ExternalPlugin(
std::string &_pathToPluginFile,
Expand Down Expand Up @@ -630,6 +630,10 @@ class ExternalPlugin : public AbstractExternalPlugin {
~ExternalPlugin() {
{
std::lock_guard<std::mutex> lock(EXTERNAL_PLUGIN_MUTEX);
if (pluginInstance) {
pluginInstance->setPlayHead(nullptr);
}

pluginInstance.reset();
NUM_ACTIVE_EXTERNAL_PLUGINS--;

Expand Down Expand Up @@ -695,6 +699,9 @@ class ExternalPlugin : public AbstractExternalPlugin {
{
std::lock_guard<std::mutex> lock(EXTERNAL_PLUGIN_MUTEX);
// Delete the plugin instance itself:
if (pluginInstance) {
pluginInstance->setPlayHead(nullptr);
}
pluginInstance.reset();
NUM_ACTIVE_EXTERNAL_PLUGINS--;
}
Expand All @@ -714,6 +721,7 @@ class ExternalPlugin : public AbstractExternalPlugin {
loadError.toStdString());
}

pluginInstance->setPlayHead(this);
pluginInstance->enableAllBuses();

auto mainInputBus = pluginInstance->getBus(true, 0);
Expand All @@ -723,6 +731,9 @@ class ExternalPlugin : public AbstractExternalPlugin {
auto exception = std::invalid_argument(
"Plugin '" + pluginInstance->getName().toStdString() +
"' does not produce audio output.");
if (pluginInstance) {
pluginInstance->setPlayHead(nullptr);
}
pluginInstance.reset();
throw exception;
}
Expand All @@ -741,6 +752,7 @@ class ExternalPlugin : public AbstractExternalPlugin {
pathToPluginFile.toStdString() + ": " +
loadError.toStdString());
}
pluginInstance->setPlayHead(this);
}
}

Expand Down Expand Up @@ -912,7 +924,10 @@ class ExternalPlugin : public AbstractExternalPlugin {
juce::AudioBuffer<float> audioBuffer(numOutputChannels, bufferSize);
audioBuffer.clear();

currentPositionInfo.isPlaying = true;
pluginInstance->processBlock(audioBuffer, emptyNoteBuffer);
currentPositionInfo.isPlaying = false;
currentPositionInfo.timeInSamples += bufferSize;
auto noiseFloor = audioBuffer.getMagnitude(0, bufferSize);

audioBuffer.clear();
Expand All @@ -922,7 +937,10 @@ class ExternalPlugin : public AbstractExternalPlugin {
// the messages in a MidiBuffer get erased every time we call processBlock!
{
juce::MidiBuffer noteOnBuffer(noteOn);
currentPositionInfo.isPlaying = true;
pluginInstance->processBlock(audioBuffer, noteOnBuffer);
currentPositionInfo.isPlaying = false;
currentPositionInfo.timeInSamples += bufferSize;
}

// Then keep pumping the message thread until we get some louder output:
Expand All @@ -945,8 +963,11 @@ class ExternalPlugin : public AbstractExternalPlugin {

audioBuffer.clear();
{
currentPositionInfo.isPlaying = true;
juce::MidiBuffer noteOnBuffer(noteOn);
pluginInstance->processBlock(audioBuffer, noteOnBuffer);
currentPositionInfo.isPlaying = false;
currentPositionInfo.timeInSamples += bufferSize;
}

if (juce::Time::currentTimeMillis() >= endTime)
Expand All @@ -958,8 +979,12 @@ class ExternalPlugin : public AbstractExternalPlugin {
audioBuffer.clear();
{
juce::MidiBuffer allNotesOffBuffer(allNotesOff);
currentPositionInfo.isPlaying = true;
pluginInstance->processBlock(audioBuffer, allNotesOffBuffer);
currentPositionInfo.isPlaying = false;
currentPositionInfo.timeInSamples += bufferSize;
}
currentPositionInfo.timeInSamples = 0;
pluginInstance->reset();
pluginInstance->releaseResources();

Expand Down Expand Up @@ -1077,6 +1102,7 @@ class ExternalPlugin : public AbstractExternalPlugin {
// Force prepare() to be called again later by invalidating lastSpec:
lastSpec.maximumBlockSize = 0;
samplesProvided = 0;
currentPositionInfo.timeInSamples = 0;
}
}

Expand All @@ -1102,6 +1128,8 @@ class ExternalPlugin : public AbstractExternalPlugin {

pluginInstance->setNonRealtime(true);
pluginInstance->prepareToPlay(spec.sampleRate, spec.maximumBlockSize);
currentPositionInfo.timeInSamples = 0;
currentPositionInfo.isPlaying = false;

lastSpec = spec;
}
Expand Down Expand Up @@ -1173,8 +1201,17 @@ class ExternalPlugin : public AbstractExternalPlugin {
channelPointers.size(),
outputBlock.getNumSamples());

currentPositionInfo.isPlaying = true;
pluginInstance->processBlock(audioBuffer, emptyMidiBuffer);
currentPositionInfo.isPlaying = false;

samplesProvided += outputBlock.getNumSamples();
currentPositionInfo.timeInSamples += outputBlock.getNumSamples();

// Pump the processBlock callback to tell the VST that we've stopped
// playing:
juce::AudioBuffer<float> emptyBuffer(channelPointers.size(), 0);
pluginInstance->processBlock(emptyBuffer, emptyMidiBuffer);

// To compensate for any latency added by the plugin,
// only tell Pedalboard to use the last _n_ samples.
Expand Down Expand Up @@ -1251,6 +1288,11 @@ class ExternalPlugin : public AbstractExternalPlugin {
std::memset((void *)outputArrayPointer, 0,
sizeof(float) * numChannels * outputSampleCount);

juce::AudioBuffer<float> emptyBuffer(numChannels, 0);
juce::MidiBuffer emptyMidiBuffer;

currentPositionInfo.isPlaying = true;

for (unsigned long i = 0; i < outputSampleCount; i += bufferSize) {
unsigned long chunkSampleCount =
std::min((unsigned long)bufferSize, outputSampleCount - i);
Expand All @@ -1268,9 +1310,14 @@ class ExternalPlugin : public AbstractExternalPlugin {

juce::MidiBuffer midiChunk;
midiChunk.addEvents(midiInputBuffer, i, chunkSampleCount, -i);

pluginInstance->processBlock(audioChunk, midiChunk);
currentPositionInfo.timeInSamples += chunkSampleCount;
}

currentPositionInfo.isPlaying = false;
// Pump the processBlock callback to tell the VST that we've stopped
// playing:
pluginInstance->processBlock(emptyBuffer, emptyMidiBuffer);
}

return outputArray;
Expand Down Expand Up @@ -1345,6 +1392,11 @@ class ExternalPlugin : public AbstractExternalPlugin {
ExternalPluginReloadType reloadType = ExternalPluginReloadType::Unknown;
juce::PluginDescription foundPluginDescription;

bool getCurrentPosition(CurrentPositionInfo &result) override {
result = currentPositionInfo;
return true;
}

private:
std::unique_ptr<juce::AudioPluginInstance>
createPluginInstance(const juce::PluginDescription &foundPluginDescription,
Expand Down Expand Up @@ -1379,6 +1431,7 @@ class ExternalPlugin : public AbstractExternalPlugin {
juce::String pathToPluginFile;
juce::AudioPluginFormatManager pluginFormatManager;
std::unique_ptr<juce::AudioPluginInstance> pluginInstance;
juce::AudioPlayHead::CurrentPositionInfo currentPositionInfo;

long samplesProvided = 0;
float initializationTimeout = DEFAULT_INITIALIZATION_TIMEOUT_SECONDS;
Expand Down
10 changes: 10 additions & 0 deletions tests/test_external_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,21 @@ def delete_installed_plugins():
if os.environ.get("ENABLE_TESTING_WITH_LOCAL_PLUGINS", False):
for plugin_class in pedalboard._AVAILABLE_PLUGIN_CLASSES:
for plugin_path in plugin_class.installed_plugins:
if os.environ.get("LOCAL_PLUGIN_NAMES", "").lower() not in plugin_path.lower():
continue
if any(
x in plugin_path.lower()
for x in os.environ.get("NOT_LOCAL_PLUGIN_NAMES", "").lower().split(",")
):
continue
try:
load_test_plugin(plugin_path)
AVAILABLE_EFFECT_PLUGINS_IN_TEST_ENVIRONMENT.append(plugin_path)
except Exception as e:
print(f"Tried to load {plugin_path} for local testing, but failed with: {e}")
import traceback

traceback.print_exception(e)

# Even if the plugin failed to load, add it to
# the list of known container plugins if necessary:
Expand Down