aboutsummaryrefslogtreecommitdiffstats
path: root/alv-lib
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-03-18 11:04:57 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-18 11:05:10 +0000
commitca06f7154ded1fc44844d24ce3e2718cd4fa5171 (patch)
tree4c74c1b8feddb19b2749d1445dde22c6215a45fd /alv-lib
parentlib: add random/int (diff)
downloadalive-ca06f7154ded1fc44844d24ce3e2718cd4fa5171.tar.gz
alive-ca06f7154ded1fc44844d24ce3e2718cd4fa5171.zip
lib: support receiving OSC
Diffstat (limited to 'alv-lib')
-rw-r--r--alv-lib/osc.moon120
1 files changed, 110 insertions, 10 deletions
diff --git a/alv-lib/osc.moon b/alv-lib/osc.moon
index 8c93b07..d54b1b1 100644
--- a/alv-lib/osc.moon
+++ b/alv-lib/osc.moon
@@ -1,7 +1,10 @@
-import Op, PureOp, Constant, Input, T, sig, any from require 'alv.base'
-import new_message, add_item from require 'alv-lib._osc'
+import Op, PureOp, Constant, Input, T, Array, sig, const, any from require 'alv.base'
+import new_message, add_item, Message from require 'alv-lib._osc'
import dns, udp from require 'socket'
+losc = require 'losc'
+losc_Pattern = require 'losc.pattern'
+
unpack or= table.unpack
connect = Constant.meta
@@ -18,7 +21,7 @@ connect = Constant.meta
host: Input.hot host
port: Input.hot port
- @setup_out '~', T['udp/socket']
+ @setup_out '~', T['osc/out']
tick: =>
{ :host, :port } = @unwrap_all!
@@ -27,6 +30,101 @@ connect = Constant.meta
@out\set with sock = udp!
\setpeername ip, port
+listen = Constant.meta
+ meta:
+ name: 'listen'
+ summary: "Create a UDP server."
+ examples: { '(osc/listen port bind-addr)' }
+
+ value: class extends Op
+ pattern = sig.num + -sig.str
+ setup: (inputs) =>
+ { port, host } = pattern\match inputs
+ super
+ port: Input.hot port
+ host: Input.hot host or Constant.str '*'
+ io: Input.hot T['osc/in']\mk_evt!
+
+ @setup_out '!', T['osc/in']
+
+ poll: =>
+ messages = while true
+ data = @state\receive!
+ break unless data
+
+ ok, msg = pcall Message.unpack, data
+ if not ok
+ print "invalid OSC message:", msg
+ continue
+
+ msg
+
+ if #messages > 0
+ @inputs.io.result\set messages
+ true
+
+ tick: =>
+ if @inputs.port\dirty! or @inputs.host\dirty!
+ @state = with udp!
+ \settimeout 0
+ assert \setsockname @inputs.host!, @inputs.port!
+
+ if msgs = @inputs.io!
+ @out\set msgs
+
+recv_evt = Constant.meta
+ meta:
+ name: 'recv!'
+ summary: "Receive OSC messages."
+ examples: { '(osc/recv! [socket] path [type-str])' }
+
+ value: class extends Op
+ pattern: any!^0
+
+ full_pattern = -sig['osc/in'] + sig.str + -const.str
+ setup: (inputs, scope) =>
+ { messages, path, types } = full_pattern\match inputs
+ types = types or Constant.str 'f'
+ super
+ messages: Input.hot messages or scope\get '*oscin*'
+ path: Input.cold path
+ types: Input.cold types
+
+ @state, type = @get_type types.result!
+ @setup_out '!', type
+
+ tick: =>
+ { :messages, :path, :types } = @unwrap_all!
+ for msg in *(messages or {})
+ continue if msg.address != path
+ continue if msg.types != types
+
+ if @state
+ @out\set [v for v in *msg]
+ else
+ @out\set msg[1]
+ break
+
+ get_type: (types) =>
+ types = types\gsub '[%[%]]', ''
+ first = types\sub 1, 1
+ assert first, "empty type string"
+
+ for i=2, #types
+ assert first == (types\sub i, 1), "mixed types in array"
+
+ typ = switch first
+ when 'f', 'i' then T.num
+ when 's', 'S', 'c' then T.str
+ when 'T', 'F' then T.bool
+ when 'I' then T.bang
+ else error "unknown typetag"
+
+ if #types > 1
+ true, Array #types, typ
+ else
+ false, typ
+
send = Constant.meta
meta:
name: 'send'
@@ -35,13 +133,13 @@ send = Constant.meta
description: "Sends an OSC message to `path` with `val…` as arguments.
- `socket` should be a `udp/socket` value. This argument can be omitted and the
- value be passed as a dynamic definition in `*sock*` instead.
+ value be passed as a dynamic definition in `*oscout*` instead.
- `path` is the OSC path to send the message to. It should be a string-value.
- the arguments can be any type:
- `num` will be sent as `f`
- `str` will be sent as `s`
- `bool` will be sent as `T`/`F`
- - `bang` will be sent as `T`
+ - `bang` will be sent as `I`
- arrays will be unwrapped
- structs will be sent as a series of key/value tuples
@@ -50,11 +148,11 @@ This is a pure op, so between the values at most one !-stream input is allowed."
value: class extends PureOp
pattern: any!^0
- full_pattern = -sig['udp/socket'] + sig.str + any!^0
+ full_pattern = -sig['osc/in'] + sig.str + any!^0
setup: (inputs, scope) =>
{ socket, path, values } = full_pattern\match inputs
super values, scope, {
- socket: Input.cold socket or scope\get '*sock*'
+ socket: Input.cold socket or scope\get '*oscout*'
path: Input.cold path
}
@@ -74,7 +172,7 @@ send_arr = Constant.meta
description: "Sends an OSC message to `path` with `val…` as arguments.
- `socket` should be a `udp/socket` value. This argument can be omitted and the
- value be passed as a dynamic definition in `*sock*` instead.
+ value be passed as a dynamic definition in `*oscout*` instead.
- `path` is the OSC path to send the message to. It should be a string-value.
- the arguments can be any type:
- `num` will be sent as `f`
@@ -89,11 +187,11 @@ This is a pure op, so between the values at most one !-stream input is allowed."
value: class extends PureOp
pattern: any!^0
- full_pattern = -sig['udp/socket'] + sig.str + any!^0
+ full_pattern = -sig['udp/out'] + sig.str + any!^0
setup: (inputs, scope) =>
{ socket, path, values } = full_pattern\match inputs
super values, scope, {
- socket: Input.cold socket or scope\get '*sock*'
+ socket: Input.cold socket or scope\get '*oscout*'
path: Input.cold path
}
@@ -112,5 +210,7 @@ Constant.meta
value:
:connect
+ :listen
:send
:sync
+ 'recv!': recv_evt