From e36e38eb63ea5dacb95da98777b884e5c126d760 Mon Sep 17 00:00:00 2001 From: tranzystorekk Date: Thu, 16 Apr 2026 16:25:17 +0200 Subject: [PATCH] chore!: adjust for Zig 0.16 --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 6 +- .github/workflows/pages.yml | 2 +- .gitignore | 1 + Dockerfile | 2 +- README.md | 2 +- build.zig.zon | 6 +- src/file.zig | 22 ++--- src/gen.zig | 6 +- src/main.zig | 89 +++++++++++--------- src/wav.zig | 156 ++++++++++++++---------------------- 11 files changed, 138 insertions(+), 156 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e9d1085..4c54d78 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,7 +54,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.16.0 - name: Show Zig version run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fa4afc..962d4dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.16.0 - name: Show Zig version run: | @@ -54,7 +54,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.16.0 - name: Install kcov run: | @@ -86,7 +86,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.16.0 - name: Check formatting run: zig fmt --check . diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 515e364..4a152c2 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -30,7 +30,7 @@ jobs: - name: Install Zig uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.16.0 - name: Show Zig version run: | diff --git a/.gitignore b/.gitignore index 2f8a917..522256b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ zig-cache/ .zig-cache/ zig-out/ +zig-pkg/ build/ build-*/ docgen_tmp/ diff --git a/Dockerfile b/Dockerfile index 56def54..11f6353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eloitor/zig:0.14.0 as builder +FROM eloitor/zig:0.16.0 as builder RUN apk update RUN apk add --no-cache git WORKDIR /app diff --git a/README.md b/README.md index 25be046..a31b149 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ #### Prerequisites -- [Zig](https://ziglang.org/download/) (`0.14`) +- [Zig](https://ziglang.org/download/) (`0.16`) #### Instructions diff --git a/build.zig.zon b/build.zig.zon index bf488df..575f3e3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .linuxwave, .version = "0.1.5", .fingerprint = 0x217eafdf4732b5f9, - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.16.0", .paths = .{ "build.zig", "build.zig.zon", @@ -12,8 +12,8 @@ }, .dependencies = .{ .clap = .{ - .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz", - .hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0", + .url = "https://github.com/Hejsil/zig-clap/archive/fc1e5cc3f6d9d3001112385ee6256d694e959d2f.tar.gz", + .hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1", }, }, } diff --git a/src/file.zig b/src/file.zig index 0a4e78a..584228a 100644 --- a/src/file.zig +++ b/src/file.zig @@ -4,30 +4,34 @@ const std = @import("std"); /// Reads the given file and returns a byte array with the length of `len`. pub fn readBytes( + io: std.Io, allocator: std.mem.Allocator, path: []const u8, len: usize, ) ![]u8 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - var list = try std.ArrayList(u8).initCapacity(allocator, len); - var buffer = list.allocatedSlice(); - const bytes_read = try file.read(buffer); - return buffer[0..bytes_read]; + const file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); + var read_buffer: [1024]u8 = undefined; + var file_reader = file.reader(io, &read_buffer); + const reader = &file_reader.interface; + return reader.readAlloc(allocator, len); } test "read bytes from the file" { + const allocator = std.testing.allocator; + const io = std.testing.io; + // Get the current directory. var cwd_buffer: [std.fs.max_path_bytes]u8 = undefined; - const cwd = try std.posix.getcwd(&cwd_buffer); + const cwd_len = try std.process.currentPath(io, &cwd_buffer); + const cwd = cwd_buffer[0..cwd_len]; // Concatenate the current directory with the builder file. - const allocator = std.testing.allocator; const path = try std.fs.path.join(allocator, &.{ cwd, "build.zig" }); defer allocator.free(path); // Read the contents of the file and compare. - const bytes = try readBytes(allocator, path, 9); + const bytes = try readBytes(io, allocator, path, 9); try std.testing.expectEqualStrings("const std", bytes); defer allocator.free(bytes); } diff --git a/src/gen.zig b/src/gen.zig index a81a52b..723de7f 100644 --- a/src/gen.zig +++ b/src/gen.zig @@ -31,7 +31,7 @@ pub const Generator = struct { /// /// Returns an array that contains the amplitudes of the sound wave at a given point in time. pub fn generate(self: Generator, allocator: std.mem.Allocator, sample: u8) ![]u8 { - var buffer = std.ArrayList(u8).init(allocator); + var buffer: std.ArrayList(u8) = .empty; var i: usize = 0; while (i < sample_count) : (i += 1) { // Calculate the frequency according to the equal temperament. @@ -45,9 +45,9 @@ pub const Generator = struct { // Apply the volume control. const volume: f32 = @floatFromInt(self.config.volume); amp = amp * volume / 100; - try buffer.append(@intFromFloat(amp)); + try buffer.append(allocator, @trunc(amp)); } - return buffer.toOwnedSlice(); + return buffer.toOwnedSlice(allocator); } }; diff --git a/src/main.zig b/src/main.zig index 14c9a31..466e473 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,20 +8,24 @@ const build_options = @import("build_options"); const clap = @import("clap"); /// Runs `linuxwave`. -fn run(allocator: std.mem.Allocator, output: anytype) !void { +fn run(io: std.Io, allocator: std.mem.Allocator, output: *std.Io.Writer, argv: std.process.Args) !void { // Parse command-line arguments. - const cli = try clap.parse(clap.Help, &args.params, args.parsers, .{ .allocator = allocator }); + const cli = try clap.parse(clap.Help, &args.params, args.parsers, argv, .{ .allocator = allocator }); defer cli.deinit(); if (cli.args.help != 0) { try output.print("{s}\n", .{args.banner}); - return clap.help(output, clap.Help, &args.params, args.help_options); - } else if (cli.args.version != 0) { + try clap.help(output, clap.Help, &args.params, args.help_options); + try output.flush(); + return; + } + if (cli.args.version != 0) { try output.print("{s} {s}\n", .{ build_options.exe_name, build_options.version }); + try output.flush(); return; } // Create encoder configuration. - const encoder_config = wav.EncoderConfig{ + const encoder_config: wav.EncoderConfig = .{ .num_channels = if (cli.args.channels) |channels| channels else defaults.channels, .sample_rate = if (cli.args.rate) |rate| @intFromFloat(rate) else defaults.sample_rate, .format = if (cli.args.format) |format| format else defaults.format, @@ -29,12 +33,12 @@ fn run(allocator: std.mem.Allocator, output: anytype) !void { // Create generator configuration. const scale = s: { - var scale = std.ArrayList(u8).init(allocator); + var scale: std.ArrayList(u8) = .empty; var splits = std.mem.splitAny(u8, if (cli.args.scale) |s| s else defaults.scale, ","); while (splits.next()) |chunk| { - try scale.append(try std.fmt.parseInt(u8, chunk, 0)); + try scale.append(allocator, try std.fmt.parseInt(u8, chunk, 0)); } - break :s try scale.toOwnedSlice(); + break :s try scale.toOwnedSlice(allocator); }; defer allocator.free(scale); const generator_config = gen.GeneratorConfig{ @@ -50,63 +54,76 @@ fn run(allocator: std.mem.Allocator, output: anytype) !void { const buffer = b: { if (std.mem.eql(u8, input_file, "-")) { try output.print("Reading {d} bytes from stdin\n", .{data_len}); - var list = try std.ArrayList(u8).initCapacity(allocator, data_len); - const buffer = list.allocatedSlice(); - const stdin = std.io.getStdIn().reader(); - try stdin.readNoEof(buffer); - break :b buffer; - } else { - try output.print("Reading {d} bytes from {s}\n", .{ data_len, input_file }); - break :b try file.readBytes(allocator, input_file, data_len); + try output.flush(); + var read_buffer: [1024]u8 = undefined; + var stdin_reader = std.Io.File.stdin().reader(io, &read_buffer); + const stdin = &stdin_reader.interface; + break :b try stdin.readAlloc(allocator, data_len); } + + try output.print("Reading {d} bytes from {s}\n", .{ data_len, input_file }); + try output.flush(); + break :b try file.readBytes(io, allocator, input_file, data_len); }; defer allocator.free(buffer); // Generate music. - const generator = gen.Generator.init(generator_config); - var data = std.ArrayList(u8).init(allocator); + const generator: gen.Generator = .init(generator_config); + var data: std.ArrayList(u8) = .empty; for (buffer) |v| { const gen_data = try generator.generate(allocator, v); defer allocator.free(gen_data); - try data.appendSlice(gen_data); + try data.appendSlice(allocator, gen_data); } // Encode WAV. const out = if (cli.args.output) |out| out else defaults.output; - const writer = w: { + var write_buffer: [1024]u8 = undefined; + var fhandle: ?std.Io.File = null; + defer if (fhandle) |f| { + f.close(io); + }; + var file_writer = w: { if (std.mem.eql(u8, out, "-")) { try output.print("Writing to stdout\n", .{}); - break :w std.io.getStdOut().writer(); - } else { - try output.print("Saving to {s}\n", .{out}); - const out_file = try std.fs.cwd().createFile(out, .{}); - break :w out_file.writer(); + try output.flush(); + break :w std.Io.File.stdout().writer(io, &write_buffer); } + + try output.print("Saving to {s}\n", .{out}); + try output.flush(); + fhandle = try std.Io.Dir.cwd().createFile(io, out, .{}); + break :w fhandle.?.writer(io, &write_buffer); }; - const wav_data = try data.toOwnedSlice(); + const wav_data = try data.toOwnedSlice(allocator); defer allocator.free(wav_data); - try wav.Encoder(@TypeOf(writer)).encode(writer, wav_data, encoder_config); + try wav.encode(&file_writer.interface, wav_data, encoder_config); } /// Entry-point. -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - const stderr = std.io.getStdErr().writer(); - run(allocator, stderr) catch |err| { +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; + var stderr_buffer: [1024]u8 = undefined; + var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer); + const stderr = &stderr_writer.interface; + run(io, allocator, stderr, init.minimal.args) catch |err| { try stderr.print("Error occurred: {}\n", .{err}); + try stderr.flush(); }; } test "run" { const allocator = std.testing.allocator; - var buffer = std.ArrayList(u8).init(allocator); - const output = buffer.writer(); - run(allocator, output) catch |err| { + const io = std.testing.io; + + const fake_args: std.process.Args = .{ .vector = &[_][*:0]const u8{} }; + var allocating: std.Io.Writer.Allocating = .init(allocator); + run(io, allocator, &allocating.writer, fake_args) catch |err| { std.debug.print("Error occurred: {s}\n", .{@errorName(err)}); return; }; - const result = buffer.toOwnedSlice() catch |err| { + const result = allocating.toOwnedSlice() catch |err| { std.debug.print("Error occurred: {s}\n", .{@errorName(err)}); return; }; diff --git a/src/wav.zig b/src/wav.zig index df37894..47dfd99 100644 --- a/src/wav.zig +++ b/src/wav.zig @@ -14,6 +14,7 @@ const FMT_ = [4]u8{ 'f', 'm', 't', ' ' }; const DATA = [4]u8{ 'd', 'a', 't', 'a' }; // Total length of the header. const header_len: u32 = 44; +const data_chunk_pos: u32 = 36; /// Format of the waveform data. pub const Format = enum { @@ -52,113 +53,72 @@ pub const EncoderConfig = struct { } }; -/// WAV encoder implementation. -pub fn Encoder(comptime Writer: type) type { - // Position of the data chunk. - const data_chunk_pos: u32 = 36; - - return struct { - // Encode WAV. - pub fn encode(writer: Writer, data: []const u8, config: EncoderConfig) !void { - try writeChunks(writer, config, data); - } - - /// Writes the headers with placeholder values for length. - /// - /// This can be used while streaming the WAV file i.e. when the total length is unknown. - pub fn writeHeader(writer: Writer, config: EncoderConfig) !void { - try writeChunks(writer, config, null); - } +pub fn encode(writer: *std.Io.Writer, data: []const u8, config: EncoderConfig) !void { + try writeChunks(writer, config, data); +} - /// Patches the headers to seek back and patch the headers for length values. - pub fn patchHeader(writer: Writer, seeker: anytype, data_len: u32) !void { - const endian = std.builtin.Endian.little; - try seeker.seekTo(4); - try writer.writeInt(u32, data_chunk_pos + 8 + data_len - 8, endian); - try seeker.seekTo(data_chunk_pos + 4); - try writer.writeInt(u32, data_len, endian); - } +/// Writes the headers with placeholder values for length. +/// +/// This can be used while streaming the WAV file i.e. when the total length is unknown. +pub fn writeHeader(writer: *std.Io.Writer, config: EncoderConfig) !void { + try writeChunks(writer, config, null); +} - /// Writes the WAV chunks with optional data. - /// - /// → RIFF('WAVE' - /// // Format - /// [] // Fact chunk - /// [] // Cue points - /// [] // Playlist - /// [] // Associated data list - /// ) // Wave data - fn writeChunks(writer: Writer, config: EncoderConfig, opt_data: ?[]const u8) !void { - // Chunk configuration. - const bytes_per_sample = config.format.getNumBytes(); - const num_channels: u16 = @intCast(config.num_channels); - const sample_rate: u32 = @intCast(config.sample_rate); - const byte_rate = sample_rate * @as(u32, num_channels) * bytes_per_sample; - const block_align: u16 = num_channels * bytes_per_sample; - const bits_per_sample: u16 = bytes_per_sample * 8; - const data_len: u32 = if (opt_data) |data| @intCast(data.len) else 0; - const endian = std.builtin.Endian.little; - // Write the file header. - try writer.writeAll(&RIFF); - if (opt_data != null) { - try writer.writeInt(u32, data_chunk_pos + 8 + data_len - 8, endian); - } else { - try writer.writeInt(u32, 0, endian); - } - try writer.writeAll(&WAVE); - // Write the format chunk. - try writer.writeAll(&FMT_); - // Encode with pulse-code modulation (LPCM). - try writer.writeInt(u32, 16, endian); - // Uncompressed. - try writer.writeInt(u16, 1, endian); - try writer.writeInt(u16, num_channels, endian); - try writer.writeInt(u32, sample_rate, endian); - try writer.writeInt(u32, byte_rate, endian); - try writer.writeInt(u16, block_align, endian); - try writer.writeInt(u16, bits_per_sample, endian); - // Write the data chunk. - try writer.writeAll(&DATA); - if (opt_data) |data| { - try writer.writeInt(u32, data_len, endian); - try writer.writeAll(data); - } else { - try writer.writeInt(u32, 0, endian); - } - } - }; +/// Writes the WAV chunks with optional data. +/// +/// → RIFF('WAVE' +/// // Format +/// [] // Fact chunk +/// [] // Cue points +/// [] // Playlist +/// [] // Associated data list +/// ) // Wave data +fn writeChunks(writer: *std.Io.Writer, config: EncoderConfig, opt_data: ?[]const u8) !void { + // Chunk configuration. + const bytes_per_sample = config.format.getNumBytes(); + const num_channels: u16 = @intCast(config.num_channels); + const sample_rate: u32 = @intCast(config.sample_rate); + const byte_rate = sample_rate * @as(u32, num_channels) * bytes_per_sample; + const block_align: u16 = num_channels * bytes_per_sample; + const bits_per_sample: u16 = bytes_per_sample * 8; + const data_len: u32 = if (opt_data) |data| @intCast(data.len) else 0; + const endian = std.builtin.Endian.little; + // Write the file header. + try writer.writeAll(&RIFF); + if (opt_data != null) { + try writer.writeInt(u32, data_chunk_pos + 8 + data_len - 8, endian); + } else { + try writer.writeInt(u32, 0, endian); + } + try writer.writeAll(&WAVE); + // Write the format chunk. + try writer.writeAll(&FMT_); + // Encode with pulse-code modulation (LPCM). + try writer.writeInt(u32, 16, endian); + // Uncompressed. + try writer.writeInt(u16, 1, endian); + try writer.writeInt(u16, num_channels, endian); + try writer.writeInt(u32, sample_rate, endian); + try writer.writeInt(u32, byte_rate, endian); + try writer.writeInt(u16, block_align, endian); + try writer.writeInt(u16, bits_per_sample, endian); + // Write the data chunk. + try writer.writeAll(&DATA); + if (opt_data) |data| { + try writer.writeInt(u32, data_len, endian); + try writer.writeAll(data); + } else { + try writer.writeInt(u32, 0, endian); + } } test "encode WAV" { var buffer: [1000]u8 = undefined; - var stream = std.io.fixedBufferStream(&buffer); - const writer = stream.writer(); - try Encoder(@TypeOf(writer)).encode(writer, &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, .{ + var writer: std.Io.Writer = .fixed(&buffer); + try encode(&writer, &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, .{ .num_channels = 1, .sample_rate = 44100, .format = .S16_LE, }); try std.testing.expectEqualSlices(u8, "RIFF", buffer[0..4]); } - -test "stream out WAV" { - var buffer: [1000]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const endian = std.builtin.Endian.little; - const WavEncoder = Encoder(@TypeOf(fbs).Writer); - try WavEncoder.writeHeader(fbs.writer(), .{ - .num_channels = 1, - .sample_rate = 44100, - .format = .S16_LE, - }); - try std.testing.expectEqual(@as(u64, 44), try fbs.getPos()); - try std.testing.expectEqual(@as(u32, 0), std.mem.readInt(u32, buffer[4..8], endian)); - try std.testing.expectEqual(@as(u32, 0), std.mem.readInt(u32, buffer[40..44], endian)); - - const data = &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; - try fbs.writer().writeAll(data); - try std.testing.expectEqual(@as(u64, 52), try fbs.getPos()); - try WavEncoder.patchHeader(fbs.writer(), fbs.seekableStream(), data.len); - try std.testing.expectEqual(@as(u32, 44), std.mem.readInt(u32, buffer[4..8], endian)); - try std.testing.expectEqual(@as(u32, 8), std.mem.readInt(u32, buffer[40..44], endian)); -}