max / audiofiles
4 files changed,
+55 insertions,
-0 deletions
| @@ -16,6 +16,12 @@ pub struct DeviceProfile { | |||
| 16 | 16 | pub name: String, | |
| 17 | 17 | /// Device manufacturer (e.g. "Roland"). | |
| 18 | 18 | pub manufacturer: String, | |
| 19 | + | /// Device category (e.g. "sampler", "groovebox", "drum machine"). | |
| 20 | + | #[serde(default)] | |
| 21 | + | pub category: Option<String>, | |
| 22 | + | /// Free-form notes about the device (e.g. *"Mounts as USB drive — drag-and-drop"*). | |
| 23 | + | #[serde(default)] | |
| 24 | + | pub notes: Option<String>, | |
| 19 | 25 | /// Audio format constraints. | |
| 20 | 26 | pub audio: AudioConstraints, | |
| 21 | 27 | /// Filename rules (case, separator, length, character stripping). | |
| @@ -93,6 +99,12 @@ pub struct DeviceProfileSummary { | |||
| 93 | 99 | /// to refetch full profiles to surface the values (M-6). | |
| 94 | 100 | #[serde(default)] | |
| 95 | 101 | pub format_summary: Option<String>, | |
| 102 | + | /// Device category from the manifest (p-5). | |
| 103 | + | #[serde(default)] | |
| 104 | + | pub category: Option<String>, | |
| 105 | + | /// Free-form notes from the manifest (p-5). | |
| 106 | + | #[serde(default)] | |
| 107 | + | pub notes: Option<String>, | |
| 96 | 108 | } | |
| 97 | 109 | ||
| 98 | 110 | /// Format an `AudioConstraints` set as a single-line summary suitable for | |
| @@ -157,6 +169,8 @@ mod tests { | |||
| 157 | 169 | let profile = DeviceProfile { | |
| 158 | 170 | name: "SP-404 MKII".to_string(), | |
| 159 | 171 | manufacturer: "Roland".to_string(), | |
| 172 | + | category: Some("sampler".to_string()), | |
| 173 | + | notes: Some("Mounts as USB drive".to_string()), | |
| 160 | 174 | audio: AudioConstraints { | |
| 161 | 175 | formats: vec![ExportFormat::Wav], | |
| 162 | 176 | sample_rates: vec![44100, 48000], | |
| @@ -179,6 +193,8 @@ mod tests { | |||
| 179 | 193 | let deserialized: DeviceProfile = serde_json::from_str(&json).unwrap(); | |
| 180 | 194 | assert_eq!(deserialized.name, "SP-404 MKII"); | |
| 181 | 195 | assert_eq!(deserialized.audio.sample_rates, vec![44100, 48000]); | |
| 196 | + | assert_eq!(deserialized.category.as_deref(), Some("sampler")); | |
| 197 | + | assert_eq!(deserialized.notes.as_deref(), Some("Mounts as USB drive")); | |
| 182 | 198 | assert_eq!( | |
| 183 | 199 | deserialized.naming.as_ref().unwrap().case, | |
| 184 | 200 | NamingCase::Upper | |
| @@ -190,6 +206,8 @@ mod tests { | |||
| 190 | 206 | let profile = DeviceProfile { | |
| 191 | 207 | name: "Generic".to_string(), | |
| 192 | 208 | manufacturer: "Unknown".to_string(), | |
| 209 | + | category: None, | |
| 210 | + | notes: None, | |
| 193 | 211 | audio: AudioConstraints { | |
| 194 | 212 | formats: vec![ExportFormat::Wav], | |
| 195 | 213 | sample_rates: vec![44100], |
| @@ -2,6 +2,8 @@ | |||
| 2 | 2 | name = "SP-404 MKII" | |
| 3 | 3 | manufacturer = "Roland" | |
| 4 | 4 | version = "1.0" | |
| 5 | + | category = "sampler" | |
| 6 | + | notes = "Hold MIDI when powering on to mount as USB drive." | |
| 5 | 7 | ||
| 6 | 8 | [audio] | |
| 7 | 9 | formats = ["wav"] |
| @@ -26,6 +26,10 @@ pub struct DeviceSection { | |||
| 26 | 26 | pub name: String, | |
| 27 | 27 | pub manufacturer: String, | |
| 28 | 28 | pub version: String, | |
| 29 | + | #[serde(default)] | |
| 30 | + | pub category: Option<String>, | |
| 31 | + | #[serde(default)] | |
| 32 | + | pub notes: Option<String>, | |
| 29 | 33 | } | |
| 30 | 34 | ||
| 31 | 35 | #[derive(Debug, Deserialize)] | |
| @@ -90,6 +94,8 @@ pub fn manifest_to_profile(manifest: &PluginManifest) -> Result<DeviceProfile, P | |||
| 90 | 94 | Ok(DeviceProfile { | |
| 91 | 95 | name: manifest.device.name.clone(), | |
| 92 | 96 | manufacturer: manifest.device.manufacturer.clone(), | |
| 97 | + | category: manifest.device.category.clone(), | |
| 98 | + | notes: manifest.device.notes.clone(), | |
| 93 | 99 | audio: AudioConstraints { | |
| 94 | 100 | formats, | |
| 95 | 101 | sample_rates: manifest.audio.sample_rates.clone(), | |
| @@ -224,6 +230,31 @@ max_file_size_bytes = 134217728 | |||
| 224 | 230 | } | |
| 225 | 231 | ||
| 226 | 232 | #[test] | |
| 233 | + | fn manifest_with_category_and_notes() { | |
| 234 | + | let toml = r#" | |
| 235 | + | [device] | |
| 236 | + | name = "OP-1" | |
| 237 | + | manufacturer = "Teenage Engineering" | |
| 238 | + | version = "1.0" | |
| 239 | + | category = "synthesizer" | |
| 240 | + | notes = "Mounts as USB drive when held with shift on boot." | |
| 241 | + | ||
| 242 | + | [audio] | |
| 243 | + | formats = ["aiff"] | |
| 244 | + | sample_rates = [44100] | |
| 245 | + | bit_depths = [16] | |
| 246 | + | channels = "mono" | |
| 247 | + | "#; | |
| 248 | + | let manifest = parse_manifest(toml).unwrap(); | |
| 249 | + | let profile = manifest_to_profile(&manifest).unwrap(); | |
| 250 | + | assert_eq!(profile.category.as_deref(), Some("synthesizer")); | |
| 251 | + | assert_eq!( | |
| 252 | + | profile.notes.as_deref(), | |
| 253 | + | Some("Mounts as USB drive when held with shift on boot.") | |
| 254 | + | ); | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | #[test] | |
| 227 | 258 | fn minimal_manifest_no_naming_no_limits() { | |
| 228 | 259 | let toml = r#" | |
| 229 | 260 | [device] |
| @@ -88,6 +88,8 @@ impl PluginRegistry { | |||
| 88 | 88 | format_summary: Some(audiofiles_core::export::profile::format_audio_constraints( | |
| 89 | 89 | &p.profile.audio, | |
| 90 | 90 | )), | |
| 91 | + | category: p.profile.category.clone(), | |
| 92 | + | notes: p.profile.notes.clone(), | |
| 91 | 93 | }) | |
| 92 | 94 | .collect(); | |
| 93 | 95 | summaries.sort_by(|a, b| a.name.cmp(&b.name)); | |
| @@ -117,6 +119,8 @@ mod tests { | |||
| 117 | 119 | DeviceProfile { | |
| 118 | 120 | name: name.to_string(), | |
| 119 | 121 | manufacturer: "Test".to_string(), | |
| 122 | + | category: None, | |
| 123 | + | notes: None, | |
| 120 | 124 | audio: AudioConstraints { | |
| 121 | 125 | formats: vec![ExportFormat::Wav], | |
| 122 | 126 | sample_rates: vec![44100], |