Something went wrong. Try again.
Plays short audio files such as a terminal bell through PipeWire on Linux and WASAPI on Windows.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Plays nothing. A stream here has no thread: the test drives it with `pull`,//! which does what a sound server's cycle does, so everything a Player does//! can be checked without a sound device.
const std = @import("std");const options_mod = @import("options.zig");const StreamOptions = options_mod.StreamOptions;
pub const Error = std.mem.Allocator.Error || error{OutputUnavailable};
/// Set to make the next `open` fail, as a missing sound server would.pub var fail_next_open: bool = false;
pub const Stream = struct { gpa: std.mem.Allocator, process: options_mod.Process, name: []u8, rate: u32, channels: u16, active: bool = true,
/// One cycle of `frames`, interleaved: the callback's output if the stream /// is running, silence if it is paused. The caller frees it. pub fn pull(s: *Stream, frames: u32) std.mem.Allocator.Error![]f32 { const planes = try s.gpa.alloc([]f32, s.channels); defer s.gpa.free(planes); const flat = try s.gpa.alloc(f32, @as(usize, frames) * s.channels); defer s.gpa.free(flat); for (planes, 0..) |*p, c| p.* = flat[c * frames ..][0..frames];
const out = try s.gpa.alloc(f32, flat.len); if (!s.active) { @memset(out, 0); return out; } // What a real backend's previous cycle would have left behind. @memset(flat, std.math.nan(f32)); s.process.func(s.process.ctx, planes, frames); for (0..frames) |i| { for (planes, 0..) |p, c| out[i * s.channels + c] = p[i]; } return out; }};
pub fn open(gpa: std.mem.Allocator, io: std.Io, options: StreamOptions) Error!*Stream { _ = io; if (fail_next_open) { fail_next_open = false; return error.OutputUnavailable; } const s = try gpa.create(Stream); errdefer gpa.destroy(s); s.* = .{ .gpa = gpa, .process = options.process, .name = try gpa.dupe(u8, options.name), .rate = options.rate, .channels = options.channels, }; return s;}
pub fn close(s: *Stream, io: std.Io) void { _ = io; s.gpa.free(s.name); s.gpa.destroy(s);}
pub fn pause(s: *Stream) void { s.active = false;}
pub fn unpause(s: *Stream) void { s.active = true;}