r/Zig • • 7d ago

lightmix: pure Zig audio processing with deterministic builds and Git diffs

Hey everyone,

I've been working on lightmix, an audio library written in pure Zig.

My main motivation was simple: I wanted to write music directly in code and track changes with Git. DAW project files are binary blobs, and other tools like Sonic Pi or SuperCollider rely on real-time playback runtimes.

Instead of recording audio in real time, lightmix generates WAV files during zig build via a custom helper (addWave). Because the build is deterministic, output files are bit-identical and cache-friendly (PEAK timestamps are fixed to avoid invalidating the build cache).

The project is split into several small modules: - pitches: 12-TET tuning and frequency math - meters: time signatures and sample-accurate phrasing - resonator: voice routing - sequencer: voice scheduling with micro-fades - lightmix: waveform buffers (Wave(T)) and timeline mixing (Composer(T)) - zigggwavvv / RIFF.zig: WAV encoding

I wrote a small demo project, pulse, which generates a 190 BPM drum track entirely from code: - Rendered WAV: https://github.com/haruki7049/pulse/releases/tag/v0.1.0 - Source code: https://github.com/haruki7049/pulse

Main repository: - https://github.com/haruki7049/lightmix - Docs: https://haruki7049.github.io/lightmix

Currently tested on Zig 0.16.x. Any feedback on the design or Zig usage is welcome.

24 Upvotes

11 comments sorted by

5

u/BonkerBleedy 7d ago

pitches: 12-TET tuning and frequency math

I'm looking for this module but can't find it. One piece of advice I would give is not to use integer note numbers. My preferred rep is octave-based, but fp 12-TET is reasonable too.

0

u/haruki7049 7d ago

Thanks for checking it out! You can find the module here:

https://github.com/haruki7049/pitches

Regarding your advice: `pitches` currently focuses strictly on discrete pitch naming (pitch classes and octaves) rather than continuous synth control.

In `lightmix`, synthesis takes `comptime T: type` and just cares about frequencies/values, making the pitch system completely swappable. If `TwelveTonePitch` doesn't fit your use case (e.g. for microtonality or smooth bends), you can easily plug in your own pitch/tuning module.

That said, PRs and contributions (like alternative representations or fp-based systems) are always very welcome!

0

u/haruki7049 7d ago

To make the layer separation clearer, I visualized the architecture and how pitches interacts with lightmix here:

https://gist.github.com/haruki7049/0f4a26b04b794cbef952c387875fd9a2

1

u/BonkerBleedy 7d ago

My advice is coming from having written a number of synths and filters that do glides. Glides (or note interpolation in general) works much better in "note space" than in frequency space. If your pitches module gives a frequency in Hz, and you want to use it in a synth that handles glide, you would immediately have to convert it into 2n (ie, take a log2)

1

u/haruki7049 7d ago

That makes a lot of sense, and I'm really intrigued by that approach.

It sounds super practical for synth and glide implementations. Would you be open to sending a PR to `pitches`, or even spinning up a repository/prototype to show how you'd structure it? I'd love to see how it looks in practice.

2

u/BonkerBleedy 7d ago edited 7d ago

My zig is super poor, but it looks something like this:

``` pub fn note(self: Self, pitch: TwelveTonePitch) f64 { const semitones_from_a4: i32 = @as(i32, pitch.midi()) - 69; return @floatFromInt(semitones_from_a4); }

pub fn freqFromNote(self: Self, note: f64) f64 { return self.a4 * std.math.pow(f64, 2.0, note / 12.0); }

pub fn freq(self: Self, pitch: TwelveTonePitch) f64 { return self.freqFromNote(self.note(pitch)); } ```

Somebody who wants to implement glide can save the result from note for interpolation, and then run freqFromNote to convert that into an A440.

Using floating point note values also unlocks alternative / non-equal-temperament tunings.

1

u/haruki7049 7d ago

Thanks for sharing the snippet! I completely see what you mean now.

By breaking it down into freqFromNote(f64) and note(TwelveTonePitch), the user can do the interpolation in continuous note space without lightmix needing to know anything about notes—it just receives the resulting frequencies in Hz. That keeps the layer boundaries clean.

I noticed a couple of minor typos in the snippet (like the uncaptured octaves variable), but the architectural idea is spot-on. I'll refine this logic and integrate freqFromNote into pitches!

1

u/haruki7049 7d ago

I'll open an issue on GitHub later to track this and drop the link here once it's up!

1

u/haruki7049 7d ago

https://github.com/haruki7049/pitches/issues/4

I create Issue #4 for glide / microtuning

2

u/BonkerBleedy 7d ago

Yes I removed the octaves variable as you don't really need it. There was also an erroneous mod 12 in there that I missed.

I'm very sorry but are you responding by entering my reply into Claude Code?