Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ on:
jobs:
build-and-test:
runs-on: macos-15
strategy:
fail-fast: false
matrix:
# The build files and sources use spellings valid in both: the
# root_module API, callconv(.c), ArrayListUnmanaged, and
# net.Stream.read. Testing both keeps that honest rather than assumed.
zig: ["0.14.1", "0.15.2"]
name: build-and-test (zig ${{ matrix.zig }})
steps:
- uses: actions/checkout@v4

- uses: mlugg/setup-zig@v2
with:
version: 0.14.1
version: ${{ matrix.zig }}

# Pinned to macos-15 rather than macos-latest. macos-latest now ships
# Xcode 26.5, whose SDK Zig 0.14.1 cannot link against: every libc
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 🎵 Danzig VST3 Framework

[![CI](https://github.com/godofecht/danzig/actions/workflows/ci.yml/badge.svg)](https://github.com/godofecht/danzig/actions/workflows/ci.yml)
[![Zig](https://img.shields.io/badge/zig-0.14.1-f7a41d)](https://ziglang.org/)
[![Zig](https://img.shields.io/badge/zig-0.14.1%20%7C%200.15.2-f7a41d)](https://ziglang.org/)

A modern, lightweight VST3 plugin development framework built in Zig with zero external dependencies.

Expand Down Expand Up @@ -112,7 +112,7 @@ zig-out/

## System Requirements

- Zig 0.12.0+
- Zig 0.14.1 or 0.15.2
- macOS (currently Mach-O format)
- VST3-compatible DAW

Expand Down Expand Up @@ -154,7 +154,7 @@ See [PLUGINVAL_REPORT.md](PLUGINVAL_REPORT.md) for detailed validation results.

## License

See main project root for license information.
MIT. See [LICENSE](LICENSE).

---

Expand Down
84 changes: 53 additions & 31 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});

// Danzig library (static)
const danzig_lib = b.addStaticLibrary(.{
const danzig_lib = b.addLibrary(.{
.name = "danzig",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});

// Danzig module for import by other targets
Expand All @@ -20,11 +23,14 @@ pub fn build(b: *std.Build) void {
});

// Danzig gain VST3 plugin (shared, embeds danzig)
const danzig_gain = b.addSharedLibrary(.{
const danzig_gain = b.addLibrary(.{
.name = "DanzigGain",
.root_source_file = b.path("examples/danzig-gain/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-gain/root.zig"),
.target = target,
.optimize = optimize,
}),
.linkage = .dynamic,
});
danzig_gain.root_module.addImport("danzig", danzig_module);
danzig_gain.linkLibrary(danzig_lib);
Expand All @@ -35,19 +41,23 @@ pub fn build(b: *std.Build) void {
// Test executable
const danzig_test = b.addExecutable(.{
.name = "danzig_test",
.root_source_file = b.path("examples/danzig-test/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-test/root.zig"),
.target = target,
.optimize = optimize,
}),
});
danzig_test.linkLibrary(danzig_lib);
b.installArtifact(danzig_test);

// Standalone audio processor
const danzig_gain_standalone = b.addExecutable(.{
.name = "danzig-gain-standalone",
.root_source_file = b.path("examples/danzig-gain-standalone/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-gain-standalone/root.zig"),
.target = target,
.optimize = optimize,
}),
});
danzig_gain_standalone.root_module.addImport("danzig", danzig_module);
danzig_gain_standalone.linkLibrary(danzig_lib);
Expand All @@ -60,9 +70,11 @@ pub fn build(b: *std.Build) void {
// Web UI server
const danzig_webui = b.addExecutable(.{
.name = "danzig-webui",
.root_source_file = b.path("examples/danzig-webui/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-webui/root.zig"),
.target = target,
.optimize = optimize,
}),
});
danzig_webui.root_module.addImport("danzig", danzig_module);
danzig_webui.linkLibrary(danzig_lib);
Expand Down Expand Up @@ -90,9 +102,11 @@ pub fn build(b: *std.Build) void {

// Unit tests — pure Zig, no artifact or host required
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/tests.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("src/tests.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_unit_tests = b.addRunArtifact(unit_tests);

Expand Down Expand Up @@ -125,18 +139,24 @@ fn addVst3Bundle(
inline for (arches, suffixes) |arch, suffix| {
const arch_target = b.resolveTargetQuery(.{ .cpu_arch = arch, .os_tag = .macos });

const lib = b.addStaticLibrary(.{
const lib = b.addLibrary(.{
.name = "danzig_" ++ suffix,
.root_source_file = b.path("src/root.zig"),
.target = arch_target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = arch_target,
.optimize = optimize,
}),
.linkage = .static,
});

const plugin = b.addSharedLibrary(.{
const plugin = b.addLibrary(.{
.name = "DanzigGain_" ++ suffix,
.root_source_file = b.path("examples/danzig-gain/root.zig"),
.target = arch_target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-gain/root.zig"),
.target = arch_target,
.optimize = optimize,
}),
.linkage = .dynamic,
});
plugin.root_module.addImport("danzig", danzig_module);
plugin.linkLibrary(lib);
Expand Down Expand Up @@ -189,9 +209,11 @@ fn addGuiExample(

const gui = b.addExecutable(.{
.name = "danzig-gain-ui",
.root_source_file = b.path("examples/danzig-gain-ui/root.zig"),
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("examples/danzig-gain-ui/root.zig"),
.target = target,
.optimize = optimize,
}),
});
gui.root_module.addImport("danzig", danzig_module);
gui.root_module.addImport("coreaudio", coreaudio_module);
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
.fingerprint = 0x6383fbb1d610c5a6,
.dependencies = .{
.webview = .{
.url = "https://github.com/thechampagne/webview-zig/archive/5abc215.tar.gz",
.hash = "webview-0.1.0-ImQK_yb6IADsgQL8f72LGd4xkaSrxqQn9RyYy3U36s1J",
.url = "https://github.com/thechampagne/webview-zig/archive/a5eef0bbff65c914ea893dae0816b956bcdee6c6.tar.gz",
.hash = "webview-0.1.0-ImQK_8_aIABj-dmipgQ_Cw1trbEFExFQwMZetkgtT8fg",
},
},
.paths = .{
Expand Down
15 changes: 9 additions & 6 deletions examples/danzig-gain-ui/coreaudio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ pub fn enumerateDevices(allocator: std.mem.Allocator) ![]AudioDevice {
const default_input = getDefaultDevice(c.kAudioDevicePropertyScopeInput);
const default_output = getDefaultDevice(c.kAudioDevicePropertyScopeOutput);

var devices = std.ArrayList(AudioDevice).init(allocator);
// Unmanaged form: Zig 0.15 made ArrayList unmanaged by default and
// dropped .init(allocator). ArrayListUnmanaged spells the same type in
// both 0.14 and 0.15, so the allocator is passed per call instead.
var devices: std.ArrayListUnmanaged(AudioDevice) = .{};

for (device_ids) |did| {
const input_channels = getChannelCount(did, c.kAudioDevicePropertyScopeInput);
Expand All @@ -128,15 +131,15 @@ pub fn enumerateDevices(allocator: std.mem.Allocator) ![]AudioDevice {
dev.name_len = fallback.len;
}

try devices.append(dev);
try devices.append(allocator, dev);
}

return devices.toOwnedSlice();
return devices.toOwnedSlice(allocator);
}

pub fn devicesToJson(allocator: std.mem.Allocator, devices: []const AudioDevice) ![]u8 {
var json = std.ArrayList(u8).init(allocator);
const w = json.writer();
var json: std.ArrayListUnmanaged(u8) = .{};
var w = json.writer(allocator);

try w.writeAll("{\"inputs\":[");
var first_in = true;
Expand Down Expand Up @@ -164,5 +167,5 @@ pub fn devicesToJson(allocator: std.mem.Allocator, devices: []const AudioDevice)
}
try w.writeAll("]}");

return json.toOwnedSlice();
return json.toOwnedSlice(allocator);
}
2 changes: 1 addition & 1 deletion examples/danzig-gain-ui/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn main() !void {
defer wv.destroy() catch {};

try wv.setTitle("DanzigGain");
try wv.setSize(600, 700, .Fixed);
try wv.setSize(600, 700, .fixed);

// Enumerate CoreAudio devices and inject into JS before loading UI
const devices = coreaudio.enumerateDevices(allocator) catch &[_]coreaudio.AudioDevice{};
Expand Down
28 changes: 14 additions & 14 deletions examples/danzig-gain/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,17 @@ pub const ModuleInfo = extern struct {
};

const IUnknownVTable = extern struct {
queryInterface: *const fn (*anyopaque, guid: [*]const u8, obj: *?*anyopaque) callconv(.C) i32,
addRef: *const fn (*anyopaque) callconv(.C) u32,
release: *const fn (*anyopaque) callconv(.C) u32,
queryInterface: *const fn (*anyopaque, guid: [*]const u8, obj: *?*anyopaque) callconv(.c) i32,
addRef: *const fn (*anyopaque) callconv(.c) u32,
release: *const fn (*anyopaque) callconv(.c) u32,
};

const IPluginFactoryVTable = extern struct {
base: IUnknownVTable,
getFactoryInfo: *const fn (*anyopaque, info: *anyopaque) callconv(.C) i32,
countClasses: *const fn (*anyopaque) callconv(.C) i32,
getClassInfo: *const fn (*anyopaque, index: i32, info: *anyopaque) callconv(.C) i32,
createInstance: *const fn (*anyopaque, cid: [*]const u8, iid: [*]const u8, obj: *?*anyopaque) callconv(.C) i32,
getFactoryInfo: *const fn (*anyopaque, info: *anyopaque) callconv(.c) i32,
countClasses: *const fn (*anyopaque) callconv(.c) i32,
getClassInfo: *const fn (*anyopaque, index: i32, info: *anyopaque) callconv(.c) i32,
createInstance: *const fn (*anyopaque, cid: [*]const u8, iid: [*]const u8, obj: *?*anyopaque) callconv(.c) i32,
};

pub const PluginFactory = extern struct {
Expand All @@ -127,40 +127,40 @@ pub const PluginFactory = extern struct {

var gFactory: PluginFactory = undefined;

fn factory_queryInterface(self: *anyopaque, guid: [*]const u8, obj: *?*anyopaque) callconv(.C) i32 {
fn factory_queryInterface(self: *anyopaque, guid: [*]const u8, obj: *?*anyopaque) callconv(.c) i32 {
_ = self;
_ = guid;
_ = obj;
return -1; // kNoInterface
}

fn factory_addRef(self: *anyopaque) callconv(.C) u32 {
fn factory_addRef(self: *anyopaque) callconv(.c) u32 {
var factory = @as(*PluginFactory, @ptrCast(@alignCast(self)));
factory.refCount += 1;
return factory.refCount;
}

fn factory_release(self: *anyopaque) callconv(.C) u32 {
fn factory_release(self: *anyopaque) callconv(.c) u32 {
var factory = @as(*PluginFactory, @ptrCast(@alignCast(self)));
if (factory.refCount > 0) factory.refCount -= 1;
return factory.refCount;
}

fn factory_getFactoryInfo(self: *anyopaque, _: *anyopaque) callconv(.C) i32 {
fn factory_getFactoryInfo(self: *anyopaque, _: *anyopaque) callconv(.c) i32 {
_ = self;
return 0;
}

fn factory_countClasses(_: *anyopaque) callconv(.C) i32 {
fn factory_countClasses(_: *anyopaque) callconv(.c) i32 {
return 1;
}

fn factory_getClassInfo(_: *anyopaque, index: i32, _: *anyopaque) callconv(.C) i32 {
fn factory_getClassInfo(_: *anyopaque, index: i32, _: *anyopaque) callconv(.c) i32 {
_ = index;
return 0;
}

fn factory_createInstance(_: *anyopaque, _: [*]const u8, _: [*]const u8, _: *?*anyopaque) callconv(.C) i32 {
fn factory_createInstance(_: *anyopaque, _: [*]const u8, _: [*]const u8, _: *?*anyopaque) callconv(.c) i32 {
return 0;
}

Expand Down
5 changes: 4 additions & 1 deletion examples/danzig-webui/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ pub fn main() !void {

fn handleConnection(stream: std.net.Stream, html_content: []const u8) !void {
var buffer: [8192]u8 = undefined;
const bytes_read = try stream.readAll(&buffer);
// A single read rather than readAll: readAll blocks until the buffer is
// full or the peer closes, which for a keep-alive HTTP client means
// hanging. It was also removed from net.Stream in Zig 0.15.
const bytes_read = try stream.read(&buffer);

if (bytes_read == 0) return;

Expand Down
Loading
Loading