const c = @import("c.zig");
const gl = @import("gl.zig");
const std = @import("std");
const cfg = @import("config.zig");
fn verify_args(expected: u8, got: []const u8) !void {
for (got) |typ| {
if (typ != expected) {
std.debug.print("expected '{c}' but got '{c}' element (expected {s})\n", .{ expected, typ, got });
return error.typeMismatch;
}
}
}
fn set_one(
comptime T: type,
dest: *T,
argc: c_int,
argv: [*c][*c]c.lo_arg,
types: []const u8,
) !void {
if (argc != 1)
return error.sizeMismatch;
switch (T) {
f32 => {
try verify_args('f', types);
dest.* = argv[0].*.f;
},
f64 => {
try verify_args('d', types);
dest.* = argv[0].*.d;
},
i32 => {
try verify_args('d', types);
dest.* = argv[0].*.i;
},
u32 => {
try verify_args('d', types);
const val = argv[0].*.i;
if (val < 0)
return error.signDisallowed;
dest.* = @as(u32, @intCast(val));
},
else => return error.invalidType,
}
}
fn set_array(
comptime T: type,
dest: []T,
argc: c_int,
argv: [*c][*c]c.lo_arg,
types: []const u8,
) !void {
if (argc != dest.len)
return error.sizeMismatch;
switch (T) {
f32 => {
try verify_args('f', types);
for (dest, 0..) |*v, i|
v.* = argv[i].*.f;
},
f64 => {
try verify_args('d', types);
for (dest, 0..) |*v, i|
v.* = argv[i].*.d;
},
i32 => {
try verify_args('i', types);
for (dest, 0..) |*v, i|
v.* = argv[i].*.i;
},
u32 => {
try verify_args('i', types);
for (dest, 0..) |*v, i| {
const val = argv[i].*.i;
if (val < 0)
return error.signDisallowed;
v.* = @as(u32, @intCast(argv[i].*.i));
}
},
else => return error.invalidType,
}
}
pub const ControlServer = struct {
server: c.lo_server,
cache: *gl.UniformCache,
allocator: std.mem.Allocator,
pub fn init(
allocator: std.mem.Allocator,
config: cfg.OSCConfig,
cache: *gl.UniformCache,
) !*ControlServer {
var self: *ControlServer = try allocator.create(ControlServer);
self.allocator = allocator;
self.cache = cache;
switch (config) {
.Manual => |conf| {
var port = [_]u8{0} ** 6;
_ = std.fmt.formatIntBuf(port[0..], conf.port, 10, .lower, std.fmt.FormatOptions{});
const proto: c_int = switch (conf.protocol) {
.udp => c.LO_UDP,
.tcp => c.LO_TCP,
.unix => c.LO_UNIX,
};
std.debug.print(
"listening for OSC messages on {} port {}\n",
.{ conf.protocol, conf.port },
);
self.server = c.lo_server_new_with_proto(port[0..], proto, handle_error);
},
.URL => |url| {
std.debug.print("listening for OSC messages at {s}\n", .{url});
self.server = c.lo_server_new_from_url(url[0..].ptr, handle_error);
},
}
if (self.server == null)
return error.serverInitializationError;
_ = c.lo_server_add_method(self.server, null, null, handle_method, @as(*anyopaque, @ptrCast(self)));
return self;
}
pub fn update(self: *ControlServer) void {
while (c.lo_server_recv_noblock(self.server, 0) > 0) {}
}
pub fn destroy(self: ControlServer) void {
c.lo_server_free(self.server);
}
fn handle_error(num: c_int, msg: [*c]const u8, where: [*c]const u8) callconv(.C) void {
std.debug.print(
"OSC error {} @ {s}: {s}\n",
.{ num, @as([*:0]const u8, @ptrCast(where)), @as([*:0]const u8, @ptrCast(msg)) },
);
}
fn set_uniform(
self: *ControlServer,
path: []const u8,
argv: [*c][*c]c.lo_arg,
argc: c_int,
types: []const u8,
) !void {
var parts = std.mem.tokenizeScalar(u8, path, '/');
var uniform: *gl.CachedUniform = undefined;
if (parts.next()) |uniform_name| {
var nameZ = try self.allocator.dupeZ(u8, uniform_name);
defer self.allocator.free(nameZ);
const res = try self.cache.get(nameZ);
uniform = res orelse return error.notFound;
} else {
return error.invalidMessage;
}
var value = uniform.value;
while (parts.next()) |component| {
const i = switch (component[0]) {
'x', 'r', 's' => 0,
'y', 'g', 't' => 1,
'z', 'b', 'p' => 2,
'w', 'a', 'q' => 3,
else => std.fmt.parseUnsigned(u8, component, 10) catch null,
};
if (i) |ii| {
value = try value.index(ii);
} else {
return error.invalidIndex;
}
}
try switch (value) {
.FLOAT => |val| set_one(f32, val, argc, argv, types),
.DOUBLE => |val| set_one(f64, val, argc, argv, types),
.INT => |val| set_one(i32, val, argc, argv, types),
.UNSIGNED_INT => |val| set_one(u32, val, argc, argv, types),
.BOOL => |val| set_one(u32, val, argc, argv, types),
.FLOAT_VEC2 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_VEC3 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_VEC4 => |val| set_array(f32, val.*[0..], argc, argv, types),
.DOUBLE_VEC2 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_VEC3 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_VEC4 => |val| set_array(f64, val.*[0..], argc, argv, types),
.INT_VEC2 => |val| set_array(i32, val.*[0..], argc, argv, types),
.INT_VEC3 => |val| set_array(i32, val.*[0..], argc, argv, types),
.INT_VEC4 => |val| set_array(i32, val.*[0..], argc, argv, types),
.UNSIGNED_INT_VEC2 => |val| set_array(u32, val.*[0..], argc, argv, types),
.UNSIGNED_INT_VEC3 => |val| set_array(u32, val.*[0..], argc, argv, types),
.UNSIGNED_INT_VEC4 => |val| set_array(u32, val.*[0..], argc, argv, types),
.BOOL_VEC2 => |val| set_array(u32, val.*[0..], argc, argv, types),
.BOOL_VEC3 => |val| set_array(u32, val.*[0..], argc, argv, types),
.BOOL_VEC4 => |val| set_array(u32, val.*[0..], argc, argv, types),
.FLOAT_MAT2 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT3 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT4 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT2x3 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT2x4 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT3x2 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT3x4 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT4x2 => |val| set_array(f32, val.*[0..], argc, argv, types),
.FLOAT_MAT4x3 => |val| set_array(f32, val.*[0..], argc, argv, types),
.DOUBLE_MAT2 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT3 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT4 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT2x3 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT2x4 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT3x2 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT3x4 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT4x2 => |val| set_array(f64, val.*[0..], argc, argv, types),
.DOUBLE_MAT4x3 => |val| set_array(f64, val.*[0..], argc, argv, types),
else => error.uniformNotSupported,
};
uniform.setShaderValue(self.cache.shader.*);
}
fn handle_method(
_path: [*c]const u8,
_types: [*c]const u8,
argv: [*c][*c]c.lo_arg,
argc: c_int,
msg: c.lo_message,
userdata: ?*anyopaque,
) callconv(.C) c_int {
_ = msg;
const self = @as(*ControlServer, @ptrCast(@alignCast(userdata.?)));
const path = _path[0..c.strlen(_path)];
const types = _types[0..@as(u32, @intCast(argc))];
self.set_uniform(path, argv, argc, types) catch |err| {
std.debug.print("{} while processing {s}\n", .{ err, path });
return 1;
};
return 0;
}
fn handle_bundle_start(time: c.lo_timetag, userdata: ?*anyopaque) callconv(.C) c_int {
_ = time;
_ = userdata;
return 1;
}
fn handle_bundle_end(userdata: ?*anyopaque) callconv(.C) c_int {
_ = userdata;
return 1;
}
};