# The engine songs

> The thirty-nine public domain songs the engine has, asked for by key, with their tempo and key changed while they play.

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

The engine has songs of its own: old pieces that belong to everyone (Beethoven, Grieg, Bach, Joplin, folk songs…) arranged for the NES, the SNES and the Genesis. `BUILTIN.MUSIC.SONGS` lists them, with their name, composer, year and console.

Each one is asked for by its key, `useLoadSong({ src: 'nacatamalon:ode-to-joy' })`, and only downloaded then. From there it is a song like any other: `setTempo` and `setTranspose` change it while it plays.

Up and down to choose, space to play, left and right for the tempo, `-` and `=` for the key.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import * as Settings from './settings';
import { Library } from './scenes/demo';

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

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

`components/browser.ts`:

```ts
import { createText, useKeyboard, useLoadSong, useSong, useUpdate, BUILTIN } from 'nacatamalon';
import * as Settings from '../settings';
import { plain } from '../helpers/plain';

export const Browser = () => {
    const keys = useKeyboard();

    // One song each, asked for now: each comes from the engine as it is asked for.
    const songs = BUILTIN.MUSIC.SONGS.map((info) => useSong(useLoadSong({
        src: 'nacatamalon:' + info.key
    })));

    let chosen = 0;
    let key = 0;

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

    const rows = Array.from({
        length: Settings.SHOWN
    }, (_, i) => createText({
        text: '',
        transform: {
            x: 24,
            y: 50 + i * 18
        }
    }));

    const about = createText({
        text: '',
        tint: Settings.DIM,
        transform: {
            x: 24,
            y: 220
        }
    });

    const status = createText({
        text: '',
        tint: Settings.ON,
        transform: {
            x: 24,
            y: 244
        }
    });

    createText({
        text: 'UP/DOWN  SPACE PLAY  LEFT/RIGHT TEMPO  -/= KEY',
        tint: Settings.DIM,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 290
        }
    });

    useUpdate(() => {
        const move = (keys.justPressed('ArrowDown') ? 1 : 0) - (keys.justPressed('ArrowUp') ? 1 : 0);

        if (move !== 0) {
            const was = songs[chosen]!.playing;

            songs[chosen]!.stop();
            chosen = (chosen + move + songs.length) % songs.length;
            key = 0;

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

        const now = songs[chosen]!;

        if (keys.justPressed(' ')) {
            if (now.playing) {
                now.stop({
                    fade: 0.3
                });
            } else {
                now.play();
            }
        }

        if (keys.justPressed('ArrowRight')) {
            now.setTempo(Math.round(now.tempo * 1.1));
        }

        if (keys.justPressed('ArrowLeft')) {
            now.setTempo(Math.round(now.tempo / 1.1));
        }

        if (keys.justPressed('=')) {
            now.setTranspose(++key);
        }

        if (keys.justPressed('-')) {
            now.setTranspose(--key);
        }

        const first = Math.min(Math.max(0, chosen - Math.floor(Settings.SHOWN / 2)), BUILTIN.MUSIC.SONGS.length - Settings.SHOWN);

        rows.forEach((row, i) => {
            const info = BUILTIN.MUSIC.SONGS[first + i]!;

            row.text = (first + i === chosen ? '> ' : '  ') + plain(info.label).slice(0, 40);
            row.tint = first + i === chosen ? Settings.ON : Settings.DIM;
        });

        const info = BUILTIN.MUSIC.SONGS[chosen]!;

        about.text = plain(info.composer + ', ' + info.year + ' - ' + info.chip);
        status.text = (now.playing ? 'PLAYING  ' : 'STOPPED  ') + now.tempo + ' BPM  KEY ' + (key > 0 ? '+' : '') + key;
    });
};
```

`helpers/plain.ts`:

```ts
export const plain = (text: string) => text.normalize('NFD').replace(/\p{M}/gu, '').toUpperCase();
```

`scenes/demo.ts`:

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

import { Browser } from '../components/browser';

export const Library = () => {
    useSpawn(Browser)();

    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('#8a86a0');
export const SHOWN = 9;
```
