const c = @import("c.zig"); const gl = @import("gl.zig"); const video = @import("video.zig"); const std = @import("std"); const cfg = @import("config.zig"); const build_config = @import("build_config"); 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, } } fn set_texture( progress: std.Progress.Node, project_root: std.fs.Dir, dest: *?gl.Texture, texture_type: gl.Texture.Type, argc: c_int, argv: [*c][*c]c.lo_arg, types: []const u8, ) !void { if (!build_config.have_ffmpeg) return error.texturesDisabled; if (argc != 1 or types[0] != 's') return error.invalidType; if (dest.*) |old| { old.destroy(); dest.* = null; } var buffer: [1024]u8 = undefined; const filepath = try project_root.realpathZ(@ptrCast(&argv[0].*.s), buffer[0..]); buffer[filepath.len] = 0; dest.* = gl.Texture.create(texture_type); try video.loadVideo(progress, @ptrCast(filepath), &dest.*.?); } pub const ControlServer = struct { server: c.lo_server, reload_requested: bool, cache: *gl.UniformCache, config: *cfg.Config, progress: std.Progress.Node, allocator: std.mem.Allocator, pub fn init( allocator: std.mem.Allocator, progress: std.Progress.Node, config: *cfg.Config, cache: *gl.UniformCache, ) !*ControlServer { var self: *ControlServer = try allocator.create(ControlServer); self.* = .{ .server = null, .reload_requested = false, .cache = cache, .config = config, .progress = progress, .allocator = allocator, }; switch (config.osc) { .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| { const 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 orelse unreachable).getPointer(); 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), .SAMPLER_2D, .SAMPLER_2D_SHADOW, .INT_SAMPLER_2D, .UNSIGNED_INT_SAMPLER_2D, => |val| set_texture(self.progress, self.config.project_root, val, .TEXTURE_2D, argc, argv, types), .SAMPLER_2D_ARRAY, .SAMPLER_2D_ARRAY_SHADOW, .INT_SAMPLER_2D_ARRAY, .UNSIGNED_INT_SAMPLER_2D_ARRAY, => |val| set_texture(self.progress, self.config.project_root, val, .TEXTURE_2D_ARRAY, argc, argv, types), .SAMPLER_3D, .INT_SAMPLER_3D, .UNSIGNED_INT_SAMPLER_3D, => |val| set_texture(self.progress, self.config.project_root, val, .TEXTURE_3D, 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))]; if (std.mem.eql(u8, path, "/-/reload")) { self.reload_requested = true; return 1; } 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; } };