diff --git a/src/app.zig b/src/app.zig index a981848..2647e00 100644 --- a/src/app.zig +++ b/src/app.zig @@ -194,7 +194,9 @@ const SumRingBuffer = struct { } }; -pub const Transform = struct { +pub const Transformation = struct { + const Block = SampleList.Block; + const max_gain_changes = 32; pub const GainChanges = std.BoundedArray(GainChange, max_gain_changes); pub const GainChange = struct { @@ -202,45 +204,74 @@ pub const Transform = struct { sample: f64, }; + destination: Id, // Sample list ID + source: Id, // Sample list ID gain_changes: GainChanges = .{}, + divider: ?Id = null, // Sample list ID - pub fn applySlice(self: Transform, index: usize, source: []f64, destination: []f64) void { - assert(source.len == destination.len); - assert(source.len <= SampleList.Block.capacity); + pub fn apply(self: Transformation, project: *Project, from_block: Block.Id, block_count: usize) !void { + const source_list = project.sample_lists.get(self.source) orelse return error.NoSource; + const destination_list = project.sample_lists.get(self.destination) orelse return error.NoDestination; - const gain_changes = self.gain_changes.constSlice(); - - const gain_segment = Transform.findGainSegment(gain_changes, index); - if (gain_segment.to > @as(f64, @floatFromInt(index+source.len))) { - for (0..source.len) |i| { - destination[i] = source[i] * (100 / gain_segment.gain); - } - - } else { - const gain_per_sample = Transform.createGainLookup(gain_changes, index, source.len); - - for (0..source.len) |i| { - destination[i] = source[i] * (100 / gain_per_sample[i]); - } + var divider: ?*SampleList = null; + if (self.divider) |divider_id| { + divider = project.sample_lists.get(divider_id); } - } - pub fn applyBlock(self: Transform, index: usize, source: *SampleList.Block, destination: *SampleList.Block) void { - destination.clear(); - destination.len = source.len; + try destination_list.ensureTotalBlocks(@min(from_block + block_count, source_list.blocks.items.len)); - self.applySlice(index, source.samplesSlice(), destination.samplesSlice()); - destination.recomputeMinMax(); - } + var empty_block_buffer: Block.Buffer = undefined; + var empty_block = Block{ .buffer = &empty_block_buffer }; - pub fn applySampleList(self: Transform, from_block_index: SampleList.Block.Id, block_count: usize, source: *SampleList, destination: *SampleList) void { for (0..block_count) |block_offset| { - const block_id = from_block_index + block_offset; + const block_id = from_block + block_offset; + const index = block_id * SampleList.Block.capacity; - const source_block = source.getBlock(block_id).?; - const destination_block = destination.getBlock(block_id).?; + const destination_block = destination_list.getBlock(block_id) orelse continue; + destination_block.clear(); - self.applyBlock(block_id * SampleList.Block.capacity, source_block, destination_block); + const source_block = source_list.getBlock(block_id) orelse continue; + destination_block.len = source_block.len; + + const source_slice = source_block.samplesSlice(); + const destination_slice = destination_block.samplesSlice(); + + assert(source_block.len == destination_block.len); + assert(source_block.len <= SampleList.Block.capacity); + + var divider_block = &empty_block; + if (divider) |sample_list| { + divider_block = sample_list.getBlock(block_id) orelse &empty_block; + } + const divider_slice = divider_block.samplesSlice(); + + const gain_changes = self.gain_changes.constSlice(); + + const gain_segment = Transformation.findGainSegment(gain_changes, index); + if (gain_segment.to > @as(f64, @floatFromInt(index+source_slice.len))) { + for (0..source_slice.len) |i| { + destination_slice[i] = source_slice[i] * (100 / gain_segment.gain); + + const divider_sample = if (i < divider_slice.len) divider_slice[i] else 1; + if (@abs(divider_sample) > 0.03) { + destination_slice[i] /= divider_sample; + } + } + + } else { + const gain_per_sample = Transformation.createGainLookup(gain_changes, index, source_slice.len); + + for (0..source_slice.len) |i| { + destination_slice[i] = source_slice[i] * (100 / gain_per_sample[i]); + + const divider_sample = if (i < divider_slice.len) divider_slice[i] else 1; + if (@abs(divider_sample) > 0.03) { + destination_slice[i] /= divider_sample; + } + } + } + + destination_block.recomputeMinMax(); } } @@ -309,11 +340,6 @@ pub const Transform = struct { return result; } - pub fn eql(self: Transform, other: Transform) bool { - _ = self; - _ = other; - return false; - } }; pub const SampleList = struct { @@ -577,7 +603,7 @@ pub const SampleList = struct { } } - pub fn apply(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: usize, transform: Transform) void { + pub fn apply(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: usize, transform: Transformation) void { transform.applySampleList(from_block_index, block_count, source, self); } @@ -728,6 +754,7 @@ pub const File = struct { pub const View = struct { pub const Reference = union(enum) { + mirror: Id, // Sample list ID file: Id, channel: Id }; @@ -749,8 +776,8 @@ pub const View = struct { unit: ?NIDaq.Unit = .Voltage, transformed_samples: Id, - computed_transform: Transform = .{}, - invalidated_transform_ranges: std.BoundedArray(RangeF64, 16) = .{}, + transformation: Transformation, + dirty_transformation_ranges: std.BoundedArray(RangeF64, 16) = .{}, pub fn clear(self: *View) void { self.graph_cache.clear(); @@ -801,7 +828,7 @@ pub const Project = struct { protocol_events: std.BoundedArray(ProtocolEvent, 32) = .{}, solutions: std.ArrayListUnmanaged(Solution) = .{}, - gain_changes: Transform.GainChanges = .{}, + gain_changes: Transformation.GainChanges = .{}, sample_lists: GenerationalArray(SampleList) = .{}, channels: GenerationalArray(Channel) = .{}, @@ -851,6 +878,9 @@ pub const Project = struct { .file => |file_id| { const file = self.files.get(file_id).?; return file.samples_id; + }, + .mirror => |sample_list_id| { + return sample_list_id; } } } @@ -887,10 +917,19 @@ pub const Project = struct { return try self.sample_lists.insert(SampleList.init(allocator)); } - pub fn removeSampleList(self: *Project, sample_list_id: Id) void { + pub fn removeSampleList(self: *Project, allocator: std.mem.Allocator, sample_list_id: Id) void { const sample_list = self.sample_lists.get(sample_list_id) orelse return; sample_list.deinit(); self.sample_lists.remove(sample_list_id); + + + var view_iter = self.views.idIterator(); + while (view_iter.next()) |view_id| { + const view = self.views.get(view_id).?; + if (view.reference == .mirror and view.reference.mirror.eql(sample_list_id)) { + self.removeView(allocator, view_id); + } + } } pub fn readF64SliceFromFile(self: *Project, allocator: Allocator, file: std.fs.File) ![]f64 { @@ -948,7 +987,7 @@ pub const Project = struct { pub fn removeFile(self: *Project, allocator: std.mem.Allocator, file_id: Id) void { const file = self.files.get(file_id) orelse return; - self.removeSampleList(file.samples_id); + self.removeSampleList(allocator, file.samples_id); file.deinit(allocator); self.files.remove(file_id); @@ -958,7 +997,7 @@ pub const Project = struct { pub fn removeChannel(self: *Project, allocator: std.mem.Allocator, channel_id: Id) void { const channel = self.channels.get(channel_id) orelse return; - self.removeSampleList(channel.collected_samples_id); + self.removeSampleList(allocator, channel.collected_samples_id); channel.deinit(allocator); self.channels.remove(channel_id); @@ -968,7 +1007,7 @@ pub const Project = struct { pub fn removeView(self: *Project, allocator: std.mem.Allocator, view_id: Id) void { const view = self.views.get(view_id) orelse return; - self.removeSampleList(view.transformed_samples); + self.removeSampleList(allocator, view.transformed_samples); switch (view.reference) { .file => |file_id| { @@ -976,6 +1015,9 @@ pub const Project = struct { }, .channel => |channel_id| { self.removeChannel(allocator, channel_id); + }, + .mirror => { + // Do nothing } } @@ -1031,13 +1073,20 @@ pub const Project = struct { } } - pub fn getViewTransform(self: *Project, view_id: Id) Transform { + pub fn getViewTransform(self: *Project, view_id: Id) Transformation { _ = view_id; - return Transform{ + return Transformation{ .gain_changes = self.gain_changes }; } + fn invalidateRangeOnAllViews(self: *Project, range: RangeF64) void { + var view_iter = self.views.iterator(); + while (view_iter.next()) |view| { + view.dirty_transformation_ranges.append(range) catch continue; + } + } + pub fn removeGainChange(self: *Project, index: usize) void { const gain_change = self.gain_changes.orderedRemove(index); @@ -1049,14 +1098,11 @@ pub const Project = struct { const invalidate_range = RangeF64.init(invalidate_lower_bound, invalidate_upper_bound); - var view_iter = self.views.iterator(); - while (view_iter.next()) |view| { - view.invalidated_transform_ranges.append(invalidate_range) catch continue; - } + self.invalidateRangeOnAllViews(invalidate_range); } pub fn updateGainChange(self: *Project, index: usize, sample_index: f64) void { - const gain_change: *Transform.GainChange = &self.gain_changes.slice()[index]; + const gain_change: *Transformation.GainChange = &self.gain_changes.slice()[index]; if (index > 0) { if (self.gain_changes.buffer[index - 1].sample + SampleList.Block.capacity > sample_index) { @@ -1078,10 +1124,7 @@ pub const Project = struct { @max(previous_sample_index, sample_index) ); - var view_iter = self.views.iterator(); - while (view_iter.next()) |view| { - view.invalidated_transform_ranges.append(invalidated_range) catch continue; - } + self.invalidateRangeOnAllViews(invalidated_range); } // ------------------- Serialization ------------------ // @@ -1128,7 +1171,7 @@ pub const Project = struct { defer allocator.free(channel_name); const sample_list_id = try self.addSampleList(allocator); - errdefer self.removeSampleList(sample_list_id); + errdefer self.removeSampleList(allocator, sample_list_id); const channel = self.channels.get(id).?; channel.* = Channel{ @@ -1149,7 +1192,7 @@ pub const Project = struct { errdefer allocator.free(path); const sample_list_id = try self.addSampleList(allocator); - errdefer self.removeSampleList(sample_list_id); + errdefer self.removeSampleList(allocator, sample_list_id); const file = self.files.get(id).?; file.* = File{ @@ -1170,27 +1213,41 @@ pub const Project = struct { defer allocator.free(name); const reference_tag = try readInt(reader, u8); + var reference_sample_list_id: Id = undefined; + var divider: ?Id = null; var reference: View.Reference = undefined; if (reference_tag == @intFromEnum(View.Reference.file)) { - reference = .{ - .file = try readId(reader) - }; + const file_id = try readId(reader); + reference = .{ .file = file_id }; + reference_sample_list_id = self.files.get(file_id).?.samples_id; } else if (reference_tag == @intFromEnum(View.Reference.channel)) { - reference = .{ - .channel = try readId(reader) - }; + const channel_id = try readId(reader); + reference = .{ .channel = channel_id }; + reference_sample_list_id = self.channels.get(channel_id).?.collected_samples_id; + } else if (reference_tag == @intFromEnum(View.Reference.mirror)) { + const sample_list_id = try readId(reader); + reference = .{ .mirror = sample_list_id }; + reference_sample_list_id = sample_list_id; + + divider = try readId(reader); } else { return error.InvalidReferenceTag; } + assert(self.sample_lists.get(reference_sample_list_id) != null); const sample_list_id = try self.addSampleList(allocator); - errdefer self.removeSampleList(sample_list_id); + errdefer self.removeSampleList(allocator, sample_list_id); const view = self.views.get(id).?; view.* = View{ .reference = reference, .transformed_samples = sample_list_id, - .name = try View.Name.fromSlice(name) + .name = try View.Name.fromSlice(name), + .transformation = Transformation{ + .destination = sample_list_id, + .source = reference_sample_list_id, + .divider = divider + } }; } } @@ -1243,6 +1300,10 @@ pub const Project = struct { }, .file => |file_id| { try writeInt(writer, u32, file_id.asInt()); + }, + .mirror => |sample_list_id| { + try writeId(writer, sample_list_id); + try writeId(writer, view.transformation.divider.?); } } } @@ -1351,119 +1412,144 @@ pub const CollectionTask = struct { }; const WorkJob = struct { - const Stage = enum { - init, - launch_threads, - finished - }; - view_id: Id, - stage: Stage = .init, - transform: Transform = .{}, + transformation: Transformation, + from_block: SampleList.Block.Id, + block_count: usize, + running_threads: std.Thread.WaitGroup = .{}, - mutex: std.Thread.Mutex = .{}, - running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{}, - - processed_up_to: u32 = 0, - - pub fn appendRunningThread(self: *WorkJob, allocator: Allocator, block_id: SampleList.Block.Id) !void { - self.mutex.lock(); - defer self.mutex.unlock(); - - try self.running_thread_jobs.append(allocator, block_id); - } - - pub fn removeRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) void { - self.mutex.lock(); - defer self.mutex.unlock(); - - if (std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id)) |index| { - _ = self.running_thread_jobs.swapRemove(index); - } - } - - pub fn getRunningThreadCount(self: *WorkJob) usize { - self.mutex.lock(); - defer self.mutex.unlock(); - - return self.running_thread_jobs.items.len; - } - - pub fn containsRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) bool { - self.mutex.lock(); - defer self.mutex.unlock(); - - return std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id) != null; + pub fn init(transformation: Transformation, from_block: SampleList.Block.Id, block_count: usize) WorkJob { + return WorkJob{ + .transformation = transformation, + .from_block = from_block, + .block_count = block_count, + }; } pub fn deinit(self: *WorkJob, allocator: Allocator) void { - self.running_thread_jobs.deinit(allocator); + _ = self; + _ = allocator; } - pub fn update(self: *WorkJob, app: *App) !bool { - if (self.stage == .finished) { - return true; - } - - const project = &app.project; - const view = project.views.get(self.view_id) orelse return true; - - const sample_list_id = project.getViewReferenceSampleListId(self.view_id); - const sample_list = project.sample_lists.get(sample_list_id).?; - - if (!Transform.eql(project.getViewTransform(self.view_id), self.transform)) { - // Transforms changed, job needs to be cancelled. - return true; - } - - switch (self.stage) { - .init => { - const transformed_samples = project.sample_lists.get(view.transformed_samples).?; - try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len); - - self.stage = .launch_threads; - }, - .launch_threads => { - const max_block_to_process = 256; - - while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) { - const block_id = self.processed_up_to; - const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process); - self.processed_up_to += block_count; - - try app.work_thread_pool.spawn(workThread, .{ self, project, block_id, block_count }); - try self.appendRunningThread(app.allocator, block_id); - } - - if (self.processed_up_to == sample_list.blocks.items.len) { - return true; - } - }, - .finished => unreachable - } - - return false; - } - - fn workThread(self: *WorkJob, project: *Project, from_block_id: SampleList.Block.Id, block_count: SampleList.Block.Len) void { - defer self.removeRunningThread(from_block_id); - - // var timer = std.time.Timer.start() catch unreachable; - // defer { - // const duration = timer.read(); - // std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms }); - // } - - const view = project.views.get(self.view_id) orelse return; - const transformed_samples = project.sample_lists.get(view.transformed_samples) orelse return; - - const sample_list_id = project.getViewReferenceSampleListId(self.view_id); - const sample_list = project.sample_lists.get(sample_list_id) orelse return; - - transformed_samples.apply(sample_list, from_block_id, block_count, self.transform); + pub fn tick(self: *WorkJob) void { + _ = self; } }; +// const WorkJob = struct { +// const Stage = enum { +// init, +// launch_threads, +// finished +// }; + +// view_id: Id, +// stage: Stage = .init, +// transform: Transform = .{}, + +// mutex: std.Thread.Mutex = .{}, +// running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{}, + +// processed_up_to: u32 = 0, + +// pub fn appendRunningThread(self: *WorkJob, allocator: Allocator, block_id: SampleList.Block.Id) !void { +// self.mutex.lock(); +// defer self.mutex.unlock(); + +// try self.running_thread_jobs.append(allocator, block_id); +// } + +// pub fn removeRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) void { +// self.mutex.lock(); +// defer self.mutex.unlock(); + +// if (std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id)) |index| { +// _ = self.running_thread_jobs.swapRemove(index); +// } +// } + +// pub fn getRunningThreadCount(self: *WorkJob) usize { +// self.mutex.lock(); +// defer self.mutex.unlock(); + +// return self.running_thread_jobs.items.len; +// } + +// pub fn containsRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) bool { +// self.mutex.lock(); +// defer self.mutex.unlock(); + +// return std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id) != null; +// } + +// pub fn deinit(self: *WorkJob, allocator: Allocator) void { +// self.running_thread_jobs.deinit(allocator); +// } + +// pub fn update(self: *WorkJob, app: *App) !bool { +// if (self.stage == .finished) { +// return true; +// } + +// const project = &app.project; +// const view = project.views.get(self.view_id) orelse return true; + +// const sample_list_id = project.getViewReferenceSampleListId(self.view_id); +// const sample_list = project.sample_lists.get(sample_list_id).?; + +// if (!Transform.eql(project.getViewTransform(self.view_id), self.transform)) { +// // Transforms changed, job needs to be cancelled. +// return true; +// } + +// switch (self.stage) { +// .init => { +// const transformed_samples = project.sample_lists.get(view.transformed_samples).?; +// try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len); + +// self.stage = .launch_threads; +// }, +// .launch_threads => { +// const max_block_to_process = 256; + +// while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) { +// const block_id = self.processed_up_to; +// const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process); +// self.processed_up_to += block_count; + +// try app.work_thread_pool.spawn(workThread, .{ self, project, block_id, block_count }); +// try self.appendRunningThread(app.allocator, block_id); +// } + +// if (self.processed_up_to == sample_list.blocks.items.len) { +// return true; +// } +// }, +// .finished => unreachable +// } + +// return false; +// } + +// fn workThread(self: *WorkJob, project: *Project, from_block_id: SampleList.Block.Id, block_count: SampleList.Block.Len) void { +// defer self.removeRunningThread(from_block_id); + +// // var timer = std.time.Timer.start() catch unreachable; +// // defer { +// // const duration = timer.read(); +// // std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms }); +// // } + +// const view = project.views.get(self.view_id) orelse return; +// const transformed_samples = project.sample_lists.get(view.transformed_samples) orelse return; + +// const sample_list_id = project.getViewReferenceSampleListId(self.view_id); +// const sample_list = project.sample_lists.get(sample_list_id) orelse return; + +// transformed_samples.apply(sample_list, from_block_id, block_count, self.transform); +// } +// }; + allocator: Allocator, ui: UI, double_pass_ui: bool = true, @@ -1857,37 +1943,78 @@ pub fn tick(self: *App) !void { try self.showUI(); { - const block_size = SampleList.Block.capacity; - var view_iter = self.project.views.idIterator(); while (view_iter.next()) |view_id| { const view = self.getView(view_id).?; + view.transformation.gain_changes = self.project.gain_changes; - const referenced_samples_id = self.project.getViewReferenceSampleListId(view_id); - const referenced_samples = self.project.sample_lists.get(referenced_samples_id).?; - const computed_samples_id = view.transformed_samples; - const computed_samples = self.project.sample_lists.get(computed_samples_id).?; + const source_id = view.transformation.source; + const source = self.project.sample_lists.get(source_id).?; + const source_len = source.getLength(); - const transform = self.project.getViewTransform(view_id); - const computed_len = computed_samples.getLength(); - const reference_len = referenced_samples.getLength(); + const destination_id = view.transformation.destination; + const destination = self.project.sample_lists.get(destination_id).?; + const destination_len = destination.getLength(); - if (reference_len > computed_len) { - view.invalidated_transform_ranges.append(RangeF64.init(@floatFromInt(computed_len), @floatFromInt(reference_len))) catch {}; + if (source_len != destination_len) { + if (view.transformation.divider) |divider| { + const divider_list = self.project.sample_lists.get(divider).?; + if (divider_list.getLength() < source_len) { + continue; + } + } + + const source_len_f64: f64 = @max(0, @as(f64, @floatFromInt(source_len)) - 1); + const destination_len_f64: f64 = @max(0, @as(f64, @floatFromInt(destination_len)) - 1); + const range = RangeF64.init(@min(source_len_f64, destination_len_f64), @max(source_len_f64, destination_len_f64)); + view.dirty_transformation_ranges.append(range) catch {}; + + // var other_view_iter = self.project.views.idIterator(); + // while (other_view_iter.next()) |other_view_id| { + // const other_view = self.project.views.get(other_view_id).?; + // if (other_view.reference == .mirror and other_view.reference.mirror.eql(source_id)) { + // view.dirty_transformation_ranges.append(range) catch {}; + // } + // } + } + } + } + + { + const block_size = SampleList.Block.capacity; + + var view_iter = self.project.views.iterator(); + while (view_iter.next()) |view| { + const source_id = view.transformation.source; + const source = self.project.sample_lists.get(source_id).?; + const source_len = source.getLength(); + + var last_source_block: usize = 0; + if (source_len > 0) { + last_source_block = source_len - 1; } - for (view.invalidated_transform_ranges.constSlice()) |range| { - const from_block: usize = @intFromFloat(@divFloor(range.lower, block_size)); - const to_block: usize = @intFromFloat(@divFloor(@min(range.upper, @as(f64, @floatFromInt(reference_len)) - 1), block_size)); - if (from_block > to_block) continue; + for (view.dirty_transformation_ranges.constSlice()) |range| { + var from_block = last_source_block; + if (!std.math.isInf(range.lower) and range.lower >= 0) { + from_block = @intFromFloat(@divFloor(range.lower, block_size)); + + } + + var to_block = last_source_block; + if (!std.math.isInf(range.upper) and range.upper >= 0) { + to_block = @intFromFloat(@divFloor(range.upper, block_size)); + } + + if (from_block > to_block) { + continue; + } + const block_count = to_block - from_block + 1; - - try computed_samples.ensureTotalBlocks(from_block + block_count); - - transform.applySampleList(@intCast(from_block), block_count, referenced_samples, computed_samples); - view.graph_cache.invalidateRange(range); + try view.transformation.apply(&self.project, from_block, block_count); + view.graph_cache.invalidate(); } - view.invalidated_transform_ranges.len = 0; + view.dirty_transformation_ranges.len = 0; } } } @@ -1954,43 +2081,43 @@ pub fn tick(self: *App) !void { } } - { - var view_iter = self.project.views.idIterator(); - while (view_iter.next()) |view_id| { - const view = self.project.views.get(view_id).?; - _ = view; + // { + // var view_iter = self.project.views.idIterator(); + // while (view_iter.next()) |view_id| { + // const view = self.project.views.get(view_id).?; + // _ = view; - // TODO: - // if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) { - // continue; - // } + // // TODO: + // // if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) { + // // continue; + // // } - // _ = try self.work_jobs.insert(WorkJob{ - // .transforms = view.transforms, - // .view_id = view_id - // }); + // // _ = try self.work_jobs.insert(WorkJob{ + // // .transforms = view.transforms, + // // .view_id = view_id + // // }); - // view.computed_transforms.len = 0; - // view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice()); - } - } + // // view.computed_transforms.len = 0; + // // view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice()); + // } + // } - { - var work_job_iter = self.work_jobs.idIterator(); - while (work_job_iter.next()) |work_job_id| { - const work_job = self.work_jobs.get(work_job_id).?; - const job_done = try work_job.update(self); + // { + // var work_job_iter = self.work_jobs.idIterator(); + // while (work_job_iter.next()) |work_job_id| { + // const work_job = self.work_jobs.get(work_job_id).?; + // const job_done = try work_job.update(self); - if (job_done) { - work_job.stage = .finished; - if (work_job.getRunningThreadCount() == 0) { - std.debug.print("job done {}\n", .{work_job_id}); - work_job.deinit(self.allocator); - self.work_jobs.remove(work_job_id); - } - } - } - } + // if (job_done) { + // work_job.stage = .finished; + // if (work_job.getRunningThreadCount() == 0) { + // std.debug.print("job done {}\n", .{work_job_id}); + // work_job.deinit(self.allocator); + // self.work_jobs.remove(work_job_id); + // } + // } + // } + // } } pub fn pushCommand(self: *App, command: Command) void { @@ -2268,7 +2395,7 @@ pub fn addFile(self: *App, path: []const u8) !Id { errdefer self.allocator.free(path_dupe); const sample_list_id = try self.project.addSampleList(self.allocator); - errdefer self.project.removeSampleList(sample_list_id); + errdefer self.project.removeSampleList(self.allocator, sample_list_id); const file = self.project.files.get(id).?; file.* = File{ @@ -2314,7 +2441,7 @@ pub fn addChannel(self: *App, channel_name: []const u8) !Id { errdefer self.project.channels.remove(id); const sample_list_id = try self.project.addSampleList(self.allocator); - errdefer self.project.removeSampleList(sample_list_id); + errdefer self.project.removeSampleList(self.allocator, sample_list_id); const channel = self.project.channels.get(id).?; channel.* = Channel{ @@ -2412,12 +2539,23 @@ pub fn addView(self: *App, reference: View.Reference) !Id { errdefer self.project.views.remove(id); const sample_list_id = try self.project.addSampleList(self.allocator); - errdefer self.project.removeSampleList(sample_list_id); + errdefer self.project.removeSampleList(self.allocator, sample_list_id); + + const reference_sample_list_id = switch (reference) { + .channel => |channel_id| self.project.channels.get(channel_id).?.collected_samples_id, + .file => |file_id| self.project.files.get(file_id).?.samples_id, + .mirror => |list_id| list_id, + }; + assert(self.project.sample_lists.get(reference_sample_list_id) != null); const view = self.project.views.get(id).?; view.* = View{ .reference = reference, - .transformed_samples = sample_list_id + .transformed_samples = sample_list_id, + .transformation = .{ + .source = reference_sample_list_id, + .destination = sample_list_id, + } }; self.loadView(id) catch |e| { @@ -2433,6 +2571,9 @@ fn refreshViewAvailableXYRanges(self: *App, id: Id) void { const sample_list = self.project.sample_lists.get(sample_list_id).?; view.available_x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength())); + if (view.available_x_range.size() == 0) { + view.available_x_range = RangeF64.init(0, 1); + } switch (view.reference) { .channel => |channel_id| if (self.getChannel(channel_id)) |channel| { @@ -2444,6 +2585,9 @@ fn refreshViewAvailableXYRanges(self: *App, id: Id) void { const min_sample = sample_list.min orelse 0; const max_sample = sample_list.max orelse 1; view.available_y_range = RangeF64.init(max_sample, min_sample); + }, + .mirror => { + view.available_y_range = RangeF64.init(sample_list.max orelse 10, sample_list.min orelse -10); } } } diff --git a/src/components/view.zig b/src/components/view.zig index 424544a..49f7ee4 100644 --- a/src/components/view.zig +++ b/src/components/view.zig @@ -163,62 +163,67 @@ fn showToolbar(ctx: Context, view_id: Id) void { } } - if (view.reference == .channel) { - const channel_id = view.reference.channel; - const channel = ctx.app.getChannel(channel_id).?; - const channel_name = utils.getBoundedStringZ(&channel.name); - // const channel_type = NIDaq.getChannelType(channel_name).?; + switch (view.reference) { + .channel => |channel_id| { + const channel = ctx.app.getChannel(channel_id).?; + const channel_name = utils.getBoundedStringZ(&channel.name); + // const channel_type = NIDaq.getChannelType(channel_name).?; - { - const follow = ui.textButton("Follow"); - follow.background = srcery.hard_black; - if (view.follow) { - follow.borders = UI.Borders.bottom(.{ - .color = srcery.green, - .size = 4 - }); - } - if (ui.signal(follow).clicked()) { - view.follow = !view.follow; + { + const follow = ui.textButton("Follow"); + follow.background = srcery.hard_black; + if (view.follow) { + follow.borders = UI.Borders.bottom(.{ + .color = srcery.green, + .size = 4 + }); + } + if (ui.signal(follow).clicked()) { + view.follow = !view.follow; + } } + + // if (channel_type == .analog_output) { + // const button = ui.button(ui.keyFromString("Output generation")); + // button.texture = Assets.output_generation; + // button.size.y = UI.Sizing.initGrowFull(); + + // const signal = ui.signal(button); + // if (signal.clicked()) { + // if (ctx.app.isChannelOutputing(channel_id)) { + // ctx.app.pushCommand(.{ + // .stop_output = channel_id + // }); + // } else { + // ctx.view_controls.view_protocol_modal = view_id; + // } + // } + + // var color = rl.Color.white; + // if (ctx.app.isChannelOutputing(channel_id)) { + // color = srcery.red; + // } + + // if (signal.active) { + // button.texture_color = color.alpha(0.6); + // } else if (signal.hot) { + // button.texture_color = color.alpha(0.8); + // } else { + // button.texture_color = color; + // } + // } + + view_name = channel_name; + }, + .file => |file_id| { + const file = ctx.app.getFile(file_id).?; + + view_name = std.fs.path.stem(file.path); + }, + .mirror => |sample_list_id| { + _ = sample_list_id; + view_name = "Proportional"; } - - // if (channel_type == .analog_output) { - // const button = ui.button(ui.keyFromString("Output generation")); - // button.texture = Assets.output_generation; - // button.size.y = UI.Sizing.initGrowFull(); - - // const signal = ui.signal(button); - // if (signal.clicked()) { - // if (ctx.app.isChannelOutputing(channel_id)) { - // ctx.app.pushCommand(.{ - // .stop_output = channel_id - // }); - // } else { - // ctx.view_controls.view_protocol_modal = view_id; - // } - // } - - // var color = rl.Color.white; - // if (ctx.app.isChannelOutputing(channel_id)) { - // color = srcery.red; - // } - - // if (signal.active) { - // button.texture_color = color.alpha(0.6); - // } else if (signal.hot) { - // button.texture_color = color.alpha(0.8); - // } else { - // button.texture_color = color; - // } - // } - - view_name = channel_name; - } else if (view.reference == .file) { - const file_id = view.reference.file; - const file = ctx.app.getFile(file_id).?; - - view_name = std.fs.path.stem(file.path); } if (view.name.len > 0) { diff --git a/src/main.zig b/src/main.zig index a1cffad..c750a36 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,7 +12,7 @@ const raylib_h = @cImport({ }); const log = std.log; -const profiler_enabled = builtin.mode == .Debug; +const profiler_enabled = true; //builtin.mode == .Debug; // TODO: Maybe move this to a config.zig or options.zig file. // Have all of the contstants in a single file. diff --git a/src/screens/main_screen.zig b/src/screens/main_screen.zig index a659b92..2a491d5 100644 --- a/src/screens/main_screen.zig +++ b/src/screens/main_screen.zig @@ -73,6 +73,10 @@ view_name_input: UI.TextInputStorage, channel_save_file_picker: ?Platform.FilePickerId = null, file_save_file_picker: ?Platform.FilePickerId = null, +// Proportional modal +proportion_view1: ?Id = null, // View ID +proportion_view2: ?Id = null, // View ID + pub fn init(app: *App) !MainScreen { const allocator = app.allocator; @@ -422,7 +426,7 @@ fn showProportionalAdd(self: *MainScreen) !void { .key = ui.keyFromString("Proportional add modal"), .background = srcery.black, .size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }), - .size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }), + .size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 200 }), .layout_direction = .top_to_bottom, .padding = UI.Padding.all(ui.rem(1.5)), .flags = &.{ .clickable }, @@ -431,6 +435,169 @@ fn showProportionalAdd(self: *MainScreen) !void { container.beginChildren(); defer container.endChildren(); defer _ = ui.signal(container); + + if (self.proportion_view1 != null and self.app.project.views.get(self.proportion_view1.?) == null) { + self.proportion_view1 = null; + } + + if (self.proportion_view2 != null and self.app.project.views.get(self.proportion_view2.?) == null) { + self.proportion_view2 = null; + } + + var arena = std.heap.ArenaAllocator.init(self.app.allocator); + defer arena.deinit(); + + var available_views = std.ArrayList(struct { view_id: App.Id, name: []const u8 }).init(arena.allocator()); + + { + var view_iter = self.app.project.views.idIterator(); + while (view_iter.next()) |view_id| { + const view = self.app.project.views.get(view_id).?; + if (view.reference != .channel) { + continue; + } + const channel_id = view.reference.channel; + const channel = self.app.project.channels.get(channel_id).?; + const view_name = utils.getBoundedStringZ(&channel.name); + + try available_views.append(.{ .view_id = view_id, .name = view_name }); + } + } + + { + var row = ui.createBox(.{ + .size_x = UI.Sizing.initGrowFull(), + .size_y = UI.Sizing.initFitChildren(), + .layout_direction = .left_to_right, + .layout_gap = ui.rem(0.5), + }); + row.beginChildren(); + defer row.endChildren(); + + { + const select = ui.textButton("Select dividend"); + if (self.proportion_view1) |view_id| { + const view = self.app.project.views.get(view_id).?; + assert(view.reference == .channel); + const channel = self.app.project.channels.get(view.reference.channel).?; + select.setFmtText("Dividend: {s}", .{utils.getBoundedStringZ(&channel.name)}); + } + + select.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 }); + const signal = ui.signal(select); + if (signal.clicked()) { + select.persistent.open = !select.persistent.open; + } + + if (select.persistent.open) { + const popup = ui.createBox(.{ + .key = ui.keyFromString("Transform 1 popup"), + .size_x = UI.Sizing.initFixedPixels(ui.rem(10)), + .size_y = UI.Sizing.initFitChildren(), + .flags = &.{ .clickable, .scrollable }, + .layout_direction = .top_to_bottom, + .float_relative_to = select, + .background = srcery.black, + .borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }), + .draw_on_top = true + }); + popup.setFloatPosition(0, select.persistent.size.y); + popup.beginChildren(); + defer popup.endChildren(); + defer _ = ui.signal(popup); + + for (available_views.items) |option| { + const select_option = ui.textButton(option.name); + select_option.alignment.x = .start; + select_option.size.x = UI.Sizing.initGrowFull(); + select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 }); + select_option.background = srcery.black; + + if (ui.signal(select_option).clicked()) { + select.persistent.open = false; + self.proportion_view1 = option.view_id; + } + } + } + + if (signal.clicked_outside) { + select.persistent.open = false; + } + } + + { + const select = ui.textButton("Select divisor"); + if (self.proportion_view2) |view_id| { + const view = self.app.project.views.get(view_id).?; + assert(view.reference == .channel); + const channel = self.app.project.channels.get(view.reference.channel).?; + select.setFmtText("Divisor: {s}", .{utils.getBoundedStringZ(&channel.name)}); + } + + select.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 }); + const signal = ui.signal(select); + if (signal.clicked()) { + select.persistent.open = !select.persistent.open; + } + + if (select.persistent.open) { + const popup = ui.createBox(.{ + .key = ui.keyFromString("Transform 1 popup"), + .size_x = UI.Sizing.initFixedPixels(ui.rem(10)), + .size_y = UI.Sizing.initFitChildren(), + .flags = &.{ .clickable, .scrollable }, + .layout_direction = .top_to_bottom, + .float_relative_to = select, + .background = srcery.black, + .borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }), + .draw_on_top = true + }); + popup.setFloatPosition(0, select.persistent.size.y); + popup.beginChildren(); + defer popup.endChildren(); + defer _ = ui.signal(popup); + + for (available_views.items) |option| { + const select_option = ui.textButton(option.name); + select_option.alignment.x = .start; + select_option.size.x = UI.Sizing.initGrowFull(); + select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 }); + select_option.background = srcery.black; + + if (ui.signal(select_option).clicked()) { + select.persistent.open = false; + self.proportion_view2 = option.view_id; + } + } + } + + if (signal.clicked_outside) { + select.persistent.open = false; + } + } + } + + { + const btn = ui.textButton("Confirm"); + btn.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 4 }); + if (self.proportion_view1 != null and self.proportion_view2 != null) { + btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 }); + + if (ui.signal(btn).clicked()) { + self.modal = null; + const view1 = self.app.project.views.get(self.proportion_view1.?).?; + const channel1 = self.app.project.channels.get(view1.reference.channel).?; + const view2 = self.app.project.views.get(self.proportion_view2.?).?; + const channel2 = self.app.project.channels.get(view2.reference.channel).?; + + const new_view_id = try self.app.addView(.{ .mirror = channel1.collected_samples_id }); + const new_view = self.app.project.views.get(new_view_id).?; + new_view.transformation.divider = channel2.collected_samples_id; + } + } + } + + } // fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void { @@ -742,6 +909,9 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void { file.path = path; self.app.pushCommand(.{ .reload_file = file_id }); } + }, + .mirror => |sample_list_id| { + _ = sample_list_id; } }