Documentación · guía

Hacer música con NacatamalOn

De la primera nota a un arreglo completo, sin programas de música ni teoría musical. La misma guía viene con el motor: node_modules/nacatamalon/guides/.

NacatamalOn toca música escrita como notas, no grabaciones. Una canción es un archivo de texto pequeño (un .song), el motor la convierte en sonido dentro del juego y, como son notas, el juego la puede cambiar mientras suena: más rápida en la última vuelta, en otra tonalidad, sin una voz, con una voz que pasa de izquierda a derecha.

Esta guía va de la primera nota a un arreglo completo para la NES, la SNES o la Genesis. No hace falta ningún programa de música ni saber teoría musical para seguirla.


1. Tu primera canción

Una canción es un tempo, un compás y lo que toca cada voz. Esta son cuatro compases de una melodía, un bajo y una batería, para la NES:

{
    "format": 1,
    "tempo": 120,
    "meter": [4, 4],
    "voices": {
        "pulse1": { "notes": "c'4 e' g' e' | f'4 a' g'2 | e'4 c' d' b | c'1 |" },
        "triangle": { "notes": "c,2 g, | f,2 c, | a,,2 g,, | c,1 |" },
        "noise": { "notes": "x4 r x r | x4 r x r | x4 r x r | x4 x x x |" }
    }
}

Guárdala como public/music/first.song y tócala:

import { createScene, useLoadSong, useSong } from 'nacatamalon';

const Title = () => {
    useSong(useLoadSong({ src: '/music/first.song' }), { autoplay: true });
    return createScene();
};

En el editor ni siquiera hace falta escribir el archivo a mano: abre una canción con Edit song, escribe en el cuadro de cada voz o haz clic en la cuadrícula, y pulsa play. Lo que oyes ahí es exactamente lo que toca el juego.


2. Escribir notas

Cada voz son sus notas como texto, separadas por espacios, leídas de izquierda a derecha.

La nota. Una letra de la a a la g, en el cifrado inglés: c es do, d re, e mi, f fa, g sol, a la y b si. c sola es el do central; a es el la de 440 Hz.

Sostenido y bemol. # la sube un semitono y b la baja: f#, bb.

La octava. ' la sube una octava y , la baja, tantas veces como haga falta: c' es el do por encima del do central, c,, dos octavas por debajo.

Cuánto dura. Un número detrás: 1 redonda, 2 blanca, 4 negra, 8 corchea, 16, 32, 64. Un punto añade la mitad: 4. es una negra y una corchea. Sin número, una nota dura lo mismo que la anterior, así que c8 d e f son cuatro corcheas.

Silencios y batería. r es un silencio. En una voz de ruido, x es un golpe de batería.

Ligadura. ~ al final mantiene una nota en la siguiente, que tiene que ser la misma: c'2~ | c'2 dura una redonda a través de la barra de compás.

Compases. | cierra un compás, y se comprueba: un compás que no suma lo que dice el meter de la canción es un error, que se señala con su línea y su columna. Es tu mejor amigo: casi todas las canciones que fallan tienen una nota de más o de menos, y la barra la encuentra.

El compás. meter es cuántos pulsos tiene un compás y qué nota es un pulso: [4, 4] cuatro negras, [3, 4] un vals, [6, 8] seis corcheas. tempo cuenta ese pulso por minuto.

Una intro que suena una vez. "loop": { "bar": 3 } hace de los compases 1 y 2 una intro: la canción los toca una vez y luego da vueltas del compás 3 al final.


3. Las consolas y sus voces

Una canción dice a qué consola suena con chip, y eso decide sus voces. Cada voz toca una nota cada vez: un acorde son varias voces, o una voz tocando un arpegio (mira la sección 4).

NES ("chip": "nes", la que se usa si no se dice)

Voz Qué es Para qué sirve
pulse1, pulse2 Ondas cuadradas La melodía y la armonía
triangle Una triangular escalonada El bajo
noise Ruido La batería: x, o una nota por su color (más aguda, más siseo)
dpcm Samples cortos con el grano de la consola Una batería, un golpe de orquesta

Una voz cuadrada puede decir su duty, su color: 0.125 fino y nasal, 0.25, 0.5 redondo y hueco, 0.75.

