OSC.jl/src/OSC.jl

344 lines
9.9 KiB
Julia
Raw Normal View History

2017-02-17 14:45:59 +01:00
# OSC.jl
2018-09-02 01:23:13 +02:00
# Copyright (c) 2018, Mark McCurry, All rights reserved.
2014-06-22 21:25:44 +02:00
#
2017-02-17 14:45:59 +01:00
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
2014-06-22 21:25:44 +02:00
#
2017-02-17 14:45:59 +01:00
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
2014-06-22 21:25:44 +02:00
#
2017-02-17 14:45:59 +01:00
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
2014-06-22 21:25:44 +02:00
2013-10-17 00:56:10 +02:00
module OSC
2017-02-17 14:45:59 +01:00
using Printf
2017-02-17 14:45:59 +01:00
import Base: show, getindex
export OscMsg, path
2015-07-16 17:15:53 +02:00
2013-10-15 02:33:08 +02:00
macro incfp(x) quote begin
local gensym_ = $(esc(x))
$(esc(x)) = $(esc(x))+1
gensym_
end end end
2017-08-15 03:16:31 +02:00
struct OscMsg
2017-02-17 14:45:59 +01:00
data::Array{UInt8}
2013-10-25 02:28:21 +02:00
end
path(msg::OscMsg) = stringify(msg.data)
2017-02-17 14:45:59 +01:00
function stringify(data::Array{UInt8})
2018-09-09 18:26:05 +02:00
zeroInd = findall(data.== 0)
2017-02-17 14:45:59 +01:00
if length(zeroInd) == 0
2015-07-16 17:15:53 +02:00
return string(map(Char, data)...)
2017-02-17 14:45:59 +01:00
elseif zeroInd[1] == 0
2013-10-17 00:56:10 +02:00
return nothing
else
2015-07-16 17:15:53 +02:00
return string(map(Char, data[1:zeroInd[1]-1])...)
2013-10-17 00:56:10 +02:00
end
end
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
function names(msg::OscMsg) #::String
2013-10-15 02:33:08 +02:00
pos = 1
2013-10-25 02:28:21 +02:00
while(msg.data[pos += 1] != 0) end #skip pattern
while(msg.data[pos += 1] == 0) end #skip null
2017-02-17 14:45:59 +01:00
return stringify(msg.data[pos+1:end]) #skip comma
2013-10-15 02:33:08 +02:00
end
2018-09-09 18:26:05 +02:00
strip_args(args::AbstractString) = replace(replace(args,"]"=>""),"["=>"")
2013-10-15 02:33:08 +02:00
2013-10-25 02:28:21 +02:00
function narguments(msg::OscMsg)
length(strip_args(names(msg)))
2013-10-15 02:33:08 +02:00
end
has_reserved(typeChar::Char) = typeChar in "isbfhtdSrmc"
2017-02-17 14:45:59 +01:00
nreserved(args::String) = sum(map(has_reserved, collect(args)))
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
function argType(msg::OscMsg, nargument::Int) #::Char
@assert(nargument > 0 && nargument <= narguments(msg))
return strip_args(names(msg))[nargument]
2013-10-15 02:33:08 +02:00
end
2013-10-17 00:21:04 +02:00
align(pos) = pos+(4-(pos-1)%4)
2017-02-17 14:45:59 +01:00
function arg_off(msg::OscMsg, idx::Int) #::Int
2013-10-25 02:28:21 +02:00
if(!has_reserved(argType(msg,idx)))
2017-02-17 14:45:59 +01:00
return 0
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
# Iterate to the right position
args::String = names(msg)
2013-10-15 02:33:08 +02:00
argc::Int = 1
pos::Int = 1
2017-02-17 14:45:59 +01:00
# Get past the Argument String
while(msg.data[pos] != UInt8(',')) pos += 1 end
2013-10-25 02:28:21 +02:00
while(msg.data[pos] != 0) pos += 1 end
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
# Alignment
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
# ignore any leading '[' or ']'
2013-10-15 02:33:08 +02:00
while(args[argc] in "[]") argc += 1 end
while(idx != 1)
2017-02-17 14:45:59 +01:00
bundle_length::UInt32 = 0
2013-10-15 02:33:08 +02:00
arg = args[argc]
argc += 1
2017-02-17 14:45:59 +01:00
if arg in "htd"
pos +=8
2013-10-15 02:33:08 +02:00
elseif(arg in "mrfci")
2017-02-17 14:45:59 +01:00
pos += 4
2013-10-15 02:33:08 +02:00
elseif(arg in "Ss")
2013-10-25 02:28:21 +02:00
while(msg.data[pos += 1] != 0) end
2013-10-17 00:21:04 +02:00
pos = align(pos)
2017-02-17 14:45:59 +01:00
elseif arg == 'b'
bundle_length |= (msg.data[@incfp(pos)] << 24)
bundle_length |= (msg.data[@incfp(pos)] << 16)
bundle_length |= (msg.data[@incfp(pos)] << 8)
bundle_length |= (msg.data[@incfp(pos)])
bundle_length += 4-bundle_length%4
pos += bundle_length
elseif(arg in "[]") # completely ignore array chars
idx += 1
else # TFI
2013-10-15 02:33:08 +02:00
end
idx -= 1
end
2017-02-17 14:45:59 +01:00
pos
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
# Calculate the size of the message without writing to a buffer
function vsosc_null(address::String,
arguments::String,
2013-10-15 02:33:08 +02:00
args...)
2017-02-17 14:45:59 +01:00
pos::Int = length(address) + 1
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
pos += 1+length(arguments)
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
arg_pos = 1
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
# Take care of varargs
for arg in arguments
if arg in "htd"
2013-10-15 02:33:08 +02:00
arg_pos += 1
2017-02-17 14:45:59 +01:00
pos += 8
elseif arg in "mrcfi"
2013-10-15 02:33:08 +02:00
arg_pos += 1
2017-02-17 14:45:59 +01:00
pos += 4
elseif arg in "sS"
s::String = args[@incfp(arg_pos)]
pos += length(s)
2013-10-17 00:21:04 +02:00
pos = align(pos)
2017-02-17 14:45:59 +01:00
elseif arg in "b"
2013-10-15 02:33:08 +02:00
i::Int32 = sizeof(args[@incfp(arg_pos)])
2017-02-17 14:45:59 +01:00
pos += 4 + i
2013-10-17 00:21:04 +02:00
pos = align(pos)
2017-02-17 14:45:59 +01:00
end # other args classes are ignored
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
return pos - 1
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
function rtosc_amessage(buffer::Array{UInt8},
2013-10-15 02:33:08 +02:00
len::Int,
2017-02-17 14:45:59 +01:00
address::String,
arguments::String,
2013-10-15 02:33:08 +02:00
args...)
2017-02-17 14:45:59 +01:00
total_len::Int = vsosc_null(address, arguments, args...)
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
for i=1:total_len
2013-10-15 02:33:08 +02:00
buffer[i] = 0
end
2017-02-17 14:45:59 +01:00
# Abort if the message cannot fit
if total_len > len
return 0
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
pos::Int = 1
2013-10-15 02:33:08 +02:00
#Address
2017-02-17 14:45:59 +01:00
for C in address
buffer[@incfp(pos)] = C
end
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
#Arguments
2017-02-17 14:45:59 +01:00
buffer[@incfp(pos)] = UInt8(',')
for A in arguments
buffer[@incfp(pos)] = A
end
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
2017-02-17 14:45:59 +01:00
arg_pos::Int = 1
for arg in arguments
@assert(UInt32(arg) != 0)
if arg in "htd"
d::UInt64 = reinterpret(UInt64, args[@incfp(arg_pos)])
buffer[@incfp(pos)] = ((d>>56) & 0xff)
buffer[@incfp(pos)] = ((d>>48) & 0xff)
buffer[@incfp(pos)] = ((d>>40) & 0xff)
buffer[@incfp(pos)] = ((d>>32) & 0xff)
buffer[@incfp(pos)] = ((d>>24) & 0xff)
buffer[@incfp(pos)] = ((d>>16) & 0xff)
buffer[@incfp(pos)] = ((d>>8) & 0xff)
buffer[@incfp(pos)] = (d & 0xff)
elseif arg in "rfci"
i::Int32 = reinterpret(Int32, args[@incfp(arg_pos)])
buffer[@incfp(pos)] = ((i>>24) & 0xff)
buffer[@incfp(pos)] = ((i>>16) & 0xff)
buffer[@incfp(pos)] = ((i>>8) & 0xff)
buffer[@incfp(pos)] = (i & 0xff)
elseif arg in "m"
m = args[@incfp(arg_pos)]
buffer[@incfp(pos)] = m[1]
buffer[@incfp(pos)] = m[2]
buffer[@incfp(pos)] = m[3]
buffer[@incfp(pos)] = m[4]
elseif arg in "Ss"
s = args[@incfp(arg_pos)]
for C in s
2013-10-15 02:33:08 +02:00
buffer[@incfp(pos)] = C
end
2013-10-17 00:21:04 +02:00
pos = align(pos)
2017-02-17 14:45:59 +01:00
elseif arg == 'b'
b = args[@incfp(arg_pos)]
i = sizeof(b)
buffer[@incfp(pos)] = ((i>>24) & 0xff)
buffer[@incfp(pos)] = ((i>>16) & 0xff)
buffer[@incfp(pos)] = ((i>>8) & 0xff)
buffer[@incfp(pos)] = (i & 0xff)
for U in b
buffer[@incfp(pos)] = UInt8(U)
2013-10-15 02:33:08 +02:00
end
2013-10-17 00:21:04 +02:00
pos = align(pos)
2013-10-15 02:33:08 +02:00
end
end
2017-02-17 14:45:59 +01:00
return pos - 1
2013-10-15 02:33:08 +02:00
end
2013-10-27 23:49:24 +01:00
OscMsg(address, arguments, args...) = message(address, arguments, args...)
2017-02-17 14:45:59 +01:00
function message(address::String,
arguments::String,
2013-10-25 02:28:21 +02:00
args...)
2017-02-17 14:45:59 +01:00
len::Int = vsosc_null(address, arguments, args...)
2018-09-09 18:26:05 +02:00
data::Vector{UInt8} = Array{UInt8}(undef, len)
2017-02-17 14:45:59 +01:00
rtosc_amessage(data,len,address,arguments,args...)
2013-10-25 02:28:21 +02:00
return OscMsg(data)
end
function rtosc_argument(msg::OscMsg, idx::Int)
2017-02-17 14:45:59 +01:00
typeChar::Char = argType(msg, idx)
# trivial case
2013-10-15 02:33:08 +02:00
if(!has_reserved(typeChar))
2017-02-17 14:45:59 +01:00
if typeChar == 'T'
2013-10-15 02:33:08 +02:00
return true
2017-02-17 14:45:59 +01:00
elseif typeChar == 'F'
return false
2013-10-15 02:33:08 +02:00
end
else
arg_pos::Int = arg_off(msg, idx)
2017-02-17 14:45:59 +01:00
if typeChar in "htd"
t::UInt64 = 0
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 56)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 48)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 40)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 32)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 24)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 16)
t |= (UInt64(msg.data[@incfp(arg_pos)]) << 8)
t |= (UInt64(msg.data[@incfp(arg_pos)]))
if typeChar == 'h'
return reinterpret(Int64, t)
2017-02-17 14:45:59 +01:00
elseif typeChar == 'd'
return reinterpret(Float64, t)
2013-10-15 02:33:08 +02:00
else
2017-02-17 14:45:59 +01:00
return t
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
elseif typeChar in "f"
2018-09-09 18:26:05 +02:00
return read(IOBuffer(msg.data[arg_pos.+(3:-1:0)]), Float32)
2017-02-17 14:45:59 +01:00
elseif typeChar in "rci"
i::UInt32 = 0
i |= (UInt32(msg.data[@incfp(arg_pos)]) << 24)
i |= (UInt32(msg.data[@incfp(arg_pos)]) << 16)
i |= (UInt32(msg.data[@incfp(arg_pos)]) << 8)
i |= (UInt32(msg.data[@incfp(arg_pos)]))
if typeChar == 'r'
return UInt32(i)
elseif typeChar == 'c'
2018-09-09 18:26:05 +02:00
return Char(i/(2^24))
2013-10-15 02:33:08 +02:00
else
2017-02-17 14:45:59 +01:00
return reinterpret(Int32, i)
2013-10-15 02:33:08 +02:00
end
2017-02-17 14:45:59 +01:00
elseif typeChar in "m"
2018-09-09 18:30:59 +02:00
m = Array{UInt8}(undef, 4)
2013-10-25 02:28:21 +02:00
m[1] = msg.data[@incfp(arg_pos)]
m[2] = msg.data[@incfp(arg_pos)]
m[3] = msg.data[@incfp(arg_pos)]
m[4] = msg.data[@incfp(arg_pos)]
2013-10-15 02:33:08 +02:00
return m
2017-02-17 14:45:59 +01:00
elseif typeChar in "b"
2015-07-16 17:15:53 +02:00
len::Int32 = 0
2017-02-17 14:45:59 +01:00
len |= (msg.data[@incfp(arg_pos)] << 24)
len |= (msg.data[@incfp(arg_pos)] << 16)
len |= (msg.data[@incfp(arg_pos)] << 8)
len |= (msg.data[@incfp(arg_pos)])
2018-09-09 18:26:05 +02:00
return msg.data[arg_pos.+(0:len-1)]
2017-02-17 14:45:59 +01:00
elseif typeChar in "Ss"
return stringify(msg.data[arg_pos:end])
2013-10-15 02:33:08 +02:00
end
end
2017-02-17 14:45:59 +01:00
return nothing
2013-10-15 02:33:08 +02:00
end
2013-10-25 02:28:21 +02:00
getindex(msg::OscMsg, idx::Int) = rtosc_argument(msg, idx)
2013-10-27 23:49:24 +01:00
function show(io::IO, msg::OscMsg)
println(io, "OSC Message to ", stringify(msg.data))
2017-02-17 14:45:59 +01:00
println(io, " Arguments:")
2013-10-25 02:28:21 +02:00
for i=1:narguments(msg)
2013-10-27 23:49:24 +01:00
showField(io, msg,i)
2013-10-25 02:28:21 +02:00
end
end
2013-10-27 23:49:24 +01:00
function showField(io::IO, msg::OscMsg, arg_id)
2017-02-22 19:13:06 +01:00
map = Any['i' Int32;
2017-02-17 14:45:59 +01:00
'f' Float32;
's' String;
'b' :Blob;
'h' Int32;
't' UInt64;
'd' Float64;
'S' Symbol;
'c' Char;
'r' :RBG;
'm' :Midi;
'T' true;
'F' false;
2017-08-15 03:16:31 +02:00
'I' Inf;
'N' Nothing]
2015-07-16 17:15:53 +02:00
dict = Dict{Char, Any}(zip(Vector{Char}(map[:,1][:]),map[:,2][:]))
typeChar::Char = argType(msg, arg_id)
value = msg[arg_id]
2017-08-15 03:16:31 +02:00
if typeof(value) <: Array
2013-10-25 02:28:21 +02:00
value = value'
end
2018-09-09 18:30:59 +02:00
if(value == nothing)
value = "nothing"
end
2017-02-17 14:45:59 +01:00
@printf(io, " #%2d %c:", arg_id, typeChar)
2015-07-16 17:15:53 +02:00
println(dict[typeChar]," - ", value)
2013-10-25 02:28:21 +02:00
end
2013-10-27 23:49:24 +01:00
2013-10-15 02:33:08 +02:00
end