//! Waveform chunk cache for scalable multi-resolution waveform generation //! //! This module provides a chunk-based waveform caching system that generates //! waveform data progressively at multiple detail levels, avoiding the limitations //! of the old fixed 20,000-peak approach. use crate::io::{WaveformChunk, WaveformChunkKey, WaveformPeak}; use crate::audio::pool::AudioFile; use std::collections::HashMap; /// Detail levels for multi-resolution waveform storage #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DetailLevel { Overview = 0, // 1 peak per second Low = 1, // 10 peaks per second Medium = 2, // 100 peaks per second High = 3, // 1000 peaks per second Max = 4, // Full resolution (sample-accurate) } impl DetailLevel { /// Get peaks per second for this detail level pub fn peaks_per_second(self) -> usize { match self { DetailLevel::Overview => 1, DetailLevel::Low => 10, DetailLevel::Medium => 100, DetailLevel::High => 1000, DetailLevel::Max => 48000, // Approximate max for sample-accurate } } /// Create from u8 value pub fn from_u8(value: u8) -> Option { match value { 0 => Some(DetailLevel::Overview), 1 => Some(DetailLevel::Low), 2 => Some(DetailLevel::Medium), 3 => Some(DetailLevel::High), 4 => Some(DetailLevel::Max), _ => None, } } } /// Priority for chunk generation #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum ChunkPriority { Low = 0, // Background generation Medium = 1, // Precache adjacent to viewport High = 2, // Visible in current viewport } /// Chunk generation request #[derive(Debug, Clone)] pub struct ChunkGenerationRequest { pub key: WaveformChunkKey, pub priority: ChunkPriority, } /// Waveform chunk cache with multi-resolution support pub struct WaveformCache { /// Cached chunks indexed by key chunks: HashMap>, /// Maximum memory usage in MB (for future LRU eviction) _max_memory_mb: usize, /// Current memory usage estimate in bytes current_memory_bytes: usize, } impl WaveformCache { /// Create a new waveform cache with the specified memory limit pub fn new(max_memory_mb: usize) -> Self { Self { chunks: HashMap::new(), _max_memory_mb: max_memory_mb, current_memory_bytes: 0, } } /// Get a chunk from the cache pub fn get_chunk(&self, key: &WaveformChunkKey) -> Option<&Vec> { self.chunks.get(key) } /// Store a chunk in the cache pub fn store_chunk(&mut self, key: WaveformChunkKey, peaks: Vec) { let chunk_size = peaks.len() * std::mem::size_of::(); self.current_memory_bytes += chunk_size; self.chunks.insert(key, peaks); // TODO: Implement LRU eviction if memory exceeds limit } /// Check if a chunk exists in the cache pub fn has_chunk(&self, key: &WaveformChunkKey) -> bool { self.chunks.contains_key(key) } /// Clear all chunks for a specific pool index (when file is unloaded) pub fn clear_pool(&mut self, pool_index: usize) { self.chunks.retain(|key, peaks| { if key.pool_index == pool_index { let chunk_size = peaks.len() * std::mem::size_of::(); self.current_memory_bytes = self.current_memory_bytes.saturating_sub(chunk_size); false } else { true } }); } /// Generate a single waveform chunk for an audio file /// /// This generates peaks for a specific time range at a specific detail level. /// The chunk covers a time range based on the detail level and chunk index. pub fn generate_chunk( audio_file: &AudioFile, detail_level: u8, chunk_index: u32, ) -> Option { let level = DetailLevel::from_u8(detail_level)?; let peaks_per_second = level.peaks_per_second(); // Calculate time range for this chunk based on detail level // Each chunk covers a varying amount of time depending on detail level let chunk_duration_seconds = match level { DetailLevel::Overview => 60.0, // 60 seconds per chunk (60 peaks) DetailLevel::Low => 30.0, // 30 seconds per chunk (300 peaks) DetailLevel::Medium => 10.0, // 10 seconds per chunk (1000 peaks) DetailLevel::High => 5.0, // 5 seconds per chunk (5000 peaks) DetailLevel::Max => 1.0, // 1 second per chunk (48000 peaks) }; let start_time = chunk_index as f64 * chunk_duration_seconds; let end_time = start_time + chunk_duration_seconds; // Check if this chunk is within the audio file duration let audio_duration = audio_file.duration_seconds(); if start_time >= audio_duration { return None; // Chunk is completely beyond file end } // Clamp end_time to file duration let end_time = end_time.min(audio_duration); // Calculate frame range let start_frame = (start_time * audio_file.sample_rate as f64) as usize; let end_frame = (end_time * audio_file.sample_rate as f64) as usize; // Calculate number of peaks for this time range let duration = end_time - start_time; let target_peaks = (duration * peaks_per_second as f64).ceil() as usize; if target_peaks == 0 { return None; } // Generate peaks using the existing method let peaks = audio_file.generate_waveform_overview_range( start_frame, end_frame, target_peaks, ); Some(WaveformChunk { audio_pool_index: 0, // Will be set by caller detail_level, chunk_index, time_range: (start_time, end_time), peaks, }) } /// Generate multiple chunks for an audio file /// /// This is a convenience method for generating several chunks at once. pub fn generate_chunks( audio_file: &AudioFile, pool_index: usize, detail_level: u8, chunk_indices: &[u32], ) -> Vec { chunk_indices .iter() .filter_map(|&chunk_index| { let mut chunk = Self::generate_chunk(audio_file, detail_level, chunk_index)?; chunk.audio_pool_index = pool_index; Some(chunk) }) .collect() } /// Calculate how many chunks are needed for a file at a given detail level pub fn calculate_chunk_count(duration_seconds: f64, detail_level: u8) -> u32 { let level = match DetailLevel::from_u8(detail_level) { Some(l) => l, None => return 0, }; let chunk_duration_seconds = match level { DetailLevel::Overview => 60.0, DetailLevel::Low => 30.0, DetailLevel::Medium => 10.0, DetailLevel::High => 5.0, DetailLevel::Max => 1.0, }; ((duration_seconds / chunk_duration_seconds).ceil() as u32).max(1) } /// Generate all Level 0 (overview) chunks for a file /// /// This should be called immediately when a file is imported to provide /// instant thumbnail display. pub fn generate_overview_chunks( &mut self, audio_file: &AudioFile, pool_index: usize, ) -> Vec { let duration = audio_file.duration_seconds(); let chunk_count = Self::calculate_chunk_count(duration, 0); let chunk_indices: Vec = (0..chunk_count).collect(); let chunks = Self::generate_chunks(audio_file, pool_index, 0, &chunk_indices); // Store chunks in cache for chunk in &chunks { let key = WaveformChunkKey { pool_index, detail_level: chunk.detail_level, chunk_index: chunk.chunk_index, }; self.store_chunk(key, chunk.peaks.clone()); } chunks } /// Get current memory usage in bytes pub fn memory_usage_bytes(&self) -> usize { self.current_memory_bytes } /// Get current memory usage in megabytes pub fn memory_usage_mb(&self) -> f64 { self.current_memory_bytes as f64 / 1_000_000.0 } /// Get number of cached chunks pub fn chunk_count(&self) -> usize { self.chunks.len() } } impl Default for WaveformCache { fn default() -> Self { Self::new(100) // Default 100MB cache } } #[cfg(test)] mod tests { use super::*; #[test] fn test_detail_level_peaks_per_second() { assert_eq!(DetailLevel::Overview.peaks_per_second(), 1); assert_eq!(DetailLevel::Low.peaks_per_second(), 10); assert_eq!(DetailLevel::Medium.peaks_per_second(), 100); assert_eq!(DetailLevel::High.peaks_per_second(), 1000); } #[test] fn test_chunk_count_calculation() { // 60 second file, Overview level (60s chunks) = 1 chunk assert_eq!(WaveformCache::calculate_chunk_count(60.0, 0), 1); // 120 second file, Overview level (60s chunks) = 2 chunks assert_eq!(WaveformCache::calculate_chunk_count(120.0, 0), 2); // 10 second file, Medium level (10s chunks) = 1 chunk assert_eq!(WaveformCache::calculate_chunk_count(10.0, 2), 1); // 25 second file, Medium level (10s chunks) = 3 chunks assert_eq!(WaveformCache::calculate_chunk_count(25.0, 2), 3); } }