# On the beat

> A stadium organ with an intro that plays once before the loop, and a crowd that jumps on every beat with onBeat.

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

`loop: { bar: 3 }` makes the first two bars an intro: they play once and the tune goes round bars 3 to 6. Recorded music does the same with `loopStart` on `useMusic` or `useSound`.

`onBeat` calls on every beat with the beat of the bar and the bar. It goes by the audio clock, not the frames, so the crowd stays in time even when a frame is late, and when the tune goes round the count goes back to bar 3, which is where the tune really is.

P to play or stop.

## Code

`index.ts`:

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

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

// Declare Scenes
game({
    Stands,
});
```

`components/crowd.ts`:

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

import * as Settings from '../settings';
import { ORGAN } from '../data/song';

export const Crowd = () => {
    const font = useLoadBitmapFont(Settings.ARCADE);
    const keys = useKeyboard();
    const organ = useSong(ORGAN);

    const fans: TSprite[] = [];

    for (let i = 0; i < 16; i++) {
        fans.push(createSprite({
            tint: Settings.SHIRTS[i % 4]!,
            width: 16,
            height: 20,
            transform: {
                x: 60 + i * 24,
                y: 170
            }
        }));
    }

    const where = createText({
        text: 'P TO PLAY THE ORGAN',
        font,
        style: {
            fontSize: 8
        },
        tint: Settings.YELLOW,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 60
        }
    });

    // Every beat the crowd jumps, highest on the first beat of the bar.
    organ.onBeat((beat, bar) => {
        fans.forEach((fan, i) => {
            fan.transform.y = 170 - (beat === 1 ? 14 : (i + beat) % 2 === 0 ? 6 : 0);
        });
        where.text = `${bar <= 2 ? 'INTRO' : 'LOOP'}   BAR ${bar}   BEAT ${beat}`;
    });

    createText({
        text: 'P PLAY OR STOP',
        font,
        style: {
            fontSize: 8
        },
        tint: Settings.WHITE,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 240,
            y: 286
        }
    });

    useUpdate(() => {
        if (keys.justPressed('p')) {
            if (organ.playing) {
                organ.stop({
                    fade: 0.5
                });
            } else {
                organ.play();
            }
        }
    });
};
```

`data/song.ts`:

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

export const ORGAN: TSongDoc = {
    format: DOCUMENTS.SONG.FORMAT,
    tempo: 132,
    meter: [4, 4],
    loop: {
        bar: 3
    },
    voices: {
        pulse1: {
            notes: "g8 c' e' g' r4 e'8 g' | c''2. r4 | c'4 e' g' e' | f'4 a' g'2 | e'4 g' f' d' | c'2 g4 r4 |",
            duty: 0.25
        },
        pulse2: {
            notes: "e8 g c' e' r4 c'8 e' | g'2. r4 | g4 c' e' c' | a4 c' b2 | c'4 e' d' b | g2 e4 r4 |",
            volume: 0.6
        },
        triangle: {
            notes: 'c,2 g,,2 | c,2. r4 | c,4 g,, c, g,, | f,,4 c, g,,2 | c,4 e, g,, b,, | c,2 g,,4 r4 |'
        },
        noise: {
            notes: 'x8 x x x x4 x | x2 r2 | x4 x8 x x4 x8 x | x4 x8 x x4 x8 x | x4 x8 x x4 x8 x | x4 x8 x x4 x8 x |',
            volume: 0.5
        },
    },
};
```

`scenes/demo.ts`:

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

import { Crowd } from '../components/crowd';

export const Stands = () => {
    useSpawn(Crowd)();

    return createScene();
};
```

`settings.ts`:

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

export const WIDTH = 480;
export const HEIGHT = 320;
export const ARCADE = {
    json: '/example-assets/fonts/bitmaps/nacatamal-arcade/nacatamal-arcade.json',
    atlas: '/example-assets/fonts/bitmaps/nacatamal-arcade/nacatamal-arcade.png',
};
export const WHITE = getColor('#ffffff');
export const YELLOW = getColor('#f5c518');
export const SHIRTS = ['#e05050', '#5080e0', '#50c070', '#e0a040'].map((hex) => getColor(hex));
```
