const c = @import("c.zig"); const gl = @import("gl.zig"); const std = @import("std"); const cfg = @import("config.zig"); const param_prefix = "/param/"; 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, else => unreachable, }; std.debug.warn("listening for OSC messages on {} port {}\n", .{ conf.protocol, conf.port }); self.server = c.lo_server_new_with_proto(port[0..].ptr, 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); }, else => unreachable, } 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 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 { 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, "/param/")) { if (!std.mem.endsWith(u8, path, "/set")) { std.debug.warn("unhandled OSC message {s} ({s})\n", .{ path, types }); return 1; } const param_name = path["/param/".len .. path.len - "/set".len]; var buffer: [256]u8 = undefined; std.mem.copy(u8, buffer[0..], param_name); buffer[param_name.len] = 0; if (self.cache.get(buffer[0 .. param_name.len + 1]) catch null) |uniform| { if (std.mem.eql(u8, types, "f")) { uniform.value.FLOAT = argv[0].*.f; } else if (std.mem.eql(u8, types, "ff")) { for (uniform.value.FLOAT_VEC2) |*dest, i| dest.* = argv[i].*.f; } else if (std.mem.eql(u8, types, "fff")) { for (uniform.value.FLOAT_VEC3) |*dest, i| dest.* = argv[i].*.f; } else if (std.mem.eql(u8, types, "ffff")) { for (uniform.value.FLOAT_VEC4) |*dest, i| dest.* = argv[i].*.f; } else if (std.mem.eql(u8, types, "i")) { uniform.value.INT = argv[0].*.i; } else if (std.mem.eql(u8, types, "ii")) { for (uniform.value.INT_VEC2) |*dest, i| dest.* = argv[i].*.i; } else if (std.mem.eql(u8, types, "iii")) { for (uniform.value.INT_VEC3) |*dest, i| dest.* = argv[i].*.i; } else if (std.mem.eql(u8, types, "iiii")) { for (uniform.value.INT_VEC4) |*dest, i| dest.* = argv[i].*.i; } else { std.debug.warn("unsupported types: {s}\n", .{types}); return 0; } uniform.setShaderValue(self.cache.shader.*); } else { std.debug.warn("param {s} doesn't exist\n", .{param_name}); } return 0; } std.debug.warn("unhandled OSC message {s} ({s})\n", .{ path, types }); return 1; } fn handle_bundle_start(time: c.lo_timetag, userdata: ?*c_void) callconv(.C) c_int { return 1; } fn handle_bundle_end(userdata: ?*c_void) callconv(.C) c_int { return 1; } };