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 Direction = enum { input, output };
@ -76,6 +76,20 @@ const DeviceChannel = struct {
pub fn getChannelName(self: *DeviceChannel) [:0]const u8 {
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 {
@ -445,7 +459,7 @@ pub fn activateDeviceChannel(self: *App, channel_view: *ChannelView) !void {
const task = try ni_daq.createTask(null);
errdefer task.clear();
const sample_rate = device_channel.max_sample_rate;
const sample_rate = channel_view.sample_rate.?;
try task.createAIVoltageChannel(.{
.channel = device_channel.getChannelName(),
.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);
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 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(.{
.channel = device_channel.getChannelName(),
.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),
.y_range = RangeF64.init(max_value, min_value),
.source = .{ .device = device_channel_index },
.sample_rate = max_sample_rate,
.sample_rate = @min(max_sample_rate, 5000),
.unit = unit
});
errdefer _ = self.channel_views.pop();

View File

@ -47,6 +47,9 @@ protocol_modal: ?*ChannelView = null,
frequency_input: UI.TextInputStorage,
amplitude_input: UI.TextInputStorage,
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 {
const allocator = app.allocator;
@ -54,10 +57,11 @@ pub fn init(app: *App) !MainScreen {
var self = MainScreen{
.app = app,
.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");
return self;
@ -66,6 +70,7 @@ pub fn init(app: *App) !MainScreen {
pub fn deinit(self: *MainScreen) void {
self.frequency_input.deinit();
self.amplitude_input.deinit();
self.preview_samples.deinit();
self.clearProtocolErrorMessage();
}
@ -697,6 +702,7 @@ fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Siz
fn openProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
self.protocol_modal = channel_view;
self.protocol_graph_cache.deinit();
}
fn closeModal(self: *MainScreen) void {
@ -706,6 +712,8 @@ fn closeModal(self: *MainScreen) void {
pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
var ui = &self.app.ui;
const device_channel = self.app.getChannelSourceDevice(channel_view).?;
const container = ui.createBox(.{
.key = ui.keyFromString("Protocol modal"),
.background = srcery.black,
@ -719,6 +727,25 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
container.beginChildren();
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 {
name: []const u8,
storage: *UI.TextInputStorage,
@ -740,6 +767,7 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
.value = &amplitude
},
};
var any_input_modified = false;
for (form_inputs) |form_input| {
const label = form_input.name;
@ -760,6 +788,35 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
label_box.alignment.y = .center;
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| {
@ -792,26 +849,9 @@ pub fn showProtocolModal(self: *MainScreen, channel_view: *ChannelView) !void {
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
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) {
try App.DeviceChannel.generateSine(&device_channel.write_pattern, channel_view.sample_rate.?, frequency, amplitude);
self.app.deferred_actions.appendAssumeCapacity(.{
.activate = channel_view
});

View File

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