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 meta: name: 'connect' summary: "Create a UDP remote." examples: { '(osc/connect host port)' } value: class extends Op pattern = sig.str + sig.num setup: (inputs) => { host, port } = pattern\match inputs super host: Input.hot host port: Input.hot port @setup_out '~', T['osc/out'] tick: => { :host, :port } = @unwrap_all! ip = dns.toip host @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: => if @inputs.io.result\dirty! return 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 \setoption 'reuseaddr', true \setoption 'reuseport', true assert \setsockname @inputs.host!, @inputs.port! if msgs = @inputs.io! @out\set msgs dump = 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'] setup: (inputs, scope) => messages = full_pattern\match inputs super Input.hot messages or scope\get '*oscin*' tick: => messages = @unwrap_all! for msg in *(messages or {}) print msg.address, msg.types, table.unpack msg 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 continue if @out\dirty! 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' summary: "Send an OSC message." examples: { '(osc/send [socket] path [val…])' } 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 `*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 `I` - arrays will be unwrapped - structs will be sent as a series of key/value tuples 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['osc/in'] + any.str + any!^0 setup: (inputs, scope) => { socket, path, values } = full_pattern\match inputs super values, scope, { socket: Input.cold socket or scope\get '*oscout*' path: Input.hot path } tick: => args = @unwrap_all! { :socket, :path } = args msg = new_message path for i=1,#args add_item msg, @inputs[i]\type!, args[i] socket\send msg.pack msg.content send_arr = Constant.meta meta: name: 'send-arr' summary: "Send an OSC message using arrays." examples: { '(osc/send [socket] path val…)' } 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 `*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` - arrays will be sent as a series of values surrounded by `[…]` - structs will be sent as a series of key (`s`)/value tuples surrounded by `[…]` 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/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 '*oscout*' path: Input.cold path } tick: => args = @unwrap_all! { :socket, :path } = args msg = new_message path for i=1,#args add_item msg, @inputs[i]\type!, args[i], true socket\send msg.pack msg.content Constant.meta meta: name: 'osc' summary: "OSC integration." value: :connect :listen :send :sync :dump 'recv!': recv_evt