SNES ("chip": "snes")

Ocho voces, de s1 a s8, y cada una toca un sample: la grabación de un instrumento. El motor trae los suyos (un piano, cuerdas, un coro…), hechos con su propio código, así que no hay grabaciones de nadie. La SNES guardaba sus samples muy pequeños y los tocaba a 32000 muestras por segundo, suavizados; el motor hace lo mismo, y por eso tienen ese sonido cálido y algo apagado. También tiene el eco de la SNES.

Genesis ("chip": "genesis")

Seis voces FM, de fm1 a fm6, y las tres cuadradas y el ruido de su segundo chip, de psg1 a psg3 y psgNoise. Una voz FM son cuatro ondas que se doblan unas a otras, y de ahí viene el sonido brillante, metálico y contundente de la Genesis: pianos eléctricos, bajos slap, metales.


4. Instrumentos

Hasta aquí cada nota es un tono plano. Un instrumento dice cómo suena una nota mientras suena: entra fuerte y se asienta, cambia de color, tiembla, toca un acorde. Es lo que hace que una canción suene de verdad.

Los instrumentos del motor

El motor trae instrumentos listos: escribe @ y el nombre antes de las notas.

"pulse1": { "notes": "@lead c'4 e' g' e' |" }
Para Instrumentos
Cuadradas de NES y de Genesis @lead @soft @pluck @bell @major @minor @power @stab
Triangular de NES @bass @tri-kick
Ruido @kick @snare @hat @open-hat @crash
Samples (dpcm, SNES) @drum-kick @drum-snare @drum-clap @drum-tom @orch-hit @zap
SNES @piano @strings @brass @flute @organ @choir @marimba @guitar @synth-bass @square-lead
FM de Genesis @epiano @slap-bass @fm-bass @fm-brass @fm-strings @fm-organ @fm-bell @dist-guitar @fm-lead

@nombre cambia el instrumento a partir de ahí, y @- vuelve a ninguno. Una voz puede empezar con uno, "instrument": "lead", y cambiar a mitad:

"pulse1": { "notes": "c'4 e' g' e' | @lead c'4 e' g' e' |" }

El primer compás suena plano y el segundo canta: una intro que florece.

Tus propios instrumentos

Una canción puede definir los suyos en instruments, por nombre. Un instrumento son listas de números que se leen una por frame, sesenta veces por segundo, desde que empieza la nota, como se hacía la música de las consolas:

"instruments": {
    "pluck": { "volume": "15 12 9 6 3 0" },
    "chord": { "volume": "12 10 9 | 8", "arpeggio": "| 0 4 7" }
}

Pasado su final, una lista se queda en su último número, a menos que un | diga desde dónde vuelve a empezar: 15 12 | 9 8 baja a 9 y luego va 8 9 8 9…; | 0 4 7 se repite desde el principio mientras dure la nota.

Dos más que no son listas:

Si la canción define un instrumento con el mismo nombre que uno del motor, suena el suyo; @nacatamalon:kick siempre es el del motor.


5. Lo que hace una nota por su cuenta

A veces solo una nota debe deslizarse o temblar. Escríbelo entre corchetes justo después de la nota, sin espacios:

Escrito Lo que hace esa nota
c'4[slide] Entra deslizándose desde la nota anterior ([slide:0.1]: en una décima de segundo)
c'2[vib] Tiembla ([vib:6,0.3]: seis veces por segundo, un tercio de semitono; [vib:6,0.3,0.2] tras un retardo)
c'4[arp:0,4,7] Toca un acorde, una nota por frame
c'4[vol:8] Suena más bajo, de 0 a 15

Se pueden juntar: c'2[vib][vol:10]. Una nota ligada lleva lo que diga su primera parte.


6. Samples, eco y paneo

Samples

La voz dpcm y las voces de la SNES tocan samples. Los del motor van por nombre (nacatamalon:kick, nacatamalon:piano…), y cualquier archivo de sonido de tu juego puede ser uno:

"instruments": {
    "hit": { "sample": "/samples/hit.wav" },
    "voice": { "sample": "/samples/ah.wav", "root": 64, "loop": 0.4, "adsr": { "attack": 0.05, "release": 0.3 } }
}

