1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
//! Manages access to wavetables. //! //! The wavetable manager is a central place for managing wavetable //! instances. It holds wavetables in a cache, handing references to them to //! clients, thereby avoiding the need for multiple instances of a single //! table. use super::Float; use super::{Wavetable, WavetableRef}; use super::WtCreator; use super::WtReader; use log::{info, trace, warn}; use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::sync::Arc; const NUM_PWM_TABLES: usize = 64; /// Identifies a wavetable. /// /// The ID is an internal identifier used for referencing the table. /// The valid flag is set to true if the wavetable was initialized /// successfully, false otherwise. /// The name is a string that can be displayed to the user. It is usually the /// filename without the path. /// The filename is the full patch to the wave file #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WtInfo { pub id: usize, // ID of wavetable, used as reference pub valid: bool, // True if wavetable file exists pub name: String, // Name of the wavetable pub filename: String // Wavetable filename, empty if internal table } pub struct WtManager { sample_rate: Float, cache: HashMap<usize, WavetableRef>, reader: WtReader, } impl WtManager { /// Generate a new WtManager instance. /// /// The data_dir is the name of the directory that is used for loading /// wavetable files. /// /// ``` /// use wavetable::WtManager; /// /// let wt_manager = WtManager::new(44100.0, "data"); /// ``` pub fn new(sample_rate: Float, data_dir: &str) -> WtManager { let cache = HashMap::new(); let reader = WtReader::new(data_dir); WtManager{sample_rate, cache, reader} } /// Add table containing basic waveshapes with the given ID. /// /// The wavetable added will contain data for sine, triangle, saw and /// square waves, 2048 samples per wave, bandlimited with one table per /// octave, for 11 octaves, covering the full range of MIDI notes for /// standard tuning. /// /// The wave indices to use for querying sine, triangle, saw and square /// are 0.0, 1/3, 2/3 and 1.0 respectively. /// /// ``` /// use wavetable::WtManager; /// /// let mut wt_manager = WtManager::new(44100.0, "data"); /// wt_manager.add_basic_tables(0); /// ``` pub fn add_basic_tables(&mut self, id: usize) { self.add_to_cache(id, WtCreator::create_default_waves(self.sample_rate)); } /// Add table containing pulse width modulated square waves with the given ID. /// /// The wavetable added will contain the specified number of square waves /// with different amounts of pulse width modulation, 2048 samples per /// wave, bandlimited with one table per octave, for 11 octaves, covering /// the full range of MIDI notes for standard tuning. /// /// ``` /// use wavetable::WtManager; /// /// let mut wt_manager = WtManager::new(44100.0, "data"); /// wt_manager.add_pwm_tables(1, 64); /// ``` pub fn add_pwm_tables(&mut self, id: usize, num_pwm_tables: usize) { self.add_to_cache(id, WtCreator::create_pwm_waves(self.sample_rate, num_pwm_tables)); } /// Get a single wavetable by id from the cache. /// /// ``` /// use wavetable::WtManager; /// /// let mut wt_manager = WtManager::new(44100.0, "data"); /// wt_manager.add_basic_tables(0); /// let table_ref = wt_manager.get_table(0); /// ``` pub fn get_table(&self, id: usize) -> Option<WavetableRef> { if self.cache.contains_key(&id) { Some(self.cache.get(&id).unwrap().clone()) } else { None } } /// Loads a wavetable file and adds the table to the cache. /// /// Tries to load the table from the given file and put it into the cache. /// If loading the file fails, the provided fallback table is inserted /// instead. /// /// The WtInfo::valid flag is set to true if loading was successfull, false /// if it failed. /// /// If the flag bandlimit is set to true, the table will automatically be /// converted to a bandlimited version, consisting of 11 tables per wav /// shape with reduced number of harmonics, which means the data will /// consume 11 times more memory than the un-bandlimited version. /// /// ``` /// use wavetable::{WtManager, WtInfo}; /// /// let mut wt_manager = WtManager::new(44100.0, "data"); /// wt_manager.add_basic_tables(0); /// let mut wt_info = WtInfo{ /// id: 1, /// valid: false, /// name: "TestMe".to_string(), /// filename: "TestMe.wav".to_string()}; /// let fallback = if let Some(table) = wt_manager.get_table(0) { /// table /// } else { /// panic!(); /// }; /// wt_manager.load_table(&mut wt_info, fallback, false); /// ``` pub fn load_table(&mut self, wt_info: &mut WtInfo, fallback: WavetableRef, bandlimit: bool) { let result = self.reader.read_file(&wt_info.filename); let table = if let Ok(wt) = result { wt_info.valid = true; if bandlimit { let harmonics = wt.convert_to_harmonics(1024); let mut wt_bandlimited = Wavetable::new(wt.table.len(), 11, 2048); wt_bandlimited.insert_harmonics(&harmonics, self.sample_rate).unwrap(); Arc::new(wt_bandlimited) } else { wt } } else { wt_info.valid = false; fallback.clone() }; self.add_to_cache(wt_info.id, table); } // Adds a wavetable with the given ID to the internal cache. fn add_to_cache(&mut self, id: usize, wt: WavetableRef) { self.cache.insert(id, wt); } }