Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
29933d9
Add util.debounce abstraction and help patch
user1303836 Feb 24, 2026
3ed9f04
Add README for util.debounce with full reference docs
user1303836 Feb 24, 2026
3ddfd47
Fix trigger ordering bugs in util.debounce
user1303836 Feb 24, 2026
5278ac0
Fix obj-34 trigger ordering: leading edge check must precede timer reset
user1303836 Feb 24, 2026
40f6893
Fix bypass to cancel pending debounce timer
user1303836 Feb 24, 2026
30fd642
Add midi.notepriority monophonic note-priority filter
user1303836 Feb 24, 2026
94d47e4
Add util.cache.ttl key-value cache with TTL expiration
user1303836 Feb 24, 2026
cb0f9d2
Fix review findings in midi.notepriority and util.cache.ttl
user1303836 Feb 24, 2026
3731a75
Fix review findings in midi.notepriority and util.cache.ttl
user1303836 Feb 24, 2026
ff6712f
Add util.throttle: rate-limit messages with drop, latest, queue policies
user1303836 Feb 24, 2026
25f8b2f
Add util.listdiff: duplicate-aware list comparison with added/removed…
user1303836 Feb 24, 2026
fb56ab8
Add util.ringbuf: bounded ring buffer with random access and dump
user1303836 Feb 24, 2026
a5c46ef
Merge remote-tracking branch 'origin/main' into feat/util-debounce
user1303836 Feb 24, 2026
57b02c6
Fix midi.notepriority stuck notes on bypass-off with held notes
user1303836 Feb 24, 2026
d70975c
Add util.dict.defaults: merge defaults into dicts with validation
user1303836 Feb 24, 2026
034ad37
Add util.state.latch: Schmitt-style binary latch with dwell time
user1303836 Feb 24, 2026
5153eb1
Add sig.schmitt.edge~: signal-rate Schmitt trigger with hysteresis
user1303836 Feb 24, 2026
973052a
Fix double-validation bug in util.dict.defaults
user1303836 Feb 24, 2026
27c1644
Add midi.notecluster: group note-ons within time window for chord det…
user1303836 Feb 24, 2026
1abe33c
Fix trigger ordering in util.state.latch request handler
user1303836 Feb 24, 2026
135108a
Fix util.ringbuf emitting full on every append after capacity reached
user1303836 Feb 24, 2026
3dd6624
Fix midi.notecluster cluster emission and bypass routing bugs
user1303836 Feb 24, 2026
c42fef1
Fix sig.schmitt.edge~ reset to use patcherarg thresholds and update s…
user1303836 Feb 24, 2026
8fc51d0
Fix 3 P1 bugs in util.throttle queue mode
user1303836 Feb 24, 2026
3eddeb7
Fix midi.notecluster bypass gate, cluster emission, and metadata orde…
user1303836 Feb 25, 2026
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
84 changes: 84 additions & 0 deletions patches/midi.notecluster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# midi.notecluster

Group note-ons arriving within a short time window and emit chord cluster metadata.

## Description

`midi.notecluster` collects MIDI note-on events that arrive within a configurable time window and outputs them as a single cluster list with metadata. Note-offs pass through a separate outlet unchanged.

This packages a common chord-detection pattern into a reusable abstraction.

## Inlets

| Inlet | Type | Description |
|-------|------|-------------|
| 1 | note pairs (pitch velocity) | MIDI note pairs from `midiparse` note outlet or similar |
| 2 | control messages | `window`, `sort`, `unique`, `flush`, `reset`, `bypass` |

## Outlets

| Outlet | Type | Description |
|--------|------|-------------|
| 1 | list | `cluster <p1> <p2> ...` - sorted/deduped pitch list |
| 2 | messages | Metadata: `count <n>`, `lowest <p>`, `highest <p>`, `single`, `done` |
| 3 | list | Note-off passthrough: `<pitch> 0` |

## Messages

### Inlet 1

- **pitch velocity** (list of two ints): Note-on if velocity > 0 (accumulated into cluster), note-off if velocity == 0 (passed through outlet 3).

### Inlet 2 (Control)

| Message | Default | Description |
|---------|---------|-------------|
| `window <ms>` | 5 | Cluster window duration in milliseconds |
| `sort <0\|1>` | 1 | Sort pitches low-to-high before output |
| `unique <0\|1>` | 1 | Remove duplicate pitches within a cluster |
| `flush` | - | Emit pending cluster immediately |
| `reset` | - | Clear pending cluster and stop timer |
| `bypass <0\|1>` | 0 | Route all input directly to outlet 3 |

## Behavior

1. The first note-on starts the cluster window timer.
2. Additional note-ons arriving within the window join the current cluster.
3. When the window closes (or `flush` is received), the accumulated pitches are optionally sorted and deduplicated, then emitted as `cluster p1 p2 ...` on outlet 1.
4. Metadata follows on outlet 2: `highest`, `lowest`, `count`, `single` (if cluster size is 1), and `done`.
5. The cluster buffer is cleared after emission, ready for the next cluster.
6. Note-offs always pass through outlet 3 immediately, regardless of cluster state.

## Metadata Output Sequence

After each cluster emission, outlet 2 outputs (in order):

1. `highest <pitch>` - highest pitch in cluster
2. `lowest <pitch>` - lowest pitch in cluster (same as first element when sorted)
3. `count <n>` - number of notes in cluster
4. `single` - only emitted if cluster contains exactly one note
5. `done` - signals completion

## Examples

```
; Two notes within 5ms window -> one cluster
60 100, 64 100 -> outlet 1: cluster 60 64
outlet 2: highest 64, lowest 60, count 2, done

; Note-off passes through
60 0 -> outlet 3: 60 0

; Single note
72 100 -> outlet 1: cluster 72
outlet 2: highest 72, lowest 72, count 1, single, done
```

## Instance Isolation

All internal state uses `#0`-scoped names (`#0_window`, `#0_sort`, `#0_unique`, etc.), ensuring multiple instances do not interfere with each other.

## Requirements

- Max 9 (uses standard `zl` objects, `delay`, `gate`, `trigger`)
- No external dependencies
Loading