Audio processing library written by Zig-lang
21
stars
244
commits
Zig
primary language
Aug 26, 2026
updated
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.
zig build run (or zig build) instantly produces a WAV file, just as it would a binary executable.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 addWave in build.zig that allows you to generate and install Wave files during the build process.
addWave function in build.zigThe addWave function is a build-time helper that allows you to generate Wave files as part of your build process. This means you can write a Zig function that generates audio, and the build system will automatically create the WAV file when you run zig build.
addWave// In your src/root.zig or similar file
const std = @import("std");
const lightmix = @import("lightmix");
pub fn generate(allocator: std.mem.Allocator) !lightmix.Wave(f64) {
const allocator = std.heap.page_allocator;
// Generate your audio data (example: 1 second of silence)
const data: [44100]f64 = [_]f64{0.0} ** 44100;
// Wave.init() creates a deep copy of the data
// The original data array can be safely discarded after this call
const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).init(data[0..], allocator, .{
.sample_rate = 44100,
.channels = 1,
});
return wave;
}
build.zig, use the addWave function:const std = @import("std");
const l = @import("lightmix");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lightmix = b.dependency("lightmix", .{});
// Create your module that contains the wave generation function
const mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "lightmix", .module = lightmix.module("lightmix") },
},
});
// Use addWave to generate the wave file during build
const wave = try l.addWave(b, mod, .{
.func_name = "generate", // Name of your wave generation function (optional, defaults to "gen")
.format = .{ .wav = .{
.name = "result.wav", // Output filename (optional, defaults to "result.wav")
.format_code = .pcm, // Wave format code (e.g., .pcm, .ieee_float)
.bits = 16, // The bits depth for this wave (e.g., 8, 16, 24, 32)
} },
.path = .{ .custom = "share" }, // Install directory (optional, defaults to "share")
});
// Add to the install step so it runs during `zig build`
// You can use installWave helper or access wave.step directly
l.installWave(b, wave);
// Or: b.getInstallStep().dependOn(wave.step);
}
zig build to generate the wave file. The file will be created in zig-out/share/result.wav (or your configured path).addWave Optionsfunc_name: The name of the function in your module that generates the Wave. The function must have the signature pub fn name() !lightmix.Wave(T) where T is your chosen sample type (e.g., f64) (default: "gen")path: The installation directory relative to the install prefix (default: .{ .custom = "share" })wave.name: The output filename for the wave file (default: "result.wav")wave.bits: The bit depth for the wave file, which is typed u16wave.format_code: Audio encoding format (e.g., .pcm, .ieee_float)You can find a complete example in ./examples/06-advanced/build-time-generation.
WaveWave is a generic type function that accepts a sample type parameter. It contains PCM audio source with samples of the specified floating-point type.
Both waves must have identical sample_rate, channels, and sample length, or the program will panic.
Supported sample types: f64, f80, f128.
const allocator = std.heap.page_allocator; // Use your allocator
const data: []const f64 = &[_]f64{ 0.0, 0.0, 0.0 }; // This array contains 3 float numbers, then this wave will be made from 3 samples.
const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).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 samples are owned by the passed allocator, so you must free this wave.
You can write your Wave to a wave file, such as result.wav.
// First, create your Wave with a specific sample type
const wave = generate_wave(); // Returns a Wave(f64)
// Second, you must create a file, typed as `std.fs.File`.
const file = try std.fs.cwd().createFile("result.wav", .{});
defer file.close();
// Wav file size calculation
const bits = 16;
const bytes_per_sample = (bits + 7) / 8;
const header_size = 44;
const total_size = header_size + (wave.samples.len * wave.channels * bytes_per_sample);
// Create a buffer for file writer
const buf = try allocator.alloc(u8, total_size);
defer allocator.free(buf);
// Create a std.fs.File.Writer variable from the file.
// It has an `interface` variable typed `std.Io.Writer`.
var writer = file.writer(buf);
// Then, write down your wave!!
try wave.write(.wav, &writer.interface, .{
.allocator = allocator,
.bits = bits, // Bit depth for the output file
.format_code = .pcm, // Format code (e.g., .pcm or .ieee_float)
});
ComposerComposer is a generic type function that accepts a sample type parameter (same as Wave). It contains a Composer(T).WaveInfo array, which contains a Wave(T) and the timing when it plays.
const allocator = std.heap.page_allocator; // Use your allocator
const wave = generate_wave(); // Returns a Wave(f64)
const info: []const lightmix.Composer(f64).WaveInfo = &.{
.{ .wave = wave, .start_point = 0 },
.{ .wave = wave, .start_point = 44100 },
};
const composer: lightmix.Composer(f64) = try lightmix.Composer(f64).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 by the passed allocator, so you must free this composer.
const result: lightmix.Wave(f64) = composer.finalize(.{}); // Let's finalize to create a Wave(f64)!!
defer result.deinit(); // Don't forget to free the Wave data.
0.16.0
This project will follows Ziglang's minor version.
233 commits
11 commits
Zig
94.3%
Nix
5.7%
Audio processing library written by Zig-lang
21
stars
244
commits
Zig
primary language
Aug 26, 2026
updated
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.
zig build run (or zig build) instantly produces a WAV file, just as it would a binary executable.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 addWave in build.zig that allows you to generate and install Wave files during the build process.
addWave function in build.zigThe addWave function is a build-time helper that allows you to generate Wave files as part of your build process. This means you can write a Zig function that generates audio, and the build system will automatically create the WAV file when you run zig build.
addWave// In your src/root.zig or similar file
const std = @import("std");
const lightmix = @import("lightmix");
pub fn generate(allocator: std.mem.Allocator) !lightmix.Wave(f64) {
const allocator = std.heap.page_allocator;
// Generate your audio data (example: 1 second of silence)
const data: [44100]f64 = [_]f64{0.0} ** 44100;
// Wave.init() creates a deep copy of the data
// The original data array can be safely discarded after this call
const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).init(data[0..], allocator, .{
.sample_rate = 44100,
.channels = 1,
});
return wave;
}
build.zig, use the addWave function:const std = @import("std");
const l = @import("lightmix");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lightmix = b.dependency("lightmix", .{});
// Create your module that contains the wave generation function
const mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "lightmix", .module = lightmix.module("lightmix") },
},
});
// Use addWave to generate the wave file during build
const wave = try l.addWave(b, mod, .{
.func_name = "generate", // Name of your wave generation function (optional, defaults to "gen")
.format = .{ .wav = .{
.name = "result.wav", // Output filename (optional, defaults to "result.wav")
.format_code = .pcm, // Wave format code (e.g., .pcm, .ieee_float)
.bits = 16, // The bits depth for this wave (e.g., 8, 16, 24, 32)
} },
.path = .{ .custom = "share" }, // Install directory (optional, defaults to "share")
});
// Add to the install step so it runs during `zig build`
// You can use installWave helper or access wave.step directly
l.installWave(b, wave);
// Or: b.getInstallStep().dependOn(wave.step);
}
zig build to generate the wave file. The file will be created in zig-out/share/result.wav (or your configured path).addWave Optionsfunc_name: The name of the function in your module that generates the Wave. The function must have the signature pub fn name() !lightmix.Wave(T) where T is your chosen sample type (e.g., f64) (default: "gen")path: The installation directory relative to the install prefix (default: .{ .custom = "share" })wave.name: The output filename for the wave file (default: "result.wav")wave.bits: The bit depth for the wave file, which is typed u16wave.format_code: Audio encoding format (e.g., .pcm, .ieee_float)You can find a complete example in ./examples/06-advanced/build-time-generation.
WaveWave is a generic type function that accepts a sample type parameter. It contains PCM audio source with samples of the specified floating-point type.
Both waves must have identical sample_rate, channels, and sample length, or the program will panic.
Supported sample types: f64, f80, f128.
const allocator = std.heap.page_allocator; // Use your allocator
const data: []const f64 = &[_]f64{ 0.0, 0.0, 0.0 }; // This array contains 3 float numbers, then this wave will be made from 3 samples.
const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).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 samples are owned by the passed allocator, so you must free this wave.
You can write your Wave to a wave file, such as result.wav.
// First, create your Wave with a specific sample type
const wave = generate_wave(); // Returns a Wave(f64)
// Second, you must create a file, typed as `std.fs.File`.
const file = try std.fs.cwd().createFile("result.wav", .{});
defer file.close();
// Wav file size calculation
const bits = 16;
const bytes_per_sample = (bits + 7) / 8;
const header_size = 44;
const total_size = header_size + (wave.samples.len * wave.channels * bytes_per_sample);
// Create a buffer for file writer
const buf = try allocator.alloc(u8, total_size);
defer allocator.free(buf);
// Create a std.fs.File.Writer variable from the file.
// It has an `interface` variable typed `std.Io.Writer`.
var writer = file.writer(buf);
// Then, write down your wave!!
try wave.write(.wav, &writer.interface, .{
.allocator = allocator,
.bits = bits, // Bit depth for the output file
.format_code = .pcm, // Format code (e.g., .pcm or .ieee_float)
});
ComposerComposer is a generic type function that accepts a sample type parameter (same as Wave). It contains a Composer(T).WaveInfo array, which contains a Wave(T) and the timing when it plays.
const allocator = std.heap.page_allocator; // Use your allocator
const wave = generate_wave(); // Returns a Wave(f64)
const info: []const lightmix.Composer(f64).WaveInfo = &.{
.{ .wave = wave, .start_point = 0 },
.{ .wave = wave, .start_point = 44100 },
};
const composer: lightmix.Composer(f64) = try lightmix.Composer(f64).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 by the passed allocator, so you must free this composer.
const result: lightmix.Wave(f64) = composer.finalize(.{}); // Let's finalize to create a Wave(f64)!!
defer result.deinit(); // Don't forget to free the Wave data.
0.16.0
This project will follows Ziglang's minor version.
233 commits
11 commits
Zig
94.3%
Nix
5.7%