El motor carga los archivos que nombra una canción antes de empezar a tocarla.

Eco (SNES)

"chip": "snes",
"echo": { "delay": 0.16, "feedback": 0.45, "volume": 0.3 },
"instruments": { "lead": { "sample": "nacatamalon:flute", "echo": true } }

Las notas de los instrumentos con "echo": true vuelven a oírse delay segundos después (hasta 0.24), y vuelven otra vez según feedback. Es el sonido de una cueva, una catedral, un sueño. Las cuerdas, el piano, la flauta, el órgano y el coro del motor para la SNES ya van al eco.

Paneo

Cada voz se puede oír en cualquier punto entre la izquierda (-1) y la derecha (1):

"pulse1": { "notes": "...", "pan": -0.3 },
"pulse2": { "notes": "...", "pan": 0.6 }

La Genesis ponía cada una de sus voces a la izquierda, a la derecha o al centro, y la SNES en cualquier punto intermedio: abrir las voces a los lados hace que una canción suene amplia. En el juego, un tween puede llevar una voz de un lado a otro (mira la sección 9).


7. Recetas

Una melodía que canta. @lead, o el tuyo propio: { "volume": "15 14 13 12 11 10 | 9", "duty": "0 0 1", "vibrato": { "speed": 6, "depth": 0.25, "delay": 0.2 } }.

Acordes con una sola voz. @major y @minor tocan el acorde de la nota en la que están: @minor a1 | @major f1 | @major c'1 | @major g1 |.

Un ritmo de batería en la NES. Bombo, charles, caja, charles, en corcheas:

@kick x8 @hat x @snare x @hat x @kick x x @snare x @hat x |

Un bombo más lleno. La triangular le da cuerpo: pon @tri-kick c,4 debajo de cada golpe de @kick.

Una intro y luego la canción. Escribe primero los compases de la intro y "loop": { "bar": 5 }: la intro suena una vez y del compás 5 en adelante da vueltas.

Música que se acelera. Nada en el archivo: en el juego, song.setTempo(song.tempo * 1.2) cuando queda poco tiempo, y sigue desde el mismo pulso.

La misma canción en tres consolas. Cambia chip y los nombres de las voces, y elige instrumentos para cada una: una melodía con @lead (NES), @flute (SNES) o @fm-lead (Genesis).


8. Las canciones que trae el motor

El motor trae treinta y nueve piezas lo bastante antiguas como para ser de todos, arregladas para sus consolas, listas para tocar por su clave:

import { createScene, useLoadSong, useSong } from 'nacatamalon';

const Chase = () => {
    useSong(useLoadSong({ src: 'nacatamalon:mountain-king' }), { autoplay: true });
    return createScene();
};
Clave Pieza Consola Para
fur-elise Beethoven, Para Elisa SNES Un título, un menú
ode-to-joy Beethoven, Himno de la alegría NES Una victoria, un final
mountain-king Grieg, En la gruta del rey de la montaña Genesis Una persecución, una batalla
turkish-march Mozart, Marcha turca NES Un mapa, un nivel
can-can Offenbach, el cancán NES Un bonus, una carrera
william-tell Rossini, Guillermo Tell, el galope Genesis Una carrera, una carga
funeral-march Chopin, Marcha fúnebre SNES Un game over
bridal-chorus Wagner, Coro nupcial SNES Un final feliz
entertainer Joplin, The Entertainer SNES Una tienda
gymnopedie Satie, Gymnopédie n.º 1 SNES Los créditos, un lugar tranquilo
toccata Bach, Tocata y fuga en re menor SNES Un castillo, el último jefe
canon Pachelbel, Canon en re SNES Un final
morning-mood Grieg, La mañana SNES El primer pueblo
minuet-in-g Petzold, Minueto en sol NES Un palacio, un tutorial
greensleeves Greensleeves SNES Una taberna, un bosque
korobeiniki Korobéiniki, canción popular rusa NES Un puzle
eine-kleine Mozart, Pequeña serenata nocturna SNES Un palacio, un título
moonlight Beethoven, Claro de luna SNES Un lugar encantado
prelude-in-c Bach, Preludio en do SNES Un menú, un punto de guardado
bumblebee Rimski-Kórsakov, El vuelo del moscardón Genesis Un enjambre, el pánico
spring Vivaldi, La primavera SNES Una pradera, un nuevo día
twinkle Estrellita, ¿dónde estás? NES Un cielo de noche, una nana
jingle-bells Pierpont, Jingle Bells NES Un nivel de nieve
saints When the Saints Go Marching In NES Un desfile
oh-susanna Foster, Oh! Susanna NES Una granja, un viaje
amazing-grace Amazing Grace SNES Un amanecer, una despedida
yankee-doodle Yankee Doodle NES Soldaditos de juguete
frere-jacques Frère Jacques, en canon SNES Un pueblo, una escuela
beethoven-fifth Beethoven, la Quinta sinfonía, el comienzo Genesis Un jefe que aparece, una revelación
hallelujah Händel, el Aleluya del Mesías SNES Un triunfo, un tesoro
stars-and-stripes Sousa, Barras y estrellas, el trío NES Un desfile, una vuelta de honor
mozart-40 Mozart, la Sinfonía n.º 40, el comienzo SNES Una persecución en un palacio, un reloj
habanera Bizet, la Habanera de Carmen SNES Una bailarina, una plaza de noche
toreador Bizet, la canción del torero de Carmen Genesis Una arena, la entrada de un héroe
blue-danube Johann Strauss II, El Danubio azul SNES Un salón de baile, flotar en el espacio
hungarian-rhapsody Liszt, Rapsodia húngara n.º 2, la danza rápida NES Una persecución de dibujos animados, un nivel frenético
gladiators Fučík, Entrada de los gladiadores NES Un circo, un payaso
valkyries Wagner, La cabalgata de las valquirias, la llamada Genesis Una batalla aérea, una carga
danse-macabre Saint-Saëns, Danza macabra, el comienzo Genesis Un cementerio, la medianoche

Un juego solo lleva las que pide. Cambian de tempo, de tonalidad y de voces como cualquier canción. En una escena, un componente song puede nombrar una: "song": "nacatamalon:canon".


9. Tocarla en un juego

import { createScene, useLoadSong, useSong, useTween, useUpdate } from 'nacatamalon';

const Level = () => {
    const song = useSong(useLoadSong({ src: '/music/level.song' }), { autoplay: true });
    const tween = useTween();

    // The drums out, then back in over a second.
    song.setVoice('noise', 0);
    song.setVoice('noise', 1, 1);

    // The harmony carried from left to right in two seconds.
    tween({ target: song.pan, to: { pulse2: 1 }, duration: 2 }).play();

    useUpdate(() => {
        // Faster as the time runs out, from the same beat it was on.
        // song.setTempo(160);
        // A step higher for the last stretch.
        // song.setTranspose(2);
    });

    song.onBeat((beat) => {
        // Something on every beat: a crowd that bounces, lights that flash.
        void beat;
    });

    return createScene();
};

Un sonido (useSound) también tiene pan: tween({ target: sound, to: { pan: 1 }, duration: 2 }).play().

Un juego de ritmo se hace con las propias notas de la canción: song.notes son las de todas las voces, en el orden en que empiezan, cada una con su voz (voice), su comienzo (start) y su duración (beats) en pulsos, y su altura (pitch). Están en cuanto llega el archivo, antes de que suene, y una nota que cae hacia la línea donde golpea el jugador se coloca por lo lejos que está de song.beat:

import { createScene, useLoadSong, useSong, useUpdate, type TSprite } from 'nacatamalon';

declare const dots: TSprite[]; // uno por cada golpe de batería
const LINE = 200; // donde golpea el jugador
const SPEED = 40; // píxeles por pulso

const Stage = () => {
    const song = useSong(useLoadSong({ src: '/music/stage.song' }), { autoplay: true });

    useUpdate(() => {
        const beat = song.beat;
        if (beat === null) return;
        const hits = song.notes.filter((note) => note.voice === 'noise');
        hits.forEach((note, i) => {
            dots[i]!.transform.y = LINE - (note.start - beat) * SPEED;
        });
    });

    return createScene();
};

Una nota se queda en su pulso cuando cambia el tempo, y su pitch se mueve con setTranspose, salvo la de la batería. Cuando la canción da la vuelta al bucle, beat vuelve a donde empieza el bucle y vuelven las mismas notas.


