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 thread
plug.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 thread
plug.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 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;
}
}
}
}
// 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 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();
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 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;
}
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 thread
plug.host_data.assert_main_thread() catch return 0;
// We can only calculate, and thus change latency
// if we are inactive
switch (plug.active_state) {
.active => {
// Use calculated latency
Logger.warn("Plugin still active, will not recalculate latency", .{});
},
.inactive => {
// Recalulate
plug.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 thread
plug.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 thread
plug.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 thread
plug.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 thread
plug.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 thread
plug.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 thread
plug.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 eof
else => |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 latency
pub 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 extension
const 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 Wayland
if (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 GUI
pub 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-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
download-wgpu version: && build-wgpu
mkdir deps/wgpu
@#Fuck git submodules
git 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/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
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 MIDI
I unno, I wanna CLAP some Zig and Rust, so I did.
## Deps
You'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".