Add level measurement to gauge background noise

This commit is contained in:
Emil Lerch 2025-10-29 12:53:41 -07:00
parent 362be00d07
commit 07308a548a
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 81 additions and 5 deletions

View file

@ -286,6 +286,70 @@ fn signalHandler(sig: i32) callconv(.c) void {
} }
} }
fn runMeasureLevels(allocator: std.mem.Allocator) !void {
const stdout = std.fs.File.stdout();
const is_tty = stdout.isTty();
var capture = try stt.AlsaCapture.init(allocator, "default", 16000, 1024);
defer capture.deinit();
try capture.open();
_ = try stdout.writeAll("Measuring audio levels... Press Ctrl+C to exit\n");
if (is_tty) {
_ = try stdout.writeAll("Histogram (0-10000):\n");
}
var buffer: [4096]i16 = undefined;
var write_buffer: [256]u8 = undefined;
var second_max: u16 = 0;
var last_print = std.time.milliTimestamp();
while (!should_exit.load(.acquire)) {
_ = try capture.readAudio();
const samples_read = capture.getAudioSamples(&buffer);
if (samples_read == 0) {
std.Thread.sleep(10 * std.time.ns_per_ms);
continue;
}
var max_amp: u16 = 0;
for (buffer[0..samples_read]) |sample| {
const abs_sample = @abs(sample);
if (abs_sample > max_amp) max_amp = abs_sample;
}
if (max_amp > second_max) second_max = max_amp;
const now = std.time.milliTimestamp();
if (now - last_print >= 1000) {
if (is_tty) {
const bar_width = (@as(u32, second_max) * 60) / 10000;
var writer = stdout.writer(&write_buffer);
const w = &writer.interface;
try w.print("{d:5} |", .{second_max});
try w.flush();
for (0..bar_width) |_| {
_ = try stdout.writeAll("");
}
_ = try stdout.writeAll("\n");
} else {
var writer = stdout.writer(&write_buffer);
const w = &writer.interface;
try w.print("{d}\n", .{second_max});
try w.flush();
}
second_max = 0;
last_print = now;
}
std.Thread.sleep(50 * std.time.ns_per_ms);
}
if (is_tty) {
_ = try stdout.writeAll("\n");
}
}
fn signalAction(sig: i32, info: *const std.posix.siginfo_t, _: ?*anyopaque) callconv(.c) void { fn signalAction(sig: i32, info: *const std.posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
// NOTE: info only works correctly if std.posix.SA.SIGINFO is in the flags // NOTE: info only works correctly if std.posix.SA.SIGINFO is in the flags
// std.log.debug("signal action. sig {d}", .{sig}); // std.log.debug("signal action. sig {d}", .{sig});
@ -353,6 +417,7 @@ pub fn main() !void {
var model_path: ?[]const u8 = null; var model_path: ?[]const u8 = null;
var exec_program: ?[]const u8 = null; var exec_program: ?[]const u8 = null;
var measure_levels = false;
// Parse arguments // Parse arguments
for (args[1..]) |arg| { for (args[1..]) |arg| {
@ -361,13 +426,15 @@ pub fn main() !void {
_ = try stdout.writeAll("USAGE:\n"); _ = try stdout.writeAll("USAGE:\n");
_ = try stdout.writeAll(" stt [OPTIONS]\n\n"); _ = try stdout.writeAll(" stt [OPTIONS]\n\n");
_ = try stdout.writeAll("OPTIONS:\n"); _ = try stdout.writeAll("OPTIONS:\n");
_ = try stdout.writeAll(" --model=<path> Path to Vosk model directory\n"); _ = try stdout.writeAll(" --model=<path> Path to Vosk model directory\n");
_ = try stdout.writeAll(" --exec=<program> Program to execute with recognized text\n"); _ = try stdout.writeAll(" --exec=<program> Program to execute with recognized text\n");
_ = try stdout.writeAll(" --help, -h Show this help message\n\n"); _ = try stdout.writeAll(" --measure-levels Display real-time audio level histogram\n");
_ = try stdout.writeAll(" --help, -h Show this help message\n\n");
_ = try stdout.writeAll("EXAMPLES:\n"); _ = try stdout.writeAll("EXAMPLES:\n");
_ = try stdout.writeAll(" stt\n"); _ = try stdout.writeAll(" stt\n");
_ = try stdout.writeAll(" stt --model=../share/vosk/models/vosk-model-small-en-us-0.15\n"); _ = try stdout.writeAll(" stt --model=../share/vosk/models/vosk-model-small-en-us-0.15\n");
_ = try stdout.writeAll(" stt --exec=echo\n\n"); _ = try stdout.writeAll(" stt --exec=echo\n");
_ = try stdout.writeAll(" stt --measure-levels\n\n");
_ = try stdout.writeAll("The application will search for models in these locations:\n"); _ = try stdout.writeAll("The application will search for models in these locations:\n");
_ = try stdout.writeAll(" vosk-model-small-en-us-0.15\n"); _ = try stdout.writeAll(" vosk-model-small-en-us-0.15\n");
_ = try stdout.writeAll(" <binary_dir>/../share/vosk/models/vosk-model-small-en-us-0.15\n"); _ = try stdout.writeAll(" <binary_dir>/../share/vosk/models/vosk-model-small-en-us-0.15\n");
@ -376,6 +443,8 @@ pub fn main() !void {
model_path = arg[8..]; // Skip "--model=" model_path = arg[8..]; // Skip "--model="
} else if (std.mem.startsWith(u8, arg, "--exec=")) { } else if (std.mem.startsWith(u8, arg, "--exec=")) {
exec_program = arg[7..]; // Skip "--exec=" exec_program = arg[7..]; // Skip "--exec="
} else if (std.mem.eql(u8, arg, "--measure-levels")) {
measure_levels = true;
} }
} }
@ -385,6 +454,13 @@ pub fn main() !void {
.exec_program = exec_program, .exec_program = exec_program,
}; };
defer handler.deinit(); defer handler.deinit();
// If measure-levels mode, run that instead of normal STT
if (measure_levels) {
try runMeasureLevels(allocator);
return;
}
const speech_handler = stt.SpeechEventHandler{ const speech_handler = stt.SpeechEventHandler{
.onSpeechFn = SpeechHandler.onSpeech, .onSpeechFn = SpeechHandler.onSpeech,
.onErrorFn = SpeechHandler.onError, .onErrorFn = SpeechHandler.onError,

View file

@ -531,7 +531,7 @@ pub const AlsaCapture = struct {
} }
/// Read audio data from ALSA device and process it /// Read audio data from ALSA device and process it
fn readAudio(self: *Self) !usize { pub fn readAudio(self: *Self) !usize {
if (self.pcm_handle == null) if (self.pcm_handle == null)
return Error.AudioDeviceError; return Error.AudioDeviceError;