| examples | ||
| src | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
Pulsar
NOTE: This code is a work in progress! Using it means participating in its development.
A pulsar is a highly magnetized rotating neutron star... [they] are very dense and have short, regular rotational periods. This produces... pulses that range from milliseconds to seconds... exceeding the accuracy of [certain] atomic clocks in keeping time. — Wikipedia page on pulsars
Pulsar is a small library for audio playback on linux, with no dependency on libc or other shared libraries. Pulsar does depend on the presence of a PulseAudio server on the target system. This can be PulseAudio itself or something like pipewire-pulse. Pulsar's target audience is game developers, so it exposes only the subset of PulseAudio meant for realtime audio playback.
Example Code
This example code connects to the PulseAudio server,
creates a playback stream,
and plays a 880Hz sine wave for 10 seconds.
It is also available in examples/sine_f32.zig,
and can be run using zig build run -Dexample=sine_f32.
var sample_offset: usize = 0;
fn audio_callback(stream: *pulsar.Stream, requested_bytes: usize, _: ?*anyopaque) []const u8 {
const samples = stream.getSlice(.Float32Ne, requested_bytes);
const sample_rate: f32 = @floatFromInt(stream.sample_spec.sample_rate);
for (0..samples.len) |i| {
const amp = @cos(880 * @as(f32, @floatFromInt(sample_offset + i)) / sample_rate);
samples[i] = amp;
}
sample_offset += samples.len;
return std.mem.sliceAsBytes(samples);
}
pub fn main() !void {
var ctx = pulsar.Context{};
try ctx.connect(.{});
defer ctx.disconnect();
var stream = pulsar.Stream{
.callback_write = &audio_callback,
.sample_spec = .{ .channels = 2 },
.suspended = false,
};
try ctx.createPlaybackStream(&stream, .{});
sample_offset = 0;
const start_time = std.time.timestamp();
while (std.time.timestamp() - start_time < 10) {
try ctx.run();
}
}
const std = @import("std");
const pulsar = @import("pulsar");
Additional examples:
examples/chibi-xmplay.zig- Demonstrates playing back an extended mod (xm) file using chibi-xmplay
examples/client_name.zig- Shows how to set client properties, like name and application id, to be displayed by the PulseAudio server.
examples/pocketmod.zig- Demonstrates playing back a mod file using pocketmod
examples/state_change.zig- Demonstrates listening for state changes with a callback.
examples/sine.zig- Like
examples/sine_f32.zig, but samples are sent to PulseAudio as 16-bit signed integers. examples/seek.zig- Shows to seek the audio stream by seeking forward to skip already sent data.
examples/xev.zig- Shows how to drive Pulsar using an external event loop, specifically
libxev
Recommendations on usage
A minimal application can use Pulsar as shown in the above example. In larger applications Pulsar should be run in its own thread, preferably with (soft) realtime priority. Memory should be pre-allocated, or handled on a seperate thread before being passed to the Pulsar thread.
Pulsar's API is very low-level, you will likely want an audio mixer or dsp library to layer on top of it.
Planning
Defintely
[ ] Zero-copy streaming withpipewire-pulse doesn't support shared memoryshm_poolormemfd- Recording streams
- Configurable sample rate, format, etc. for streams
- More examples
- Using a synthesis library
- Audio thread
- Using libxev for asynchronous reads and writes (see
examples/xev.zig)
Maybe
- PulseAudio Sample Cache
- Non-unix domain socket transport (TCP)
- PulseAudio introspection
- Reference counting