Skip to content
Merged
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
6 changes: 6 additions & 0 deletions src/detail/os/linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ void LinuxHelper::run()
if (!anyoneWantsTicking())
{
LOGDETAIL("clap-wrapper: every attached object has a run loop, pausing the idle thread");
// Unbounded on purpose: every change that can turn the predicate back to
// "tick me" reaches this thread under _standInLock, so none can be lost.
_standInWakeup.wait(guard, [this] { return !_standInRunning || anyoneWantsTicking(); });
continue;
}
Expand Down Expand Up @@ -270,6 +272,10 @@ void LinuxHelper::detach(IPlugObject *plugobject)

void LinuxHelper::idleSourceChanged()
{
// The lock is not optional: run() tests its predicate and parks in one step,
// so an unlocked notify can be lost, leaving the helper parked forever. Safe
// to block on: lock order is helper-then-plug-object, onIdle() only try_locks.
std::lock_guard<std::recursive_mutex> guard(_standInLock);
_standInWakeup.notify_all();
}

Expand Down
19 changes: 17 additions & 2 deletions src/detail/vst3/parameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,27 @@ Vst3Parameter *Vst3Parameter::createPresetSelector(Steinberg::Vst::ParamID id, i
// load rebuilds a plugin's state, which is not something to draw a curve
// through.
v.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kIsList;
v.stepCount = presetCount > 1 ? presetCount - 1 : 0;
v.stepCount = 0;

auto *p = new Vst3Parameter(v, 0, 0, 0);
p->isMidi = false;
p->isPreset = true;
p->min_value = 0;
p->max_value = v.stepCount;
p->max_value = 0;
p->resizePresetSelector(presetCount);
return p;
}

void Vst3Parameter::resizePresetSelector(int32_t presetCount)
{
auto &v = getInfo();
// A host reads stepCount+1 as the program count, so a single preset and an
// empty list are both stepCount 0 - kIsHidden is what tells them apart.
v.stepCount = presetCount > 1 ? presetCount - 1 : 0;
if (presetCount > 0)
v.flags &= ~Vst::ParameterInfo::kIsHidden;
else
v.flags |= Vst::ParameterInfo::kIsHidden;
min_value = 0;
max_value = v.stepCount;
}
20 changes: 19 additions & 1 deletion src/detail/vst3/parameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ class Vst3Parameter : public Steinberg::Vst::Parameter
{
return floor(clapvalue - min_value) / float(info.stepCount);
}
return (clapvalue - min_value) / (max_value - min_value);
// min == max - an empty preset selector, or a CLAP parameter that declares
// it - has exactly one plain value, and dividing would hand the host a NaN.
const auto range = max_value - min_value;
if (range <= 0.0) return 0.0;
return (clapvalue - min_value) / range;
}
static Vst3Parameter *create(const clap_param_info_t *info,
std::function<Steinberg::Vst::UnitID(const char *modulepath)> getUnitId);
Expand All @@ -83,7 +87,21 @@ class Vst3Parameter : public Steinberg::Vst::Parameter
// separate kind because the process adapter turns every isMidi program
// change into an actual 0xC0 message, which is emphatically not what
// selecting a preset should do.
//
// Created even for presetCount 0 (hidden, stepCount 0): the parameter count
// must not change while the component is active - see resizePresetSelector().
static Vst3Parameter *createPresetSelector(Steinberg::Vst::ParamID id, int32_t presetCount);
// Resizes an existing selector in place. The caller must exclude process()
// while the fields are written, and afterwards announce kParamTitlesChanged.
void resizePresetSelector(int32_t presetCount);
// Programs published to the host: 0 while hidden, stepCount+1 otherwise. The
// live index may be larger while a crawl is still running.
int32_t presetCount() const
{
auto &info = this->getInfo();
if (!isPreset || (info.flags & Steinberg::Vst::ParameterInfo::kIsHidden)) return 0;
return info.stepCount + 1;
}
// copies from the clap_param_info_t
uint32_t param_index_for_clap_get_info = 0;
clap_id id = 0;
Expand Down
35 changes: 28 additions & 7 deletions src/wrapasauv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <set>
#include <limits>
#include <cassert>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <Block.h>
Expand Down Expand Up @@ -1641,6 +1642,27 @@ OSStatus WrapAsAUV2::Render(AudioUnitRenderActionFlags &inFlags, const AudioTime
// {}
// );
}
else
{
// Nothing rendered this cycle (the CLAP is down while onIdle() cycles it
// for a restart), and AUBase will not silence anything for us: the AU stays
// initialized throughout, so DoRenderBus copies the output element's cache
// into the host's buffer after every noErr Render and returning without
// writing replays the last block. Zero every output element, not just the
// bus being rendered - RenderBus answers the others from their caches - and
// the silence flag on top is only a hint. _renderedSinceIdle is left alone:
// the idle tick's flush still has to make up for this block.
const auto numOutputs = Outputs().GetNumberOfElements();
for (UInt32 i = 0; i < numOutputs; ++i)
{
AudioBufferList &buffers = Output(i).PrepareBuffer(inFrames);
for (UInt32 j = 0; j < buffers.mNumberBuffers; ++j)
{
std::memset(buffers.mBuffers[j].mData, 0, buffers.mBuffers[j].mDataByteSize);
}
}
inFlags |= kAudioUnitRenderAction_OutputIsSilence;
}
return noErr;
}

Expand Down Expand Up @@ -1814,8 +1836,8 @@ void WrapAsAUV2::onIdle()
// rebuild. Render holds it for its whole body, so once it is acquired no
// render is inside the process adapter; clearing _initialized under it
// keeps the ones that follow out while the plugin is torn down and stood
// back up. Those renders return without touching the buffers, exactly as
// they do before the AU is initialized.
// back up. The AU stays initialized throughout, so those renders still
// reach Render(), which zeroes the output (see the else branch there).
bool wasInitialized;
{
ClapWrapper::detail::shared::SpinLockGuard processGuard(_processLock);
Expand All @@ -1827,8 +1849,8 @@ void WrapAsAUV2::onIdle()
deactivateCLAP();
// Cannot fail for the format-pair reason Initialize guards against:
// the formats have not changed since the last successful activation.
// If it fails anyway, _initialized stays false and renders return
// silence, the same state as before Initialize.
// If it fails anyway, _initialized stays false and renders are silent
// until request_process() below or the host's next Initialize recovers.
if (!activateCLAP())
{
LOGINFO("[clap-wrapper] restart: could not reactivate the plugin");
Expand All @@ -1848,9 +1870,8 @@ void WrapAsAUV2::onIdle()
// activate/start_processing pair, which it drives from AU Initialize() --
// so if the AU is initialized and the CLAP is not running underneath it,
// stand it back up. No lock is needed to decide that: activateCLAP()
// publishes _initialized last, and a render that reads it false returns
// without touching the plugin, exactly as it does before the AU is
// initialized at all.
// publishes _initialized last, and a render that reads it false outputs
// silence without touching the plugin.
if (IsInitialized() && !_initialized)
{
activateCLAP();
Expand Down
Loading
Loading