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.warn("expected '{c}' but got '{c}' element (expected {})\n", .{expected, typ, got}); return error.typeMismatch; } } } 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) |*v, i| v.* = argv[i].*.f; }, f64 => { try verify_args('d', types); for (dest) |*v, i| v.* = argv[i].*.d; }, i32 => { try verify_args('i', types); for (dest) |*v, i| v.* = argv[i].*.i; }, u32 => { try verify_args('i', types); for (dest) |*v, i| { const val = argv[i].*.i; if (val < 0) return error.signDisallowed; v.* = @intCast(u32, argv[i].*.i); } }, else => return error.invalidType, } } pub const ControlServer = struct { server: c.lo_server, cache: *gl.UniformCache, pub fn init( allocator: *std.mem.Allocator, config: cfg.OSCConfig, cache: *gl.UniformCache, ) !*ControlServer { var self: *ControlServer = try allocator.create(ControlServer); self.cache = cache; switch (config) { .Manual => |conf| { var port = [_]u8{0} ** 6; _ = std.fmt.formatIntBuf(port[0..], conf.port, 10, false, std.fmt.FormatOptions{}); const proto: c_int = switch (conf.protocol) { .udp => c.LO_UDP, .tcp => c.LO_TCP, .unix => c.LO_UNIX, }; std.debug.warn( "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.warn("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, @ptrCast(*c_void, 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.warn( "OSC error {} @ {s}: {s}\n", .{ num, @ptrCast([*:0]const u8, where), @ptrCast([*:0]const u8, msg) }, ); } fn set_uniform( self: *ControlServer, uniform_name: []const u8, argv: [*c][*c]c.lo_arg, argc: c_int, types: []const u8, ) !void { var buffer: [256]u8 = undefined; std.mem.copy(u8, buffer[0..], uniform_name); buffer[uniform_name.len] = 0; const name = buffer[0 .. uniform_name.len + 1]; const uniform = (try self.cache.get(name)) orelse return error.notFound; try switch (uniform.value) { .FLOAT => |*val| set_array(f32, @ptrCast([*]f32, val)[0..1], argc, argv, types), .DOUBLE => |*val| set_array(f64, @ptrCast([*]f64, val)[0..1], argc, argv, types), .INT => |*val| set_array(i32, @ptrCast([*]i32, val)[0..1], argc, argv, types), .UNSIGNED_INT => |*val| set_array(u32, @ptrCast([*]u32, val)[0..1], argc, argv, types), .BOOL => |*val| set_array(u32, @ptrCast([*]u32, val)[0..1], 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: ?*c_void, ) callconv(.C) c_int { _ = msg; const self = @ptrCast(*ControlServer, @alignCast(@alignOf(ControlServer), userdata.?)); const path = _path[0..c.strlen(_path)]; const types = _types[0..@intCast(u32, argc)]; if (!std.mem.startsWith(u8, path, "/")) { std.debug.warn("invalid OSC message {s} ({s})\n", .{ path, types }); return 1; } self.set_uniform(path[1..], argv, argc, types) catch |err| { std.debug.warn("{} while processing {}\n", .{err, path}); }; return 0; } fn handle_bundle_start(time: c.lo_timetag, userdata: ?*c_void) callconv(.C) c_int { _ = time; _ = userdata; return 1; } fn handle_bundle_end(userdata: ?*c_void) callconv(.C) c_int { _ = userdata; return 1; } };