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
2 changes: 2 additions & 0 deletions docs/releases/max-v0.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions docs/releases/pd-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ Renamed to `pd-v<ver>.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
Expand Down
2 changes: 1 addition & 1 deletion maxmsp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)");
Expand Down Expand Up @@ -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);
}

Expand Down
23 changes: 21 additions & 2 deletions maxmsp/source/projects/zerr.envelopes_tilde/zerr_envelopes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ class ZerrEnvelopes {
const long count = std::min<long>(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;
Expand Down Expand Up @@ -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
Expand All @@ -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<bool> 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" */
Expand Down
3 changes: 2 additions & 1 deletion puredata/src/zerr_envelopes~.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading