Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cómo se escribe una demostración

Archivos: crates/lineup-core/src/library/song.rs, crates/lineup-core/src/library/song/, crates/lineup-core/src/library/chart.rs, crates/lineup-core/src/library/demo.rs, crates/lineup-core/src/library/interpret.rs y crates/lineup-app/src/app/demo_menu.rs

Archivo > Demostraciones abre 343 canciones, de canciones infantiles a La consagración de la primavera, y una demostración corta de cada estilo, de cada estilo dodecafónico y de cada estilo de micromodos. Haces clic en una y se abre, con sus acordes en la hoja de acordes, y suena. Qué escuchar en ellas está en la guía de escucha del manual. Este capítulo trata de qué es una demostración.

Una demostración de canción no es un archivo. Es una constante de Rust, compilada dentro del programa: la melodía y los acordes escritos como texto, como los escribe una partitura guía, y unos pocos campos que dicen cómo se toca la canción. song_demo convierte la constante en un Project corriente cada vez que la abres. Eso tiene tres ventajas:

  • Los tests leen todas las canciones. Un compás con una semicorchea de más, un acorde que no se puede leer, una forma que nombra una parte que no existe: cada cosa hace fallar cargo test antes de que nadie la oiga.
  • Una demostración es igual en todas partes. No puede faltar, estar dañada ni quedar vieja; cada versión nueva de la aplicación trae su propia copia.
  • Lo que necesitan las canciones, lo aprende la biblioteca. Una pieza que cambia de tonalidad, de compás o de tempo, una ronda, una orquesta que pasa su melodía de la flauta al clarinete: cada una se convirtió en una función de Part, y cualquier canción puede usarla.

Por lo mismo, una demostración es de solo lectura: no hay archivo donde escribir tus cambios, así que Guardar pide uno nuevo (ver La barra de título).

Una canción es una constante

Esta es una canción entera, la ronda Frère Jacques:

#![allow(unused)]
fn main() {
pub(super) const FRERE_JACQUES: Song = Song {
    name: "Frère Jacques",
    origin: "French round, 18th century",
    feel: "Round",
    genre: Genre::Folk,
    tonic: C,
    mode: Major,
    time: COMMON,
    tempo: 104.0,
    comping: Comping::ArpUpDown,
    bass: Bass::RootFifth,
    rhythm: "Folk",
    band: SONG_AND_GUITAR,
    style: "Folk",
    parts: &[
        Part::new("Alone", FRERE_JACQUES_TUNE, FRERE_JACQUES_CHORDS),
        Part::new("Round", FRERE_JACQUES_TUNE, FRERE_JACQUES_CHORDS).with_voices(&[
            (1, 0),
            (2, 2),
            (3, 4),
            (4, 6),
        ]),
        // ...
    ],
    form: &["Alone", "Round", "Round again", "Last voices"],
    drums_from: 0,
    about: "A round: four voices sing the same tune, each two bars after the last. It works \
            because every two bars of the tune fit the same chord.",
};
}

La melodía y los acordes son constantes aparte, encima, porque las usan cuatro partes:

#![allow(unused)]
fn main() {
const FRERE_JACQUES_TUNE: &str = "C5:4 D5 E5 C5 | C5 D5 E5 C5 | E5 F5 G5:8 | E5:4 F5 G5:8 | \
     G5:2 A5 G5 F5 E5:4 C5 | G5:2 A5 G5 F5 E5:4 C5 | C5 G4 C5:8 | C5:4 G4 C5:8";
const FRERE_JACQUES_CHORDS: &str = "I | I | I | I | I | I | I | I";
}

Los campos se leen de arriba abajo como la cabecera de una partitura guía: qué es y de dónde viene, su tonalidad, su compás y su tempo; luego cómo la viste la banda, una manera de acompañar y una línea de bajo de la biblioteca (ver El comping: cómo se tocan los acordes), un ritmo por su nombre, los instrumentos (band) y el estilo al que más se parece, que la Biblioteca muestra seleccionado mientras la demostración está abierta. Después, la música: las partes (parts), cada una una sección como una estrofa o un estribillo, y la forma (form), el orden en que se tocan, por nombre. Una parte que se toca tres veces se escribe una vez y se nombra tres. drums_from es la entrada de la forma donde entra la batería: 0 para el principio, 1 cuando la canción abre con una anacrusa o una introducción que la batería deja tranquila.

