# Grand piano

A player grand whose roll drives 88 solved actions. The jack lets each hammer go before it reaches the string, because you cannot hold a hammer against one.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/robot-grand-piano.json
```

Registry item: `robot-grand-piano` · [`https://robocn.dev/r/robot-grand-piano.json`](https://robocn.dev/r/robot-grand-piano.json)

## Notes

- The hammer is released before the note, not at it. The jack drives the knuckle until its toe meets the let-off button and then leaves the hammer to cover the last of the blow unpowered — which is the one thing a piano action exists to do, and the thing a drawing that tweens the hammer off the key gets wrong.
- It comes back to the check, not to rest: the back check holds the hammer part-way down while the key is still held, which is what lets a note repeat. The key is held for as long as its string rings, so the damper falls with the sound.
- The bent side of the case is the envelope of the scale. Each string runs from an agraffe one strike point in front of the hammers to a bridge pin one speaking length behind it; take the hitch pins, push them out by the rim's thickness and that curve is a grand.
- Travels are drawn magnified — a key dips a three-hundredth of the instrument's length — but only the travels. Every ratio, the escapement, the after-touch and the check are solved life-size, and `data-travel`, `data-dip` and `data-lift` carry the unmagnified numbers.
- The case is drawn as a section: the wall between you and the inside is cut away, which is why the harp and the action read from every angle. In plan a raised lid really does cover most of the instrument, so it is drawn as the plate it is with the harp reading through it.
- No acoustics and no dynamics. Nothing computes a frequency, an inharmonicity, a tension or a decay, there is no hammer mass and no velocity, and the flight after let-off is the remaining gap covered in a fixed window rather than an integration. The choirs, the wound strings and the plate are illustrated.
- An original archetype. No maker, model, decal, plate casting, piece of music or performer is reproduced here or in the demo.

## Usage

```tsx
import { RobotGrandPiano } from "@/components/ui/robot-grand-piano"

<RobotGrandPiano />

// Your own roll: one row per lane, and the key each lane strikes.
<RobotGrandPiano roll={["x...x...", "..x...x."]} lanes={[28, 40]} />
<RobotGrandPiano view="profile" pedal="damper" lid="half" interactive />
```

## Props

| name | type | default | description |
| --- | --- | --- | --- |
| `view` | `"plan" | "front" | "profile" | "iso"` | `"plan"` | Where the camera stands. One instrument, four projections: straight down, straight on, side elevation, or three-quarter from above. |
| `beat` | `number` | — | Controlled position through the roll, in steps. Omit it and the roll runs behavior. |
| `behavior` | `"perform" | "rubato" | "static"` | `"perform"` | Run the roll at a steady pass, or with the rate swelling and easing inside it — never backwards. |
| `notes` | `number` | `88` | Notes in the compass, rounded and clamped to 12–88. The scale, the case and the keyboard are all redrawn around it. |
| `roll` | `readonly string[]` | — | One row per lane; any non-blank character is a perforation at that step. An empty roll is a piano that runs and plays nothing. |
| `lanes` | `readonly number[]` | — | Which key each lane strikes, as an index into the compass — so three rows can be a chord rather than three neighbours. |
| `lid` | `"closed" | "half" | "full"` | `"full"` | Which prop the lid stands on. The angle is solved from the stick, the notch and the cup, not picked. |
| `pedal` | `"none" | "damper" | "shift"` | `"none"` | Sustain lifts every damper off its string; una corda slides the whole action across by one string spacing. |
| `speed` | `number` | `0.3` | Passes of the whole roll per second. |
| `animate` | `boolean` | `true` | Off parks the machine at phase and stops rendering. A reduced-motion preference does the same. |
| `paused` | `boolean` | `false` | Freeze where it stands. |
| `phase` | `number` | `0` | Seconds of offset, so a row of machines breaks step. |
| `interactive` | `boolean` | `false` | Drag across to scrub the roll — the frame's width is one pass — or arrow-key it a note at a time. |
| `onBeatChange` | `(beat: number) => void` | — | Position through the roll throughout a drag or a key press. |
| `onStepChange` | `(step: number) => void` | — | The step now under the reading bar, 0-based. |
| `showLegs` | `boolean` | `true` | Draw the legs and the pedal lyre. |
| `label` | `string` | — | Caption below the state readout. |
| `variant` | `"solid" | "outline" | "blueprint" | "wire"` | `"solid"` | How the machine is painted. Geometry never changes between variants. |
| `size` | `"xs" | "sm" | "md" | "lg" | "xl" | number` | `"md"` | Rendered width in pixels, or a step on the scale. |
| `color` | `string` | `var(--robot-shell)` | Body panels — the colour the machine reads as. |
| `accent` | `string` | `var(--robot-accent)` | Status colour: tip light, live tool, readouts. |
| `metal` | `string` | `var(--robot-metal)` | Bare machined parts: collars, bolts, tool bodies. |
| `dark` | `string` | `var(--robot-dark)` | Cast joints, base, shadow side. |
| `palette` | `Partial<RobotPalette>` | — | Override any subset of roles at once, including glow and grid. |

## Source

- `src/components/ui/robot-grand-piano.tsx`
