# Song instruments

> The same four bars as plain tones and with instruments: volume, duty, arpeggio and pitch a frame at a time, vibrato and slide.

Example `song-instruments`, running at https://nacatamalon.com/en/examples/song-instruments. The whole catalogue: https://nacatamalon.com/llms/examples.md.

Without instruments each note of a song is one flat tone. `instruments` gives the voices the shape an 8-bit tracker gives them: lists of numbers read one a frame, a sixtieth of a second, that move a note's `volume`, its colour (`duty`) and its `pitch` while it sounds. A `|` marks where a list starts again once the note is held. `vibrato` adds a wobble and `slide` a glide into each note; `arpeggio` plays a chord one note a frame, which is how the console did chords.

A voice starts with its `instrument`, and `@name` in its notes changes it from there on. Version 3 is version 1 once as an intro and then version 2, in one song with a `loop`. Version 4 defines nothing: it uses only the engine's instruments, effects written after a single note in brackets (`[slide]`, `[vib:5,0.4]`), and drum samples on the fifth voice, `dpcm`.

Space to play, 1 to 4 to compare the versions.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';

import * as Settings from './settings';
import { Player } from './scenes/demo';

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#14121c'
});

// Declare Scenes
game({
    Player
});
```

`components/musicplayer.ts`:

```ts
import { createSprite, createText, useKeyboard, useSong, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';
import { PLAIN, INSTRUMENTS, MIXED, BUILTIN } from '../data/songs';

export const MusicPlayer = () => {
    const keys = useKeyboard();
    const songs = [useSong(PLAIN), useSong(INSTRUMENTS), useSong(MIXED), useSong(BUILTIN)];
    let chosen = 1;

    createText({
        text: 'INSTRUMENTS',
        style: {
            fontSize: 16
        },
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 40
        }
    });

    const labels = ['1 AS TODAY', '2 WITH INSTRUMENTS', '3 INTRO, THEN INSTRUMENTS', "4 THE ENGINE'S, AND SAMPLES"].map((text, i) =>
        createText({
            text,
            anchor: {
                x: 0.5,
                y: 0
            },
            transform: {
                x: 240,
                y: 100 + i * 24
            }
        }));

    const marks = [0, 1, 2, 3].map((i) => createSprite({
        width: 8,
        height: 8,
        tint: Settings.ON,
        transform: {
            x: 90,
            y: 104 + i * 24
        }
    }));

    const status = createText({
        text: '',
        tint: Settings.ON,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 240
        }
    });

    createText({
        text: 'SPACE PLAY   1-4 VERSION',
        tint: Settings.DIM,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 280
        }
    });

    // From one version to the other at once, carrying on if it was playing.
    const choose = (index: number) => {
        if (index === chosen) {
            return;
        }

        const wasPlaying = songs[chosen]!.playing;

        songs[chosen]!.stop();
        chosen = index;

        if (wasPlaying) {
songs[chosen]!.play();
        }
    };

    useUpdate(() => {
        if (keys.justPressed(' ')) {
            if (songs[chosen]!.playing) {
            songs[chosen]!.stop({
                fade: 0.2
            });
            } else {
            songs[chosen]!.play();
            }
        }

        if (keys.justPressed('1')) {
            choose(0);
        }

        if (keys.justPressed('2')) {
            choose(1);
        }

        if (keys.justPressed('3')) {
            choose(2);
        }

        if (keys.justPressed('4')) {
            choose(3);
        }

        labels.forEach((label, i) => {
            label.tint = i === chosen ? Settings.ON : Settings.DIM;
        });
        marks.forEach((mark, i) => {
            mark.visible = i === chosen;
        });
        status.text = songs[chosen]!.playing ? 'PLAYING' : 'STOPPED';
    });
};
```

`data/songs.ts`:

```ts
import { type TSongDoc } from 'nacatamalon';
import { DOCUMENTS } from 'nacatamalon/files';

const LEAD = "g'4 f'8 d#' d'4 c'8 d' | d#'4 d'8 c' a#2 | c'4 d#'8 f' g'4 a#'8 g' | f'2 r2 |";

export const PLAIN: TSongDoc = {
    format: DOCUMENTS.SONG.FORMAT,
    tempo: 140,
    meter: [4, 4],
    voices: {
        pulse1: {
            notes: LEAD,
            duty: 0.25
        },
        pulse2: {
            notes: "c'1 | a#1 | g#1 | g1 |",
            duty: 0.5,
            volume: 0.6
        },
        triangle: {
            notes: 'c,4 c, c c, | a#,,4 a#,, a#, a#,, | g#,,4 g#,, g#, g#,, | g,,4 g,, g, g,, |'
        },
        noise: {
            notes: 'x4 x x x | x4 x x x | x4 x x x | x4 x x x |',
            volume: 0.6
        },
    },
};

