// This is where we store plugin stuff const std = @import("std"); const clap = @import("../clap.zig"); // Extensions const gui = @import("./gui.zig"); const state = @import("./state.zig"); const latency = @import("./latency.zig"); // Extension Implementations const Gui = @import("./_gui.zig"); const State = @import("./_state.zig"); const Latency = @import("./_latency.zig"); pub const Logger = std.log.scoped(.gmsynth); pub const HostData = struct { host: *const clap.clap_host_t, host_latency: ?*const clap.clap_host_latency_t, host_log: ?*const clap.clap_host_log_t, host_thread_check: ?*const clap.clap_host_thread_check_t, host_state: ?*const clap.clap_host_state_t, host_gui: ?*const clap.clap_host_gui_t, const ThreadType = enum { Main, Audio, }; // Technically failible w/ badly programmed host, so // make sure that is preserved pub fn get_thread_type(self: *const HostData) !ThreadType { if (self.host_thread_check) |thread_check| { if (thread_check.is_main_thread.?(self.host)) { return .Main; } else if (thread_check.is_audio_thread.?(self.host)) { return .Audio; } return error.BadHostThreadModel; } else { // If we don't have access to the thread check, // assume everything is on 1 thread: the main thread // b/c main thread can be used as audio thread, but // not vice-versa return .Main; } } pub fn assert_main_thread(self: *const HostData) !void { if (self.host_thread_check) |thread_check| { if (!thread_check.is_main_thread.?(self.host)) { Logger.err("Host Error: called main thread func on non-main thread", .{}); return error.NotMainThread; } } } pub fn assert_audio_thread(self: *const HostData) !void { if (self.host_thread_check) |thread_check| { if (!thread_check.is_audio_thread.?(self.host)) { Logger.err("Host Error: called audio thread func on non-audio thread", .{}); return error.NotMainThread; } } } // Tell the host either (a) latency has changed, or to restart // (deactivate then activate) pub fn latency_changed(self: *const HostData, plugin: *const Self) void { switch (plugin.active_state) { .inactive => { switch (self.get_thread_type() catch .Audio) { .Main => { if (self.host_latency) |host_latency| { host_latency.changed.?(self.host); return; } }, .Audio => {}, } }, .active => {}, } // We have sent the host latency change message, re-init self.host.request_restart(); } }; pub const ActiveState = union(enum) { inactive, active: struct { sample_rate: f64, min_frames_count: u32, max_frames_count: u32, }, }; // allocator allocator: std.mem.Allocator, // CLAP plugin struct plugin: *const clap.clap_plugin_t, host_data: HostData, // Our actual shared data test_alloc: []u8, active_state: ActiveState, // Our extensions gui_ext: Gui, state_ext: State, latency_ext: Latency, const Self = @This(); pub fn init(allocator: std.mem.Allocator, host_data: HostData, plugin: *const clap.clap_plugin_t) !Self { // How to use the allocator var test_alloc = try allocator.alloc(u8, 256); errdefer allocator.free(test_alloc); std.mem.set(u8, test_alloc, 0xde); // Create extensions // Gui extension // TODO Prefer different API depending on OS const gui_ext = Gui.init(.{ .prefer_floating = true, }); errdefer gui_ext.deinit(); const state_ext = State.init(.{}); errdefer state_ext.deinit(); const latency_ext = Latency.init(.{}); errdefer latency_ext.deinit(); return .{ .host_data = host_data, .test_alloc = test_alloc, .plugin = plugin, .allocator = allocator, .active_state = .{ .inactive = void{} }, .gui_ext = gui_ext, .state_ext = state_ext, .latency_ext = latency_ext, }; } pub fn deinit(self: *Self) void { // Deinit extensions self.gui_ext.deinit(); self.state_ext.deinit(); self.latency_ext.deinit(); self.allocator.free(self.test_alloc); } pub fn on_main_thread(self: *const Self) void { _ = self; // TODO Run main thread callbacks here. Logger.info("Main thread stuff", .{}); } pub const PluginProcessStatus = enum { Continue, ContinueIfNotQuiet, Sleep, Tail, }; // Notice how std.mem.Allocator.Error is not a part of this error enum // Keep it that way. // You shouldn't be allocating here (or on start/stop_processing) pub const PluginProcessError = error{}; pub fn plugin_process(self: *Self, process: *const clap.clap_process_t) PluginProcessError!PluginProcessStatus { _ = self; _ = process; // NOTE Error isn't meant for allocation, it means to throw out the buffer return PluginProcessStatus.Continue; } pub fn activate( self: *Self, sample_rate: f64, min_frames_count: u32, max_frames_count: u32, ) !void { switch (self.active_state) { .inactive => { self.active_state = .{ .active = .{ .sample_rate = sample_rate, .min_frames_count = min_frames_count, .max_frames_count = max_frames_count, }, }; }, .active => return error.AlreadyActive, } } pub fn deactivate(self: *Self) !void { switch (self.active_state) { .active => { self.active_state = .{ .inactive = void{}, }; }, .inactive => return error.AlreadyInactive, } } // No errors, mostly because NO ALLOCATIONS ALLOWED pub fn start_processing(self: *Self) bool { Logger.debug("S", .{}); _ = self; return false; } pub fn stop_processing(self: *Self) void { _ = self; } pub fn reset(self: *Self) void { _ = self; } // If we allocated, we would have to manage it and dealloc ourself. pub fn extension(self: *const Self, id: []const u8) ?*const anyopaque { _ = self; if (std.mem.eql(u8, id, &clap.CLAP_EXT_GUI)) { return &gui.ext; } else if (std.mem.eql(u8, id, &clap.CLAP_EXT_STATE)) { return &state.ext; } else if (std.mem.eql(u8, id, &clap.CLAP_EXT_LATENCY)) { return &latency.ext; } return null; }