Compare commits

...

3 commits

Author SHA1 Message Date
b1c2ee20ff
more hardware detection and adjustment
All checks were successful
Generic zig build / build (push) Successful in 23s
2025-10-27 15:38:03 -07:00
1363b2bfc2
better failure handling in main 2025-10-27 15:34:56 -07:00
e787e707cb
just change fix the binary unconditionally - that was too confusing 2025-10-27 14:42:17 -07:00
3 changed files with 65 additions and 24 deletions

View file

@ -75,21 +75,16 @@ pub fn build(b: *std.Build) void {
const install_exe = b.addInstallArtifact(exe, .{});
// Fix NEEDED entries in release builds to make binary portable
if (optimize != .Debug) {
const script_path = b.pathFromRoot("fix_needed.sh");
const fix_needed = b.addSystemCommand(&.{script_path});
fix_needed.step.dependOn(&install_exe.step);
fix_needed.addFileInput(install_exe.emitted_bin.?);
fix_needed.has_side_effects = true;
_ = fix_needed.captureStdOut();
b.getInstallStep().dependOn(&fix_needed.step);
const script_path = b.pathFromRoot("fix_needed.sh");
const fix_needed = b.addSystemCommand(&.{script_path});
fix_needed.step.dependOn(&install_exe.step);
fix_needed.addFileInput(install_exe.emitted_bin.?);
fix_needed.has_side_effects = true;
_ = fix_needed.captureStdOut();
b.getInstallStep().dependOn(&fix_needed.step);
const fix_step = b.step("fix-needed", "Fix NEEDED entries to make binary portable");
fix_step.dependOn(&fix_needed.step);
} else {
b.getInstallStep().dependOn(&install_exe.step);
}
const fix_step = b.step("fix-needed", "Fix NEEDED entries to make binary portable");
fix_step.dependOn(&fix_needed.step);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);

View file

@ -6,6 +6,7 @@ const stt = @import("stt.zig");
/// Global flag for signal handling
var should_exit = std.atomic.Value(bool).init(false);
var fatal_error = std.atomic.Value(bool).init(false);
// SAFETY: we are setting this value at top of main before use
/// We need a global here to reclaim process when getting SIGCHLD
@ -265,6 +266,12 @@ const SpeechHandler = struct {
else
log.err("{s}", .{message}),
}
// Signal shutdown on fatal errors
if (!error_info.recoverable) {
fatal_error.store(true, .release);
should_exit.store(true, .release);
}
}
};
@ -315,7 +322,7 @@ pub fn main() !void {
if (std.posix.getenv("ALSA_CONFIG_PATH") == null) {
std.fs.cwd().access("alsa.conf", .{}) catch {
_ = std.fs.File.stderr().writeAll("Error: alsa.conf file not found. Please put alsa.conf in the current directory or set ALSA_CONFIG_PATH\n") catch {};
std.process.exit(1);
return error.ConfigNotFound;
};
const c = @cImport({
@cInclude("stdlib.h");
@ -414,12 +421,12 @@ pub fn main() !void {
}
_ = try stderr.writeAll("Please download the model. A fine model can be downloaded from:\n");
_ = try stderr.writeAll("\thttps://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip\n");
std.process.exit(1);
return error.ModelNotFound;
}
std.fs.cwd().access(model_path.?, .{}) catch {
std.log.err("Model path does not exist: {s}", .{model_path.?});
std.process.exit(1);
return error.ModelNotFound;
};
// Initialize STT session with resolved model path
@ -438,7 +445,7 @@ pub fn main() !void {
std.log.err(" - Audio device '{s}' is available", .{options.audio_device});
std.log.err(" - Model directory exists at: {s}", .{options.model_path});
std.log.err(" - You have permission to access the audio device", .{});
return;
return err;
};
defer session.deinit();
@ -468,7 +475,7 @@ pub fn main() !void {
std.log.err("Unexpected error during startup.", .{});
},
}
return;
return err;
};
defer session.stop();
@ -504,6 +511,8 @@ pub fn main() !void {
std.log.err("✗ {d} fatal errors occurred during speech recognition.", .{handler.error_count});
_ = stdout.writeAll("Session completed successfully.\n") catch {};
if (fatal_error.load(.acquire)) return error.FatalError;
}
test "handler callbacks" {

View file

@ -454,19 +454,56 @@ pub const AlsaCapture = struct {
self.sample_rate = actual_rate;
}
// Set buffer size
// Get hardware constraints for buffer size
// SAFETY: this is set immediately as an out parameter in c API
var min_buffer: c.snd_pcm_uframes_t = undefined;
// SAFETY: this is set immediately as an out parameter in c API
var max_buffer: c.snd_pcm_uframes_t = undefined;
_ = c.snd_pcm_hw_params_get_buffer_size_min(hw_params, &min_buffer);
_ = c.snd_pcm_hw_params_get_buffer_size_max(hw_params, &max_buffer);
// Calculate minimum buffer size for ~40ms of audio
const calculated_min: u32 = @intCast((self.sample_rate * 40) / 1000);
const target_buffer = @max(self.buffer_size, calculated_min);
if (target_buffer < min_buffer) {
std.log.info("Buffer size {} too small, using minimum {}", .{ target_buffer, min_buffer });
self.buffer_size = @intCast(min_buffer);
} else if (target_buffer > max_buffer) {
std.log.info("Buffer size {} too large, using maximum {}", .{ target_buffer, max_buffer });
self.buffer_size = @intCast(max_buffer);
} else {
self.buffer_size = @intCast(target_buffer);
}
// Set buffer size first
var actual_buffer_size: c.snd_pcm_uframes_t = self.buffer_size;
err = c.snd_pcm_hw_params_set_buffer_size_near(self.pcm_handle, hw_params, &actual_buffer_size);
if (err < 0) return Error.SetBufferSizeError;
self.buffer_size = @intCast(actual_buffer_size);
// Set period size
var actual_period_size: c.snd_pcm_uframes_t = self.period_size;
err = c.snd_pcm_hw_params_set_period_size_near(self.pcm_handle, hw_params, &actual_period_size, null);
// Set period time (in microseconds) instead of period size for better compatibility
// Target ~10ms periods (10000 microseconds)
var period_time: c_uint = std.time.us_per_ms * 10;
var dir: c_int = 0;
err = c.snd_pcm_hw_params_set_period_time_near(self.pcm_handle, hw_params, &period_time, &dir);
if (err < 0) return Error.SetPeriodSizeError;
// Get the actual period size that was set
// SAFETY: this is set immediately as an out parameter in c API
var actual_period_size: c.snd_pcm_uframes_t = undefined;
err = c.snd_pcm_hw_params_get_period_size(hw_params, &actual_period_size, null);
if (err >= 0) {
self.period_size = @intCast(actual_period_size);
}
std.log.debug("ALSA params: rate={}, channels={}, buffer={}, period={}", .{ self.sample_rate, self.channels, actual_buffer_size, self.period_size });
// Apply hardware parameters
err = c.snd_pcm_hw_params(self.pcm_handle, hw_params);
if (err < 0) return Error.ApplyParametersError;
if (err < 0) {
std.log.err("snd_pcm_hw_params failed with error code: {}", .{err});
return Error.ApplyParametersError;
}
// Prepare the PCM for use
err = c.snd_pcm_prepare(self.pcm_handle);