# Sound made in code

> A sound the game works out itself, sample by sample, with no file and no preset: the idle of a two-stroke engine that revs.

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

`useSound({ samples, sampleRate })` plays sound the game made itself. Here it is the idle of a two-stroke engine, a buzzy pulse with a little noise, made once and looped.

Revving does not make new sound: it plays the same loop faster with `setRate`, which raises the pitch the way an engine does. The browser only allows sound after the player presses something, so it starts on the first key or click.

## Code

`index.ts`:

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

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#0d1020',
    actions: [
        {
            name: 'rev',
            bindings: [
                {
                    type: 'key',
                    key: 'ArrowUp'
                },
                {
                    type: 'key',
                    key: ' '
                },
                {
                    type: 'button',
                    button: 'a'
                }
            ]
        }
    ],
});

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

`audio/engine_idle.ts`:

```ts
import * as Settings from '../settings';

export const engineIdle = (): Float32Array => {
    const samples = new Float32Array(Settings.RATE);
    let seed = 1;

    const noise = () => {
        seed = (seed * 16807) % 2147483647;

        return seed / 2147483647 * 2 - 1;
    };

    for (let i = 0; i < samples.length; i++) {
        const t = i / Settings.RATE;
        const a = (t * 55) % 1 < 0.22 ? 1 : -0.4;
        const b = (t * 110) % 1 < 0.12 ? 0.5 : -0.2;

        samples[i] = (a * 0.5 + b * 0.25 + noise() * 0.12) * 0.35;
    }

    return samples;
};
```

`components/motorbike.ts`:

```ts
import { createSprite, createText, getColor, useActions, usePointer, useSound, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';
import { engineIdle } from '../audio/engine_idle';

export const Motorbike = () => {
    const actions = useActions();
    const pointer = usePointer();

    const engine = useSound({
        samples: engineIdle(),
        sampleRate: Settings.RATE,
        key: 'engine'
    }, {
        loop: true,
        volume: 0.6
    });

    createSprite({
        width: Settings.WIDTH,
        height: 40,
        tint: '#262d48',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 0,
            y: 130
        }
    });

    const bike = createSprite({
        width: 28,
        height: 14,
        tint: '#ff6b3d',
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 90,
            y: 130
        }
    });

    createText({
        text: 'RPM',
        transform: {
            x: 8,
            y: 8
        }
    });
    createSprite({
        width: 200,
        height: 8,
        tint: '#1b2036',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 40,
            y: 8
        }
    });

    const needle = createSprite({
        width: 0,
        height: 8,
        tint: '#5bd6a0',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 40,
            y: 8
        }
    });

    const prompt = createText({
        text: 'PRESS ANY KEY OR CLICK TO START',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: Settings.WIDTH / 2,
            y: 70
        }
    });

    createText({
        text: 'HOLD ↑ / SPACE / Ⓐ / MOUSE: REV',
        transform: {
            x: 8,
            y: Settings.HEIGHT - 14
        },
        tint: '#6b7390'
    });

    let started = false;
    let rpm = 0; // 0 idle, 1 redline
    let wobble = 0;

    useUpdate((delta) => {
        const throttle = actions.isDown('rev') || pointer.isDown();

        if (!started && (throttle || actions.justPressed('rev'))) {
            started = true;
            engine.play();
            prompt.text = '';
        }

        // Up quickly with the throttle, down slowly without it.
        rpm += ((throttle ? 1 : 0) - rpm) * Math.min(1, delta * (throttle ? 2.5 : 1.2));
        engine.setRate(0.8 + rpm * 1.9);

        needle.width = 30 + rpm * 170;
        needle.tint = getColor(rpm > 0.85 ? '#ff5a6e' : '#5bd6a0');
        wobble += delta * (8 + rpm * 30);
        bike.transform.y = 130 - Math.abs(Math.sin(wobble)) * (0.5 + rpm);
    });
};
```

`scenes/demo.ts`:

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

import { Motorbike } from '../components/motorbike';

export const Level = () => {
    useSpawn(Motorbike)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 320;
export const HEIGHT = 180;
export const RATE = 22050;
```