const DRUMS = '@kick x8 @hat x @snare x @hat x @kick x x @snare x @hat x |';

export const INSTRUMENTS: TSongDoc = {
    format: DOCUMENTS.SONG.FORMAT,
    tempo: 140,
    meter: [4, 4],
    instruments: {
        // Thin at the attack, then rounder, settling, and a wobble once it is held.
        lead: {
            volume: '15 14 13 12 11 10 10 9 | 9',
            duty: '0 0 1',
            vibrato: {
                speed: 6,
                depth: 0.25,
                delay: 0.2
            }
        },
        // Chords played one note a frame, as the console did.
        minor: {
            volume: '11 10 9 8 7 | 7 6',
            duty: '2',
            arpeggio: '| 0 3 7'
        },
        major: {
            volume: '11 10 9 8 7 | 7 6',
            duty: '2',
            arpeggio: '| 0 4 7'
        },
        // A bass that glides a little into each note.
        bass: {
            slide: 0.03
        },
        kick: {
            volume: '15 13 10 7 4 2 0',
            pitch: '0 -8 -16 -20'
        },
        snare: {
            volume: '15 13 11 9 7 5 4 3 2 1 0',
            pitch: '8 4 0'
        },
        hat: {
            volume: '9 5 2 0',
            pitch: '20'
        },
    },
    voices: {
        pulse1: {
            notes: LEAD,
            instrument: 'lead'
        },
        pulse2: {
            notes: "@minor c'1 | @major a#1 | g#1 | g1 |",
            volume: 0.6
        },
        triangle: {
            notes: 'c,4 c, c c, | a#,,4 a#,, a#, a#,, | g#,,4 g#,, g#, g#,, | g,,4 g,, g, g,, |',
            instrument: 'bass'
        },
        noise: {
            notes: [DRUMS, DRUMS, DRUMS, DRUMS].join(' '),
            volume: 0.8
        },
    },
};

// Both in one song: the plain four bars once, as an intro, then the same bars with instruments,
// round and round. A voice plays with no instrument until its first @name.
const BASS = 'c,4 c, c c, | a#,,4 a#,, a#, a#,, | g#,,4 g#,, g#, g#,, | g,,4 g,, g, g,, |';

export const MIXED: TSongDoc = {
    ...INSTRUMENTS,
    loop: {
        bar: 5
    },
    voices: {
        pulse1: {
            notes: `${LEAD} @lead ${LEAD}`,
            duty: 0.25
        },
        pulse2: {
            notes: "c'1 | a#1 | g#1 | g1 | @minor c'1 | @major a#1 | g#1 | g1 |",
            volume: 0.6
        },
        triangle: {
            notes: `${BASS} @bass ${BASS}`
        },
        noise: {
            notes: ['x4 x x x | x4 x x x | x4 x x x | x4 x x x |', DRUMS, DRUMS, DRUMS, DRUMS].join(' '),
            volume: 0.7
        },
    },
};

// The same tune written with nothing defined: only the engine's instruments, what a note does on
// its own in brackets ([slide], [vib:5,0.4]), and a fifth voice, dpcm, playing drum samples.
const BUILTIN_LEAD = "g'4 f'8 d#' d'4[slide] c'8 d' | d#'4 d'8 c' a#2[vib:5,0.4] | c'4 d#'8 f' g'4 a#'8 g' | f'2[vib:5,0.4] r2 |";
const BEAT = "@drum-kick c'4 @drum-snare c' @drum-kick c'8 c' @drum-snare c'4 |";

export const BUILTIN: TSongDoc = {
    format: DOCUMENTS.SONG.FORMAT,
    tempo: 140,
    meter: [4, 4],
    voices: {
        pulse1: {
            notes: BUILTIN_LEAD,
            instrument: 'lead'
        },
        pulse2: {
            notes: "@minor c'1 | @major a#1 | g#1 | g1 |",
            volume: 0.6
        },
        triangle: {
            notes: BASS,
            instrument: 'bass'
        },
        noise: {
            notes: ['@hat x8 x x x x x x x |', 'x8 x x x x x x x |', 'x8 x x x x x x x |', 'x8 x x x x x x x |'].join(' '),
            volume: 0.5
        },
        dpcm: {
            notes: [BEAT, BEAT, BEAT, "@orch-hit c'2 @drum-kick c'4 @drum-snare c' |"].join(' ')
        },
    },
};
```

`scenes/demo.ts`:

```ts
import { createScene, useSpawn } from 'nacatamalon';

import { MusicPlayer } from '../components/musicplayer';

export const Player = () => {
    useSpawn(MusicPlayer)();

    return createScene();
};
```

`settings.ts`:

```ts
import { getColor } from 'nacatamalon';

export const WIDTH = 480;
export const HEIGHT = 320;
export const ON = getColor('#f5c518');
export const DIM = getColor('#6a6680');
```
