haybale-protokol-osc 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haybale_protokol_osc-0.0.3/.gitignore +42 -0
- haybale_protokol_osc-0.0.3/PKG-INFO +12 -0
- haybale_protokol_osc-0.0.3/README.md +75 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/NOTES.md +49 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/OVERVIEW.md +11 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/QUICKREF.md +8 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/__init__.py +103 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/adapters/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/arg_pins.py +241 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/docs/haybale-protokol-osc.node.OscReceiveNode.md +50 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/docs/haybale-protokol-osc.node.OscSendNode.md +38 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/docs/haybale-protokol-osc.state.OscState.md +9 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/driver.py +608 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/editors/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/framing.py +79 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/haybale.toml +48 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/macros/.gitkeep +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/nodes/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/nodes/osc_receive.py +402 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/nodes/osc_send.py +230 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/panels/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/pattern.py +88 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/settings/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/shape.py +293 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/skins/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/states/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/states/osc_state.py +27 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/themes/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/types/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/haybale_protokol_osc/widgets/__init__.py +0 -0
- haybale_protokol_osc-0.0.3/pyproject.toml +33 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Patterns below are anchored with a leading slash (/build/) so they match only
|
|
2
|
+
# at the repo root. An unanchored pattern (build/) matches at EVERY depth —
|
|
3
|
+
# including inside barn/, where it would silently exclude your library's own
|
|
4
|
+
# files. See the note at the end of this file before adding patterns.
|
|
5
|
+
|
|
6
|
+
# Byte-compiled / optimized / DLL files
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging (root only)
|
|
12
|
+
/build/
|
|
13
|
+
/dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
*.egg
|
|
16
|
+
|
|
17
|
+
# Virtual environments (root only)
|
|
18
|
+
/.venv/
|
|
19
|
+
/venv/
|
|
20
|
+
/env/
|
|
21
|
+
|
|
22
|
+
# Test / type-check caches
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.tox/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# Editor / OS
|
|
31
|
+
.DS_Store
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
|
|
35
|
+
# Haywire per-session UI state (open graph, pan/zoom) — not project state
|
|
36
|
+
.haywire/workspace_state.json
|
|
37
|
+
|
|
38
|
+
# ── Before you add a pattern ────────────────────────────────────────────────
|
|
39
|
+
# Anything ignored inside barn/<your-library>/ will be MISSING for everyone who
|
|
40
|
+
# installs your library — haywire share publishes by git URL, so consumers get
|
|
41
|
+
# a clone of this repo, not a built package. If a pattern is only meant for the
|
|
42
|
+
# repo root, anchor it with a leading slash: /build/ not build/.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: haybale-protokol-osc
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: OSC for haybale-protokol: UDP and TCP gates, OSC Receive and Send nodes
|
|
5
|
+
Project-URL: Source, https://github.com/going-haywire/haybale-protokol
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: control,network,osc,protocol
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: haybale-core>=0.4.2
|
|
10
|
+
Requires-Dist: haybale-protokol
|
|
11
|
+
Requires-Dist: haywire-core>=0.5.0
|
|
12
|
+
Requires-Dist: python-osc==1.10.2
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Protokol OSC
|
|
2
|
+
|
|
3
|
+
# Protokol OSC
|
|
4
|
+
|
|
5
|
+
OSC over UDP and TCP for Haywire, on top of `haybale-protokol`: an OSC Receive node and an OSC Send
|
|
6
|
+
node, each using a gate from the project's `.haywire/protokol.toml` (see the Protokol notes
|
|
7
|
+
for the file format).
|
|
8
|
+
|
|
9
|
+
## TCP
|
|
10
|
+
|
|
11
|
+
A TCP gate is a client (`role = "client"`, connects to `host`) or a server
|
|
12
|
+
(`role = "server"`, accepts any number of peers on `bind`). Choose the framing your peer
|
|
13
|
+
speaks: `framing = "1.0"` (length prefix) or `"1.1"` (SLIP, the default). A client keeps
|
|
14
|
+
reconnecting (0.5 s, 1 s, 2 s, 4 s, then every 5 s) and shows `reconnecting (n)`; anything sent
|
|
15
|
+
while it is disconnected is dropped and counted, never queued. A server sends each message to
|
|
16
|
+
all its peers. TCP gates may be `two-way`, so one gate can feed an OSC Receive and an OSC Send.
|
|
17
|
+
|
|
18
|
+
A peer that disappears without closing (power cut, lost Wi-Fi) is noticed by TCP keepalive
|
|
19
|
+
after about 11 s; a client then reconnects. A peer that stops reading is disconnected once
|
|
20
|
+
it holds up a send for 0.2 s, rather than slowing the graph down.
|
|
21
|
+
|
|
22
|
+
## Gates on a node
|
|
23
|
+
|
|
24
|
+
A node's **Gate** setting takes one or more gates. Pick them as chips from the list, which
|
|
25
|
+
offers this project's OSC gates that fit the node (input or two-way for Receive, output or
|
|
26
|
+
two-way for Send), or type a name and press Enter. OSC Receive listens on every listed gate;
|
|
27
|
+
its **From gate** outlet says where a message came in. OSC Send sends each message to every
|
|
28
|
+
listed gate. A listed gate that cannot be used is reported in `status`; the others still work.
|
|
29
|
+
|
|
30
|
+
## OSC Receive
|
|
31
|
+
|
|
32
|
+
Runs its flow for each message on its input gate whose address matches **Address**, an OSC
|
|
33
|
+
address pattern (`*`, `?`, `[0-9]`, `{a,b}`; empty takes everything). Each wildcard gets a
|
|
34
|
+
`match_N` outlet, which takes the type of whatever you connect it to.
|
|
35
|
+
|
|
36
|
+
**Arg Shape** lists the arguments: OSC type tags (`i f d h s b T r`), groupings
|
|
37
|
+
`vi2`–`vi4`, `vf2`–`vf4`, `m3`, `m4` for vectors and matrices, and `_` / `_n` to skip
|
|
38
|
+
arguments. The `args` outlet always carries every argument as a list; each token adds its own outlet. A message that does not fit is skipped and counted
|
|
39
|
+
in `status`. By default the node fires once at start with the last message the gate saw
|
|
40
|
+
(**Start Value**) and keeps only the newest message when its flow runs behind.
|
|
41
|
+
|
|
42
|
+
## OSC Send
|
|
43
|
+
|
|
44
|
+
Sends **Address** with one argument per inlet, in inlet order. Connect anything to the empty
|
|
45
|
+
inlet to add an argument. Vectors go out as their components (`vf3` → three `f`). Use `d` or
|
|
46
|
+
`h` in the shape for 64-bit numbers. An int that does not fit a 32-bit `i` is dropped, and
|
|
47
|
+
`status` says which argument it was. Each control tick sends, unless the **Only send changes** setting is on: then
|
|
48
|
+
a tick sends only when the address, type tags or values differ from the last message sent.
|
|
49
|
+
|
|
50
|
+
The Arg shape and the pins follow each other: typing the shape rebuilds the pins, and
|
|
51
|
+
growing, unplugging (a grown pin) or reordering pins rewrites the shape.
|
|
52
|
+
|
|
53
|
+
<!-- marketstall:share-url:start -->
|
|
54
|
+
Always the latest (tracks the current branch):
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
https://github.com/going-haywire/haybale-protokol/blob/master/marketstall.toml
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Frozen to this version:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
https://github.com/going-haywire/haybale-protokol/blob/v0.0.3/marketstall.toml
|
|
64
|
+
```
|
|
65
|
+
<!-- marketstall:share-url:end -->
|
|
66
|
+
|
|
67
|
+
OSC for haybale-protokol: UDP and TCP gates, OSC Receive and Send nodes
|
|
68
|
+
|
|
69
|
+
## Nodes
|
|
70
|
+
### Protokol
|
|
71
|
+
- **OSC Receive** — Triggered for each OSC message on an input gate that matches the address pattern
|
|
72
|
+
- **OSC Send** — Sends one OSC message to an output gate each time it runs
|
|
73
|
+
|
|
74
|
+
## States
|
|
75
|
+
- **Protokol OSC** — Registers the OSC driver with Protokol
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Protokol OSC
|
|
2
|
+
|
|
3
|
+
OSC over UDP and TCP for Haywire, on top of `haybale-protokol`: an OSC Receive node and an OSC Send
|
|
4
|
+
node, each using a gate from the project's `.haywire/protokol.toml` (see the Protokol notes
|
|
5
|
+
for the file format).
|
|
6
|
+
|
|
7
|
+
## TCP
|
|
8
|
+
|
|
9
|
+
A TCP gate is a client (`role = "client"`, connects to `host`) or a server
|
|
10
|
+
(`role = "server"`, accepts any number of peers on `bind`). Choose the framing your peer
|
|
11
|
+
speaks: `framing = "1.0"` (length prefix) or `"1.1"` (SLIP, the default). A client keeps
|
|
12
|
+
reconnecting (0.5 s, 1 s, 2 s, 4 s, then every 5 s) and shows `reconnecting (n)`; anything sent
|
|
13
|
+
while it is disconnected is dropped and counted, never queued. A server sends each message to
|
|
14
|
+
all its peers. TCP gates may be `two-way`, so one gate can feed an OSC Receive and an OSC Send.
|
|
15
|
+
|
|
16
|
+
A peer that disappears without closing (power cut, lost Wi-Fi) is noticed by TCP keepalive
|
|
17
|
+
after about 11 s; a client then reconnects. A peer that stops reading is disconnected once
|
|
18
|
+
it holds up a send for 0.2 s, rather than slowing the graph down.
|
|
19
|
+
|
|
20
|
+
## Gates on a node
|
|
21
|
+
|
|
22
|
+
A node's **Gate** setting takes one or more gates. Pick them as chips from the list, which
|
|
23
|
+
offers this project's OSC gates that fit the node (input or two-way for Receive, output or
|
|
24
|
+
two-way for Send), or type a name and press Enter. OSC Receive listens on every listed gate;
|
|
25
|
+
its **From gate** outlet says where a message came in. OSC Send sends each message to every
|
|
26
|
+
listed gate. A listed gate that cannot be used is reported in `status`; the others still work.
|
|
27
|
+
|
|
28
|
+
## OSC Receive
|
|
29
|
+
|
|
30
|
+
Runs its flow for each message on its input gate whose address matches **Address**, an OSC
|
|
31
|
+
address pattern (`*`, `?`, `[0-9]`, `{a,b}`; empty takes everything). Each wildcard gets a
|
|
32
|
+
`match_N` outlet, which takes the type of whatever you connect it to.
|
|
33
|
+
|
|
34
|
+
**Arg Shape** lists the arguments: OSC type tags (`i f d h s b T r`), groupings
|
|
35
|
+
`vi2`–`vi4`, `vf2`–`vf4`, `m3`, `m4` for vectors and matrices, and `_` / `_n` to skip
|
|
36
|
+
arguments. The `args` outlet always carries every argument as a list; each token adds its own outlet. A message that does not fit is skipped and counted
|
|
37
|
+
in `status`. By default the node fires once at start with the last message the gate saw
|
|
38
|
+
(**Start Value**) and keeps only the newest message when its flow runs behind.
|
|
39
|
+
|
|
40
|
+
## OSC Send
|
|
41
|
+
|
|
42
|
+
Sends **Address** with one argument per inlet, in inlet order. Connect anything to the empty
|
|
43
|
+
inlet to add an argument. Vectors go out as their components (`vf3` → three `f`). Use `d` or
|
|
44
|
+
`h` in the shape for 64-bit numbers. An int that does not fit a 32-bit `i` is dropped, and
|
|
45
|
+
`status` says which argument it was. Each control tick sends, unless the **Only send changes** setting is on: then
|
|
46
|
+
a tick sends only when the address, type tags or values differ from the last message sent.
|
|
47
|
+
|
|
48
|
+
The Arg shape and the pins follow each other: typing the shape rebuilds the pins, and
|
|
49
|
+
growing, unplugging (a grown pin) or reordering pins rewrites the shape.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Protokol OSC
|
|
2
|
+
|
|
3
|
+
OSC for haybale-protokol: UDP and TCP gates, OSC Receive and Send nodes
|
|
4
|
+
|
|
5
|
+
## Nodes
|
|
6
|
+
### Protokol
|
|
7
|
+
- **OSC Receive** — Triggered for each OSC message on an input gate that matches the address pattern
|
|
8
|
+
- **OSC Send** — Sends one OSC message to an output gate each time it runs
|
|
9
|
+
|
|
10
|
+
## States
|
|
11
|
+
- **Protokol OSC** — Registers the OSC driver with Protokol
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# haybale-protokol-osc — component index (v0.0.3)
|
|
2
|
+
|
|
3
|
+
## node
|
|
4
|
+
- `haybale-protokol-osc:node:OscReceiveNode` — OSC Receive — Triggered for each OSC message on an input gate that matches the address pattern _tags: osc, receive, control, network, protokol, touchosc_
|
|
5
|
+
- `haybale-protokol-osc:node:OscSendNode` — OSC Send — Sends one OSC message to an output gate each time it runs _tags: osc, send, control, network, protokol, touchosc_
|
|
6
|
+
|
|
7
|
+
## state
|
|
8
|
+
- `haybale-protokol-osc:state:OscState` — Protokol OSC — Registers the OSC driver with Protokol
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OSC for haybale-protokol: the OSC Protocol driver and the OSC Receive and Send nodes.
|
|
3
|
+
|
|
4
|
+
Add your custom components in the corresponding folders:
|
|
5
|
+
- nodes/ — node definitions
|
|
6
|
+
- macros/ — macro documents (.hwm)
|
|
7
|
+
- types/ — custom data types
|
|
8
|
+
- widgets/ — UI widgets for data types
|
|
9
|
+
- skins/ — custom node skins
|
|
10
|
+
- adapters/ — type-to-type conversion adapters
|
|
11
|
+
- settings/ — library settings definitions
|
|
12
|
+
- states/ — library app and session states
|
|
13
|
+
- themes/ — workbench and node themes
|
|
14
|
+
- panels/ — custom UI panels
|
|
15
|
+
- editors/ — custom UI editors
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from haywire.api import (
|
|
21
|
+
AdapterRegistry,
|
|
22
|
+
BaseLibrary,
|
|
23
|
+
MacroRegistry,
|
|
24
|
+
NodeRegistry,
|
|
25
|
+
SettingsRegistry,
|
|
26
|
+
SkinRegistry,
|
|
27
|
+
ThemeRegistry,
|
|
28
|
+
TypeRegistry,
|
|
29
|
+
WidgetRegistry,
|
|
30
|
+
library,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from haywire.api.workbench import EditorRegistry, PanelRegistry, StateRegistry
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@library(
|
|
37
|
+
file_watcher=True,
|
|
38
|
+
)
|
|
39
|
+
class Library(BaseLibrary):
|
|
40
|
+
"""OSC over UDP for haybale-protokol."""
|
|
41
|
+
|
|
42
|
+
def register_components(self):
|
|
43
|
+
"""Register all components with the global registries."""
|
|
44
|
+
base_path = Path(__file__).parent
|
|
45
|
+
|
|
46
|
+
self.add_folder_to_registry(
|
|
47
|
+
folder_path=str(base_path / "settings"),
|
|
48
|
+
registry_cls=SettingsRegistry,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.add_folder_to_registry(
|
|
52
|
+
folder_path=str(base_path / "states"),
|
|
53
|
+
registry_cls=StateRegistry,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self.add_folder_to_registry(
|
|
57
|
+
folder_path=str(base_path / "themes"),
|
|
58
|
+
registry_cls=ThemeRegistry,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.add_folder_to_registry(
|
|
62
|
+
folder_path=str(base_path / "types"),
|
|
63
|
+
registry_cls=TypeRegistry,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self.add_folder_to_registry(
|
|
67
|
+
folder_path=str(base_path / "adapters"),
|
|
68
|
+
registry_cls=AdapterRegistry,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.add_folder_to_registry(
|
|
72
|
+
folder_path=str(base_path / "widgets"),
|
|
73
|
+
registry_cls=WidgetRegistry,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.add_folder_to_registry(
|
|
77
|
+
folder_path=str(base_path / "skins"),
|
|
78
|
+
registry_cls=SkinRegistry,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.add_folder_to_registry(
|
|
82
|
+
folder_path=str(base_path / "nodes"),
|
|
83
|
+
registry_cls=NodeRegistry,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
self.add_folder_to_registry(
|
|
87
|
+
folder_path=str(base_path / "macros"),
|
|
88
|
+
registry_cls=MacroRegistry,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self.add_folder_to_registry(
|
|
92
|
+
folder_path=str(base_path / "panels"),
|
|
93
|
+
registry_cls=PanelRegistry,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
self.add_folder_to_registry(
|
|
97
|
+
folder_path=str(base_path / "editors"),
|
|
98
|
+
registry_cls=EditorRegistry,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def validate(self) -> bool:
|
|
102
|
+
"""Validate library structure."""
|
|
103
|
+
return True
|
|
File without changes
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Argument pins that follow the Arg shape, shared by OSC Receive and OSC Send.
|
|
2
|
+
|
|
3
|
+
The Arg shape text is the source of truth. Each named token has a pin with a stable id
|
|
4
|
+
(``arg_<n>``, numbers never reused, because an edge's id contains its port's id); the node
|
|
5
|
+
store keeps the ids in shape order. The pin edits a user makes rewrite the text:
|
|
6
|
+
|
|
7
|
+
- connecting the empty ``ADD`` slot appends the edge type's token (undo takes it back);
|
|
8
|
+
- deleting the edge that grew a pin removes the pin and its token (typed pins stay);
|
|
9
|
+
- reordering pins reorders the tokens; ``_`` skips stay where they are.
|
|
10
|
+
|
|
11
|
+
Every rewrite goes through the ``arg_shape`` port, whose ``on_change`` rebuilds the pins,
|
|
12
|
+
inside the user's one action, so each edit is one undo step.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from haywire.api import ADD, PortOrigin
|
|
21
|
+
|
|
22
|
+
from haybale_protokol_osc.shape import ShapeError, Token, parse_shape, token_for_type
|
|
23
|
+
|
|
24
|
+
ARG_ID = re.compile(r"arg_(\d+)")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ArgPins:
|
|
28
|
+
"""Mixin for a ``BaseNode`` with an ``arg_shape`` config port and ``arg_<n>`` pins."""
|
|
29
|
+
|
|
30
|
+
#: True for Receive (outlets, ``_`` allowed), False for Send (inlets).
|
|
31
|
+
receive: bool = False
|
|
32
|
+
|
|
33
|
+
# Provided by BaseNode (the mixin comes first in the bases, so it defines no stubs).
|
|
34
|
+
ports: dict[str, Any]
|
|
35
|
+
store: Any
|
|
36
|
+
value: Any
|
|
37
|
+
add: Any
|
|
38
|
+
post_value: Any
|
|
39
|
+
|
|
40
|
+
# -- the node supplies --------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def _arg_spec(self, port_id: str, type_cls: Any, origin: PortOrigin, label: str) -> Any:
|
|
43
|
+
"""The inlet or outlet spec of one argument pin."""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
# -- reading ------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def _tokens(self) -> list[Token]:
|
|
49
|
+
"""The current Arg shape; raises ``ShapeError`` when the text is invalid."""
|
|
50
|
+
return parse_shape(str(self.value("arg_shape") or ""), receive=self.receive)
|
|
51
|
+
|
|
52
|
+
def _arg_ids(self) -> list[str]:
|
|
53
|
+
return list(getattr(self.store, "arg_ids", []))
|
|
54
|
+
|
|
55
|
+
def _slot_id(self) -> str:
|
|
56
|
+
return str(getattr(self.store, "arg_slot", "arg_1"))
|
|
57
|
+
|
|
58
|
+
def _arg_ports(self) -> list[Any]:
|
|
59
|
+
"""The typed argument pins, in shape order."""
|
|
60
|
+
return [self.ports[port_id] for port_id in self._arg_ids() if port_id in self.ports]
|
|
61
|
+
|
|
62
|
+
def _pins_mismatch(self) -> str | None:
|
|
63
|
+
"""Why the argument pins no longer match the Arg shape (a pin removed from its menu), or ``None``."""
|
|
64
|
+
try:
|
|
65
|
+
named = [token for token in self._tokens() if token.haywire_type is not None]
|
|
66
|
+
except ShapeError as exc:
|
|
67
|
+
return f"Arg shape: {exc}"
|
|
68
|
+
if len(self._arg_ports()) != len(named):
|
|
69
|
+
return "pins and Arg shape differ: retype the Arg shape"
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
# -- building -----------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def _add_arg_ports(self, tokens: list[Token]) -> None:
|
|
75
|
+
"""Add one pin per named token, then the empty slot. Call inside the node's ``rejig``."""
|
|
76
|
+
import haywire.api as builtin
|
|
77
|
+
|
|
78
|
+
from haybale_core.types.specs import BYTES
|
|
79
|
+
|
|
80
|
+
named = [token for token in tokens if token.haywire_type is not None]
|
|
81
|
+
ids = self._arg_ids()[: len(named)]
|
|
82
|
+
slot = self._slot_id()
|
|
83
|
+
while len(ids) < len(named):
|
|
84
|
+
# The empty slot holds no edge (connecting grows it), so its id is free to take.
|
|
85
|
+
ids.append(slot if slot not in ids else self._new_arg_id(ids))
|
|
86
|
+
if slot in ids:
|
|
87
|
+
slot = self._new_arg_id(ids)
|
|
88
|
+
self.store.arg_ids = ids
|
|
89
|
+
self.store.arg_slot = slot
|
|
90
|
+
grown = getattr(self.store, "arg_grown", [])
|
|
91
|
+
self.store.arg_grown = [port_id for port_id in grown if port_id in ids]
|
|
92
|
+
for position, (port_id, token) in enumerate(zip(ids, named, strict=True), start=1):
|
|
93
|
+
name = str(token.haywire_type)
|
|
94
|
+
type_cls = BYTES if name == "BYTES" else getattr(builtin, name)
|
|
95
|
+
self.add(self._arg_spec(port_id, type_cls, PortOrigin.RESOLVED, f"{position}: {token.text}"))
|
|
96
|
+
self.add(self._arg_spec(slot, ADD, PortOrigin.DECLARED, "+"))
|
|
97
|
+
|
|
98
|
+
def _move_slot_last(self) -> None:
|
|
99
|
+
"""Put the empty slot after every other port, so it is the last pin of its side."""
|
|
100
|
+
slot = self.ports.get(self._slot_id())
|
|
101
|
+
if slot is None:
|
|
102
|
+
return
|
|
103
|
+
others = [port.order for port in self.ports.values() if port is not slot]
|
|
104
|
+
if others and slot.order <= max(others):
|
|
105
|
+
slot.order = max(others) + 1
|
|
106
|
+
wrapper = getattr(self, "wrapper", None)
|
|
107
|
+
if wrapper is not None:
|
|
108
|
+
wrapper.mark_layout_changed()
|
|
109
|
+
|
|
110
|
+
def _new_arg_id(self, taken: list[str]) -> str:
|
|
111
|
+
used = [int(m.group(1)) for port_id in (*self.ports, *taken) if (m := ARG_ID.fullmatch(port_id))]
|
|
112
|
+
floor = int(getattr(self.store, "arg_next", 1))
|
|
113
|
+
number = max([floor - 1, *used]) + 1
|
|
114
|
+
self.store.arg_next = number + 1
|
|
115
|
+
return f"arg_{number}"
|
|
116
|
+
|
|
117
|
+
def _accept_text(self) -> None:
|
|
118
|
+
"""Remember the current config texts as the last ones that applied."""
|
|
119
|
+
for port_id in ("address", "arg_shape"):
|
|
120
|
+
if port_id in self.ports:
|
|
121
|
+
setattr(self.store, f"good_{port_id}", str(self.value(port_id) or ""))
|
|
122
|
+
|
|
123
|
+
def _reject_text(self, port: Any, error: Exception) -> None:
|
|
124
|
+
"""Put ``port`` back to its last applied text and say why the new one was refused."""
|
|
125
|
+
self._report(f"not applied: {error}")
|
|
126
|
+
good = str(getattr(self.store, f"good_{getattr(port, 'id', '')}", ""))
|
|
127
|
+
if getattr(port, "id", None) in ("address", "arg_shape") and self.value(port.id) != good:
|
|
128
|
+
port.set_value(good)
|
|
129
|
+
|
|
130
|
+
def _write_shape(self, tokens: list[Token]) -> None:
|
|
131
|
+
"""Set the Arg shape text; its ``on_change`` rebuilds the pins."""
|
|
132
|
+
self.ports["arg_shape"].set_value(" ".join(token.text for token in tokens))
|
|
133
|
+
|
|
134
|
+
# -- pin edits ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def hb_grow(self, port: Any, edge_wrapper: Any) -> None:
|
|
137
|
+
"""An edge on the empty slot becomes an argument of the edge's type."""
|
|
138
|
+
|
|
139
|
+
if port.type_cls is None or not issubclass(port.type_cls, ADD) or port.id != self._slot_id():
|
|
140
|
+
return
|
|
141
|
+
other = edge_wrapper._outlet_port if port.is_inlet() else edge_wrapper._inlet_port
|
|
142
|
+
if other is None:
|
|
143
|
+
return
|
|
144
|
+
type_name = getattr(other.stored_type, "__name__", "")
|
|
145
|
+
text = token_for_type(type_name)
|
|
146
|
+
if text is None:
|
|
147
|
+
self._report(f"OSC cannot carry {type_name}")
|
|
148
|
+
return
|
|
149
|
+
try:
|
|
150
|
+
tokens = self._tokens()
|
|
151
|
+
except ShapeError as exc:
|
|
152
|
+
self._report(f"not applied: {exc}")
|
|
153
|
+
return
|
|
154
|
+
self.store.arg_ids = [*self._arg_ids(), port.id]
|
|
155
|
+
self.store.arg_grown = [*getattr(self.store, "arg_grown", []), port.id]
|
|
156
|
+
self.store.arg_slot = self._new_arg_id(self._arg_ids())
|
|
157
|
+
self._write_shape([*tokens, *parse_shape(text, receive=self.receive)])
|
|
158
|
+
|
|
159
|
+
def release_pins(self, port_ids: list[str]) -> list[str]:
|
|
160
|
+
"""Remove the grown argument pins whose edge is gone, with their tokens."""
|
|
161
|
+
grown = list(getattr(self.store, "arg_grown", []))
|
|
162
|
+
candidates = [
|
|
163
|
+
port_id
|
|
164
|
+
for port_id in port_ids
|
|
165
|
+
if port_id in grown and port_id in self.ports and not self.ports[port_id].is_linked()
|
|
166
|
+
]
|
|
167
|
+
if not candidates:
|
|
168
|
+
return []
|
|
169
|
+
try:
|
|
170
|
+
tokens = self._tokens()
|
|
171
|
+
except ShapeError:
|
|
172
|
+
return []
|
|
173
|
+
ids = self._arg_ids()
|
|
174
|
+
stash = dict(getattr(self.store, "arg_released", {}))
|
|
175
|
+
for port_id in candidates:
|
|
176
|
+
index = ids.index(port_id)
|
|
177
|
+
position = _named_positions(tokens)[index]
|
|
178
|
+
stash[port_id] = [index, position, tokens[position].text]
|
|
179
|
+
del tokens[position]
|
|
180
|
+
del ids[index]
|
|
181
|
+
grown.remove(port_id)
|
|
182
|
+
self.store.arg_released = stash
|
|
183
|
+
self.store.arg_ids = ids
|
|
184
|
+
self.store.arg_grown = grown
|
|
185
|
+
self._write_shape(tokens)
|
|
186
|
+
return candidates
|
|
187
|
+
|
|
188
|
+
def restore_pins(self, port_ids: list[str]) -> None:
|
|
189
|
+
"""Bring back pins ``release_pins`` removed, at their places in the shape."""
|
|
190
|
+
stash = dict(getattr(self.store, "arg_released", {}))
|
|
191
|
+
try:
|
|
192
|
+
tokens = self._tokens()
|
|
193
|
+
except ShapeError:
|
|
194
|
+
return
|
|
195
|
+
ids = self._arg_ids()
|
|
196
|
+
grown = list(getattr(self.store, "arg_grown", []))
|
|
197
|
+
for port_id in reversed(port_ids):
|
|
198
|
+
if port_id not in stash:
|
|
199
|
+
continue
|
|
200
|
+
index, position, text = stash.pop(port_id)
|
|
201
|
+
tokens.insert(position, *parse_shape(text, receive=self.receive))
|
|
202
|
+
ids.insert(index, port_id)
|
|
203
|
+
grown.append(port_id)
|
|
204
|
+
self.store.arg_released = stash
|
|
205
|
+
self.store.arg_ids = ids
|
|
206
|
+
self.store.arg_grown = grown
|
|
207
|
+
self._write_shape(tokens)
|
|
208
|
+
|
|
209
|
+
def reorder_ports(self, ordered_ids: list[str]) -> None:
|
|
210
|
+
"""Reorder pins as the base does, then reorder the Arg shape to match."""
|
|
211
|
+
super().reorder_ports(ordered_ids) # type: ignore[misc]
|
|
212
|
+
try:
|
|
213
|
+
self._reorder_tokens()
|
|
214
|
+
finally:
|
|
215
|
+
self._move_slot_last()
|
|
216
|
+
|
|
217
|
+
def _reorder_tokens(self) -> None:
|
|
218
|
+
ids = self._arg_ids()
|
|
219
|
+
if any(port_id not in self.ports for port_id in ids):
|
|
220
|
+
return # a pin was removed from its menu; the status guard reports it
|
|
221
|
+
new_ids = sorted(ids, key=lambda port_id: self.ports[port_id].order)
|
|
222
|
+
if new_ids == ids:
|
|
223
|
+
return
|
|
224
|
+
try:
|
|
225
|
+
tokens = self._tokens()
|
|
226
|
+
except ShapeError:
|
|
227
|
+
return
|
|
228
|
+
positions = _named_positions(tokens)
|
|
229
|
+
named = {port_id: tokens[position] for port_id, position in zip(ids, positions, strict=True)}
|
|
230
|
+
for position, port_id in zip(positions, new_ids, strict=True):
|
|
231
|
+
tokens[position] = named[port_id]
|
|
232
|
+
self.store.arg_ids = new_ids
|
|
233
|
+
self._write_shape(tokens)
|
|
234
|
+
|
|
235
|
+
def _report(self, text: str) -> None:
|
|
236
|
+
"""Show ``text`` on the node's status outlet."""
|
|
237
|
+
self.post_value("status", text)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _named_positions(tokens: list[Token]) -> list[int]:
|
|
241
|
+
return [position for position, token in enumerate(tokens) if token.haywire_type is not None]
|
haybale_protokol_osc-0.0.3/haybale_protokol_osc/docs/haybale-protokol-osc.node.OscReceiveNode.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# OSC Receive
|
|
2
|
+
|
|
3
|
+
`haybale-protokol-osc:node:OscReceiveNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Triggered for each OSC message on an input gate that matches the address pattern
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `gate` | config | `haywire-core:type:STRING` | One or more input (or two-way) gates from the project's Gate file, .haywire/protokol.toml; pick them from the list, which offers this project's OSC gates that can receive, or type a name and press Enter. Make gates in the Gates editor. The node opens each gate's Line when the graph starts and gives it back when it stops; messages from every gate run the same flow, and the From gate outlet says which one a message came in on. A gate that is missing, of another protocol or whose port is busy is reported in status; the others still work. A change applies on the next start. |
|
|
12
|
+
| `address` | config | `haywire-core:type:STRING` | The OSC address pattern to listen to; empty takes every message on the gate. Wildcards: \* (any text within one /part), ? (one character), [0-9] or [!a] (one character from a set), {left,right} (one of the words). Each wildcard adds a capture outlet, match_1, match_2 … from left to right. A capture starts as an empty + pin and takes the type of the inlet you connect it to (text, int, float or bool); a part that does not convert is reported in status. Changing the pattern rebuilds the match_ outlets; an invalid pattern is put back. |
|
|
13
|
+
| `arg_shape` | config | `haywire-core:type:STRING` | The Arg shape: the arguments the node expects, as tokens separated by spaces. i or h: int; f or d: float; s: text; b: blob; T: bool; r: colour; vi2–vi4 and vf2–vf4: 2 to 4 ints or floats as one vector; m3, m4: 9 or 16 floats as a matrix; _ skips one argument, _3 skips three. The args outlet always carries every argument as a list; each token adds its own outlet, in order. The shape and the pins follow each other: connecting the + pin at the bottom adds a token for the connected type, deleting the edge that grew a pin removes the pin and its token, and dragging pins into another order reorders the tokens. A message that does not fit is skipped and counted in status; extra arguments are ignored. An invalid shape is put back. |
|
|
14
|
+
| `received` | outlet | `haywire-core:type:EXEC` | Signal for controlling execution flow between nodes |
|
|
15
|
+
| `address_out` | outlet | `haywire-core:type:STRING` | Text data |
|
|
16
|
+
| `from_gate` | outlet | `haywire-core:type:STRING` | The gate the message came in on |
|
|
17
|
+
| `args` | outlet | `haybale-core:type:LIST` | Every argument of the message, as a list |
|
|
18
|
+
| `arg_1` | outlet | `haywire-core:type:ADD` | Grows a new port from whatever connects to it |
|
|
19
|
+
| `timetag` | outlet | `haywire-core:type:FLOAT` | Decimal number |
|
|
20
|
+
| `status` | outlet | `haywire-core:type:STRING` | Text data |
|
|
21
|
+
|
|
22
|
+
## Settings
|
|
23
|
+
|
|
24
|
+
| name | bag | default | description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `queue_mode` | dispatch | `'Auto (by message kind)'` | Block runs the flow for every message; a slow flow then falls behind, never the Line. Drop keeps only the newest message. Auto uses Drop for values (faders, axes, OSC) and Block for events (notes, buttons). |
|
|
27
|
+
| `max_queue_size` | dispatch | `100` | How many messages Block holds while the flow runs behind. Drop always holds one. |
|
|
28
|
+
| `start_value` | dispatch | `'Auto (by message kind)'` | Fire once with the last message the Line saw when the graph starts. Auto: on for values, off for events. |
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
Runs its flow for each OSC message on its gate that matches its address.
|
|
33
|
+
|
|
34
|
+
`Gate` names an input gate in the project's Gate file (`.haywire/protokol.toml`). `Address`
|
|
35
|
+
is an OSC address pattern (`*`, `?`, `[0-9]`, `{a,b}`); empty takes every message. Each
|
|
36
|
+
wildcard gets a capture outlet, `match_1`, `match_2`..., which takes the type of what it is
|
|
37
|
+
connected to. `args` always carries every argument as a list. `Arg Shape` lists the
|
|
38
|
+
arguments (`f i vf3 s`, `_` to skip one), each token adding its own outlet. A message that
|
|
39
|
+
does not fit the shape is skipped and counted in `status`.
|
|
40
|
+
|
|
41
|
+
The outlets keep the latest values, so other flows can read them.
|
|
42
|
+
|
|
43
|
+
Outputs:
|
|
44
|
+
received: Control flow for each matching message
|
|
45
|
+
address: The address that matched
|
|
46
|
+
args: Every argument, as a list
|
|
47
|
+
arg_N: One argument (or a group, such as a vector) per Arg Shape token
|
|
48
|
+
match_N: What each wildcard matched
|
|
49
|
+
timetag: The OSC bundle's timetag, 0 for a message outside a bundle
|
|
50
|
+
status: The gate's state and how many messages were skipped
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# OSC Send
|
|
2
|
+
|
|
3
|
+
`haybale-protokol-osc:node:OscSendNode` · kind: node
|
|
4
|
+
|
|
5
|
+
Sends one OSC message to an output gate each time it runs
|
|
6
|
+
|
|
7
|
+
## Ports
|
|
8
|
+
|
|
9
|
+
| id | direction | type | description |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `exec` | inlet | `haywire-core:type:EXEC` | Signal for controlling execution flow between nodes |
|
|
12
|
+
| `address` | inlet | `haywire-core:type:STRING` | Text data |
|
|
13
|
+
| `arg_1` | inlet | `haywire-core:type:ADD` | Grows a new port from whatever connects to it |
|
|
14
|
+
| `gate` | config | `haywire-core:type:STRING` | One or more output (or two-way) gates from the project's Gate file, .haywire/protokol.toml; pick them from the list, which offers this project's OSC gates that can send, or type a name and press Enter. Make gates in the Gates editor. Each send goes to every listed gate. The node opens each gate's Line when the graph starts and gives it back when it stops. A gate that is missing, of another protocol or cannot reach its peer is reported in status and the others still get the message; a message no gate could take counts as dropped, never queued. A change applies on the next start. |
|
|
15
|
+
| `arg_shape` | config | `haywire-core:type:STRING` | The Arg shape: one token per argument inlet, in inlet order. i: int; f: float; s: text; b: blob; T: bool; r: colour; vi2–vi4 and vf2–vf4: a vector, sent as its components; m3, m4: a matrix, sent as 9 or 16 floats. Use d or h instead of f or i for 64-bit numbers. There is no _: every inlet is sent, and an unconnected inlet sends its own value. Typing the shape rebuilds the inlets; connecting the + pin at the bottom adds a token for the connected type, deleting the edge that grew a pin removes the pin and its token, and dragging pins into another order reorders the tokens. An int too large for i is dropped and named in status; an invalid shape is put back. |
|
|
16
|
+
| `next` | outlet | `haywire-core:type:EXEC` | Signal for controlling execution flow between nodes |
|
|
17
|
+
| `shape` | outlet | `haywire-core:type:STRING` | Text data |
|
|
18
|
+
| `status` | outlet | `haywire-core:type:STRING` | Text data |
|
|
19
|
+
|
|
20
|
+
## Settings
|
|
21
|
+
|
|
22
|
+
| name | bag | default | description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `only_changes` | sending | `False` | Off: every control tick sends the message. On: a tick sends only when the address, type tags or values differ from the last message sent; the first tick of a run always sends, and a dropped send is retried on the next tick. Skipped ticks are counted as 'unchanged' in status. |
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
Sends `Address` with one argument per argument inlet, in inlet order, to its gate.
|
|
29
|
+
|
|
30
|
+
`Gate` names an output gate in the project's Gate file. Connect anything to the empty
|
|
31
|
+
inlet at the bottom to add an argument; `Arg Shape` lists the arguments (`f i vf3 s`) and
|
|
32
|
+
follows the inlets. A vector goes out as its components (`vf3` → three `f`); use `d` or `h`
|
|
33
|
+
in the shape for 64-bit numbers. An unconnected inlet sends its own value.
|
|
34
|
+
|
|
35
|
+
Outputs:
|
|
36
|
+
next: Control flow after sending
|
|
37
|
+
shape: The OSC type tags sent last
|
|
38
|
+
status: What was sent and dropped, or why nothing can be sent
|