Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .agents/skills/render-musicxml/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: render-musicxml
description: Render a MusicXML file in vexml by running `vex render -i <path/to/musicxml>`, inspect the generated screenshot when needed, and delete ephemeral render output when finished.
description: Render a MusicXML file in vexml by running `vex render -i <path/to/musicxml>`, optionally pass render configuration with `--config <json>`, inspect the generated screenshot when needed, and delete ephemeral render output when finished.
---

# Render MusicXML
Expand All @@ -17,10 +17,18 @@ vex render -i <path/to/musicxml>

Replace `<path/to/musicxml>` with the project-relative or absolute path to the MusicXML file the user wants rendered.

To override render settings, pass a partial render config as JSON with `-c`/`--config`:

```sh
vex render -i <path/to/musicxml> --config '{"noteSpacing":40,"showPartLabels":true}'
```

The JSON object corresponds to `Partial<Config>` from `src/config.ts`; use that file as the source of truth for available options and defaults.

## Workflow

1. Identify the MusicXML input path from the user's request or from the repository.
2. Run `vex render -i <path/to/musicxml>` from `vexml`.
2. Run `vex render -i <path/to/musicxml>` from `vexml`. If the user asks for non-default render settings, include `-c`/`--config <json>` using option names from `src/config.ts`.
3. Read the command output to find the generated screenshot path.
4. If visual inspection is needed, open or inspect the generated screenshot with available tools.
5. When finished, delete the generated screenshot if it was only meant to be ephemeral.
Expand Down
80 changes: 79 additions & 1 deletion site/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export default function App() {
const [config, setConfig] = useState<Partial<Config>>({});
const noteSpacing = config.noteSpacing ?? 36;
const softmaxFactor = config.softmaxFactor ?? 10;
const reset = (key: 'noteSpacing' | 'softmaxFactor') =>
const systemSpacing = config.systemSpacing ?? 30;
const notationFont = config.fonts?.notation?.family ?? 'Bravura';
const reset = (key: 'noteSpacing' | 'softmaxFactor' | 'systemSpacing') =>
setConfig(({ [key]: _, ...rest }) => rest);

// `config` stays live so the sliders/reset respond instantly; `renderConfig` lags
Expand Down Expand Up @@ -394,6 +396,41 @@ export default function App() {
<span className="text-xs font-semibold uppercase tracking-wide text-zinc-500">
Config
</span>
<div className="flex flex-col gap-1.5">
<label
htmlFor="notationFont"
className="text-xs font-medium text-zinc-500"
>
Notation font
</label>
<select
id="notationFont"
value={notationFont}
onChange={(e) =>
setConfig((c) =>
e.target.value === 'Bravura'
? (({ fonts: _, ...rest }) => rest)(c)
: {
...c,
fonts: {
...c.fonts,
notation: { family: e.target.value },
},
},
)
}
className="rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm text-zinc-700"
>
<option value="Bravura">Bravura</option>
<option value="Petaluma">Petaluma</option>
<option value="Gonville">Gonville</option>
</select>
<p className="text-xs text-zinc-400">
The engraving font for noteheads, clefs, accidentals, and
rests. Bravura is the default.
</p>
</div>

<div className="flex flex-col gap-1.5">
<label
htmlFor="noteSpacing"
Expand Down Expand Up @@ -475,6 +512,47 @@ export default function App() {
width difference between long and short notes.
</p>
</div>

<div className="flex flex-col gap-1.5">
<label
htmlFor="systemSpacing"
className="flex items-center justify-between text-xs font-medium text-zinc-500"
>
System spacing
<span className="flex items-center gap-1.5">
<span className="font-mono text-zinc-400">
{systemSpacing}
</span>
<button
type="button"
onClick={() => reset('systemSpacing')}
disabled={config.systemSpacing === undefined}
aria-label="Reset system spacing"
className="text-zinc-400 hover:text-zinc-600 disabled:cursor-default disabled:text-zinc-300 disabled:hover:text-zinc-300"
>
<ResetIcon />
</button>
</span>
</label>
<input
id="systemSpacing"
type="range"
min={10}
max={50}
step={1}
value={systemSpacing}
onChange={(e) =>
setConfig((c) => ({
...c,
systemSpacing: e.target.valueAsNumber,
}))
}
/>
<p className="text-xs text-zinc-400">
Vertical gap between stacked systems. Lower packs systems
closer together down the page.
</p>
</div>
</div>
</div>
</aside>
Expand Down
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SYSTEM_GAP } from './constants';
import type { FontConfig } from './fonts';
import type { Layout, MeasureNumbering } from './layout';

Expand All @@ -23,6 +24,9 @@ export type Config = {
* Given the width noteSpacing allots, higher exaggerates the long-vs-short note ratio. A
* shape constant, independent of overall density. */
softmaxFactor: number;
/** Vertical gap in px between stacked systems (default: SYSTEM_GAP). Smaller packs
* systems closer together down the page. */
systemSpacing: number;
/** Print each part's instrument name to the left of the first system (default: false). */
showPartLabels: boolean;
/** When to print measure numbers above the staff (default: 'system'). 'none' prints
Expand Down Expand Up @@ -52,6 +56,7 @@ export const DEFAULT_CONFIG: Config = {
layout: { type: 'standard', width: 1000 },
noteSpacing: 36,
softmaxFactor: 10,
systemSpacing: SYSTEM_GAP,
showPartLabels: false,
measureNumbering: 'system',
showTabHammerPullText: false,
Expand Down
26 changes: 24 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const PAGE_MARGIN_TOP_WITH_TEMPO = 70;
export const PAGE_MARGIN_BOTTOM = 40;

/** Vertical gap between stacked systems, plus room for the next system's notes that
* rise above its top staff. */
export const SYSTEM_GAP = 90;
* rise above its top staff. The default for `Config.systemSpacing`. */
export const SYSTEM_GAP = 30;

/** Vertical gap between staves within one part (a brace-joined group reads as one
* instrument because this exceeds INTER_PART_SPACING). */
Expand Down Expand Up @@ -95,6 +95,28 @@ export const TAB_TIE_CP2 = 12;
* the system's left line with a small gap. */
export const BRACKET_X_SHIFT = 3;

/** Chord-symbol (from `<harmony>`) text size — a touch smaller than the part label so it
* reads as an annotation above the notes. */
export const HARMONY_FONT_SIZE = 13;

/** How far a chord symbol's baseline sits above the top staff line. */
export const HARMONY_Y_OFFSET = 14;

/** Clearance kept between a chord symbol's baseline and the top of a high note it
* sits over, so the symbol lifts clear instead of colliding with the notehead. */
export const HARMONY_NOTE_CLEARANCE = 8;

/** Words-direction (e.g. "ritardando") text size — matches the chord-symbol size so
* both read as annotations above the notes. */
export const WORDS_FONT_SIZE = 13;

/** How far a words-direction baseline sits above the top staff line. */
export const WORDS_Y_OFFSET = 14;

/** Clearance kept between a words-direction baseline and the top of a high note it sits
* over, so the directive lifts clear instead of colliding with the notehead. */
export const WORDS_NOTE_CLEARANCE = 8;

/** Clearance between the bottom of a metronome mark and the top of the first note. */
export const TEMPO_NOTE_CLEARANCE = 6;

Expand Down
Loading
Loading