add custom draw callback to UI
This commit is contained in:
parent
23c4b99455
commit
ae26d2e1ba
22
build.zig
22
build.zig
@ -98,6 +98,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const stb_image_lib = buildStbImage(b);
|
const stb_image_lib = buildStbImage(b);
|
||||||
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
||||||
|
|
||||||
|
const generate_tool = b.addExecutable(.{
|
||||||
|
.name = "generate",
|
||||||
|
.root_source_file = b.path("tools/generate.zig"),
|
||||||
|
.target = target,
|
||||||
|
});
|
||||||
|
|
||||||
const png_to_icon_tool = b.addExecutable(.{
|
const png_to_icon_tool = b.addExecutable(.{
|
||||||
.name = "png-to-icon",
|
.name = "png-to-icon",
|
||||||
.root_source_file = b.path("tools/png-to-icon.zig"),
|
.root_source_file = b.path("tools/png-to-icon.zig"),
|
||||||
@ -142,6 +148,19 @@ pub fn build(b: *std.Build) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
{ // Run genration tool
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(generate_tool);
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_step = b.step("generate", "Run sample generation tool");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Run main program
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
if (b.args) |args| {
|
if (b.args) |args| {
|
||||||
@ -150,7 +169,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
const run_step = b.step("run", "Run the program");
|
const run_step = b.step("run", "Run the program");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Unit tests
|
||||||
const unit_tests = b.addTest(.{
|
const unit_tests = b.addTest(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
@ -161,3 +182,4 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&run_unit_tests.step);
|
test_step.dependOn(&run_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -751,7 +751,7 @@ pub fn tick(self: *App) !void {
|
|||||||
if (view.reference != .channel) continue;
|
if (view.reference != .channel) continue;
|
||||||
if (!view.follow) continue;
|
if (!view.follow) continue;
|
||||||
|
|
||||||
const sample_rate = self.project.sample_rate orelse 1;
|
const sample_rate = self.project.getSampleRate() orelse 1;
|
||||||
|
|
||||||
view.graph_opts.y_range = view.available_y_range;
|
view.graph_opts.y_range = view.available_y_range;
|
||||||
view.graph_opts.x_range.lower = 0;
|
view.graph_opts.x_range.lower = 0;
|
||||||
|
27
src/main.zig
27
src/main.zig
@ -103,15 +103,35 @@ pub fn main() !void {
|
|||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
_ = try app.addView(.{
|
_ = try app.addView(.{
|
||||||
.channel = try app.addChannel("Dev1/ai0")
|
.file = try app.addFile("./samples-5k.bin")
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = try app.addView(.{
|
_ = try app.addView(.{
|
||||||
.channel = try app.addChannel("Dev3/ao0")
|
.file = try app.addFile("./samples-50k.bin")
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = try app.addView(.{
|
_ = try app.addView(.{
|
||||||
.file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
.file = try app.addFile("./samples-300k.bin")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_ = try app.addView(.{
|
||||||
|
.file = try app.addFile("./samples-9m.bin")
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = try app.addView(.{
|
||||||
|
.file = try app.addFile("./samples-18m.bin")
|
||||||
|
});
|
||||||
|
|
||||||
|
// _ = try app.addView(.{
|
||||||
|
// .channel = try app.addChannel("Dev1/ai0")
|
||||||
|
// });
|
||||||
|
// _ = try app.addView(.{
|
||||||
|
// .channel = try app.addChannel("Dev3/ao0")
|
||||||
|
// });
|
||||||
|
// _ = try app.addView(.{
|
||||||
|
// .file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
||||||
|
// });
|
||||||
|
|
||||||
var cwd_realpath_buff: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
var cwd_realpath_buff: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||||
const cwd_realpath = try std.fs.cwd().realpath(".", &cwd_realpath_buff);
|
const cwd_realpath = try std.fs.cwd().realpath(".", &cwd_realpath_buff);
|
||||||
|
|
||||||
@ -119,6 +139,7 @@ pub fn main() !void {
|
|||||||
errdefer allocator.free(allocator);
|
errdefer allocator.free(allocator);
|
||||||
|
|
||||||
app.project.save_location = save_location;
|
app.project.save_location = save_location;
|
||||||
|
app.project.sample_rate = 5000;
|
||||||
|
|
||||||
// try app.appendChannelFromDevice("Dev1/ai0");
|
// try app.appendChannelFromDevice("Dev1/ai0");
|
||||||
// try app.appendChannelFromDevice("Dev3/ao0");
|
// try app.appendChannelFromDevice("Dev3/ao0");
|
||||||
|
@ -52,6 +52,7 @@ protocol_error_message: ?[]const u8 = null,
|
|||||||
protocol_graph_cache: Graph.Cache = .{},
|
protocol_graph_cache: Graph.Cache = .{},
|
||||||
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
||||||
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
|
||||||
|
view_settings: ?Id = null,
|
||||||
|
|
||||||
pub fn init(app: *App) !MainScreen {
|
pub fn init(app: *App) !MainScreen {
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
@ -223,203 +224,129 @@ fn showChannelViewGraph(self: *MainScreen, view_id: Id) *UI.Box {
|
|||||||
return graph_box;
|
return graph_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getLineOnRuler(
|
const RulerContext = struct {
|
||||||
self: *MainScreen,
|
one_unit: ?f64,
|
||||||
view_id: Id,
|
render_range: RangeF64,
|
||||||
ruler: *UI.Box,
|
available_range: RangeF64,
|
||||||
axis: UI.Axis,
|
axis: UI.Axis,
|
||||||
|
rect: rl.Rectangle = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
||||||
|
|
||||||
along_axis_pos: f64,
|
fn init(axis: UI.Axis, project: *App.Project, view_id: Id) RulerContext {
|
||||||
cross_axis_pos: f64,
|
const view = project.views.get(view_id).?;
|
||||||
cross_axis_size: f64
|
|
||||||
) rl.Rectangle {
|
|
||||||
const view = self.app.getView(view_id).?;
|
|
||||||
const shown_size = view.getGraphView(axis).size();
|
|
||||||
|
|
||||||
const along_axis_size = shown_size / switch (axis) {
|
return RulerContext{
|
||||||
.X => ruler.persistent.size.x,
|
.one_unit = switch (axis) {
|
||||||
.Y => ruler.persistent.size.y,
|
.X => project.getSampleRate(),
|
||||||
|
.Y => 1
|
||||||
|
},
|
||||||
|
.render_range = view.getGraphView(axis).*,
|
||||||
|
.available_range = view.getAvailableView(axis),
|
||||||
|
.axis = axis,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPoint(self: *RulerContext, along_axis_pos: f64, cross_axis_pos: f64) rl.Vector2 {
|
||||||
|
const rect_width: f64 = @floatCast(self.rect.width);
|
||||||
|
const rect_height: f64 = @floatCast(self.rect.height);
|
||||||
|
|
||||||
|
var x: f64 = undefined;
|
||||||
|
var y: f64 = undefined;
|
||||||
|
|
||||||
|
if (self.axis == .X) {
|
||||||
|
x = remap(f64, self.render_range.lower, self.render_range.upper, 0, rect_width, along_axis_pos);
|
||||||
|
y = cross_axis_pos * rect_height;
|
||||||
|
} else {
|
||||||
|
assert(self.axis == .Y);
|
||||||
|
|
||||||
|
x = (1 - cross_axis_pos) * rect_width;
|
||||||
|
y = remap(f64, self.render_range.lower, self.render_range.upper, 0, rect_height, along_axis_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rl.Vector2{
|
||||||
|
.x = self.rect.x + @as(f32, @floatCast(x)),
|
||||||
|
.y = self.rect.y + @as(f32, @floatCast(y)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getLine(self: *RulerContext, along_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||||
|
const along_axis_size = self.render_range.size() / switch(self.axis) {
|
||||||
|
.X => @as(f64, @floatCast(self.rect.width)),
|
||||||
|
.Y => @as(f64, @floatCast(self.rect.height))
|
||||||
};
|
};
|
||||||
|
|
||||||
return self.getRectOnRuler(
|
return self.getRect(
|
||||||
view_id,
|
|
||||||
ruler,
|
|
||||||
axis,
|
|
||||||
|
|
||||||
along_axis_pos,
|
along_axis_pos,
|
||||||
along_axis_size,
|
along_axis_size,
|
||||||
cross_axis_pos,
|
0,
|
||||||
cross_axis_size
|
cross_axis_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getRectOnRuler(
|
fn getRect(self: *RulerContext, along_axis_pos: f64, along_axis_size: f64, cross_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||||
self: *MainScreen,
|
const pos = self.getPoint(along_axis_pos, cross_axis_pos);
|
||||||
view_id: Id,
|
const corner = self.getPoint(along_axis_pos + along_axis_size, cross_axis_pos + cross_axis_size);
|
||||||
ruler: *UI.Box,
|
var rect = rl.Rectangle{
|
||||||
axis: UI.Axis,
|
.x = pos.x,
|
||||||
|
.y = pos.y,
|
||||||
along_axis_pos: f64,
|
.width = corner.x - pos.x,
|
||||||
along_axis_size: f64,
|
.height = corner.y - pos.y
|
||||||
cross_axis_pos: f64,
|
|
||||||
cross_axis_size: f64
|
|
||||||
) rl.Rectangle {
|
|
||||||
assert(0 <= cross_axis_size and cross_axis_size <= 1);
|
|
||||||
|
|
||||||
const rect = ruler.rect();
|
|
||||||
const rect_height: f64 = @floatCast(rect.height);
|
|
||||||
const rect_width: f64 = @floatCast(rect.width);
|
|
||||||
|
|
||||||
const view = self.app.getView(view_id).?;
|
|
||||||
const view_range = view.getGraphView(axis).*;
|
|
||||||
|
|
||||||
if (axis == .X) {
|
|
||||||
const width_range = RangeF64.init(0, rect.width);
|
|
||||||
var result = rl.Rectangle{
|
|
||||||
.width = @floatCast(along_axis_size / view_range.size() * rect_width),
|
|
||||||
.height = @floatCast(rect_height * cross_axis_size),
|
|
||||||
.x = @floatCast(view_range.remapTo(width_range, along_axis_pos)),
|
|
||||||
.y = @floatCast(rect_height * cross_axis_pos),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (result.width < 0) {
|
if (rect.width < 0) {
|
||||||
result.x += result.width;
|
rect.x += rect.width;
|
||||||
result.width *= -1;
|
rect.width *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
if (rect.height < 0) {
|
||||||
} else {
|
rect.y += rect.height;
|
||||||
const height_range = RangeF64.init(0, rect.height);
|
rect.height *= -1;
|
||||||
var result = rl.Rectangle{
|
|
||||||
.width = @floatCast(rect_width * cross_axis_size),
|
|
||||||
.height = @floatCast(along_axis_size / view_range.size() * rect_height),
|
|
||||||
.x = @floatCast(rect_width * (1 - cross_axis_pos - cross_axis_size)),
|
|
||||||
.y = @floatCast(view_range.remapTo(height_range, along_axis_pos + along_axis_size)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (result.height < 0) {
|
|
||||||
result.y += result.height;
|
|
||||||
result.height *= -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return rect;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showRulerTicksRange(
|
fn drawLine(self: *RulerContext, along_axis_pos: f64, cross_axis_size: f64, color: rl.Color) void {
|
||||||
self: *MainScreen,
|
rl.drawLineV(
|
||||||
view_id: Id,
|
self.getPoint(along_axis_pos, 0),
|
||||||
ruler: *UI.Box,
|
self.getPoint(along_axis_pos, cross_axis_size),
|
||||||
axis: UI.Axis,
|
color
|
||||||
|
|
||||||
from: f64,
|
|
||||||
to: f64,
|
|
||||||
step: f64,
|
|
||||||
|
|
||||||
marker_size: f64
|
|
||||||
) void {
|
|
||||||
var marker = from;
|
|
||||||
while (marker < to) : (marker += step) {
|
|
||||||
_ = self.app.ui.createBox(.{
|
|
||||||
.background = srcery.yellow,
|
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, marker, 0, marker_size),
|
|
||||||
.float_relative_to = ruler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showRulerTicks(self: *MainScreen, view_id: Id, axis: UI.Axis) void {
|
|
||||||
const view = self.app.getView(view_id).?;
|
|
||||||
|
|
||||||
const view_range = view.getGraphView(axis);
|
|
||||||
const full_range = view.getAvailableView(axis);
|
|
||||||
|
|
||||||
var ui = &self.app.ui;
|
|
||||||
const ruler = ui.parentBox().?;
|
|
||||||
|
|
||||||
const ruler_rect = ruler.rect();
|
|
||||||
const ruler_rect_size_along_axis = switch (axis) {
|
|
||||||
.X => ruler_rect.width,
|
|
||||||
.Y => ruler_rect.height
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ruler_rect_size_along_axis == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view_range.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (full_range.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ideal_pixels_per_division = 150;
|
|
||||||
var subdivisions: f32 = 20;
|
|
||||||
subdivisions = 20;
|
|
||||||
while (true) {
|
|
||||||
assert(subdivisions > 0);
|
|
||||||
const step = full_range.size() / subdivisions;
|
|
||||||
const pixels_per_division = step / view_range.size() * ruler_rect_size_along_axis;
|
|
||||||
assert(pixels_per_division > 0);
|
|
||||||
|
|
||||||
if (pixels_per_division > ideal_pixels_per_division*2) {
|
|
||||||
subdivisions *= 2;
|
|
||||||
} else if (pixels_per_division < ideal_pixels_per_division/2) {
|
|
||||||
subdivisions /= 2;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const step = full_range.size() / subdivisions;
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = self.app.ui.createBox(.{
|
|
||||||
.background = srcery.yellow,
|
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.lower, 0, 0.75),
|
|
||||||
.float_relative_to = ruler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = self.app.ui.createBox(.{
|
|
||||||
.background = srcery.yellow,
|
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, full_range.upper, 0, 0.75),
|
|
||||||
.float_relative_to = ruler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (full_range.hasExclusive(0)) {
|
|
||||||
_ = ui.createBox(.{
|
|
||||||
.background = srcery.yellow,
|
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, 0, 0, 0.75),
|
|
||||||
.float_relative_to = ruler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ticks_range = view_range.grow(step).intersectPositive(full_range);
|
|
||||||
|
|
||||||
self.showRulerTicksRange(
|
|
||||||
view_id,
|
|
||||||
ruler,
|
|
||||||
axis,
|
|
||||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step) + step/2,
|
|
||||||
ticks_range.upper,
|
|
||||||
step,
|
|
||||||
0.5
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.showRulerTicksRange(
|
fn drawTicks(self: *RulerContext, from: f64, to: f64, step: f64, line_size: f64, color: rl.Color) void {
|
||||||
view_id,
|
var position = from;
|
||||||
ruler,
|
while (position < to) : (position += step) {
|
||||||
axis,
|
self.drawLine(position, line_size, color);
|
||||||
utils.roundNearestTowardZero(f64, ticks_range.lower, step),
|
}
|
||||||
ticks_range.upper,
|
}
|
||||||
step,
|
};
|
||||||
0.25
|
|
||||||
);
|
fn drawRulerTicks(_ctx: ?*anyopaque, box: *UI.Box) void {
|
||||||
|
const ctx: *RulerContext = @ptrCast(@alignCast(_ctx));
|
||||||
|
ctx.rect = box.rect();
|
||||||
|
|
||||||
|
ctx.drawLine(ctx.available_range.lower, 1, srcery.yellow);
|
||||||
|
ctx.drawLine(ctx.available_range.upper, 1, srcery.yellow);
|
||||||
|
|
||||||
|
if (ctx.available_range.hasExclusive(0)) {
|
||||||
|
ctx.drawLine(0, 0.75, srcery.yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
var one_unit = ctx.one_unit orelse return;
|
||||||
|
|
||||||
|
while (ctx.render_range.size() < 5*one_unit) {
|
||||||
|
one_unit /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ctx.render_range.size() > 50*one_unit) {
|
||||||
|
one_unit *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ticks_range = ctx.render_range.grow(one_unit).intersectPositive(ctx.available_range);
|
||||||
|
ticks_range.lower = utils.roundNearestTowardZero(f64, ticks_range.lower, one_unit);
|
||||||
|
|
||||||
|
ctx.drawTicks(ticks_range.lower, ticks_range.upper, one_unit, 0.5, srcery.yellow);
|
||||||
|
ctx.drawTicks(ticks_range.lower + one_unit/2, ticks_range.upper, one_unit, 0.25, srcery.yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
||||||
@ -428,7 +355,7 @@ fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
|||||||
var ruler = ui.createBox(.{
|
var ruler = ui.createBox(.{
|
||||||
.key = key,
|
.key = key,
|
||||||
.background = srcery.hard_black,
|
.background = srcery.hard_black,
|
||||||
.flags = &.{ .clip_view, .clickable, .scrollable },
|
.flags = &.{ .clickable, .scrollable, .clip_view },
|
||||||
.hot_cursor = .mouse_cursor_pointing_hand
|
.hot_cursor = .mouse_cursor_pointing_hand
|
||||||
});
|
});
|
||||||
if (axis == .X) {
|
if (axis == .X) {
|
||||||
@ -442,15 +369,42 @@ fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
|||||||
return ruler;
|
return ruler;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) void {
|
fn formatDuration(allocator: std.mem.Allocator, total_seconds: f64) ![]u8 {
|
||||||
|
const seconds = @mod(total_seconds, @as(f64, @floatFromInt(std.time.s_per_min)));
|
||||||
|
const minutes = total_seconds / std.time.s_per_min;
|
||||||
|
return try std.fmt.allocPrint(allocator, "{d:.0}m {d:.3}s", .{ minutes, seconds });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) !void {
|
||||||
var ui = &self.app.ui;
|
var ui = &self.app.ui;
|
||||||
|
|
||||||
const view = self.app.getView(view_id) orelse return;
|
const view = self.app.getView(view_id) orelse return;
|
||||||
|
|
||||||
|
var ruler_ctx = RulerContext.init(axis, &self.app.project, view_id);
|
||||||
|
var graph_ctx = ruler_ctx;
|
||||||
|
ruler_ctx.rect = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = ruler.persistent.size.x,
|
||||||
|
.height = ruler.persistent.size.y
|
||||||
|
};
|
||||||
|
graph_ctx.rect = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = graph_box.persistent.size.x,
|
||||||
|
.height = graph_box.persistent.size.y
|
||||||
|
};
|
||||||
|
|
||||||
ruler.beginChildren();
|
ruler.beginChildren();
|
||||||
defer ruler.endChildren();
|
defer ruler.endChildren();
|
||||||
|
|
||||||
self.showRulerTicks(view_id, axis);
|
const ctx = try ui.frameAllocator().create(RulerContext);
|
||||||
|
ctx.* = RulerContext.init(axis, &self.app.project, view_id);
|
||||||
|
|
||||||
|
ruler.draw = .{
|
||||||
|
.ctx = ctx,
|
||||||
|
.do = drawRulerTicks
|
||||||
|
};
|
||||||
|
|
||||||
const signal = ui.signal(ruler);
|
const signal = ui.signal(ruler);
|
||||||
const mouse_position = switch (axis) {
|
const mouse_position = switch (axis) {
|
||||||
@ -480,12 +434,16 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
|||||||
const project = &self.app.project;
|
const project = &self.app.project;
|
||||||
|
|
||||||
if (view.getAvailableView(axis).hasInclusive(mouse_position_on_graph)) {
|
if (view.getAvailableView(axis).hasInclusive(mouse_position_on_graph)) {
|
||||||
|
const sample_rate = project.getSampleRate();
|
||||||
|
|
||||||
if (axis == .Y and view.unit != null) {
|
if (axis == .Y and view.unit != null) {
|
||||||
const unit_name = view.unit.?.name() orelse "Unknown";
|
const unit_name = view.unit.?.name() orelse "Unknown";
|
||||||
_ = ui.label("{s}: {d:.3}", .{unit_name, mouse_position_on_graph});
|
_ = ui.label("{s}: {d:.3}", .{unit_name, mouse_position_on_graph});
|
||||||
} else if (axis == .X and project.sample_rate != null) {
|
} else if (axis == .X and sample_rate != null) {
|
||||||
const sample_rate = project.sample_rate.?;
|
const seconds = mouse_position_on_graph / sample_rate.?;
|
||||||
_ = ui.label("{d:.3}s", .{mouse_position_on_graph / sample_rate});
|
const frame_allocator = ui.frameAllocator();
|
||||||
|
_ = ui.label("{s}", .{ formatDuration(frame_allocator, seconds) catch "-" });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
_ = ui.label("{d:.3}", .{mouse_position_on_graph});
|
_ = ui.label("{d:.3}", .{mouse_position_on_graph});
|
||||||
}
|
}
|
||||||
@ -510,13 +468,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
|||||||
if (zoom_start != null) {
|
if (zoom_start != null) {
|
||||||
_ = ui.createBox(.{
|
_ = ui.createBox(.{
|
||||||
.background = srcery.green,
|
.background = srcery.green,
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_start.?, 0, 1),
|
.float_rect = ruler_ctx.getLine(zoom_start.?, 1),
|
||||||
.float_relative_to = ruler,
|
.float_relative_to = ruler,
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = ui.createBox(.{
|
_ = ui.createBox(.{
|
||||||
.background = srcery.green,
|
.background = srcery.green,
|
||||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_start.?, 0, 1),
|
.float_rect = graph_ctx.getLine(zoom_start.?, 1),
|
||||||
.float_relative_to = graph_box,
|
.float_relative_to = graph_box,
|
||||||
.parent = graph_box
|
.parent = graph_box
|
||||||
});
|
});
|
||||||
@ -525,13 +483,13 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
|||||||
if (zoom_end != null) {
|
if (zoom_end != null) {
|
||||||
_ = ui.createBox(.{
|
_ = ui.createBox(.{
|
||||||
.background = srcery.green,
|
.background = srcery.green,
|
||||||
.float_rect = self.getLineOnRuler(view_id, ruler, axis, zoom_end.?, 0, 1),
|
.float_rect = ruler_ctx.getLine(zoom_end.?, 1),
|
||||||
.float_relative_to = ruler,
|
.float_relative_to = ruler,
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = ui.createBox(.{
|
_ = ui.createBox(.{
|
||||||
.background = srcery.green,
|
.background = srcery.green,
|
||||||
.float_rect = self.getLineOnRuler(view_id, graph_box, axis, zoom_end.?, 0, 1),
|
.float_rect = graph_ctx.getLine(zoom_end.?, 1),
|
||||||
.float_relative_to = graph_box,
|
.float_relative_to = graph_box,
|
||||||
.parent = graph_box
|
.parent = graph_box
|
||||||
});
|
});
|
||||||
@ -541,15 +499,7 @@ fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, view_id: Id,
|
|||||||
_ = ui.createBox(.{
|
_ = ui.createBox(.{
|
||||||
.background = srcery.green.alpha(0.5),
|
.background = srcery.green.alpha(0.5),
|
||||||
.float_relative_to = ruler,
|
.float_relative_to = ruler,
|
||||||
.float_rect = self.getRectOnRuler(
|
.float_rect = ruler_ctx.getRect(zoom_start.?, zoom_end.? - zoom_start.?, 0, 1),
|
||||||
view_id,
|
|
||||||
ruler,
|
|
||||||
axis,
|
|
||||||
zoom_start.?,
|
|
||||||
zoom_end.? - zoom_start.?,
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,14 +545,13 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
|||||||
.key = UI.Key.initUsize(view_id.asInt()),
|
.key = UI.Key.initUsize(view_id.asInt()),
|
||||||
.layout_direction = .top_to_bottom,
|
.layout_direction = .top_to_bottom,
|
||||||
.size_x = UI.Sizing.initGrowFull(),
|
.size_x = UI.Sizing.initGrowFull(),
|
||||||
.size_y = height
|
.size_y = height,
|
||||||
});
|
});
|
||||||
view_box.beginChildren();
|
view_box.beginChildren();
|
||||||
defer view_box.endChildren();
|
defer view_box.endChildren();
|
||||||
|
|
||||||
const toolbar = ui.createBox(.{
|
const toolbar = ui.createBox(.{
|
||||||
.layout_direction = .left_to_right,
|
.layout_direction = .left_to_right,
|
||||||
.layout_gap = 16,
|
|
||||||
.background = srcery.hard_black,
|
.background = srcery.hard_black,
|
||||||
.size_x = UI.Sizing.initGrowFull(),
|
.size_x = UI.Sizing.initGrowFull(),
|
||||||
.size_y = UI.Sizing.initFixed(.{ .pixels = ui.rem(2) })
|
.size_y = UI.Sizing.initFixed(.{ .pixels = ui.rem(2) })
|
||||||
@ -611,9 +560,36 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
|||||||
toolbar.beginChildren();
|
toolbar.beginChildren();
|
||||||
defer toolbar.endChildren();
|
defer toolbar.endChildren();
|
||||||
|
|
||||||
|
const view = self.app.getView(view_id).?;
|
||||||
var view_name: ?[]const u8 = null;
|
var view_name: ?[]const u8 = null;
|
||||||
|
|
||||||
const view = self.app.getView(view_id).?;
|
{
|
||||||
|
const btn = ui.textButton("Settings");
|
||||||
|
btn.background = srcery.hard_black;
|
||||||
|
if (self.view_settings != null and self.view_settings.?.eql(view_id)) {
|
||||||
|
btn.borders.bottom = .{
|
||||||
|
.color = srcery.green,
|
||||||
|
.size = 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ui.signal(btn).clicked()) {
|
||||||
|
if (self.view_settings != null and self.view_settings.?.eql(view_id)) {
|
||||||
|
self.view_settings = null;
|
||||||
|
} else {
|
||||||
|
self.view_settings = view_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const btn = ui.textButton("Reset view");
|
||||||
|
btn.background = srcery.hard_black;
|
||||||
|
if (ui.signal(btn).clicked()) {
|
||||||
|
self.pushViewMoveCommand(view_id, view.available_x_range, view.available_y_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (view.reference == .channel) {
|
if (view.reference == .channel) {
|
||||||
const channel_id = view.reference.channel;
|
const channel_id = view.reference.channel;
|
||||||
const channel = self.app.getChannel(channel_id).?;
|
const channel = self.app.getChannel(channel_id).?;
|
||||||
@ -737,8 +713,8 @@ fn showView(self: *MainScreen, view_id: Id, height: UI.Sizing) !void {
|
|||||||
x_ruler = self.addRulerPlaceholder(ui.keyFromString("X ruler"), .X);
|
x_ruler = self.addRulerPlaceholder(ui.keyFromString("X ruler"), .X);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.showRuler(x_ruler, graph_box, view_id, .X);
|
try self.showRuler(x_ruler, graph_box, view_id, .X);
|
||||||
self.showRuler(y_ruler, graph_box, view_id, .Y);
|
try self.showRuler(y_ruler, graph_box, view_id, .Y);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -936,22 +912,89 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
|||||||
.right = .{ .color = srcery.hard_black, .size = 4 }
|
.right = .{ .color = srcery.hard_black, .size = 4 }
|
||||||
},
|
},
|
||||||
.layout_direction = .top_to_bottom,
|
.layout_direction = .top_to_bottom,
|
||||||
.padding = UI.Padding.all(ui.rem(1))
|
.padding = UI.Padding.all(ui.rem(1)),
|
||||||
|
.layout_gap = ui.rem(0.2)
|
||||||
});
|
});
|
||||||
container.beginChildren();
|
container.beginChildren();
|
||||||
defer container.endChildren();
|
defer container.endChildren();
|
||||||
|
|
||||||
const project = &self.app.project;
|
const project = &self.app.project;
|
||||||
|
const sample_rate = project.getSampleRate();
|
||||||
|
|
||||||
|
if (self.view_settings) |view_id| {
|
||||||
|
const view = project.views.get(view_id) orelse return;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
const label = ui.label("Settings", .{});
|
||||||
|
label.borders.bottom = .{
|
||||||
|
.color = srcery.bright_white,
|
||||||
|
.size = 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||||
|
|
||||||
|
var sample_count: ?usize = null;
|
||||||
|
switch (view.reference) {
|
||||||
|
.channel => |channel_id| {
|
||||||
|
const channel = project.channels.get(channel_id).?;
|
||||||
|
const channel_name = utils.getBoundedStringZ(&channel.name);
|
||||||
|
const channel_type = NIDaq.getChannelType(channel_name);
|
||||||
|
const samples = channel.collected_samples.items;
|
||||||
|
|
||||||
|
_ = ui.label("Channel: {s}", .{ channel_name });
|
||||||
|
|
||||||
|
if (channel_type != null) {
|
||||||
|
_ = ui.label("Type: {s}", .{ channel_type.?.name() });
|
||||||
|
} else {
|
||||||
|
_ = ui.label("Type: unknown", .{ });
|
||||||
|
}
|
||||||
|
|
||||||
|
sample_count = samples.len;
|
||||||
|
},
|
||||||
|
.file => |file_id| {
|
||||||
|
const file = project.files.get(file_id).?;
|
||||||
|
|
||||||
|
if (file.samples) |samples| {
|
||||||
|
sample_count = samples.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sample_count != null) {
|
||||||
|
_ = ui.label("Samples: {d}", .{ sample_count.? });
|
||||||
|
|
||||||
|
var duration_str: []const u8 = "-";
|
||||||
|
if (sample_rate != null) {
|
||||||
|
const duration = @as(f64, @floatFromInt(sample_count.?)) / sample_rate.?;
|
||||||
|
if (formatDuration(ui.frameAllocator(), duration)) |str| {
|
||||||
|
duration_str = str;
|
||||||
|
} else |_| {}
|
||||||
|
}
|
||||||
|
_ = ui.label("Duration: {s}", .{ duration_str });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
const label = ui.label("Project", .{});
|
||||||
|
label.borders.bottom = .{
|
||||||
|
.color = srcery.bright_white,
|
||||||
|
.size = 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
|
||||||
|
|
||||||
var placeholder: ?[]const u8 = null;
|
var placeholder: ?[]const u8 = null;
|
||||||
if (project.getDefaultSampleRate()) |sample_rate| {
|
if (project.getDefaultSampleRate()) |default_sample_rate| {
|
||||||
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
|
||||||
}
|
}
|
||||||
|
|
||||||
var initial: ?[]const u8 = null;
|
var initial: ?[]const u8 = null;
|
||||||
if (project.sample_rate) |sample_rate| {
|
if (project.sample_rate) |selected_sample_rate| {
|
||||||
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ selected_sample_rate });
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = ui.label("Sample rate", .{});
|
_ = ui.label("Sample rate", .{});
|
||||||
@ -960,13 +1003,14 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
|||||||
.storage = &self.sample_rate_input,
|
.storage = &self.sample_rate_input,
|
||||||
.placeholder = placeholder,
|
.placeholder = placeholder,
|
||||||
.initial = initial,
|
.initial = initial,
|
||||||
.invalid = self.parsed_sample_rate != project.sample_rate
|
.invalid = self.parsed_sample_rate != project.sample_rate,
|
||||||
|
.editable = !self.app.isCollectionInProgress()
|
||||||
});
|
});
|
||||||
project.sample_rate = self.parsed_sample_rate;
|
project.sample_rate = self.parsed_sample_rate;
|
||||||
|
|
||||||
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
|
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
|
||||||
if (project.sample_rate) |sample_rate| {
|
if (project.sample_rate) |selected_sample_rate| {
|
||||||
if (!allowed_sample_rates.hasInclusive(sample_rate)) {
|
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
|
||||||
project.sample_rate = null;
|
project.sample_rate = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1117,6 +1161,8 @@ pub fn tick(self: *MainScreen) !void {
|
|||||||
self.closeModal();
|
self.closeModal();
|
||||||
} else if (self.fullscreen_view != null) {
|
} else if (self.fullscreen_view != null) {
|
||||||
self.fullscreen_view = null;
|
self.fullscreen_view = null;
|
||||||
|
} else if (self.view_settings != null) {
|
||||||
|
self.view_settings = null;
|
||||||
} else {
|
} else {
|
||||||
self.app.should_close = true;
|
self.app.should_close = true;
|
||||||
}
|
}
|
||||||
|
26
src/ui.zig
26
src/ui.zig
@ -476,6 +476,11 @@ pub const Box = struct {
|
|||||||
|
|
||||||
pub const Flags = std.EnumSet(Flag);
|
pub const Flags = std.EnumSet(Flag);
|
||||||
|
|
||||||
|
pub const Draw = struct {
|
||||||
|
ctx: ?*anyopaque = null,
|
||||||
|
do: *const fn(ctx: ?*anyopaque, box: *Box) void
|
||||||
|
};
|
||||||
|
|
||||||
const max_wrapped_lines = 64;
|
const max_wrapped_lines = 64;
|
||||||
|
|
||||||
ui: *UI,
|
ui: *UI,
|
||||||
@ -507,6 +512,7 @@ pub const Box = struct {
|
|||||||
scientific_number: ?f64 = null,
|
scientific_number: ?f64 = null,
|
||||||
scientific_precision: u32 = 1,
|
scientific_precision: u32 = 1,
|
||||||
float_relative_to: ?*Box = null,
|
float_relative_to: ?*Box = null,
|
||||||
|
draw: ?Draw = null,
|
||||||
|
|
||||||
// Variables that you probably shouldn't be touching
|
// Variables that you probably shouldn't be touching
|
||||||
last_used_frame: u64 = 0,
|
last_used_frame: u64 = 0,
|
||||||
@ -725,6 +731,7 @@ pub const BoxOptions = struct {
|
|||||||
float_relative_to: ?*Box = null,
|
float_relative_to: ?*Box = null,
|
||||||
parent: ?*UI.Box = null,
|
parent: ?*UI.Box = null,
|
||||||
texture_color: ?rl.Color = null,
|
texture_color: ?rl.Color = null,
|
||||||
|
draw: ?Box.Draw = null
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const root_box_key = Key.initString(0, "$root$");
|
pub const root_box_key = Key.initString(0, "$root$");
|
||||||
@ -1531,6 +1538,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
|||||||
.scientific_precision = opts.scientific_precision orelse 1,
|
.scientific_precision = opts.scientific_precision orelse 1,
|
||||||
.float_relative_to = opts.float_relative_to,
|
.float_relative_to = opts.float_relative_to,
|
||||||
.texture_color = opts.texture_color,
|
.texture_color = opts.texture_color,
|
||||||
|
.draw = opts.draw,
|
||||||
|
|
||||||
.last_used_frame = self.frame_index,
|
.last_used_frame = self.frame_index,
|
||||||
.key = key,
|
.key = key,
|
||||||
@ -1683,6 +1691,10 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (box.draw) |box_draw| {
|
||||||
|
box_draw.do(box_draw.ctx, box);
|
||||||
|
}
|
||||||
|
|
||||||
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
||||||
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
||||||
|
|
||||||
@ -2206,6 +2218,7 @@ pub const TextInputStorage = struct {
|
|||||||
pub const TextInputOptions = struct {
|
pub const TextInputOptions = struct {
|
||||||
key: Key,
|
key: Key,
|
||||||
storage: *TextInputStorage,
|
storage: *TextInputStorage,
|
||||||
|
editable: bool = true,
|
||||||
|
|
||||||
initial: ?[]const u8 = null,
|
initial: ?[]const u8 = null,
|
||||||
placeholder: ?[]const u8 = null,
|
placeholder: ?[]const u8 = null,
|
||||||
@ -2216,6 +2229,7 @@ pub const NumberInputOptions = struct {
|
|||||||
key: Key,
|
key: Key,
|
||||||
storage: *TextInputStorage,
|
storage: *TextInputStorage,
|
||||||
invalid: bool = false,
|
invalid: bool = false,
|
||||||
|
editable: bool = true,
|
||||||
|
|
||||||
initial: ?[]const u8 = null,
|
initial: ?[]const u8 = null,
|
||||||
placeholder: ?[]const u8 = null,
|
placeholder: ?[]const u8 = null,
|
||||||
@ -2423,7 +2437,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const container_signal = self.signal(container);
|
const container_signal = self.signal(container);
|
||||||
if (container_signal.hot) {
|
if (opts.editable and container_signal.hot) {
|
||||||
container.borders = UI.Borders.all(.{
|
container.borders = UI.Borders.all(.{
|
||||||
.color = srcery.red,
|
.color = srcery.red,
|
||||||
.size = 2
|
.size = 2
|
||||||
@ -2467,7 +2481,7 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container_signal.active) {
|
if (opts.editable and container_signal.active) {
|
||||||
storage.editing = true;
|
storage.editing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2623,7 +2637,6 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
}
|
}
|
||||||
try storage.insertSingle(storage.cursor_start, @intCast(char));
|
try storage.insertSingle(storage.cursor_start, @intCast(char));
|
||||||
|
|
||||||
|
|
||||||
no_blinking = true;
|
no_blinking = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2640,6 +2653,10 @@ pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
|||||||
if (container_signal.clicked_outside and !container_signal.is_mouse_inside) {
|
if (container_signal.clicked_outside and !container_signal.is_mouse_inside) {
|
||||||
storage.editing = false;
|
storage.editing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!opts.editable) {
|
||||||
|
storage.editing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2651,7 +2668,8 @@ pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
|||||||
.storage = opts.storage,
|
.storage = opts.storage,
|
||||||
.initial = opts.initial,
|
.initial = opts.initial,
|
||||||
.text_color = opts.text_color,
|
.text_color = opts.text_color,
|
||||||
.placeholder = opts.placeholder
|
.placeholder = opts.placeholder,
|
||||||
|
.editable = opts.editable
|
||||||
};
|
};
|
||||||
|
|
||||||
var is_invalid = opts.invalid;
|
var is_invalid = opts.invalid;
|
||||||
|
44
tools/generate.zig
Normal file
44
tools/generate.zig
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn show_usage() void {
|
||||||
|
std.debug.print("Usage: zig build generate <sample-rate> <sample-count> <filename>\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
|
var args = try std.process.argsWithAllocator(allocator);
|
||||||
|
defer args.deinit();
|
||||||
|
|
||||||
|
_ = args.next();
|
||||||
|
|
||||||
|
const sample_rate_str = args.next() orelse {
|
||||||
|
show_usage();
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sample_count_str = args.next() orelse {
|
||||||
|
show_usage();
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filename = args.next() orelse {
|
||||||
|
show_usage();
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sample_rate = try std.fmt.parseFloat(f64, sample_rate_str);
|
||||||
|
const sample_count = try std.fmt.parseInt(u32, sample_count_str, 10);
|
||||||
|
|
||||||
|
const f = try std.fs.cwd().createFile(filename, .{ .exclusive = true });
|
||||||
|
defer f.close();
|
||||||
|
|
||||||
|
for (0..sample_count) |i| {
|
||||||
|
const i_f64: f64 = @floatFromInt(i);
|
||||||
|
const sample: f64 = std.math.sin( i_f64 / sample_rate * std.math.pi * 2 ) * 10;
|
||||||
|
const sample_bytes = std.mem.toBytes(sample);
|
||||||
|
try f.writeAll(&sample_bytes);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user