1use std::collections::BTreeMap;
26use std::error::Error;
27use std::fmt;
28use std::sync::OnceLock;
29
30use serde::{Deserialize, Serialize};
31
32pub mod check;
33
34#[derive(Clone, Copy, Debug)]
36pub struct AxisFile {
37 pub axis: Axis,
39 data: &'static str,
40}
41
42pub const AXES: [AxisFile; 8] = [
44 AxisFile {
45 axis: Axis::Tone,
46 data: include_str!("../dictionary/words/tone.json"),
47 },
48 AxisFile {
49 axis: Axis::Body,
50 data: include_str!("../dictionary/words/body.json"),
51 },
52 AxisFile {
53 axis: Axis::Texture,
54 data: include_str!("../dictionary/words/texture.json"),
55 },
56 AxisFile {
57 axis: Axis::Structure,
58 data: include_str!("../dictionary/words/structure.json"),
59 },
60 AxisFile {
61 axis: Axis::Space,
62 data: include_str!("../dictionary/words/space.json"),
63 },
64 AxisFile {
65 axis: Axis::Movement,
66 data: include_str!("../dictionary/words/movement.json"),
67 },
68 AxisFile {
69 axis: Axis::Envelope,
70 data: include_str!("../dictionary/words/envelope.json"),
71 },
72 AxisFile {
73 axis: Axis::Mood,
74 data: include_str!("../dictionary/words/mood.json"),
75 },
76];
77
78const AGREEMENT: &str = include_str!("../dictionary/words/agreement.json");
79const LADDER: &str = include_str!("../dictionary/words/ladder.json");
80const STYLE: &str = include_str!("../dictionary/words/style.json");
81const TECHNIQUE: &str = include_str!("../dictionary/words/technique.json");
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
85#[serde(rename_all = "lowercase")]
86pub enum Axis {
87 Tone,
89 Body,
91 Texture,
93 Structure,
95 Space,
97 Movement,
99 Envelope,
101 Mood,
103}
104
105impl Axis {
106 #[must_use]
108 pub fn as_str(self) -> &'static str {
109 match self {
110 Self::Tone => "tone",
111 Self::Body => "body",
112 Self::Texture => "texture",
113 Self::Structure => "structure",
114 Self::Space => "space",
115 Self::Movement => "movement",
116 Self::Envelope => "envelope",
117 Self::Mood => "mood",
118 }
119 }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(rename_all = "lowercase")]
126pub enum Class {
127 High,
129 Medium,
131 Low,
133 Unknown,
135 #[serde(rename = "no data")]
137 NoData,
138}
139
140pub const KNOWN_FROM: u32 = 8;
142
143impl Class {
144 #[must_use]
147 pub fn of(value: Option<f64>, n: u32) -> Self {
148 match value {
149 _ if n == 0 => Self::NoData,
150 Some(value) if n >= KNOWN_FROM => {
151 if value >= 0.45 {
152 Self::High
153 } else if value >= 0.20 {
154 Self::Medium
155 } else {
156 Self::Low
157 }
158 }
159 _ => Self::Unknown,
160 }
161 }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
166#[serde(deny_unknown_fields)]
167pub struct Agreement {
168 pub value: Option<f64>,
170 pub n: u32,
172 pub class: Class,
174}
175
176#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
179#[serde(deny_unknown_fields)]
180#[allow(missing_docs)]
181pub struct Effects {
182 pub delay_time: f64,
183 pub decay: f64,
184 pub stereo_spread: f64,
185 pub cutoff_freq: f64,
186 pub wet_gain: f64,
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
191#[serde(deny_unknown_fields)]
192pub struct Reverb {
193 pub n: u32,
195 pub spread_ratio: f64,
198 pub effects: Effects,
200}
201
202#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct Measure {
206 pub key: String,
208 pub band: Option<String>,
210 pub direction: String,
212}
213
214#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
216pub enum Tag {
217 #[serde(rename = "source states")]
219 SourceStates,
220 #[serde(rename = "single source")]
222 SingleSource,
223 #[serde(rename = "snippet")]
225 Snippet,
226 #[serde(rename = "crowd data")]
228 CrowdData,
229 #[serde(rename = "inferred")]
231 Inferred,
232}
233
234#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
236#[serde(deny_unknown_fields)]
237pub struct Value {
238 pub what: String,
240 pub low: f64,
242 pub high: f64,
244 pub unit: String,
246 pub sources: Vec<String>,
248 pub tag: Tag,
250}
251
252#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
254#[serde(deny_unknown_fields)]
255pub struct Entry {
256 pub id: String,
258 pub word: String,
260 pub aliases: Vec<String>,
262 pub axis: Axis,
264 pub sense: String,
266 pub label: Option<String>,
268 pub meaning: String,
270 pub roles: Vec<String>,
272 pub conflicts: Vec<String>,
274 pub default: bool,
276 pub agreement: Agreement,
278 pub few_sample: bool,
280 pub reverb: Option<Reverb>,
282 pub measure: Option<Measure>,
284 pub values: Vec<Value>,
286 pub control: Option<String>,
288 pub card_hint: Option<String>,
290 #[serde(default)]
292 pub music: Vec<String>,
293 pub tag: Tag,
295 pub sources: Vec<String>,
297}
298
299#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
301#[serde(deny_unknown_fields)]
302pub struct EqWord {
303 pub word: String,
305 pub n: u32,
307 pub value: Option<f64>,
309 pub class: Class,
311 pub few_sample: bool,
313 pub bands: Option<BTreeMap<String, f64>>,
315 pub peak_hz: Option<u32>,
317}
318
319#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
321#[serde(deny_unknown_fields)]
322pub struct ReverbWord {
323 pub word: String,
325 pub n: u32,
327 pub spread_ratio: f64,
329 pub effects: Effects,
331}
332
333#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
335#[serde(deny_unknown_fields)]
336pub struct Listeners {
337 pub source: String,
339 pub revision: String,
341 pub tools: BTreeMap<String, String>,
343 pub eq_curves: u32,
345 pub reverb_rows: u32,
347 pub reverb_distinct_settings: u32,
349 pub eq: Vec<EqWord>,
351 pub reverb: Vec<ReverbWord>,
353}
354
355#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
357#[serde(deny_unknown_fields)]
358pub struct Ranked {
359 pub rank: u32,
361 pub word: String,
363 pub aliases: Vec<String>,
365 pub hint: Option<String>,
367 pub group: Option<String>,
369}
370
371#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
373#[serde(deny_unknown_fields)]
374pub struct Modifier {
375 pub words: Vec<String>,
377 pub step: String,
379 #[serde(default)]
381 pub warn: bool,
382}
383
384#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
386#[serde(deny_unknown_fields)]
387pub struct Anchor {
388 pub control: String,
390 pub step: String,
392 pub value: Value,
394}
395
396#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
399#[serde(deny_unknown_fields)]
400pub struct Ladder {
401 pub tag: Tag,
403 pub meaning: String,
405 pub modifiers: Vec<Modifier>,
407 pub anchors: Vec<Anchor>,
409}
410
411#[derive(Debug)]
413pub struct Words {
414 pub entries: Vec<Entry>,
416 pub listeners: Listeners,
418 pub ladder: Ladder,
420 pub style: Vec<Ranked>,
422 pub technique: Vec<Ranked>,
424}
425
426impl Words {
427 #[must_use]
429 pub fn entry(&self, id: &str) -> Option<&Entry> {
430 self.entries.iter().find(|entry| entry.id == id)
431 }
432
433 #[must_use]
435 pub fn senses(&self, word: &str) -> Vec<&Entry> {
436 let word = word.to_lowercase();
437 let lead = self
438 .entries
439 .iter()
440 .find(|entry| entry.word == word || entry.aliases.contains(&word))
441 .map(|entry| entry.word.as_str());
442 self.entries
443 .iter()
444 .filter(|entry| Some(entry.word.as_str()) == lead)
445 .collect()
446 }
447
448 #[must_use]
451 pub fn pick(&self, word: &str, role: Option<&str>) -> Option<&Entry> {
452 let senses = self.senses(word);
453 if let Some(role) = role {
454 let named: Vec<&&Entry> = senses
455 .iter()
456 .filter(|entry| entry.roles.iter().any(|r| r == role))
457 .collect();
458 if let [only] = named.as_slice() {
459 return Some(only);
460 }
461 }
462 senses.into_iter().find(|entry| entry.default)
463 }
464
465 #[must_use]
469 pub fn question(&self, word: &str) -> Option<String> {
470 let senses = self.senses(word);
471 if senses.len() < 2 {
472 return None;
473 }
474 let mut options: Vec<String> = Vec::new();
475 for entry in senses.iter().filter(|entry| entry.default) {
476 options.extend(entry.label.iter().map(|label| format!("{label} (default)")));
477 }
478 for entry in senses.iter().filter(|entry| !entry.default) {
479 options.extend(entry.label.iter().cloned());
480 }
481 let last = options.pop()?;
482 let mut lead = senses[0].word.clone();
483 if let Some(first) = lead.get_mut(..1) {
484 first.make_ascii_uppercase();
485 }
486 Some(format!("{lead}: {} or {last}?", options.join(", ")))
487 }
488
489 #[must_use]
491 pub fn eq(&self, word: &str) -> Option<&EqWord> {
492 self.listeners.eq.iter().find(|row| row.word == word)
493 }
494
495 #[must_use]
497 pub fn reverb(&self, word: &str) -> Option<&ReverbWord> {
498 self.listeners.reverb.iter().find(|row| row.word == word)
499 }
500}
501
502pub fn dictionary() -> Result<&'static Words, WordsError> {
509 static WORDS: OnceLock<Result<Words, String>> = OnceLock::new();
510 WORDS
511 .get_or_init(build)
512 .as_ref()
513 .map_err(|cause| WordsError(cause.clone()))
514}
515
516fn build() -> Result<Words, String> {
517 let words = parse()?;
518 let problems = check::problems(&words);
519 if problems.is_empty() {
520 Ok(words)
521 } else {
522 Err(problems.join("; "))
523 }
524}
525
526pub(crate) fn parse() -> Result<Words, String> {
528 fn read<T: serde::de::DeserializeOwned>(name: &str, data: &str) -> Result<T, String> {
529 serde_json::from_str(data).map_err(|error| format!("dictionary/words/{name}: {error}"))
530 }
531 let mut entries = Vec::new();
532 for file in AXES {
533 entries.extend(read::<Vec<Entry>>(
534 &format!("{}.json", file.axis.as_str()),
535 file.data,
536 )?);
537 }
538 let ladder = read("ladder.json", LADDER)?;
539 let listeners = read("agreement.json", AGREEMENT)?;
540 let style = read("style.json", STYLE)?;
541 let technique = read("technique.json", TECHNIQUE)?;
542 Ok(Words {
543 entries,
544 listeners,
545 ladder,
546 style,
547 technique,
548 })
549}
550
551#[derive(Clone, Debug, PartialEq, Eq)]
553pub struct WordsError(pub String);
554
555impl fmt::Display for WordsError {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 write!(f, "the word dictionary is broken: {}", self.0)
558 }
559}
560
561impl Error for WordsError {}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 fn the_class_follows_the_thresholds_and_the_curve_count() {
569 assert_eq!(Class::of(Some(0.45), 8), Class::High);
570 assert_eq!(Class::of(Some(0.44), 8), Class::Medium);
571 assert_eq!(Class::of(Some(0.20), 29), Class::Medium);
572 assert_eq!(Class::of(Some(0.19), 9), Class::Low);
573 assert_eq!(Class::of(Some(-0.03), 15), Class::Low);
574 assert_eq!(Class::of(Some(0.82), 7), Class::Unknown);
575 assert_eq!(Class::of(None, 1), Class::Unknown);
576 assert_eq!(Class::of(None, 0), Class::NoData);
577 }
578}