From 030573df27b6bdf242244ec6ff10a940edf5a5ad Mon Sep 17 00:00:00 2001 From: Arduano Date: Fri, 20 Oct 2023 02:16:52 +1100 Subject: [PATCH] Count passed notes properly --- src/gui/window/stats.rs | 40 ++++++++++---------------- src/midi/cake/mod.rs | 14 ++++++++- src/midi/live/column.rs | 7 +++++ src/midi/live/mod.rs | 40 +++++++++++++++----------- src/midi/live/view.rs | 17 +++++++++++ src/midi/mod.rs | 8 +++++- src/midi/ram/column.rs | 18 +++++++++--- src/midi/ram/mod.rs | 22 ++++---------- src/midi/ram/view.rs | 64 +++++++++++++++++++++++++---------------- 9 files changed, 142 insertions(+), 88 deletions(-) diff --git a/src/gui/window/stats.rs b/src/gui/window/stats.rs index 26f13c7..b5d0aba 100644 --- a/src/gui/window/stats.rs +++ b/src/gui/window/stats.rs @@ -5,7 +5,6 @@ use crate::{gui::window::GuiWasabiWindow, midi::MIDIFileBase}; pub struct GuiMidiStats { time_passed: f64, time_total: f64, - notes_total: u64, notes_on_screen: u64, voice_count: u64, } @@ -15,7 +14,6 @@ impl GuiMidiStats { GuiMidiStats { time_passed: 0.0, time_total: 0.0, - notes_total: 0, notes_on_screen: 0, voice_count: 0, } @@ -59,7 +57,7 @@ pub fn draw_stats(win: &mut GuiWasabiWindow, ctx: &Context, pos: Pos2, mut stats let mut length_sec: u64 = 0; let mut length_min: u64 = 0; - let mut load_type = -1; // 0=RAM, 1=Live + let mut note_stats = Default::default(); if let Some(midi_file) = win.midi_file.as_mut() { stats.time_total = if let Some(length) = midi_file.midi_length() { @@ -83,14 +81,7 @@ pub fn draw_stats(win: &mut GuiWasabiWindow, ctx: &Context, pos: Pos2, mut stats time_sec = stats.time_passed as u64 % 60; time_min = stats.time_passed as u64 / 60; - stats.notes_total = midi_file.stats().total_notes; - - // FIXME: Use an enum instead lmao - match midi_file { - crate::midi::MIDIFileUnion::InRam(..) => load_type = 0, - crate::midi::MIDIFileUnion::Cake(..) => load_type = 0, - crate::midi::MIDIFileUnion::Live(..) => load_type = 1, - } + note_stats = midi_file.stats(); } ui.horizontal(|ui| { @@ -130,21 +121,20 @@ pub fn draw_stats(win: &mut GuiWasabiWindow, ctx: &Context, pos: Pos2, mut stats }); }); - match load_type { - 1 => { - ui.horizontal(|ui| { - ui.monospace("Notes:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.monospace(format!("{}", 0)); - }); - }); - } - 0 => { - ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { - ui.monospace(format!("0 / {}", stats.notes_total)); - }); + fn num_or_q(num: Option) -> String { + if let Some(num) = num { + num.to_string() + } else { + "?".to_string() } - _ => {} } + + ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { + ui.monospace(format!( + "{} / {}", + num_or_q(note_stats.passed_notes), + num_or_q(note_stats.total_notes) + )); + }); }); } diff --git a/src/midi/cake/mod.rs b/src/midi/cake/mod.rs index 701a358..054ef03 100644 --- a/src/midi/cake/mod.rs +++ b/src/midi/cake/mod.rs @@ -211,7 +211,19 @@ impl MIDIFileBase for CakeMIDIFile { } fn stats(&self) -> MIDIFileStats { - MIDIFileStats::new(self.note_count) + let time = self.timer.get_time().as_secs_f64(); + let time_int = (time * self.ticks_per_second as f64) as u32; + + let passed_notes = self + .key_blocks() + .iter() + .map(|b| b.get_notes_passed_at(time_int) as u64) + .sum(); + + MIDIFileStats { + total_notes: Some(self.note_count), + passed_notes: Some(passed_notes), + } } fn signature(&self) -> &MIDIFileUniqueSignature { diff --git a/src/midi/live/column.rs b/src/midi/live/column.rs index 9bc9c91..529fcb4 100644 --- a/src/midi/live/column.rs +++ b/src/midi/live/column.rs @@ -8,6 +8,11 @@ pub struct InRamNoteColumnViewData { /// Exclusive end block for when notes go outside of view. /// We iterate over notes backwards, so we start at the block before this one and iterate to 0. pub end_block: usize, + + /// The number of blocks that have passed the keyboard in the current blocks vec + pub blocks_passed_keyboard_index: usize, + /// The number of notes that have passed the keyboard overall + pub notes_passed_keyboard: u64, } impl InRamNoteColumnViewData { @@ -15,6 +20,8 @@ impl InRamNoteColumnViewData { InRamNoteColumnViewData { rendered_notes: 0, end_block: 0, + blocks_passed_keyboard_index: 0, + notes_passed_keyboard: 0, } } } diff --git a/src/midi/live/mod.rs b/src/midi/live/mod.rs index 3644966..7d4e4e8 100644 --- a/src/midi/live/mod.rs +++ b/src/midi/live/mod.rs @@ -1,9 +1,8 @@ use std::{ - sync::{atomic::Ordering, Arc, RwLock}, + sync::{Arc, RwLock}, thread, }; -use atomic_float::AtomicF64; use midi_toolkit::{io::MIDIFile as TKMIDIFile, sequence::event::get_channels_array_statistics}; use crate::audio_playback::SimpleTemporaryPlayer; @@ -23,10 +22,15 @@ pub mod column; mod parse; pub mod view; +struct ParseStats { + length: f64, + note_count: u64, +} + pub struct LiveLoadMIDIFile { view_data: LiveNoteViewData, timer: TimeKeeper, - length: Arc, + stats: Arc>>, signature: MIDIFileUniqueSignature, } @@ -40,18 +44,19 @@ impl LiveLoadMIDIFile { let midi = TKMIDIFile::open_from_stream(file, None).unwrap(); - let parse_length_outer = Arc::new(AtomicF64::new(f64::NAN)); - let parse_length = parse_length_outer.clone(); + let stats_outer = Arc::new(RwLock::new(None)); + let stats = stats_outer.clone(); let ppq = midi.ppq(); let tracks = midi.iter_all_tracks().collect(); thread::spawn(move || { let stats = get_channels_array_statistics(tracks); if let Ok(stats) = stats { - parse_length.store( - stats.calculate_total_duration(ppq).as_secs_f64(), - Ordering::Relaxed, - ); + let mut parser_stats = stats_outer.write().unwrap(); + *parser_stats = Some(ParseStats { + length: stats.calculate_total_duration(ppq).as_secs_f64(), + note_count: stats.note_count(), + }); } }); @@ -63,7 +68,7 @@ impl LiveLoadMIDIFile { LiveLoadMIDIFile { view_data: file, timer, - length: parse_length_outer, + stats, signature, } } @@ -71,12 +76,8 @@ impl LiveLoadMIDIFile { impl MIDIFileBase for LiveLoadMIDIFile { fn midi_length(&self) -> Option { - let value = self.length.load(Ordering::Relaxed); - if value.is_nan() { - None - } else { - Some(value) - } + let data = self.stats.read().unwrap(); + data.as_ref().map(|data| data.length) } fn parsed_up_to(&self) -> Option { @@ -96,7 +97,12 @@ impl MIDIFileBase for LiveLoadMIDIFile { } fn stats(&self) -> MIDIFileStats { - MIDIFileStats::new(0) + let stats = self.stats.read().unwrap(); + + MIDIFileStats { + passed_notes: Some(self.view_data.passed_notes()), + total_notes: stats.as_ref().map(|stats| stats.note_count), + } } fn signature(&self) -> &MIDIFileUniqueSignature { diff --git a/src/midi/live/view.rs b/src/midi/live/view.rs index a325b71..3f42f6c 100644 --- a/src/midi/live/view.rs +++ b/src/midi/live/view.rs @@ -74,9 +74,19 @@ impl LiveNoteViewData { data.end_block += 1; } + while data.blocks_passed_keyboard_index < blocks.len() { + if blocks[data.blocks_passed_keyboard_index].start > new_view_range.start { + break; + } + data.notes_passed_keyboard += + blocks[data.blocks_passed_keyboard_index].notes.len() as u64; + data.blocks_passed_keyboard_index += 1; + } + while let Some(block) = blocks.front() { if block.max_end() < new_view_range.start { data.rendered_notes -= block.notes.len(); + data.blocks_passed_keyboard_index -= 1; blocks.pop_front(); // Unconditionally reduce this value because blocks that have an @@ -93,6 +103,13 @@ impl LiveNoteViewData { pub fn parse_time(&self) -> f64 { self.parser.parse_time() } + + pub fn passed_notes(&self) -> u64 { + self.columns + .iter() + .map(|column| column.data.notes_passed_keyboard) + .sum() + } } pub struct LiveNoteColumnView<'a> { diff --git a/src/midi/mod.rs b/src/midi/mod.rs index 750bfea..1d72ffd 100644 --- a/src/midi/mod.rs +++ b/src/midi/mod.rs @@ -16,10 +16,16 @@ use rand::Rng; pub use cake::{blocks::CakeBlock, intvec4::IntVector4, CakeMIDIFile, CakeSignature}; pub use live::LiveLoadMIDIFile; -pub use ram::{InRamMIDIFile, MIDIFileStats}; +pub use ram::InRamMIDIFile; use self::shared::timer::TimeKeeper; +#[derive(Debug, Clone, Copy, Default)] +pub struct MIDIFileStats { + pub total_notes: Option, + pub passed_notes: Option, +} + /// A struct that represents the view range of a midi screen render #[derive(Debug, Clone, Copy, Default)] pub struct MIDIViewRange { diff --git a/src/midi/ram/column.rs b/src/midi/ram/column.rs index 94481a6..cfa3f25 100644 --- a/src/midi/ram/column.rs +++ b/src/midi/ram/column.rs @@ -3,17 +3,27 @@ use std::ops::Range; use super::block::InRamNoteBlock; pub struct InRamNoteColumnViewData { - pub notes_to_end: usize, - pub notes_to_start: usize, + /// Number of notes from the beginning of the midi to the start of the render view + pub notes_to_render_end: u64, + /// Number of notes from the beginning of the midi to the end of the render view + pub notes_to_render_start: u64, + /// The range of blocks that are in the view pub block_range: Range, + + /// Number of notes that have passed the keyboard + pub notes_to_keyboard: u64, + /// Number of blocks that have passed the keyboard + pub blocks_to_keyboard: usize, } impl InRamNoteColumnViewData { pub fn new() -> Self { InRamNoteColumnViewData { - notes_to_end: 0, - notes_to_start: 0, + notes_to_render_end: 0, + notes_to_render_start: 0, block_range: 0..0, + notes_to_keyboard: 0, + blocks_to_keyboard: 0, } } } diff --git a/src/midi/ram/mod.rs b/src/midi/ram/mod.rs index 49b2abc..37fe508 100644 --- a/src/midi/ram/mod.rs +++ b/src/midi/ram/mod.rs @@ -1,7 +1,8 @@ use self::view::{InRamCurrentNoteViews, InRamNoteViewData}; use super::{ - shared::timer::TimeKeeper, MIDIFile, MIDIFileBase, MIDIFileUniqueSignature, MIDIViewRange, + shared::timer::TimeKeeper, MIDIFile, MIDIFileBase, MIDIFileStats, MIDIFileUniqueSignature, + MIDIViewRange, }; pub mod block; @@ -9,20 +10,6 @@ pub mod column; mod parse; pub mod view; -pub struct MIDIFileStats { - pub total_notes: u64, - pub passed_notes: u64, -} - -impl MIDIFileStats { - pub fn new(notes: u64) -> Self { - Self { - total_notes: notes, - passed_notes: 0, - } - } -} - pub struct InRamMIDIFile { view_data: InRamNoteViewData, timer: TimeKeeper, @@ -55,7 +42,10 @@ impl MIDIFileBase for InRamMIDIFile { } fn stats(&self) -> MIDIFileStats { - MIDIFileStats::new(self.note_count) + MIDIFileStats { + total_notes: Some(self.note_count), + passed_notes: Some(self.view_data.passed_notes()), + } } fn signature(&self) -> &MIDIFileUniqueSignature { diff --git a/src/midi/ram/view.rs b/src/midi/ram/view.rs index b682a63..8bdf01f 100644 --- a/src/midi/ram/view.rs +++ b/src/midi/ram/view.rs @@ -1,7 +1,3 @@ -#![allow(dead_code)] - -use std::ops::Range; - use gen_iter::GenIter; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; @@ -40,21 +36,12 @@ impl InRamNoteViewData { }, } } -} - -pub struct InRamNoteColumnViewData { - notes_to_end: usize, - notes_to_start: usize, - block_range: Range, -} -impl InRamNoteColumnViewData { - pub fn new() -> Self { - InRamNoteColumnViewData { - notes_to_end: 0, - notes_to_start: 0, - block_range: 0..0, - } + pub fn passed_notes(&self) -> u64 { + self.columns + .iter() + .map(|column| column.data.notes_to_keyboard) + .sum() } } @@ -80,7 +67,7 @@ impl InRamNoteViewData { if block.start >= new_view_range.end { break; } - data.notes_to_end += block.notes.len(); + data.notes_to_render_end += block.notes.len() as u64; new_block_end += 1; } } else if new_view_range.end < old_view_range.end { @@ -89,7 +76,7 @@ impl InRamNoteViewData { if block.start < new_view_range.end { break; } - data.notes_to_end -= block.notes.len(); + data.notes_to_render_end -= block.notes.len() as u64; new_block_end -= 1; } } else { @@ -97,25 +84,54 @@ impl InRamNoteViewData { } if new_view_range.start > old_view_range.start { + // Increment the note view start while new_block_start < blocks.len() { let block = &blocks[new_block_start]; if block.max_end() >= new_view_range.start { break; } - data.notes_to_start += block.notes.len(); + data.notes_to_render_start += block.notes.len() as u64; new_block_start += 1; } + + // Increment the keyboard passed notes/blocks + while data.blocks_to_keyboard < blocks.len() { + let block = &blocks[data.blocks_to_keyboard]; + if block.start > new_view_range.start { + break; + } + data.notes_to_keyboard += block.notes.len() as u64; + data.blocks_to_keyboard += 1; + } } else if new_view_range.start < old_view_range.start { // It is smaller, we have to start from the beginning - data.notes_to_start = 0; + data.notes_to_render_start = 0; new_block_start = 0; + + data.notes_to_keyboard = 0; + data.blocks_to_keyboard = 0; + + // Increment both view start notes and keyboard notes until we reach view start cutoff while new_block_start < blocks.len() { let block = &blocks[new_block_start]; if block.max_end() >= new_view_range.start { break; } - data.notes_to_start += block.notes.len(); + data.notes_to_render_start += block.notes.len() as u64; new_block_start += 1; + + data.notes_to_keyboard += block.notes.len() as u64; + data.blocks_to_keyboard += 1; + } + + // Increment the remaining keyboard blocks + while data.blocks_to_keyboard < blocks.len() { + let block = &blocks[data.blocks_to_keyboard]; + if block.start > new_view_range.start { + break; + } + data.notes_to_keyboard += block.notes.len() as u64; + data.blocks_to_keyboard += 1; } } else { // No change in view start @@ -189,7 +205,7 @@ impl> Iterator for InRamNoteBlockIter<' impl> ExactSizeIterator for InRamNoteBlockIter<'_, Iter> { fn len(&self) -> usize { let data = &self.view.column.data; - data.notes_to_end - data.notes_to_start + (data.notes_to_render_end - data.notes_to_render_start) as usize } }