10. Una voz que la canta

El motor tiene su propia voz, hecha como la música en lugar de grabada: una garganta y una boca, como hablaban las máquinas de los ochenta. Está en nacatamalon/speech, así que un juego que nunca habla no lleva nada de ella.

La voz de una canción se escribe aparte de la canción, en un archivo .vocal: sus versos, cada uno una lista de notas con su sílaba, y quién los canta.

{
    "format": 1,
    "start": 8,
    "singers": { "robot": { "throat": 1.15, "vibrato": 0.1 } },
    "lines": [
        { "notes": [
            { "pitch": "E4", "beats": 2, "lyric": "Que-" },
            { "pitch": "D4", "beats": 1, "lyric": "da-" },
            { "pitch": "C4", "beats": 1, "lyric": "te" },
            { "pitch": "D4", "beats": 2, "lyric": "aquí" }
        ] }
    ]
}

accompany la canta sobre una canción, y useSong toca las dos:

import { createScene, createText, useLoadSong, useSong, useUpdate, type TBitmapFont } from 'nacatamalon';
import { accompany, lyricsAt, useLoadVocal } from 'nacatamalon/speech';

declare const font: TBitmapFont;

const Stage = () => {
    const vocal = useLoadVocal({ src: '/music/daisy.vocal' });
    const song = useSong(accompany(vocal, useLoadSong({ src: '/music/daisy.song' })), { autoplay: true });

    // La letra a medida que se canta.
    const words = createText({ text: '', font, transform: { x: 20, y: 20 } });
    useUpdate(() => {
        words.text = lyricsAt(vocal, song.beat)?.words ?? '';
    });

    return createScene();
};

Las palabras habladas y una línea cantada sueltas son sonidos como cualquier otro:

import { useSound } from 'nacatamalon';
import { renderSinging, renderSpeech } from 'nacatamalon/speech';

const hello = useSound(renderSpeech({ text: '¡Hola, mundo!' }));
const tune = useSound(renderSinging({ notes: [{ pitch: 'C4', lyric: 'la' }, { pitch: 'E4', lyric: 'la' }], tempo: 100 }));

11. Traer música de FamiTracker o FamiStudio

Si compones en un tracker, exporta la canción como texto: en FamiTracker, File, Export text; en FamiStudio, File, Export, FamiStudio Text. Suelta el .txt en el editor: se convierte en public/music/<nombre>.song, con sus samples en public/samples/<nombre>/, y el editor te dice lo que no pudo traer.

Lo que se trae: las macros de los instrumentos como nuestras listas, las notas y sus duraciones, la columna de volumen, los arpegios, el vibrato, el portamento, los samples y dónde da la vuelta la canción. Lo que no: los chips que añadían algunos cartuchos (VRC6, VRC7, FDS, MMC5, N163, S5B), la afinación fina (Pxx), los deslizamientos hacia arriba y hacia abajo, y un cambio de velocidad a mitad de canción. Desde código, importTrackerText de nacatamalon/authoring hace lo mismo.

Un juego también puede cargar la exportación tal cual, sin hacer antes el .song: useLoadTrackerSong la lee al llegar y da la misma canción que useLoadSong, con sus samples en memoria, y suena en la NES del motor con su tempo, su tono, su paneo y sus pulsos.

import { createScene, useLoadTrackerSong, useSong } from 'nacatamalon';
import type { TSceneFn } from 'nacatamalon';

export const Title: TSceneFn = () => {
    // Un archivo con varias canciones: { song: 2 } para la tercera.
    useSong(useLoadTrackerSong({ src: '/music/theme.txt' }), { autoplay: true });
    return createScene();
};

Lee el archivo cada vez que el juego arranca, y avisa una vez de lo que dejó fuera. Para un juego terminado, el .song que hace el editor carga más rápido. Un juego que no lo usa no lo lleva.


12. Cuando algo está mal

El motor nunca para una canción por un error: toca lo que puede leer y avisa del resto.

En el editor, esto aparece debajo de cada voz y en la tarjeta de cada instrumento mientras escribes; useSong avisa del primero en la consola; renderSong los devuelve todos en errors y instrumentErrors.