Skip to main content

Defining our Plugin

Since we have our parameters set up, we now want to declare our main interface with nectar: Our plugin struct

src/main.zig
// snip

pub const Amplifier = struct {
params: Parameters,
allocator: std.mem.Allocator,

pub fn create(into: *Amplifier, allocator: std.mem.Allocator) void {
into.* = Amplifier.init(allocator);
}

pub fn init(allocator: std.mem.Allocator) Amplifier {
return Amplifier{
.allocator = allocator,
.params = .{
.params = .{},
},
};
}

pub fn deinit(self: *Amplifier) void {
// We would want to deallocate any used resources here
_ = self;
}
};

This lays the groundwork of our plugin, but we're missing one crucial feature: the actual audio processing. We can do this by defining a function called process inside Amplifier:

src/main.zig
pub const Amplifier = struct {
// snip

pub fn process(self: *Amplifier, input: anytype, output: anytype) void {
var frame: usize = 0;
var gain = self.params.get("gain") * 2;

while (frame < output.frames) : (frame += 1) {
output.setFrame("Left", frame, input.getFrame("Left", frame) * gain);
output.setFrame("Right", frame, input.getFrame("Right", frame) * gain);
}
}
};

This lets us process audio, but it still leaves some questions:

  • What are the types of input and output?
  • What is all this setFrame/getFrame magic?

Introducing AudioBuffer

input and output are both AudioBuffers. The type AudioBuffer comes from nectar.core.AudioBuffer, and the concept is stolen borrowed from zig-vst.

Although the implementation can be scary, the actual concept is simple. An AudioBuffer takes an I/O layout (basically a list of channels and their names and format) and lets you reference this layout by name. So, instead of having to use magic numbers to get a channel, they're easily accessible.

tip

When dealing with named channels, things like capitalization and trailing spaces matter. Watch out, and always check your names and references (this bit me in the ass at least a couple times while writing examples). But the difference between juggling names and juggling magic numbers is that the names are compile-time errors, and the numbers are logic errors.

For now, just take it for granted that our layout will have two channels ("Left" and "Right") and each is mono. In the next guide, we'll define our I/O layout and finish up our plugin.