Un archivo por género

343 canciones harían un archivo de decenas de miles de líneas. Así que song.rs guarda los tipos, las constantes compartidas, la lista y los tests, y las canciones viven al lado, un archivo por género:

library/
  song.rs          Song, Part, Band, SONGS, los tests
  song/
    balkan.rs
    blues.rs
    classical.rs
    ...
    tango_played.rs

mod tango; dentro de song.rs hace que Rust busque song/tango.rs: cada archivo de género es un módulo hijo de song. Una canción es pub(super) const, visible para su padre y nada más: fuera de la biblioteca nadie necesita el nombre HABANERA, solo la canción, que encuentra en la lista.

Cada archivo de género empieza con una línea:

#![allow(unused)]
fn main() {
use super::*;
}

super es el módulo padre y * trae todos los nombres que este ve, también los privados: en Rust un elemento privado de un módulo es visible para los hijos de ese módulo. Así una canción puede usar COMMON, SIX_EIGHT, las bandas y cualquier otra constante que song.rs se guarda para sí. Dos de los use del propio padre son los que más importan:

#![allow(unused)]
fn main() {
use PitchClass::*;
use ScaleKind::*;
}

Permiten que una canción escriba tonic: A y mode: Dorian en lugar de PitchClass::A y ScaleKind::Dorian. Genre no se importa así, a propósito: Genre::Blues y ScaleKind::Blues serían los dos Blues, y dos importaciones con * que traen el mismo nombre son un error en cuanto se usa ese nombre. Así que un género se escribe siempre completo, genre: Genre::Blues, y mode: Blues es la escala.

La lista

