lightmix is an audio processing library written by Zig-lang.
I created this project because I felt a disconnect between existing audio synthesis environments and the standard software development workflow I use every day.
- From "Recording" to "Building":
In many existing tools, exporting audio feels like a manual task. I often had to click a record button or write specific code to manage recording buffers, essentially capturing the output in real-time. I wanted a workflow where audio is treated as a build artifact—where running
zig build run(orzig build) instantly produces a WAV file, just as it would a binary executable. - Integration with the Modern Toolchain: I found it cumbersome to set up dedicated runtimes or specialized IDEs just to generate sound. I wanted to use my preferred editor and the standard Zig toolchain without any external dependencies or complex server setups.
lightmix is my attempt to bridge these two worlds. It allows me to "build" sound with the same precision, automation, and simplicity that I expect from any other software project.
In build.zig, import lightmix from build.zig.zon using b.dependency():
const lightmix = b.dependency("lightmix", .{});
const lib_mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
lib_mod.addImport("lightmix", lightmix.module("lightmix")); // Add lightmix to your library or executable module.You can find some examples in ./examples directory. If you want to copy an example, edit .lightmix = .{ .path = "../../.." } in its build.zig.zon.
lightmix provides a helper function addWaveInstallFile in build.zig that allows you to generate and install Wave files during the build process.
This function writes a Wave object to a file and creates an install step to copy it to the output directory.
const std = @import("std");
const lightmix = @import("lightmix");
pub fn build(b: *std.Build) !void {
const root = @import("./src/root.zig");
// Generate your wave
const wave: lightmix.Wave = try root.generate(.{ .example_option = 7 });
// Create an install step for the wave file
const wave_install_file = try lightmix.addWaveInstallFile(b, wave, .{
.wave = .{
.name = "result.wav", // Output filename
.bit_type = .i16, // Bit depth (e.g., .i16, .f32)
},
.path = .{ .custom = "share" }, // Install path relative to prefix
});
// Add to default build step
b.default_step = &wave_install_file.step;
}The function accepts the following options via EmitWaveOptions:
wave: AWavefileOptionsstruct containing:name: The output filename (default:"result.wav")bit_type: The bit depth for the wave file (e.g.,.i16,.f32)
path: Destination path relative to the install prefix (default:"")
The wave file is first written to .zig-cache/lightmix/ and then installed to the specified output directory when you run zig build.
You can find a complete example in ./examples/Wave/generate_by_build_zig.
Contains a PCM audio source.
const allocator = std.heap.page_allocator; // Use your allocator
const data: []const f32 = &[_]f32{ 0.0, 0.0, 0.0 }; // This array contains 3 float number, then this wave will make from 3 samples.
const wave: lightmix.Wave = Wave.init(data, allocator, .{
.sample_rate = 44100, // Samples per second.
.channels = 1, // Channels for this Wave. If this wave has two channels, it means this wave is stereo.
});
defer wave.deinit(); // Wave.data is owned data by passed allocator, then you must `free` this wave.You can write your Wave to your wave file, such as result.wav.
// First, create your `Wave`.
const wave: lightmix.Wave = generate_wave();
// Second, you must create a file, typed as `std.fs.File`.
var file = try std.fs.cwd().createFile("result.wav", .{});
defer file.close();
// Then, write down your wave!!
try wave.write(file, .i16);Contains a Composer.WaveInfo array, which contains a Wave and the timing when it plays.
const allocator = std.heap.page_allocator; // Use your allocator
const wave: lightmix.Wave = generate_wave();
const info: []const lightmix.Composer.WaveInfo = &[_]lightmix.Composer.WaveInfo{
.{ .wave = wave, .start_point = 0 },
.{ .wave = wave, .start_point = 44100 },
};
const composer: lightmix.Composer = Composer.init_with(info, allocator, .{
.sample_rate = 44100, // Samples per second.
.channels = 1, // Channels for the Wave. If this composer has two channels, it means the wave is stereo.
});
defer composer.deinit(); // Composer.info is also owned data by passed allocator, then you must `free` this wave.
const result: lightmix.Wave = composer.finalize(.{}); // Let's finalize to create a `Wave`!!
defer result.deinit(); // Don't forget to free the `Wave` data.0.15.2
This project will follows Ziglang's minor version.