//! The one home of the session-handle wordlist (#53). //! //! Previously duplicated in `web/src/names.ts`; every client now gets handles //! from here, served by `GET /api/names`. /// Every word a handle can be: instruments, forms, and the vocabulary of /// tempo, dynamics, notation and theory. One word per handle — the list is /// wide enough that a single word still reads as distinct. pub const WORDS: &[&str] = &[ // Instruments "cello", "violin", "viola", "oboe", "flute", "piccolo", "clarinet", "bassoon", "trumpet", "trombone", "tuba", "horn", "cornet", "bugle", "flugelhorn", "euphonium", "saxophone", "recorder", "timpani", "harp", "piano", "organ", "lyre", "lute", "mandolin", "banjo", "ukulele", "guitar", "sitar", "koto", "shamisen", "balalaika", "zither", "dulcimer", "kantele", "erhu", "guzheng", "pipa", "duduk", "ocarina", "kalimba", "harmonica", "accordion", "bandoneon", "bagpipe", "harpsichord", "clavichord", "celesta", "theremin", "marimba", "xylophone", "vibraphone", "glockenspiel", "snare", "cymbal", "tambourine", "castanet", "djembe", "tabla", "bodhran", "gong", "chimes", "contrabass", "shawm", "crumhorn", "sackbut", "didgeridoo", // Forms and pieces "sonata", "fugue", "canon", "prelude", "overture", "rondo", "nocturne", "scherzo", "etude", "ballade", "barcarolle", "bagatelle", "caprice", "chaconne", "passacaglia", "toccata", "intermezzo", "interlude", "minuet", "gavotte", "sarabande", "gigue", "allemande", "courante", "mazurka", "polonaise", "waltz", "bolero", "habanera", "tarantella", "fandango", "serenade", "aubade", "requiem", "cantata", "oratorio", "madrigal", "motet", "chorale", "hymn", "anthem", "lullaby", "elegy", "rhapsody", "symphony", "concerto", "fantasia", "divertimento", "quartet", "quintet", "sextet", "septet", "octet", "trio", // Structure, notation and theory "octave", "chord", "cadence", "cadenza", "coda", "motif", "refrain", "verse", "chorus", "bridge", "finale", "arpeggio", "glissando", "ostinato", "counterpoint", "descant", "harmony", "melody", "rhythm", "tempo", "timbre", "treble", "clef", "staff", "quaver", "crotchet", "semibreve", "fermata", "trill", "mordent", "appoggiatura", "portamento", "vibrato", "triplet", "tuplet", "downbeat", "upbeat", "anacrusis", "syncopation", "modulation", "transposition", "unison", "semitone", "tritone", "diapason", "tonic", "dominant", "mediant", "ionian", "dorian", "phrygian", "lydian", "mixolydian", "aeolian", "locrian", "pentatonic", "chromatic", "diatonic", // Tempo, dynamics and articulation "allegro", "andante", "adagio", "presto", "vivace", "largo", "grave", "lento", "moderato", "crescendo", "diminuendo", "staccato", "legato", "forte", "dolce", "agitato", "cantabile", "sostenuto", "tremolo", "rubato", "marcato", "brillante", "maestoso", "grazioso", "espressivo", "tranquillo", "animato", "spirito", "sotto", "vivo", "feroce", "giocoso", "affettuoso", "risoluto", "leggiero", "pesante", "morendo", "accelerando", "ritardando", ]; /// Upper bound for a `GET /api/names?count=N` batch. pub const MAX_BATCH: usize = 100; /// A fresh one-word handle, e.g. `nocturne`. pub fn generate() -> String { use rand::seq::SliceRandom; let mut rng = rand::thread_rng(); (*WORDS.choose(&mut rng).unwrap()).to_string() } /// `count` fresh handles, distinct while the wordlist lasts (the caller /// clamps; this only trusts its input). pub fn generate_batch(count: usize) -> Vec { use rand::seq::SliceRandom; let mut rng = rand::thread_rng(); let mut pool: Vec<&str> = WORDS.to_vec(); pool.shuffle(&mut rng); (0..count) .map(|i| match pool.get(i) { // Past the wordlist a batch has to repeat itself. Some(word) => (*word).to_string(), None => generate(), }) .collect() } #[cfg(test)] mod tests { use super::*; use std::collections::HashSet; #[test] fn generated_name_is_a_single_known_word() { let name = generate(); assert!(!name.contains('-'), "{name} is not one word"); assert!(WORDS.contains(&name.as_str()), "{name} not in WORDS"); } #[test] fn the_wordlist_has_no_duplicates_and_covers_a_full_batch() { let unique: HashSet<&&str> = WORDS.iter().collect(); assert_eq!(unique.len(), WORDS.len(), "WORDS has duplicates"); assert!( WORDS.len() >= MAX_BATCH, "a full batch would have to repeat" ); } #[test] fn batch_returns_the_requested_count_without_repeats() { assert_eq!(generate_batch(1).len(), 1); let batch = generate_batch(MAX_BATCH); assert_eq!(batch.len(), MAX_BATCH); for name in &batch { assert!(WORDS.contains(&name.as_str()), "{name} not in WORDS"); } let unique: HashSet<&String> = batch.iter().collect(); assert_eq!(unique.len(), batch.len(), "batch repeated a handle"); } #[test] fn rerolls_can_differ() { let first = generate(); let differs = (0..20).any(|_| generate() != first); assert!(differs); } }