add preview for sample generation

This commit is contained in:
Rokas Puzonas 2025-04-08 21:36:10 +03:00
parent 31d0af0a5c
commit c588956226
3 changed files with 92 additions and 28 deletions

View File

@ -42,7 +42,7 @@ const FileChannel = struct {
} }
}; };
const DeviceChannel = struct { pub const DeviceChannel = struct {
const ChannelName = std.BoundedArray(u8, NIDaq.max_channel_name_size + 1); // +1 for null byte const ChannelName = std.BoundedArray(u8, NIDaq.max_channel_name_size + 1); // +1 for null byte
const Direction = enum { input, output }; const Direction = enum { input, output };
@ -76,6 +76,20 @@ const DeviceChannel = struct {
pub fn getChannelName(self: *DeviceChannel) [:0]const u8 { pub fn getChannelName(self: *DeviceChannel) [:0]const u8 {
return utils.getBoundedStringZ(&self.channel_name); return utils.getBoundedStringZ(&self.channel_name);
} }
pub fn generateSine(samples: *std.ArrayList(f64), sample_rate: f64, frequency: f64, amplitude: f64) !void {
samples.clearRetainingCapacity();
const sample_count: usize = @intFromFloat(@ceil(sample_rate));
assert(sample_count >= 1);
try samples.ensureTotalCapacity(sample_count);
for (0..sample_count) |i| {
const i_f64: f64 = @floatFromInt(i);
const sample = std.math.sin(i_f64 / sample_rate * frequency * std.math.pi * 2) * amplitude;
samples.appendAssumeCapacity(sample);
}
}
}; };
pub const ChannelView = struct { pub const ChannelView = struct {
@ -445,7 +459,7 @@ pub fn activateDeviceChannel(self: *App, channel_view: *ChannelView) !void {
const task = try ni_daq.createTask(null); const task = try ni_daq.createTask(null);
errdefer task.clear(); errdefer task.clear();
const sample_rate = device_channel.max_sample_rate; const sample_rate = channel_view.sample_rate.?;
try task.createAIVoltageChannel(.{ try task.createAIVoltageChannel(.{
.channel = device_channel.getChannelName(), .channel = device_channel.getChannelName(),
.min_value = device_channel.min_value, .min_value = device_channel.min_value,
@ -468,13 +482,10 @@ pub fn activateDeviceChannel(self: *App, channel_view: *ChannelView) !void {
const task = try ni_daq.createTask(null); const task = try ni_daq.createTask(null);
errdefer task.clear(); errdefer task.clear();
device_channel.write_pattern.clearAndFree();
try device_channel.write_pattern.appendNTimes(2, 1000);
const write_pattern = device_channel.write_pattern.items; const write_pattern = device_channel.write_pattern.items;
const samples_per_channel: u32 = @intCast(write_pattern.len); const samples_per_channel: u32 = @intCast(write_pattern.len);
const sample_rate = 5000; // device_channel.max_sample_rate; const sample_rate = channel_view.sample_rate.?;
try task.createAOVoltageChannel(.{ try task.createAOVoltageChannel(.{
.channel = device_channel.getChannelName(), .channel = device_channel.getChannelName(),
.min_value = device_channel.min_value, .min_value = device_channel.min_value,
@ -648,7 +659,7 @@ pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
.x_range = RangeF64.init(0, 0), .x_range = RangeF64.init(0, 0),
.y_range = RangeF64.init(max_value, min_value), .y_range = RangeF64.init(max_value, min_value),
.source = .{ .device = device_channel_index }, .source = .{ .device = device_channel_index },
.sample_rate = max_sample_rate, .sample_rate = @min(max_sample_rate, 5000),
.unit = unit .unit = unit
}); });
errdefer _ = self.channel_views.pop(); errdefer _ = self.channel_views.pop();

View File

@ -47,6 +47,9 @@ protocol_modal: ?*ChannelView = null,
frequency_input: UI.TextInputStorage, frequency_input: UI.TextInputStorage,
amplitude_input: UI.TextInputStorage, amplitude_input: UI.TextInputStorage,
protocol_error_message: ?[]const u8 = null, protocol_error_message: ?[]const u8 = null,
protocol_graph_cache: Graph.Cache = .{},
preview_samples: std.ArrayList(f64),
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
pub fn init(app: *App) !MainScreen { pub fn init(app: *App) !MainScreen {
const allocator = app.allocator; const allocator = app.allocator;
@ -54,10 +57,11 @@ pub fn init(app: *App) !MainScreen {
var self = MainScreen{ var self = MainScreen{
.app = app, .app = app,
.frequency_input = UI.TextInputStorage.init(allocator), .frequency_input = UI.TextInputStorage.init(allocator),
.amplitude_input = UI.TextInputStorage.init(allocator) .amplitude_input = UI.TextInputStorage.init(allocator),
.preview_samples = std.ArrayList(f64).init(allocator)
}; };
try self.frequency_input.setText("1000"); try self.frequency_input.setText("10");
try self.amplitude_input.setText("10"); try self.amplitude_input.setText("10");
return self; return self;
@ -66,6 +70,7 @@ pub fn init(app: *App) !MainScreen {
pub fn deinit(self: *MainScreen) void { pub fn deinit(self: *MainScreen) void {
self.frequency_input.deinit(); self.frequency_input.deinit();
self.amplitude_input.deinit(); self.amplitude_input.deinit();
self.preview_samples.deinit();
self.clearProtocolErrorMessage(); self.clearProtocolErrorMessage();
} }
@ -697,6 +702,7 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
fn openProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { fn openProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
self.protocol_modal = channel_view; self.protocol_modal = channel_view;
self.protocol_graph_cache.deinit();
} }
fn closeModal(self: *MainScreen) void { fn closeModal(self: *MainScreen) void {
@ -706,6 +712,8 @@ fn closeModal(self: *MainScreen) void {
pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void { pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
var ui = &self.app.ui; var ui = &self.app.ui;
const device_channel = self.app.getChannelSourceDevice(channel_view).?;
const container = ui.createBox(.{ const container = ui.createBox(.{
.key = ui.keyFromString("Protocol modal"), .key = ui.keyFromString("Protocol modal"),
.background = srcery.black, .background = srcery.black,
@ -719,6 +727,25 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
container.beginChildren(); container.beginChildren();
defer container.endChildren(); defer container.endChildren();
{
const protocol_view = ui.createBox(.{
.key = ui.keyFromString("Protocol view"),
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixedPixels(ui.rem(4)),
.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 })
});
const samples = self.preview_samples.items;
const view_rect = Graph.ViewOptions{
.x_range = RangeF64.init(0, @floatFromInt(samples.len)),
.y_range = self.preview_samples_y_range
};
Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, samples);
if (self.protocol_graph_cache.texture) |texture| {
protocol_view.texture = texture.texture;
}
}
const FormInput = struct { const FormInput = struct {
name: []const u8, name: []const u8,
storage: *UI.TextInputStorage, storage: *UI.TextInputStorage,
@ -740,6 +767,7 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
.value = &amplitude .value = &amplitude
}, },
}; };
var any_input_modified = false;
for (form_inputs) |form_input| { for (form_inputs) |form_input| {
const label = form_input.name; const label = form_input.name;
@ -760,6 +788,35 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
label_box.alignment.y = .center; label_box.alignment.y = .center;
try ui.textInput(ui.keyFromString("Text input"), text_input_storage); try ui.textInput(ui.keyFromString("Text input"), text_input_storage);
any_input_modified = any_input_modified or text_input_storage.modified;
}
if (any_input_modified) {
self.clearProtocolErrorMessage();
for (form_inputs) |form_input| {
const label = form_input.name;
const text_input_storage: *UI.TextInputStorage = form_input.storage;
const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
continue;
};
if (number <= 0) {
try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
continue;
}
form_input.value.* = number;
}
}
if (self.protocol_error_message == null and any_input_modified) {
try App.DeviceChannel.generateSine(&self.preview_samples, channel_view.sample_rate.?, frequency, amplitude);
self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
self.protocol_graph_cache.invalidate();
} }
if (self.protocol_error_message) |message| { if (self.protocol_error_message) |message| {
@ -792,26 +849,9 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 }); btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) { if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
self.clearProtocolErrorMessage();
for (form_inputs) |form_input| {
const label = form_input.name;
const text_input_storage: *UI.TextInputStorage = form_input.storage;
const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
continue;
};
if (number <= 0) {
try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
continue;
}
form_input.value.* = number;
}
if (self.protocol_error_message == null) { if (self.protocol_error_message == null) {
try App.DeviceChannel.generateSine(&device_channel.write_pattern, channel_view.sample_rate.?, frequency, amplitude);
self.app.deferred_actions.appendAssumeCapacity(.{ self.app.deferred_actions.appendAssumeCapacity(.{
.activate = channel_view .activate = channel_view
}); });

View File

@ -2014,6 +2014,7 @@ pub fn isCtrlDown(self: *UI) bool {
pub const TextInputStorage = struct { pub const TextInputStorage = struct {
buffer: std.ArrayList(u8), buffer: std.ArrayList(u8),
modified: bool = false,
editing: bool = false, editing: bool = false,
last_pressed_at_ns: i128 = 0, last_pressed_at_ns: i128 = 0,
cursor_start: usize = 0, cursor_start: usize = 0,
@ -2032,11 +2033,15 @@ pub const TextInputStorage = struct {
} }
pub fn setText(self: *TextInputStorage, text: []const u8) !void { pub fn setText(self: *TextInputStorage, text: []const u8) !void {
self.modified = true;
self.buffer.clearAndFree(); self.buffer.clearAndFree();
try self.buffer.appendSlice(text); try self.buffer.appendSlice(text);
} }
pub fn insertSingle(self: *TextInputStorage, index: usize, symbol: u8) !void { pub fn insertSingle(self: *TextInputStorage, index: usize, symbol: u8) !void {
self.modified = true;
try self.buffer.insert(index, symbol); try self.buffer.insert(index, symbol);
if (self.cursor_start >= index) { if (self.cursor_start >= index) {
@ -2058,6 +2063,8 @@ pub const TextInputStorage = struct {
const to_clamped = std.math.clamp(to , 0, self.buffer.items.len); const to_clamped = std.math.clamp(to , 0, self.buffer.items.len);
if (from_clamped == to_clamped) return; if (from_clamped == to_clamped) return;
self.modified = true;
const lower = @min(from_clamped, to_clamped); const lower = @min(from_clamped, to_clamped);
const upper = @max(from_clamped, to_clamped); const upper = @max(from_clamped, to_clamped);
assert(lower < upper); assert(lower < upper);
@ -2172,6 +2179,8 @@ pub const TextInputStorage = struct {
fn insertMany(self: *TextInputStorage, index: usize, text: []const u8) !void { fn insertMany(self: *TextInputStorage, index: usize, text: []const u8) !void {
if (index > self.buffer.items.len) return; if (index > self.buffer.items.len) return;
self.modified = true;
try self.buffer.insertSlice(index, text); try self.buffer.insertSlice(index, text);
if (self.cursor_start >= index) { if (self.cursor_start >= index) {
@ -2184,6 +2193,10 @@ pub const TextInputStorage = struct {
} }
}; };
pub const TextInputResult = struct {
changed: bool = false
};
pub fn mouseTooltip(self: *UI) *Box { pub fn mouseTooltip(self: *UI) *Box {
const tooltip = self.getBoxByKey(mouse_tooltip_box_key).?; const tooltip = self.getBoxByKey(mouse_tooltip_box_key).?;