Skip to content

Commit

Permalink
Add option to always split block, otherwise only split block for para…
Browse files Browse the repository at this point in the history
…meter events
  • Loading branch information
jatinchowdhury18 committed Jun 26, 2022
1 parent 2d8e79d commit acd23c3
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 33 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ are available
* `CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES` can be set to any integer value to choose the
resolution (in samples) used by the wrapper for doing sample-accurate event processing.
Setting the value to `0` (the default value) will turn off sample-accurate event processing.
* `CLAP_ALWAYS_SPLIT_BLOCK` can be set to `1` (on), or `0` (off, default), to tell the
wrapper to _always_ attempt to split incoming audio buffers into chunks of size
`CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES`, regardless of any input events being
sent from the host. Note that if the block size provided by the host is not an
even multiple of `CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES`, the plugin may be
required to process a chunk smaller than the chosen resolution.

## Risks of using this library

Expand Down
12 changes: 11 additions & 1 deletion cmake/ClapTargetHelpers.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
function(clap_juce_extensions_plugin_internal)
set(oneValueArgs TARGET TARGET_PATH PLUGIN_NAME IS_JUCER PLUGIN_VERSION DO_COPY CLAP_MANUAL_URL CLAP_SUPPORT_URL CLAP_MISBEHAVIOUR_HANDLER_LEVEL CLAP_CHECKING_LEVEL CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES)
set(oneValueArgs TARGET TARGET_PATH PLUGIN_NAME IS_JUCER PLUGIN_VERSION DO_COPY CLAP_MANUAL_URL CLAP_SUPPORT_URL
CLAP_MISBEHAVIOUR_HANDLER_LEVEL CLAP_CHECKING_LEVEL CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES
CLAP_ALWAYS_SPLIT_BLOCK)
set(multiValueArgs CLAP_ID CLAP_FEATURES)

cmake_parse_arguments(CJA "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
Expand Down Expand Up @@ -38,6 +40,13 @@ function(clap_juce_extensions_plugin_internal)
message( STATUS "Setting event resolution to ${CJA_CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES} samples")
endif()

if ("${CJA_CLAP_ALWAYS_SPLIT_BLOCK}" STREQUAL "")
message( STATUS "Setting \"Always split block\" to OFF")
set(CJA_CLAP_ALWAYS_SPLIT_BLOCK 0)
else()
message( STATUS "Setting \"Always split block\" to ${CJA_CLAP_ALWAYS_SPLIT_BLOCK}")
endif()

# we need the list of features as comma separated quoted strings
foreach(feature IN LISTS CJA_CLAP_FEATURES)
list (APPEND CJA_CLAP_FEATURES_PARSED "\"${feature}\"")
Expand Down Expand Up @@ -104,6 +113,7 @@ function(clap_juce_extensions_plugin_internal)
CLAP_MISBEHAVIOUR_HANDLER_LEVEL=${CJA_CLAP_MISBEHAVIOUR_HANDLER_LEVEL}
CLAP_CHECKING_LEVEL=${CJA_CLAP_CHECKING_LEVEL}
CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES=${CJA_CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES}
CLAP_ALWAYS_SPLIT_BLOCK=${CJA_CLAP_ALWAYS_SPLIT_BLOCK}
)

if(${CJA_IS_JUCER})
Expand Down
89 changes: 57 additions & 32 deletions src/wrapper/clap-juce-wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC(4996) // allow strncpy
#define CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES 0 // sample-accurate events are off by default
#endif

#if !defined(CLAP_ALWAYS_SPLIT_BLOCK)
#define CLAP_ALWAYS_SPLIT_BLOCK 0
#endif

// This is useful for debugging overrides
// #undef CLAP_MISBEHAVIOUR_HANDLER_LEVEL
// #define CLAP_MISBEHAVIOUR_HANDLER_LEVEL Terminate
Expand Down Expand Up @@ -834,7 +838,7 @@ class ClapJuceWrapper : public clap::helpers::Plugin<
int currentEvent = 0;
int nextEventTime = numSamples;

if (numEvents > 0) // load first event
if (numEvents > 0) // get timestamp for first event
{
auto event = events->get(events, 0);
nextEventTime = (int)event->time;
Expand Down Expand Up @@ -863,41 +867,62 @@ class ClapJuceWrapper : public clap::helpers::Plugin<
// so we'll increment it inside the loop.
for (int n = 0; n < numSamples;)
{
// in order to know how many samples to process, we need to know when
// is the next timestamp that has an event. In order to know that next
// timestamp, we first need to process all of the events at the current timestamp.
while (nextEventTime == n)
processEvent(n);
#if CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES <= 0
// Sample-accurate events are turned off, so just process the
// whole block.
const auto numSamplesToProcess = numSamples;
#endif

#if CLAP_ALWAYS_SPLIT_BLOCK && CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES > 0
// process a block of the given resolution size, or a smaller block
// if there's not enough samples available
const auto numSamplesToProcess =
juce::jmin(CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES, numSamples - n);
#endif

#if !CLAP_ALWAYS_SPLIT_BLOCK && CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES > 0
const auto numSamplesToProcess = [&]() {
if (CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES <= 0)
{
// Sample-accurate events are turned off, so just process the
// whole block.
return numSamples;
}
else
{
const auto samplesUntilEndOfBlock = numSamples - n;
const auto samplesUntilNextEvent = nextEventTime - n;

// the number of samples left is less than
// CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES so let's just
// process the rest of the block
if (samplesUntilEndOfBlock <= CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES)
return samplesUntilEndOfBlock;

// process up until the next event, rounding up to the nearest multiple
// of CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES
const auto numSmallBlocks =
(samplesUntilNextEvent + CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES - 1) /
CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES;
return juce::jmin(numSmallBlocks * CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES,
samplesUntilEndOfBlock);
}
const auto samplesUntilEndOfBlock = numSamples - n;
const auto samplesUntilNextEvent = [&]() {
for (int eventIndex = currentEvent; eventIndex < numEvents; ++eventIndex)
{
auto event = events->get(events, (uint32_t)eventIndex);
if ((int)event->time < n + CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES)
// this event is within the resolution size, so we don't need to split
continue;

// For now we're only splitting the block on parameter events
// so we can get sample-accurate automation.
// @TODO: are there other events that will require us to split the block?
// like maybe transport events?
if (event->type == CLAP_EVENT_PARAM_VALUE ||
event->type == CLAP_EVENT_PARAM_MOD ||
event->type == CLAP_EVENT_PARAM_GESTURE_BEGIN ||
event->type == CLAP_EVENT_PARAM_GESTURE_END)
{
return (int)event->time - n;
}
}
return samplesUntilEndOfBlock;
}();

// the number of samples left is less than
// CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES so let's just
// process the rest of the block
if (samplesUntilEndOfBlock <= CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES)
return samplesUntilEndOfBlock;

// process up until the next event, rounding up to the nearest multiple
// of CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES
const auto numSmallBlocks =
(samplesUntilNextEvent + CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES - 1) /
CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES;
return juce::jmin(numSmallBlocks * CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES,
samplesUntilEndOfBlock);
}();
#endif

// process the rest of the events in this sub-block
// process the events in this sub-block
while (nextEventTime < n + numSamplesToProcess && currentEvent < numEvents)
processEvent(n);

Expand Down

0 comments on commit acd23c3

Please sign in to comment.