#![allow(unused)]
fn main() {
pub static SONGS: &[Song] = &[
    pop::AULD_LANG_SYNE,
    pop::LONDON_BRIDGE,
    pop::OLD_MACDONALD,
}

Cada canción aparece en la lista por su nombre. Rust no puede unir slices mientras compila, así que los archivos de género no pueden traer cada uno su propia lista; nombrar cada const es la manera sencilla. Una const se copia allí donde se usa, así que la lista guarda las canciones mismas, y como es una static, tiene un único lugar en memoria durante toda la ejecución: el menú y la Biblioteca pueden repartir referencias &'static Song a ella. La lista va agrupada por género, en el orden de Genre::ALL, y un test lo comprueba con windows(2), que da cada par de vecinos:

#![allow(unused)]
fn main() {
SONGS.windows(2).all(|pair| pair[0].genre <= pair[1].genre),
}

<= funciona con géneros porque Genre deriva PartialOrd, que ordena las variantes de un enum tal como están escritas. El menú ordena las canciones por título por su cuenta (ver El menú Demostraciones), así que una canción nueva va al final del bloque de su género, y dos cambios que agregan canciones a la vez no se pelean por las mismas líneas.

De dónde salen las notas

Cada canción lleva origin, quién la escribió y cuándo, y encima un comentario // Source: que nombra la partitura, el cifrado, el grabado o la grabación de donde se tomaron sus notas, y qué se simplificó. El comentario de la Promenade, por ejemplo, nombra el grabado de LilyPond del que se leyó y explica cómo se eligió un acorde para cada pulso. Casi todas las canciones son tan viejas que son de todos; las pocas que todavía tienen derechos de autor en algún lugar lo dicen en su comentario. Veinticinco son melodías nuevas para géneros con pocas libres, escritas como esos géneros escriben las suyas, y su origin dice «Written for Ferrous Lineup». Cómo una interpretación o una partitura se convierte en una constante es el capítulo siguiente, De una interpretación MIDI o una partitura a una demostración.

La melodía, como texto

Una melodía se escribe en una notación pequeña, que lee chart::parse_melody:

TextoSignifica
C4, F#5, Bb3una nota: su letra, # o b, y su octava (C4 es el do central)
:4su duración en pasos (4 es una negra); sin ella, tan larga como la nota anterior
:1/2, :1/4más corta que un paso: una fusa, una semifusa, para adornos y escalas rápidas
-:8un silencio, aquí medio compás de 4/4
_:8una ligadura: la nota anterior sigue, también a través de una barra de compás
|una barra de compás: no cambia nada, pero cada compás se comprueba
A3+C#4+E4:4un acorde, sus notas a la vez
&otra voz, desde el principio, al mismo tiempo: E5:4 D5 C5 B4 & C3:16
(3las tres notas o silencios siguientes son un tresillo, tres en el tiempo de dos
!p, !f, !sfzuna dinámica, de !pp a !ff, o un sforzando en la nota siguiente
!<, !>un crescendo o un diminuendo, hasta la dinámica siguiente
>A4:2, D5:2.un acento delante de una nota; un punto de staccato después

Las marcas no ocupan tiempo. Las notas de un tresillo empiezan entre los pasos de la cuadrícula, así que se colocan al tick (ver Notas entre pasos). La estrofa de Greensleeves se lee así:

C5:8 D5:4 | E5:6 F#5:2 E5:4 | D5:8 B4:4 | G4:6 A4:2 B4:4 | ...

Una melodía que empieza antes de su primer compás, como el «In a» de «Clementine», tiene una anacrusa. Se escribe como una parte propia, un compás que es casi todo silencio:

#![allow(unused)]
fn main() {
Part::new("Pickup", "-:8 C5:3 C5:1", "I"),
}

Eso es un compás de 3/4: dos pulsos de silencio y luego «In a», una corchea con puntillo y una semicorchea. El primer tiempo fuerte de la melodía cae entonces en el primer tiempo de la estrofa, donde lo esperan la batería y los acordes.

Los acordes, como cifrado

Debajo de la melodía va un cifrado, compás por compás (ver Cifrados: acordes compás por compás): números romanos como i | bIII | bVII | v, o nombres como los escribe una partitura guía, C#m | G#7/B#, leídos en la tonalidad de la parte. Las marcas de la hoja de acordes también funcionan: un cifrado puede anticipar un acorde o detener la banda (ver Golpes, sostenidos, silencios y anticipaciones). El cifrado es lo que toca la banda, con el acompañamiento y la línea de bajo de la canción, y lo que muestra la hoja de acordes.

En algunas músicas el acompañamiento es parte de la pieza: la mano izquierda de un rag, un coral de Bach, un coro. Entonces la parte lo escribe, y sus acordes no se acompañan:

  • with_left(...) escribe notas para el canal Teclados, en la notación de las melodías, & incluido;
  • with_lines(&[...]) escribe otros instrumentos, cada uno una Line con el nombre de su canal, un programa de General MIDI y sus notas. Las líneas con el mismo nombre en cualquier parte suenan en el mismo canal, así que el coro de la Misa Criolla es una sola línea, «Choir» (Coro), con su contralto, su tenor y su bajo unidos por &.

Una parte escrita entera puede tener cifrado igual: entonces nombra lo que toca la música, para la hoja de acordes, y la banda no lo toca.

Partes que cambian algo

Part::new(name, melody, chords) crea una parte sencilla. Las demás son la misma parte con un campo cambiado, por una const fn que usa la sintaxis de actualización de structs:

#![allow(unused)]
fn main() {
const fn in_key(self, tonic: PitchClass, mode: ScaleKind) -> Part {
    Part {
        key: Some((tonic, mode)),
        ..self
    }
}
}

..self quiere decir «todos los demás campos como en self». Como solo mueve valores de un lado a otro, puede ser const, y una canción sigue siendo una constante con .in_key(FSharp, Major) al final de una de sus partes. Las demás funcionan igual:

MétodoLa parte…Por ejemplo
in_key(tonic, mode)está en otra tonalidad: una modulaciónel rey de la montaña sube a fa♯ mayor
in_time(time)está en otro compásun estribillo en 3/4 en una canción en 2/4
in_meters(&[...])tiene un compás para cada compáslos 5/4 y 6/4 de la Promenade
with_tempo(&[...])tiene las indicaciones de tempo de la partitura«Più vivo», un ritardando, un calderón
played(comping, bass, rhythm)se acompaña a su manerael estribillo en 3/4 necesita el acompañamiento de un vals
with_voices(&[...])la cantan varias vocesuna ronda
played_by(&[...])pasa su melodía a otros instrumentosBoléro, de la flauta al clarinete
marked(dynamic)se toca así de fuerteel comienzo pianissimo de Boléro
with_drums(&[...])tiene la batería escritala caja de Boléro
with_left, with_linesescribe su acompañamientola mano izquierda de un rag, un coro

En la gruta del rey de la montaña usa dos:

#![allow(unused)]
fn main() {
Part::new("Higher", MOUNTAIN_HIGHER, MOUNTAIN_HIGHER_CHORDS).in_key(FSharp, Major),
// "Più vivo": faster at once, at Grieg's own metronome mark.
Part::new("Più vivo", MOUNTAIN_THEME, MOUNTAIN_CHORDS).with_tempo(&[tempo(0, 154)]),
}

Una tonalidad propia

Una parte en otra tonalidad lee sus números romanos en esa tonalidad, así que I en la parte en fa♯ mayor es un acorde de fa♯, y la demostración pone una marca de tonalidad donde empieza y otra donde la música vuelve. La canción responde en qué tonalidad está una parte:

#![allow(unused)]
fn main() {
pub fn part_scale(&self, part: &Part) -> Scale {
    match part.key {
        Some((tonic, mode)) => written_as_tune(Scale::new(tonic, mode), [part]),
        None => self.scale(),
    }
}
}

written_as_tune decide el nombre de la tonalidad. Un PitchClass tiene una variante para cada una de las doce notas, con sostenidos (ASharp, nunca un si bemol), pero la Promenade está en si♭ mayor, no en la♯ mayor, y Take Five en mi♭ menor, no en re♯ menor. En lugar de hacer que cada canción deletree su tonalidad, la biblioteca cuenta las alteraciones de la melodía y nombra la tonalidad como está escrita la melodía:

#![allow(unused)]
fn main() {
fn written_as_tune<'a>(key: Scale, parts: impl IntoIterator<Item = &'a Part>) -> Scale {
    let (sharps, flats) = parts
        .into_iter()
        .map(|part| accidentals(part.melody))
        .fold((0, 0), |(sharps, flats), (s, f)| (sharps + s, flats + f));
    match sharps.cmp(&flats) {
        Ordering::Greater => key.with_spelling(Spelling::Sharps),
        Ordering::Less => key.with_spelling(Spelling::Flats),
        Ordering::Equal => key,
    }
}
}

fold lleva un par a través de las partes y suma las dos cuentas a la vez, y cmp devuelve un Ordering, que el match lee como «más sostenidos», «más bemoles» o «igual». Una melodía con tantos de unos como de otros, o sin ninguno, deja a la tonalidad su nombre habitual. Cómo se escribe una tonalidad está en Sostenidos o bemoles: cómo se escribe una tonalidad.

La música que no está en ninguna tonalidad, donde las doce notas están en casa, lo dice con la escala cromática: el Juego del rapto de principio a fin, con mode: Chromatic, y partes de Petrushka con .in_key(C, Chromatic). Una parte así no tiene armadura, y cada nota que toca cuenta como dentro de su tonalidad.

Un compás para cada compás

La mayoría de las partes está en el compás de la canción o en uno propio. Algunas músicas cambian de compás casi en cada compás: la Promenade alterna 5/4 y 6/4, La consagración de la primavera pasa por 2/8, 3/8, 3/4, 5/8 y más. Una parte con in_meters(&[...]) tiene una indicación de compás por compás. Un patrón tiene una sola indicación de compás, así que Song::part_runs corta una parte así en tramos de compases con la misma indicación, cada uno una parte propia con su porción de la melodía y del cifrado:

#![allow(unused)]
fn main() {
let mut runs: Vec<(usize, usize, TimeSignature)> = Vec::new();
for (bar, &time) in part.meters.iter().enumerate() {
    match runs.last_mut() {
        Some((_, bars, last)) if *last == time => *bars += 1,
        _ => runs.push((bar, 1, time)),
    }
}
}

last_mut da el último tramo, si lo hay, para cambiarlo en su sitio: un compás con la misma indicación lo alarga un compás, y cualquier otro empieza un tramo nuevo. Los trozos del texto salen de bars_of, que devuelve un slice del propio &'static str de la parte entre dos barras de compás, así que un tramo es una Part como cualquier otra y no cuesta ninguna copia. La demostración nombra el patrón de cada tramo siguiente por el compás donde empieza, «Llamada, compás 2», y pone una marca de compás donde cambia la indicación. La consagración de la primavera necesitaría más patrones de los que admite un proyecto, así que un tramo con las mismas notas que un tramo anterior, en el mismo compás, vuelve a tocar ese patrón anterior, como un productor reusa un loop.

El tempo de la partitura

Un TempoMark es una de las tres cosas que escribe una partitura:

#![allow(unused)]
fn main() {
pub enum TempoMark {
    /// From `at` on, `bpm` beats a minute, at once: a new section with
    /// a new tempo, such as "Più mosso" (more movement), "Adagio" or
    /// "Tempo I" (the first tempo again).
    At { at: usize, bpm: u16 },
    /// From `at`, slowing down or speeding up bit by bit until `bpm`,
    /// reached `over` steps later: "rit." or "rall." (slowing down),
    /// "accel." or "stringendo" (speeding up).
    Glide { at: usize, bpm: u16, over: usize },
    /// A fermata (𝄐), the "bird's eye" over a note or a rest: from `at`,
    /// for `steps` steps, the music waits, lasting twice as long; then
    /// it carries on at the tempo before.
    Fermata { at: usize, steps: usize },
}
}

tempo(at, bpm), glide(at, bpm, over) y fermata(at, steps) son const fn que los crean, contando pasos desde el principio de la parte. La demostración convierte cada uno en marcas de tempo en la lista de reproducción, cada vez que se toca la parte. Un calderón se convierte en dos: la mitad del tempo mientras dura la nota sostenida, y después el tempo de antes. El proyecto no tiene calderón propio; la mitad de rápido es exactamente «el doble de largo» (ver Cambios de tempo).

Rondas y cánones

with_voices dice quién canta la melodía, y desde qué compás de la parte: un (voice, bar) para cada copia. La ronda de Frère Jacques son cuatro voces, cada una dos compases después de la anterior:

#![allow(unused)]
fn main() {
Part::new("Round", FRERE_JACQUES_TUNE, FRERE_JACQUES_CHORDS).with_voices(&[
    (1, 0),
    (2, 2),
    (3, 4),
    (4, 6),
]),
}

Un compás negativo es una voz que empezó en la parte anterior, así que solo se oye su final: «Round again» deja que las voces 2 a 4 terminen sus últimos compases mientras empiezan de nuevo. La voz 1 es el canal Melodía, y cada otra voz recibe un canal propio, «Voz 2», «Voz 3» y así.

Otros instrumentos, más fuerte y más suave

played_by toma una lista de (program, semitones): cada instrumento toca la melodía, esa cantidad de semitonos más arriba, en un canal con su nombre. Boléro pasa su melodía de la flauta al clarinete, al fagot y así; su clarinete en mi♭ la toca una octava más arriba, (gm::CLARINET, 12). marked fija lo fuerte que se toca la parte, de pianissimo a fortissimo:

#![allow(unused)]
fn main() {
pub fn scale(self) -> f32 {
    match self {
        Dynamic::Pianissimo => 0.4,
        Dynamic::Piano => 0.55,
        Dynamic::MezzoPiano => 0.75,
        Dynamic::MezzoForte => 1.0,
        Dynamic::Forte => 1.15,
        Dynamic::Fortissimo => 1.3,
    }
}
}

with_drums escribe una parte de batería, en la notación de las melodías, con x para un golpe y X para uno acentuado. En lugar de un segundo analizador, drum_line convierte cada x en un C4 y cada X en un D4, lee la línea como una melodía, y después hace de cada nota un golpe de un paso, acentuado si era un D4, tan fuerte como dicen las dinámicas de la línea: tresillos, silencios y dinámicas salen gratis.

La banda de una canción

band nombra los programas de General MIDI de los canales Melodía, Teclados y Bajo, y quizá una sección de vientos:

#![allow(unused)]
fn main() {
pub struct Band {
    pub lead: u8,
    pub keys: u8,
    pub bass: u8,
    pub horns: Option<Horns>,
}
}

La mayoría de las bandas son constantes que comparten las canciones de un género:

#![allow(unused)]
fn main() {
const BOSSA: Band = Band::new(gm::TENOR_SAX, gm::NYLON_GUITAR, gm::ACOUSTIC_BASS);
}

Band::new y with_horns son const fn, así que una banda se puede construir dentro de una constante. Una sección de vientos es un programa y una manera de acompañar: los saxofones de una big band sostienen los acordes (Comping::Held) y sus metales los golpean en el pulso y justo antes del pulso 3 (Comping::Charleston). Los violines de una orquesta típica de tango también son una «sección de vientos», con un programa de cuerdas: la demostración llama al canal «Cuerdas» para los programas 40 a 51 y «Vientos» para cualquier otro. Una canción con una banda con vientos es algo distinto de la sección de vientos del estilo de banda, que cualquier canción puede encender.

De una canción a un proyecto

song_demo(song) construye el proyecto en cuatro pasos.

Canales. La Melodía, un canal para cada voz de más, los Teclados solo si alguna parte toca acordes o una mano izquierda, el Bajo salvo que la canción no tenga línea de bajo, los vientos, un canal para cada instrumento al que se pasa una melodía, y uno para cada nombre de línea. Cada uno recibe su programa.

Un patrón para cada parte, o para cada tramo de una parte cuyo compás cambia. Ahí va la melodía en los canales de sus voces, el cifrado acompañado con el acompañamiento y el bajo de la parte (accompany_in_bars, que mantiene el acompañamiento al paso de los compases, así un acorde de un pulso igual se toca), la parte de los vientos, la mano izquierda y las líneas escritas, y la batería escrita. Después dos pasadas hacen que suenen tocados (ver la sección siguiente) y la dinámica de la parte los escala.

Un patrón de batería para cada parte, con el ritmo de la canción o el de la parte, en el compás de la parte, con un fill al final de las partes de cuatro compases o más, salvo que la parte escriba su batería.

El arreglo. La forma se recorre una vez, y cada entrada pone los patrones de su parte uno tras otro en la pista Canción y su batería en la pista Batería desde drums_from, con marcas de tonalidad, de tempo y de compás donde cambian, y el cifrado escrito en la hoja de acordes. Un último compás en el acorde de tónica, con un platillo, termina la canción, en la tonalidad de la última parte, así una canción que modula termina en la tonalidad nueva. Una pieza cuya última parte está escrita entera termina donde termina su partitura, y lo mismo un cifrado que termina en un corte, como el «chan-chan» de un tango: la partitura ya dice cómo se detiene la música.

Dos detalles la terminan. Las canciones, los himnos, los tangos y las piezas de concierto se frenan un poco en su último compás, como hace quien toca donde no hay caja de ritmos a la que seguir:

#![allow(unused)]
fn main() {
fn ends_broadening(song: &Song) -> bool {
    use super::Genre::*;
    matches!(song.genre, Classical | Folk | Soul | Tango | Folklore)
}
}

Eso es un poco ritardando hasta nueve décimos del tempo, salvo que la partitura ya marque cómo va el tempo de la última parte. Y el swing es el del ritmo de la canción, salvo en el folk y la música clásica, cuyas corcheas quedan parejas sobre cualquier ritmo:

#![allow(unused)]
fn main() {
pub fn swing(&self) -> u8 {
    match self.genre {
        Genre::Folk | Genre::Classical => 0,
        _ => Rhythm::named(self.rhythm).map_or(0, |rhythm| rhythm.swing),
    }
}
}

El resultado es un proyecto corriente, para desarmar y cambiar: nada en él está marcado como demostración.

Tocar como una persona

Una partitura escrita nota a nota, cada nota tan fuerte como la siguiente, suena a máquina. interpret.rs cambia las velocidades unos pocos puntos sobre 127, como lo haría quien toca; las notas se quedan donde están. Para una melodía, perform le da forma a cada frase:

  • el primer tiempo del compás un poco más fuerte, los otros tiempos menos, las notas a contratiempo más ligeras;
  • cuanto más aguda una nota en su frase, más se canta;
  • una nota larga, apoyada; la última nota de una frase, relajada;
  • y nunca dos notas del todo iguales.

Una frase termina en un silencio de una corchea o más, la respiración más corta de quien canta, o después de cuatro compases. Los acordes y el bajo, cuyos acentos ya escribió el acompañamiento, solo pierden su regularidad: las notas de un acorde un poco distintas entre sí, los acordes un poco más. Después follow hace que la banda toque tan fuerte como está marcada la melodía donde empieza cada una de sus notas, así un crescendo en la melodía arrastra a la banda, y un acento en la melodía es medio acento en la banda.

«Nunca dos iguales» no puede querer decir «distinto cada vez»: una demostración tiene que sonar igual cada vez que se abre, y un test que comprueba sus notas tiene que ver las mismas notas. Así que las pequeñas diferencias salen de una función que parece aleatoria pero no lo es:

#![allow(unused)]
fn main() {
fn wobble(seed: u64, value: u64) -> f32 {
    let mut x = seed ^ value.wrapping_mul(0x9E37_79B9_7F4A_7C15);
    x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
    x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
    x ^= x >> 31;
    (x >> 40) as f32 / (1u64 << 23) as f32 - 1.0
}
}

Es el paso de mezcla de SplitMix64: multiplicaciones y desplazamientos que esparcen los bits de la entrada tan a fondo que entradas vecinas dan salidas sin relación. La entrada es el paso y la altura de la nota; la semilla se hace con los nombres de la canción, la parte y el canal, con FNV-1a (ver FNV-1a). Los 24 bits más altos del resultado, divididos por 2²³, dan un número de 0 a 2, y el menos uno lo lleva de −1 a 1. wrapping_mul multiplica y descarta lo que no cabe en 64 bits, lo que un * normal trataría como desbordamiento. La forma que se le da queda pequeña al lado de la diferencia entre piano y forte: una velocidad cambiada por clamp queda entre 24 y 124, o donde estaba escrita si eso queda más afuera, así un pianissimo sigue suave.

El menú Demostraciones

El menú muestra las demostraciones por tipo (Canciones, Estilos, Dodecafónico, Micromodos), las canciones y los estilos por género, y cada lista en orden alfabético. Las listas de la biblioteca guardan el orden en que se escribieron; el menú ordena una copia, que construye la primera vez que se muestra, en un LazyLock:

#![allow(unused)]
fn main() {
static DEMOS: LazyLock<Demos> = LazyLock::new(|| Language::English.with(all_demos));
static DEMOS_ES: LazyLock<Demos> = LazyLock::new(|| Language::Spanish.with(all_demos));
}

Una static tiene que conocerse cuando se compila el programa; un LazyLock es una static cuyo valor calcula la clausura la primera vez que alguien la lee, y lo guarda. Hay dos, porque los títulos se ordenan tal como se muestran, y en español muchos se muestran en español.

Alfabético, para personas

Comparar texto letra por letra, por número de Unicode, pone todas las mayúsculas antes de todas las minúsculas y «Ábalos» después de «Zamba». Así que el menú ordena por una copia plegada de cada título: en minúsculas, sin tildes, sin puntuación, con el guion como un espacio. «Für Elise» se ordena como «fur elise», justo después de «Funeral March»:

#![allow(unused)]
fn main() {
fn by_title(a: &str, b: &str) -> Ordering {
    fold(a).cmp(&fold(b)).then_with(|| a.cmp(b))
}
}

then_with desempata, dos títulos que se pliegan igual, por los títulos mismos, así el orden nunca depende del azar. fold es un solo match sobre los caracteres: cada letra con tilde se convierte en la simple, ♯ y ♭ en # y b, las letras, los dígitos, #, / y + se quedan (así se pueden buscar «F#», «3/4» y «2+2+3/8»), y cualquier grupo de espacios y signos entre palabras se convierte en un espacio.

Buscar

Un cuadro de búsqueda está encima de los submenús y tiene el teclado en cuanto se abre el menú. Cada demostración tiene tres textos plegados: su título (en los dos idiomas, así «Noche de paz» encuentra Silent Night en cualquiera de los dos), sus detalles (quién la escribió, su estilo, su género, su tonalidad y su compás) y las notas sobre ella. Cada palabra escrita tiene que empezar una palabra en uno de ellos: bach encuentra a Bach, no a Offenbach:

#![allow(unused)]
fn main() {
fn starts_a_word(text: &str, words: &str) -> bool {
    text.match_indices(words)
        .any(|(at, _)| at == 0 || text[..at].ends_with(' '))
}
}

Las coincidencias se ordenan, la mejor primero:

#![allow(unused)]
fn main() {
enum Rank {
    TitleStart,
    TitleWords,
    Details,
    Words,
    About,
}
}

Primero va el título que empieza con lo escrito, luego cada palabra encontrada en el título, luego la frase encontrada en los detalles, como «re menor» en una canción en re menor, luego las palabras encontradas por separado, y al final una palabra encontrada solo en las notas. Rank deriva Ord, así que las variantes se comparan en el orden en que están escritas, y un orden estable por rango mantiene el orden alfabético entre iguales. Los dos rangos más débiles se muestran más tenues, así resaltan las coincidencias que más probablemente buscabas.

Cómo se prueba

Las canciones son datos, y los tests los leen todos. En song.rs: every_bar_of_every_part_adds_up (cada compás de cada melodía y mano izquierda dura lo que su indicación de compás, cada cifrado se puede leer, una parte con un compás por compás tiene tantas indicaciones como compases), forms_name_real_parts_and_rhythms_exist, every_song_names_a_real_style, songs_in_three_play_in_three, songs_that_change_meter_have_a_style_that_follows_it, every_genre_has_songs_and_they_are_in_order, keys_are_written_as_their_tunes_are (Take Five en mi♭ menor, el trío del Maple Leaf Rag en re♭ mayor), melodies_are_mostly_in_their_key (al menos el 80% de las notas de una canción en la tonalidad de su parte, lo que atrapa un fa escrito por un fa♯) y the_first_notes_are_right (Oda a la alegría empieza fa♯ fa♯ sol la).

En demo.rs, every_song_makes_a_demo construye las 343, y otros comprueban una cosa cada uno: a_round_has_its_voices_in_the_right_places, a_part_in_its_own_time_gets_time_markers, runs_of_the_same_bars_share_a_pattern, tempo_markings_go_on_the_playlist, songs_and_hymns_slow_down_a_little_at_the_end, a_song_that_ends_after_a_modulation_ends_in_the_new_key, a_piece_written_out_in_full_ends_where_its_score_ends, a_big_band_has_its_horns, the_band_follows_the_dynamics_of_the_tune_and_the_breaks_of_the_chart y every_demo_keeps_a_sensible_tempo (cada marca de tempo entre tres décimos y dos veces y media el tempo de la canción). interpret.rs comprueba que piano_stays_soft_and_fortissimo_loud y que a_tune_gets_its_accents_but_stays_close_to_how_it_was_written. demo_menu.rs comprueba el plegado, el orden y los rangos, como matches_only_in_the_notes_come_last.

Además de los tests unitarios, cualquier demostración se puede tocar sin ventana con la traza de reproducción, ferrous-lineup --trace "Frère Jacques" --out round.tsv, y ferrous-lineup --demos lista los nombres que acepta.