Hi folks! 
I am learning the JACK API and Zig at the same time, by porting to Zig the example client from here.
I was able to write enough Zig code to connect to JACK, but I’m having trouble to write the callback code (see here the C version). The problem is that to write the audio data in the buffer obtained from JACK C API I’d need to use a pointer with an index, and this isn’t allowed in Zig.
Here is my attempt of writing the callback in Zig (note: I’m using globals for the shared values for simplicity, and float instead of jack_default_audio_sample_t as it’s just an alias to float):
fn process(nframes: c.jack_nframes_t, arg: ?*anyopaque) callconv(.C) c_int {
_ = arg;
var out1: *f32 = @ptrCast(@alignCast(c.jack_port_get_buffer(output_port1, nframes)));
var out2: *f32 = @ptrCast(@alignCast(c.jack_port_get_buffer(output_port1, nframes)));
for (0..nframes) |i| {
out1[i] = sine_table[left_phase];
out2[i] = sine_table[right_phase];
left_phase += 1;
right_phase += 3;
if (left_phase >= TABLE_SIZE) {
left_phase -= TABLE_SIZE;
}
if (right_phase >= TABLE_SIZE) {
right_phase -= TABLE_SIZE;
}
}
return 0;
}
This doesn’t compile:
simple_client.zig:25:13: error: type '*f32' does not support indexing
out1[i] = sine_table[left_phase];
~~~~^~~
simple_client.zig:25:13: note: operand must be an array, slice, tuple, or vector
I understand Zig doesn’t allow to do pointer arithmetic as C does. So how can I consume a C API in a case like this?
1 Like
You could try casting the opaque pointers to [*]f32
(many-item pointer) instead of *f32
(single-item pointer). Unlike C, Zig has distinct pointer types for single-item and many-item pointers with different properties. [*]f32
not only supports indexing like out1[i]
but also pointer arithmetic like out1 += 1
.
From https://ziglang.org/documentation/master/#Pointers:
Zig has two kinds of pointers: single-item and many-item.
*T
- single-item pointer to exactly one item.
- Supports deref syntax:
ptr.*
- Supports slice syntax:
ptr[0..1]
- Supports pointer subtraction:
ptr - ptr
[*]T
- many-item pointer to unknown number of items.
- Supports index syntax:
ptr[i]
- Supports slice syntax:
ptr[start..end]
and ptr[start..]
- Supports pointer-integer arithmetic:
ptr + int
, ptr - int
- Supports pointer subtraction:
ptr - ptr
T
must have a known size, which means that it cannot be anyopaque
or any other opaque type.
You might also want to read up on [*c]
pointers which show up in translated C code and function like both single- and many-item pointers at the same time, as a compromise since the C type system does not carry the information necessary to translate to a more specific Zig type.
When interoping with C APIs, it’s important to recognize the difference between single- and many-item pointers and cast/coerce to the corresponding Zig pointer type appropriately.
4 Likes
You might also want to consider using the slice syntax to convert the [*]f32
many-item pointer to a []f32
slice for added type safety. This lets you make use of multi-object for
loops and rewrite your function as:
fn process(nframes: c.jack_nframes_t, arg: ?*anyopaque) callconv(.C) c_int {
_ = arg;
const out1_ptr: [*]f32 = @ptrCast(@alignCast(c.jack_port_get_buffer(output_port1, nframes)));
const out1 = out1_ptr[0..nframes];
const out2_ptr: [*]f32 = @ptrCast(@alignCast(c.jack_port_get_buffer(output_port2, nframes)));
const out2 = out2_ptr[0..nframes];
for (out1, out2) |*o1, *o2| {
o1.* = sine_table[left_phase];
o2.* = sine_table[right_phase];
left_phase += 1;
right_phase += 3;
if (left_phase >= TABLE_SIZE) {
left_phase -= TABLE_SIZE;
}
if (right_phase >= TABLE_SIZE) {
right_phase -= TABLE_SIZE;
}
}
return 0;
}
5 Likes
Thank you so much @castholm, for the explanation and the documentation pointers (!), I changed it for a many-item pointers and my code works now !
I feel silly for not having spotted it, I had already went through that section of the docs twice, but for some reason I was assuming I would have to know the size at compile-time, that was silly. 
Nice touch the conversion to slices, that’s cool indeed, will try that, thank you!
Good day to you!
The trick to convert to slice doesn’t seem to work, though.
If I write it with const
as in your answer, I get a compile error:
simple_client.zig:26:9: error: cannot assign to constant
left = volume * sine_table[left_phase];
^~~~
If I change those to var, I get a compile error saying it’s never mutated:
$ zig run simple_client.zig -lc -ljack
simple_client.zig:22:9: error: local variable is never mutated
var out2 = out2_ptr[0..nframes];
^~~~
simple_client.zig:22:9: note: consider using 'const'
simple_client.zig:20:9: error: local variable is never mutated
var out1 = out1_ptr[0..nframes];
^~~~
simple_client.zig:20:9: note: consider using 'const'
It seems that Zig doesn’t “see” that we’re modifying it inside the loop?
What is left
here? It seems that it isn’t a pointer into the buffer since you are assigning to it directly. In the example posted above the elements of the buffer were iterated over by reference and assigned with a pointer de-reference:
1 Like
Oh, my mistake, I was missing the .*
dereferencing! I really need to review how to use pointers… Thanks, sorry for the bother!
1 Like