tawasuyu

tawasuyu/takiy

> takiy (Quechua: to sing). Music in Rust — synthesis, sequencing, playback — on Llimphi.

Rust 116Markdown 41TOML 192075 Definitions

⎇ 5802bf9ee1f162194bcbfdc797293f2d20d829ae 02_ruway/takiy/takiy-acordes/src/banco.rs Rust · 332 lines · 10.5 KB History Who touched what raw

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) 5802bf9ee · Sergio ·

1//! El **banco de patrones**: los prediseñados de fábrica y los del usuario,
2//! en una sola lista indexable.
3//!
4//! Los prediseñados son inmutables a propósito. Pedir editar uno no lo
5//! rompe: lo **copia** al final del banco y devuelve el índice de la copia
6//! ([`Banco::editar`]). Ésa es la costura entre «prediseñados» y
7//! «creables» — la única forma de tener un patrón propio es partir de uno
8//! que ya sonaba, o de la grilla vacía.
9
10use serde::{Deserialize, Serialize};
11
12use crate::patron::{Direccion::*, Golpe::*, Paso, Patron};
13
14/// Atajo: un paso que suena con velocidad y largo dados.
15fn p(golpe: crate::patron::Golpe, vel: u8, largo: f32) -> Paso {
16    Paso::nuevo(golpe, vel, largo)
17}
18
19/// Atajo: silencio.
20fn s() -> Paso {
21    Paso::default()
22}
23
24/// Los patrones de fábrica, en orden de menor a mayor movimiento.
25pub fn de_fabrica() -> Vec<Patron> {
26    vec![
27        // Un acorde por compás, dejado sonar. La balada.
28        Patron::desde(
29            "Redonda",
30            0.10,
31            vec![p(Rasgueo(Abajo), 88, 8.0), s(), s(), s(), s(), s(), s(), s()],
32        ),
33        // Cuatro golpes secos: el acompañamiento de piano más simple.
34        Patron::desde(
35            "Negras",
36            0.06,
37            vec![
38                p(Rasgueo(Abajo), 104, 2.0), s(),
39                p(Rasgueo(Abajo), 88, 2.0), s(),
40                p(Rasgueo(Abajo), 96, 2.0), s(),
41                p(Rasgueo(Abajo), 88, 2.0), s(),
42            ],
43        ),
44        // El rasgueo de guitarra de toda canción pop: ↓ ↓↑ ↑↓↑.
45        Patron::desde(
46            "Rasgueo pop",
47            0.18,
48            vec![
49                p(Rasgueo(Abajo), 108, 2.0), s(),
50                p(Rasgueo(Abajo), 96, 1.0), p(Rasgueo(Arriba), 78, 1.0),
51                s(), p(Rasgueo(Arriba), 78, 1.0),
52                p(Rasgueo(Abajo), 100, 1.0), p(Rasgueo(Arriba), 76, 1.0),
53            ],
54        ),
55        // El mismo espíritu en semicorcheas: más aire entre golpe y golpe.
56        Patron::desde(
57            "Rasgueo folk",
58            0.20,
59            vec![
60                p(Rasgueo(Abajo), 110, 4.0), s(), s(), s(),
61                p(Rasgueo(Abajo), 94, 2.0), s(),
62                p(Rasgueo(Arriba), 74, 2.0), s(),
63                s(), s(),
64                p(Rasgueo(Arriba), 74, 2.0), s(),
65                p(Rasgueo(Abajo), 100, 2.0), s(),
66                p(Rasgueo(Arriba), 72, 2.0), s(),
67            ],
68        ),
69        // Pum-chas: fundamental al 1 y al 3, acorde al 2 y al 4.
70        Patron::desde(
71            "Pum-chas",
72            0.08,
73            vec![
74                p(Fundamental, 104, 2.0), s(),
75                p(Rasgueo(Abajo), 90, 1.5), s(),
76                p(Fundamental, 96, 2.0), s(),
77                p(Rasgueo(Abajo), 90, 1.5), s(),
78            ],
79        ),
80        // Arpegios: una voz por paso, dejadas sonar (la mano abierta).
81        Patron::desde(
82            "Arpegio sube",
83            0.0,
84            vec![
85                p(Voz(0), 92, 3.0), p(Voz(1), 84, 3.0),
86                p(Voz(2), 88, 3.0), p(Voz(3), 82, 3.0),
87                p(Voz(0), 90, 3.0), p(Voz(1), 82, 3.0),
88                p(Voz(2), 86, 3.0), p(Voz(3), 80, 3.0),
89            ],
90        ),
91        Patron::desde(
92            "Arpegio baja",
93            0.0,
94            vec![
95                p(Voz(3), 92, 3.0), p(Voz(2), 84, 3.0),
96                p(Voz(1), 88, 3.0), p(Voz(0), 82, 3.0),
97                p(Voz(3), 90, 3.0), p(Voz(2), 82, 3.0),
98                p(Voz(1), 86, 3.0), p(Voz(0), 80, 3.0),
99            ],
100        ),
101        // Alternado: fundamental de ancla y voces altas entre medio.
102        Patron::desde(
103            "Arpegio alterno",
104            0.0,
105            vec![
106                p(Fundamental, 96, 4.0), p(Voz(2), 80, 2.0),
107                p(Voz(1), 84, 2.0), p(Voz(2), 78, 2.0),
108                p(Fundamental, 92, 4.0), p(Voz(3), 80, 2.0),
109                p(Voz(1), 84, 2.0), p(Voz(2), 78, 2.0),
110            ],
111        ),
112        // Reggae: sólo los contratiempos, cortos.
113        Patron::desde(
114            "Contratiempo",
115            0.10,
116            vec![
117                s(), p(Rasgueo(Arriba), 96, 0.5),
118                s(), p(Rasgueo(Arriba), 88, 0.5),
119                s(), p(Rasgueo(Arriba), 96, 0.5),
120                s(), p(Rasgueo(Arriba), 88, 0.5),
121            ],
122        ),
123        // Bossa: la síncopa característica en semicorcheas.
124        Patron::desde(
125            "Bossa",
126            0.06,
127            vec![
128                p(Rasgueo(Abajo), 96, 3.0), s(), s(),
129                p(Rasgueo(Abajo), 84, 3.0), s(), s(),
130                p(Rasgueo(Abajo), 90, 4.0), s(), s(), s(),
131                p(Rasgueo(Abajo), 84, 2.0), s(),
132                p(Rasgueo(Abajo), 88, 2.0), s(), s(), s(),
133            ],
134        ),
135        // Vals: tres tiempos — bajo, acorde, acorde. Seis pasos, no ocho.
136        Patron::desde(
137            "Vals",
138            0.10,
139            vec![
140                p(Fundamental, 100, 2.0), s(),
141                p(Rasgueo(Abajo), 84, 1.5), s(),
142                p(Rasgueo(Abajo), 80, 1.5), s(),
143            ],
144        ),
145    ]
146}
147
148/// La lista completa de patrones de una sesión: primero los de fábrica,
149/// después los del usuario.
150#[derive(Debug, Clone)]
151pub struct Banco {
152    patrones: Vec<Patron>,
153    fijos: usize,
154}
155
156/// Lo único que se guarda en disco: los patrones propios. Los de fábrica
157/// vienen del código, así que una versión nueva los mejora sin migración.
158#[derive(Debug, Clone, Default, Serialize, Deserialize)]
159pub struct BancoGuardado {
160    pub propios: Vec<Patron>,
161}
162
163impl Default for Banco {
164    fn default() -> Self {
165        Self::nuevo()
166    }
167}
168
169impl Banco {
170    pub fn nuevo() -> Self {
171        let patrones = de_fabrica();
172        let fijos = patrones.len();
173        Self { patrones, fijos }
174    }
175
176    /// Banco de fábrica + los propios que venían de disco.
177    pub fn con_propios(propios: Vec<Patron>) -> Self {
178        let mut b = Self::nuevo();
179        b.patrones.extend(propios);
180        b
181    }
182
183    pub fn patrones(&self) -> &[Patron] {
184        &self.patrones
185    }
186
187    pub fn len(&self) -> usize {
188        self.patrones.len()
189    }
190
191    pub fn is_empty(&self) -> bool {
192        false
193    }
194
195    pub fn get(&self, i: usize) -> Option<&Patron> {
196        self.patrones.get(i)
197    }
198
199    /// El patrón `i`, o el primero de fábrica si el índice quedó viejo.
200    /// El render nunca se queda sin patrón.
201    pub fn get_o_primero(&self, i: usize) -> &Patron {
202        self.patrones.get(i).unwrap_or(&self.patrones[0])
203    }
204
205    pub fn es_fijo(&self, i: usize) -> bool {
206        i < self.fijos
207    }
208
209    pub fn fijos(&self) -> usize {
210        self.fijos
211    }
212
213    /// Los propios, para guardar.
214    pub fn propios(&self) -> &[Patron] {
215        &self.patrones[self.fijos..]
216    }
217
218    pub fn guardado(&self) -> BancoGuardado {
219        BancoGuardado { propios: self.propios().to_vec() }
220    }
221
222    /// Devuelve un índice **editable** para `i`: si `i` es de fábrica, lo
223    /// copia al final con nombre libre y devuelve el índice de la copia.
224    pub fn editar(&mut self, i: usize) -> usize {
225        if !self.es_fijo(i) {
226            return i.min(self.patrones.len().saturating_sub(1));
227        }
228        let mut copia = self.patrones[i].clone();
229        copia.nombre = self.nombre_libre(&copia.nombre);
230        self.patrones.push(copia);
231        self.patrones.len() - 1
232    }
233
234    /// Agrega un patrón propio y devuelve su índice.
235    pub fn agregar(&mut self, mut patron: Patron) -> usize {
236        patron.nombre = self.nombre_libre(&patron.nombre);
237        self.patrones.push(patron);
238        self.patrones.len() - 1
239    }
240
241    /// Muta un patrón propio. Sobre uno de fábrica no hace nada — el caller
242    /// tiene que pasar por [`Banco::editar`] primero.
243    pub fn con_mut<R>(&mut self, i: usize, f: impl FnOnce(&mut Patron) -> R) -> Option<R> {
244        if self.es_fijo(i) {
245            return None;
246        }
247        self.patrones.get_mut(i).map(f)
248    }
249
250    /// Borra un patrón propio. Los de fábrica no se borran.
251    pub fn quitar(&mut self, i: usize) -> bool {
252        if self.es_fijo(i) || i >= self.patrones.len() {
253            return false;
254        }
255        self.patrones.remove(i);
256        true
257    }
258
259    /// Un nombre que no choque con ninguno del banco: «Rasgueo pop 2», 3…
260    fn nombre_libre(&self, base: &str) -> String {
261        if !self.patrones.iter().any(|p| p.nombre == base) {
262            return base.to_string();
263        }
264        for n in 2..999 {
265            let cand = format!("{base} {n}");
266            if !self.patrones.iter().any(|p| p.nombre == cand) {
267                return cand;
268            }
269        }
270        base.to_string()
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn todos_los_de_fabrica_suenan_y_tienen_nombre_unico() {
280        let b = Banco::nuevo();
281        assert!(b.len() >= 10);
282        for p in b.patrones() {
283            assert!(p.ataques() > 0, "«{}» no toca nada", p.nombre);
284            assert!(!p.nombre.is_empty());
285        }
286        let mut nombres: Vec<_> = b.patrones().iter().map(|p| p.nombre.as_str()).collect();
287        nombres.sort_unstable();
288        let antes = nombres.len();
289        nombres.dedup();
290        assert_eq!(nombres.len(), antes, "hay nombres repetidos");
291    }
292
293    #[test]
294    fn editar_un_fijo_lo_copia_en_vez_de_romperlo() {
295        let mut b = Banco::nuevo();
296        let original = b.get(2).unwrap().clone();
297        let i = b.editar(2);
298        assert!(i >= b.fijos(), "la copia tiene que ser propia");
299        b.con_mut(i, |p| p.limpiar());
300        assert_eq!(b.get(2).unwrap(), &original, "el de fábrica quedó intacto");
301        assert_eq!(b.get(i).unwrap().ataques(), 0);
302        assert_ne!(b.get(i).unwrap().nombre, original.nombre);
303    }
304
305    #[test]
306    fn los_fijos_no_se_borran_ni_se_mutan() {
307        let mut b = Banco::nuevo();
308        assert!(!b.quitar(0));
309        assert!(b.con_mut(0, |p| p.limpiar()).is_none());
310        let i = b.agregar(Patron::vacio("mío", 8));
311        assert!(b.quitar(i));
312        assert_eq!(b.len(), b.fijos());
313    }
314
315    #[test]
316    fn solo_los_propios_van_a_disco_y_vuelven() {
317        let mut b = Banco::nuevo();
318        b.agregar(Patron::vacio("mío", 8));
319        let json = serde_json::to_string(&b.guardado()).unwrap();
320        let leido: BancoGuardado = serde_json::from_str(&json).unwrap();
321        assert_eq!(leido.propios.len(), 1);
322        let b2 = Banco::con_propios(leido.propios);
323        assert_eq!(b2.len(), b.len());
324        assert_eq!(b2.propios()[0].nombre, "mío");
325    }
326
327    #[test]
328    fn el_indice_viejo_no_deja_al_render_sin_patron() {
329        let b = Banco::nuevo();
330        assert_eq!(b.get_o_primero(9999).nombre, b.get(0).unwrap().nombre);
331    }
332}