diff --git a/docs/releases/max-v0.3.0.md b/docs/releases/max-v0.3.0.md index a296be3..c930d06 100644 --- a/docs/releases/max-v0.3.0.md +++ b/docs/releases/max-v0.3.0.md @@ -28,6 +28,8 @@ First release of Zerr* for Max, as a package for Max 9 on Apple Silicon (arm64, ## Fixes +- `zerr.envelopes~`: the gain inlet reads 1 when nothing is connected to it. It used to read + 0, so a plain object with only a control signal was silent. - `zerr.envelopes~` and `zerr.features~` no longer overflow when the signal vector size grows after creation or inside a resampled `poly~`; they adopt the chain's rate and vector size. Neither can throw into Max's audio thread any more. (#28, #29) diff --git a/docs/releases/pd-next.md b/docs/releases/pd-next.md index ec5e45b..d90359a 100644 --- a/docs/releases/pd-next.md +++ b/docs/releases/pd-next.md @@ -3,6 +3,12 @@ Renamed to `pd-v.md` when the version in `puredata/zerr-meta.pd` is decided since 2026-10-15 only; everything earlier since `v0.2` still has to be enumerated with `git log v0.2..HEAD --oneline -- core puredata` before the first `pd-v` tag._ +## Changed + +- `zerr_envelopes~`: the gain inlet (third) reads 1 when nothing is connected to it; a float + sent there sets a constant gain, as for any Pd signal inlet. It used to read 0, so a plain + object with only a control signal was silent. + ## Fixed - The externals no longer trust the block size they were created with. A `[block~]` subpatch of diff --git a/maxmsp/README.md b/maxmsp/README.md index 54077b2..2df5dce 100644 --- a/maxmsp/README.md +++ b/maxmsp/README.md @@ -104,7 +104,7 @@ Inlets (all signals): 1. control signal: 0 to 1 position (trajectory) or pulses (trigger) 2. spread, 0 to 1 (trigger mode only) -3. gain, applied to every envelope +3. gain, applied to every envelope (1 when nothing is connected) Outlet: multichannel signal, one channel per speaker, in the layout's numbering. diff --git a/maxmsp/source/projects/zerr.envelopes_tilde/zerr.envelopes_tilde.cpp b/maxmsp/source/projects/zerr.envelopes_tilde/zerr.envelopes_tilde.cpp index cc0fd34..e61ef47 100644 --- a/maxmsp/source/projects/zerr.envelopes_tilde/zerr.envelopes_tilde.cpp +++ b/maxmsp/source/projects/zerr.envelopes_tilde/zerr.envelopes_tilde.cpp @@ -256,7 +256,7 @@ void zerr_envelopes_assist(t_zerr_envelopes* x, void* b, long m, long a, char* s "mode)"); break; case 2: - strcpy(s, "(signal) gain applied to every envelope"); + strcpy(s, "(signal) gain applied to every envelope; 1 when nothing is connected"); break; default: strcpy(s, "(signal)"); @@ -295,6 +295,9 @@ void zerr_envelopes_dsp64(t_zerr_envelopes* x, t_object* dsp64, short* count, do maxvectorsize, samplerate); } + // count[] says which signal inlets have a cord; the gain inlet is unity without one + x->ze->setGainConnected(count != NULL && count[2] != 0); + dsp_add64(dsp64, (t_object*)x, (t_perfroutine64)zerr_envelopes_perform64, 0, NULL); } diff --git a/maxmsp/source/projects/zerr.envelopes_tilde/zerr_envelopes.hpp b/maxmsp/source/projects/zerr.envelopes_tilde/zerr_envelopes.hpp index fdfea85..2538009 100644 --- a/maxmsp/source/projects/zerr.envelopes_tilde/zerr_envelopes.hpp +++ b/maxmsp/source/projects/zerr.envelopes_tilde/zerr_envelopes.hpp @@ -156,6 +156,12 @@ class ZerrEnvelopes { const long count = std::min(sampleframes, block); for (int i = 0; i < inputCount; ++i) { + if (i == gainInlet && !gainConnected.load(std::memory_order_relaxed)) { + // Nothing on the gain inlet: Max hands over zeros, which would mute every + // envelope. Unity gain instead, so a plain object plays. + std::fill(inputBuffer[i].begin(), inputBuffer[i].end(), 1.0); + continue; + } if (!ins[i]) { silence(outs, numouts, sampleframes); return; @@ -236,6 +242,17 @@ class ZerrEnvelopes { /** @brief Posts the speaker setup to the Console */ void printParameters() { generator->printParameters(); } + /** + * @brief Whether a patch cord is on the gain inlet, from dsp64's count[] + * + * Unconnected, the inlet counts as unity gain. Main thread, at DSP setup; the running + * perform() picks it up on its next block. + */ + void setGainConnected(bool connected) + { + gainConnected.store(connected, std::memory_order_relaxed); + } + private: /** * @brief Zeroes every output buffer; the fallback whenever processing cannot proceed @@ -252,8 +269,10 @@ class ZerrEnvelopes { } } - static constexpr int inputCount = 3; /**< Signal inlets: control(0), spread(1), gain(2) */ - int outputCount = 0; /**< One envelope per speaker in the layout */ + static constexpr int inputCount = 3; /**< Signal inlets: control(0), spread(1), gain(2) */ + static constexpr int gainInlet = 2; /**< The inlet that reads 1 when nothing is connected */ + std::atomic gainConnected{false}; /**< A cord is on the gain inlet (dsp64 count[]) */ + int outputCount = 0; /**< One envelope per speaker in the layout */ zerr::SystemConfigs systemConfigs; /**< Shape the generator is currently sized for */ std::string selectionMode; /**< "trajectory" or "trigger" */ diff --git a/puredata/src/zerr_envelopes~.cpp b/puredata/src/zerr_envelopes~.cpp index afee940..3956681 100644 --- a/puredata/src/zerr_envelopes~.cpp +++ b/puredata/src/zerr_envelopes~.cpp @@ -103,7 +103,8 @@ void* zerr_envelopes_tilde_new(__attribute__((unused)) t_symbol* s, int argc, t_ // create inlets x->spread_inlet = inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal); - x->volume_inlet = inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal); + // gain: 1 when nothing is connected (a float sent here sets a constant, as usual) + x->volume_inlet = signalinlet_new(&x->x_obj, 1.0f); // create outlets x->n_outlet = x->z->numOutlet; // get the number of outlets