ODPQ3ITB2UCTFHQJOEWWJK3H75OMMMGLGRB7LHB6LVBGSOSOINPAC VTSVRT72HF6QSRZPSI62JKFQA4XD7PISDQCLQVOWGNF7LLMUGQFAC 7QEDFCERYIUV6IV4QK4QF6JST6BHPEFMA5WMF27DUYNVMCRHQEDQC HFVSTXMSKTTDRFWUUDP7QOHEI773WZ2VMLAWVCOAJXNI2FVS2MEQC YO35U3UA6BUXJFNGGOFZ3NL5AWPANX7JSJUXZLPJ3CXEBBBBOCPAC 6XFY27FK4UT452P4UNB7MHCAWGACGUSUG5AVPYJDIGYSUYK23W2QC KTRMFGOSPFXGIGYUQGESG2RTM26S6JXUE5KV4PPQLMC6RCBKTQAAC // so that we can share the same allocator.// We don't use the thread-safe mode (yet), because// we aren't on the thread pool, and audio thread *must*// not allocate. We might move to thread pool, so we would need// .thread_safe = true,
// so that we can share the same allocator between// multiple files (plugins).
.host_log = get_host_extension(clap.clap_host_log_t, host, &clap.CLAP_EXT_LOG),.host_thread_check = get_host_extension(clap.clap_host_thread_check_t, host, &clap.CLAP_EXT_THREAD_CHECK),.host_latency = null,.host_state = null,
.host_log = get_host_extension(clap.clap_host_log, host, &clap.CLAP_EXT_LOG),.host_thread_check = get_host_extension(clap.clap_host_thread_check, host, &clap.CLAP_EXT_THREAD_CHECK),.host_latency = get_host_extension(clap.clap_host_latency, host, &clap.CLAP_EXT_LATENCY),.host_state = get_host_extension(clap.clap_host_state, host, &clap.CLAP_EXT_STATE),.host_gui = get_host_extension(clap.clap_host_gui, host, &clap.CLAP_EXT_GUI),
if (std.mem.eql(u8, std.mem.span(id), &clap.CLAP_EXT_GUI)) {return &gui_ext.ext;
if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.uninited, .ready => {return null;},.inited => |plug| {return plug.extension(std.mem.span(id));},}
_ = max_frames_count;_ = min_frames_count;_ = sample_rate;_ = plugin;
if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.uninited, .ready => {return false;},.inited => |*plug| {plug.host_data.assert_main_thread() catch return false;plug.activate(sample_rate, min_frames_count, max_frames_count) catch |err| {Logger.err("Failed to activate: {}", .{err});return false;};return true;},}}
_ = plugin;
if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.uninited, .ready => {},.inited => |*plug| {plug.host_data.assert_main_thread() catch return;plug.deactivate() catch |err| {Logger.err("Failed to deactivate: {}", .{err});};},}}
_ = plugin;return true;
if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.uninited, .ready => {return false;},.inited => |*plug| {// TODO Maybe be more noisy?plug.host_data.assert_audio_thread() catch return false;return plug.start_processing();},}}return false;
return plug.plugin_process(process);
const res = plug.plugin_process(process) catch |err| {Logger.err("Failed to process: {}", .{err});return clap.CLAP_PROCESS_ERROR;};return switch (res) {.Continue => clap.CLAP_PROCESS_CONTINUE,.ContinueIfNotQuiet => clap.CLAP_PROCESS_CONTINUE_IF_NOT_QUIET,.Tail => clap.CLAP_PROCESS_TAIL,.Sleep => clap.CLAP_PROCESS_SLEEP,};
const std = @import("std");const clap = @import("../clap.zig");const shared = @import("../shared.zig");const State = @import("./_state.zig");const get_state = @import("../gmsynth.zig").get_state;const Logger = State.Logger;pub const ext = clap.clap_plugin_state_t{.save = save,.load = load,};fn save(plugin: ?*const clap.clap_plugin_t, stream: ?*const clap.clap_ostream_t) callconv(.C) bool {if (plugin) |cplug| {switch (get_state(cplug)) {.inited => |plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;if (stream) |out| {const writer = State.Writer{.context = out,};plug.state_ext.save(writer, &plug) catch |err| {Logger.err("Failed to save state: {}", .{err});return false;};return true;}},else => return false,}}return false;}fn load(plugin: ?*const clap.clap_plugin_t, stream: ?*const clap.clap_istream_t) callconv(.C) bool {if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;if (stream) |in| {const reader = State.Reader{.context = in,};plug.state_ext.load(reader, plug) catch |err| {Logger.err("Failed to load state: {}", .{err});return false;};return true;}},else => return false,}}return false;}
// Technically failible w/ badly programmed host, so// make sure that is preservedpub 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-versareturn .Main;}}
}}// 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 => {},
// Create extensions// Gui extension// TODO Prefer different API depending on OSconst 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();
pub fn plugin_process(self: *Self, process: *const clap.clap_process_t) clap.clap_process_status {
pub const PluginProcessStatus = enum {Continue,ContinueIfNotQuiet,Sleep,Tail,};pub fn plugin_process(self: *Self, process: *const clap.clap_process_t) !PluginProcessStatus {
return clap.CLAP_PROCESS_SLEEP;
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 ALLOWEDpub 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;}
const std = @import("std");const clap = @import("../clap.zig");const shared = @import("../shared.zig");const Latency = @import("./_latency.zig");const get_state = @import("../gmsynth.zig").get_state;const Logger = Latency.Logger;pub const ext = clap.clap_plugin_latency_t{.get = get_latency,};fn get_latency(plugin: ?*const clap.clap_plugin_t) callconv(.C) u32 {if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return 0;// We can only calculate, and thus change latency// if we are inactiveswitch (plug.active_state) {.active => {// Use calculated latencyLogger.warn("Plugin still active, will not recalculate latency", .{});},.inactive => {// Recalulateplug.latency_ext.calculate(plug);},}return plug.latency_ext.latency;},else => {},}}return 0;}
.get_preferred_api = null,.create = null,.destroy = null,.set_scale = null,.get_size = null,.can_resize = null,.get_resize_hints = null,.adjust_size = null,.set_size = null,.set_parent = null,.set_transient = null,.suggest_title = null,.show = null,.hide = null,
.get_preferred_api = get_preferred_api,.create = create,.destroy = destroy,.set_scale = set_scale,.get_size = get_size,.can_resize = can_resize,.get_resize_hints = get_resize_hints,.adjust_size = adjust_size,.set_size = set_size,.set_parent = set_parent,.set_transient = set_transient,.suggest_title = suggest_title,.show = show,.hide = hide,
_ = is_floating;
if (plugin) |cplug| {if (api) |target_api| {switch (get_state(cplug)) {.inited => |plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;return plug.gui_ext.is_api_supported(std.mem.span(target_api), is_floating);},else => return false,}}}return false;}fn get_preferred_api(plugin: ?*const clap.clap_plugin_t,api: ?*?[*:0]const u8,is_floating: ?*bool,) callconv(.C) bool {if (plugin) |cplug| {switch (get_state(cplug)) {.inited => |plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;const pref = plug.gui_ext.get_preferred_api();if (pref.api) |papi| {var papiptr = @ptrCast(*const [*:0]const u8, api);papiptr = &papi.ptr;}if (pref.floating) |pfloat| {var pfloatptr = @ptrCast(*bool, @constCast(is_floating));pfloatptr.* = pfloat;}},else => return false,}}return false;}fn create(plugin: ?*const clap.clap_plugin_t,api: ?[*:0]const u8,is_floating: bool,) callconv(.C) bool {if (plugin) |cplug| {if (api) |target_api| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;return plug.gui_ext.create(std.mem.span(target_api), is_floating) catch return false;},else => return false,}}}return false;}fn destroy(plugin: ?*const clap.clap_plugin_t) callconv(.C) void {if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return;return plug.gui_ext.destroy();},else => {},}}}fn show(plugin: ?*const clap.clap_plugin_t) callconv(.C) bool {if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;return plug.gui_ext.show() catch return false;},else => return false,}}return false;}fn hide(plugin: ?*const clap.clap_plugin_t) callconv(.C) bool {if (plugin) |cplug| {var state = get_state(cplug);switch (state) {.inited => |*plug| {// Make sure this is main threadplug.host_data.assert_main_thread() catch return false;return plug.gui_ext.hide() catch return false;},else => return false,}}return false;}fn set_scale(plugin: ?*const clap.clap_plugin_t, scale: f64) callconv(.C) bool {_ = scale;
std.log.info("{?s}", .{api});
return false;}fn get_size(plugin: ?*const clap.clap_plugin_t, width: ?*u32, height: ?*u32) callconv(.C) bool {_ = height;_ = width;_ = plugin;return false;}fn can_resize(plugin: ?*const clap.clap_plugin_t) callconv(.C) bool {_ = plugin;return false;}fn get_resize_hints(plugin: ?*const clap.clap_plugin_t, hints: ?*clap.clap_gui_resize_hints_t) callconv(.C) bool {_ = hints;_ = plugin;return false;}fn adjust_size(plugin: ?*const clap.clap_plugin_t, width: ?*u32, height: ?*u32) callconv(.C) bool {_ = height;_ = width;_ = plugin;return false;}fn set_size(plugin: ?*const clap.clap_plugin_t, width: u32, height: u32) callconv(.C) bool {_ = height;_ = width;_ = plugin;return false;}fn set_parent(plugin: ?*const clap.clap_plugin_t, window: ?*const clap.clap_window_t) callconv(.C) bool {_ = window;_ = plugin;return false;}fn set_transient(plugin: ?*const clap.clap_plugin_t, window: ?*const clap.clap_window_t) callconv(.C) bool {_ = window;_ = plugin;
fn suggest_title(plugin: ?*const clap.clap_plugin_t, title: ?[*:0]const u8) callconv(.C) void {_ = title;_ = plugin;}
const std = @import("std");const builtin = @import("builtin");const clap = @import("../clap.zig");const GmSynth = @import("./plugin.zig");pub const Logger = std.log.scoped(.gmsynth_state);const Self = @This();pub const Config = struct {};pub const ReadError = error{ReadError,};pub const Reader = std.io.Reader(*const clap.clap_istream, ReadError, read_from_clap_istream);fn read_from_clap_istream(context: *const clap.clap_istream, buffer: []u8) ReadError!usize {const res = context.read.?(context, buffer.ptr, buffer.len);return switch (res) {-1 => error.ReadError,// 0 size read means eofelse => |size| @intCast(usize, size),};}pub const WriteError = error{WriteError,};pub const Writer = std.io.Writer(*const clap.clap_ostream, WriteError, write_to_clap_ostream);fn write_to_clap_ostream(context: *const clap.clap_ostream, buffer: []const u8) WriteError!usize {const res = context.write.?(context, buffer.ptr, buffer.len);return switch (res) {-1 => error.WriteError,else => |size| @intCast(usize, size),};}config: Config,pub fn init(config: Config) Self {return .{.config = config,};}pub fn deinit(self: *Self) void {_ = self;}pub fn save(self: *const Self, writer: Writer, plugin: *const GmSynth) !void {_ = plugin;_ = self;try writer.print("PISS", .{});}pub fn load(self: *const Self, reader: Reader, plugin: *GmSynth) !void {_ = plugin;_ = self;var buf = [_]u8{0} ** 4;const len = try reader.readAll(&buf);if (!std.mem.eql(u8, buf[0..len], "PISS")) {return error.LoadError;}}
const std = @import("std");const builtin = @import("builtin");const clap = @import("../clap.zig");const GmSynth = @import("./plugin.zig");pub const Logger = std.log.scoped(.gmsynth_latency);const Self = @This();pub const Config = struct {};config: Config,// The current latency (in frames!)latency: u32,pub fn init(config: Config) Self {return .{.config = config,.latency = 1337,};}pub fn deinit(self: *Self) void {_ = self;}// Calculate latency and store in latencypub fn calculate(self: *Self, plugin: *const GmSynth) void {_ = plugin;self.latency = 1000;}
const std = @import("std");const builtin = @import("builtin");const clap = @import("../clap.zig");const GmSynth = @import("./plugin.zig");const wgpu = @cImport(@cInclude("wgpu.h"));// TODO Support floating windows (self-created windows)// by using GLFW!const Logger = std.log.scoped(.gmsynth_gui);// Internal implementation of gui extensionconst Self = @This();pub const Config = struct {prefer_floating: ?bool,};config: Config,pub fn init(config: Config) Self {return .{.config = config,};}pub fn deinit(self: *Self) void {_ = self;}pub fn is_api_supported(self: *const Self, api: []const u8, is_floating: bool) bool {_ = self;if (is_floating) {return false;}switch (builtin.os.tag) {.macos => {if (std.mem.eql(u8, api, &clap.CLAP_WINDOW_API_COCOA)) {return true;}return false;},.windows => {if (std.mem.eql(u8, api, &clap.CLAP_WINDOW_API_WIN32)) {return true;}return false;},.linux => {// TODO Support Waylandif (std.mem.eql(u8, api, &clap.CLAP_WINDOW_API_X11)) {return true;}return false;},else => return false,}return true;}const PreferredApi = struct {api: ?[:0]const u8,floating: ?bool,};pub fn get_preferred_api(self: *const Self) PreferredApi {return .{.api = switch (builtin.os.tag) {.windows => &clap.CLAP_WINDOW_API_WIN32,// TODO Research SDL on Wayland.linux => &clap.CLAP_WINDOW_API_X11,.macos => &clap.CLAP_WINDOW_API_COCOA,else => null,},.floating = self.config.prefer_floating,};}fn create_surface_from_window(instance: wgpu.WGPUInstance, window: *clap.clap_window_t) wgpu.WGPUSurface {switch (builtin.os.tag) {.macos => {// We only support cocoa on MacOS, so yeah...return wgpu.wgpuInstanceCreateSurface(instance,&.{ .label = null, .nextInChain = &.{.chain = .{.next = null,.sType = wgpu.WGPUSType_SurfaceDescriptorFromMetalLayer,},.layer = window.unnamed_0.cocoa,} },);},else => @panic("In progress"),}}// Create and allocate resources for GUI (temporal resources, that depend on api// or if the window is floating or not)pub fn create(self: *Self, api: []const u8, is_floating: bool) !bool {_ = self;if (is_floating) {// We don't support creating our own window yet.return false;}Logger.debug("{s} {}", .{ api, is_floating });return true;}// Deallocate resources for GUIpub fn destroy(self: *Self) void {_ = self;}pub fn show(self: *Self) !bool {_ = self;return true;}pub fn hide(self: *Self) !bool {_ = self;return true;}
download-sdl release: && build-sdlcurl -L -o tmp/sdl.tar.gz https://github.com/libsdl-org/SDL/archive/refs/tags/release-{{release}}.tar.gzmkdir deps/sdltar xzvf tmp/sdl.tar.gz -C deps/sdl/ --strip-components=1
download-wgpu version: && build-wgpumkdir deps/wgpu@#Fuck git submodulesgit clone --recurse-submodules https://github.com/gfx-rs/wgpu-native.git deps/wgpu/cd deps/wgpu && git checkout v{{version}}
build-sdl:mkdir deps/sdl/buildcd deps/sdl/build && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../outcd deps/sdl/build && cmake --build . --config Release --parallelcd deps/sdl/build && cmake --install . --config Release
build-wgpu:cd deps/wgpu && make lib-native-release# download-sdl release: && build-sdl# curl -L -o tmp/sdl.tar.gz https://github.com/libsdl-org/SDL/archive/refs/tags/release-{{release}}.tar.gz# mkdir deps/sdl# tar xzvf tmp/sdl.tar.gz -C deps/sdl/ --strip-components=1# build-sdl:# mkdir deps/sdl/build# cd deps/sdl/build && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../out# cd deps/sdl/build && cmake --build . --config Release --parallel# cd deps/sdl/build && cmake --install . --config Release
lib.addIncludePath("deps/sdl/out/include");lib.addLibraryPath("deps/sdl/out/lib");lib.linkSystemLibrary("SDL2");
lib.addIncludePath("deps/wgpu/ffi");lib.addLibraryPath("deps/wgpu/target/release/");lib.linkSystemLibrary("wgpu_native");
# GMSynth - A CLAP attempt to do General MIDII unno, I wanna CLAP some Zig and Rust, so I did.## DepsYou'll need:- Zig latest- Rust (tested on 1.68.2)- Just (casey/just)- A basic Unix environment (git, tar, curl, make)- (MacOS) probably xcode-select## Instructions```sh$> just download-deps$> just```The result should be in the root dir, by default under the name "GmSynth.clap".