tawasuyu/takiy
> takiy (Quechua: to sing). Music in Rust — synthesis, sequencing, playback — on Llimphi.
⎇ 5802bf9ee1f162194bcbfdc797293f2d20d829ae
02_ruway/takiy/takiy-acordes/src/banco.rs
History
Who touched what
raw
chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0)
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}