Metadata-Version: 2.4
Name: chordsketch
Version: 0.7.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Rust
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Text Processing :: Markup
Summary: ChordPro file format parser and renderer
Keywords: chordpro,music,chord,parser,lyrics,rust,uniffi
Home-Page: https://github.com/koedame/chordsketch
Author: koedame
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/koedame/chordsketch
Project-URL: Issues, https://github.com/koedame/chordsketch/issues
Project-URL: Repository, https://github.com/koedame/chordsketch

<p align="center">
  <img src="https://raw.githubusercontent.com/koedame/chordsketch/main/assets/logo.svg" alt="ChordSketch" width="80" height="80">
</p>

# chordsketch

[ChordSketch](https://github.com/koedame/chordsketch) Python bindings —
parse and render [ChordPro](https://www.chordpro.org/) and
[iReal Pro](https://www.irealpro.com/) chord charts from Python via
native Rust extensions generated by [UniFFI](https://mozilla.github.io/uniffi-rs/).

**Native extension — no pure-Python fallback.**
The library compiles to a platform wheel; prebuilt wheels for common platforms
are published to PyPI so no Rust toolchain is needed to install.

## Installation

```bash
pip install chordsketch
```

Requires Python 3.8+ (CPython or PyPy).

## Quick Start

```python
import chordsketch

source = """{title: Amazing Grace}
{key: G}

[G]Amazing [G7]grace, how [C]sweet the [G]sound"""

html = chordsketch.parse_and_render_html(source)
text = chordsketch.parse_and_render_text(source)
pdf  = chordsketch.parse_and_render_pdf(source)   # bytes

print(chordsketch.version())
```

## API

The tables below cover every function in
[`crates/ffi/src/chordsketch.udl`](src/chordsketch.udl) (UniFFI's
`namespace chordsketch { ... }` block is the authoritative export
surface for all language bindings).

### Render-function parameters

`parse_and_render_*` functions all accept the same three arguments:

| Parameter | Type | Description |
|-----------|------|-------------|
| `input` | `str` | ChordPro source text |
| `config_json` | `str \| None` | Preset name (`"guitar"`, `"ukulele"`) or inline RRJSON; `None` for defaults |
| `transpose` | `int \| None` | Semitone offset; must fit in `i8` (`-128..=127`). Out-of-range values raise `ChordSketchError.InvalidConfig`. `None` defaults to 0. |

### Basic rendering

| Function | Returns | Description |
|----------|---------|-------------|
| `parse_and_render_text(input, config_json, transpose)` | `str` | Plain text output |
| `parse_and_render_html(input, config_json, transpose)` | `str` | Full HTML document |
| `parse_and_render_pdf(input, config_json, transpose)` | `bytes` | Raw PDF bytes |

### Body-only HTML and stylesheet

| Function | Returns | Description |
|----------|---------|-------------|
| `parse_and_render_html_body(input, config_json, transpose)` | `str` | Body-only `<div class="song">…</div>` HTML fragment with no `<!DOCTYPE>` / `<html>` / `<head>` / `<title>` / embedded `<style>` — pair with `render_html_css` when the host supplies its own document envelope |
| `render_html_css()` | `str` | Canonical chord-over-lyrics CSS that `parse_and_render_html` embeds inside `<style>` (byte-stable; safe to hash for cache-busting) |
| `render_html_css_with_config_json(config_json)` | `str` | Variant of `render_html_css` that honours `settings.wraplines` from the supplied config (when `wraplines` is false, `.line` emits `flex-wrap: nowrap`) |

### Captured warnings

| Function | Returns | Description |
|----------|---------|-------------|
| `parse_and_render_text_with_warnings(input, config_json, transpose)` | `TextRenderWithWarnings { output: str, warnings: list[str] }` | Plain text + captured warnings |
| `parse_and_render_html_with_warnings(input, config_json, transpose)` | `TextRenderWithWarnings { output: str, warnings: list[str] }` | HTML + captured warnings |
| `parse_and_render_pdf_with_warnings(input, config_json, transpose)` | `PdfRenderWithWarnings { output: bytes, warnings: list[str] }` | PDF + captured warnings |
| `parse_and_render_html_body_with_warnings(input, config_json, transpose)` | `TextRenderWithWarnings { output: str, warnings: list[str] }` | Body-only HTML fragment + captured warnings (body counterpart to `parse_and_render_html_with_warnings`) |

### iReal Pro conversion

| Function | Returns | Description |
|----------|---------|-------------|
| `convert_chordpro_to_irealb(input)` | `ConversionWithWarnings { output: str, warnings: list[str] }` | Convert ChordPro source to an `irealb://` URL (lossy — drops lyrics, fonts, capo) |
| `convert_irealb_to_chordpro_text(input)` | `ConversionWithWarnings { output: str, warnings: list[str] }` | Convert an `irealb://` URL to rendered ChordPro text |
| `render_ireal_svg(input)` | `str` (SVG document) | Render an `irealb://` URL as an iReal Pro-style SVG chart |
| `render_ireal_png(input)` | `bytes` (PNG byte stream) | Render an `irealb://` URL as a PNG image (300 DPI default, A4-equivalent canvas) |
| `render_ireal_pdf(input)` | `bytes` (PDF byte stream) | Render an `irealb://` URL as a single-page A4 PDF document |
| `parse_irealb(input)` | `str` (JSON) | Parse an `irealb://` URL into AST-shaped JSON (mirrors `IrealSong`) |
| `serialize_irealb(input)` | `str` (URL) | Serialize an AST-shaped JSON string back into an `irealb://` URL (round-trips with `parse_irealb`) |

`output` of `convert_irealb_to_chordpro_text` is the rendered text
representation of the converted song (`chordsketch-render-text`
output), not raw ChordPro source. Each `warnings` entry is a
`"<kind>: <message>"` string (`kind` is `lossy-drop`,
`approximated`, or `unsupported`).

```python
result = chordsketch.convert_chordpro_to_irealb("{title: Test}\n[C]Hello")
print(result.output)    # "irealb://..."
print(result.warnings)  # ["lossy-drop: lyrics are dropped", ...]

text = chordsketch.convert_irealb_to_chordpro_text(result.output)
print(text.output)
```

The `*_with_warnings` variants return the render warnings (transpose
saturation, chorus recall limits, `{columns}` clamp, etc.) as a list
alongside the output instead of forwarding them to `sys.stderr` /
`NSLog` / `System.err` / `$stderr`. Use them when embedding ChordSketch
in an app that needs to surface warnings in the UI or aggregate them.
See #1827.

### Validation

| Function | Returns | Description |
|----------|---------|-------------|
| `validate(input)` | `list[ValidationError]` (`{ line: int, column: int, message: str }`, line / column one-based) | Validate ChordPro input and return any parse errors as structured records (empty list if clean). Mirrors the WASM `validate` shape and the NAPI `ValidationError[]` interface. |

```python
errors = chordsketch.validate(source)  # list[ValidationError] — empty if clean
for e in errors:
    print(f"line {e.line}, column {e.column}: {e.message}")
```

### Chord diagrams

| Function | Returns | Description |
|----------|---------|-------------|
| `chord_diagram_svg(chord, instrument)` | `str \| None` (SVG markup) | Render a chord diagram as inline SVG. `instrument` is case-insensitive: `"guitar"`, `"ukulele"` (alias `"uke"`), or `"piano"` (aliases `"keyboard"`, `"keys"`). Returns `None` when the chord is not in the built-in voicing database; raises `ChordSketchError.InvalidConfig` on unknown instrument. |
| `chord_diagram_svg_with_defines(chord, instrument, defines)` | `str \| None` | Same as `chord_diagram_svg` but consults song-level `{define}` voicings first. `defines` is a list of `(name, raw)` tuples. |
| `chord_diagram_svg_with_orientation(chord, instrument, orientation=None)` | `str \| None` | Orientation-aware variant. `orientation`: `"vertical"` (default) or `"horizontal"` (nut on the left, Japanese tablature convention). Horizontal mode is reader-view only (high pitch on top, matches tablature stave order); see ADR-0026. Unrecognised strings fall back to vertical. |
| `chord_diagram_svg_with_defines_orientation(chord, instrument, defines, orientation=None)` | `str \| None` | Combined surface — accepts both song-level `{define}` voicings and the orientation knob. |
| `chord_pitches(chord)` | `list[int] \| None` | Constituent pitches of a chord as MIDI note numbers, for driving an audio synth. Returns a block voicing (root, third, fifth, plus any extension / altered / added tones, with a slash bass an octave below); `None` when the chord is not parseable. |
| `diagram_pitches(chord, instrument, defines)` | `list[int] \| None` | MIDI note numbers **sounded** by the chord diagram drawn for `(chord, instrument)` — for auditioning a diagram as exactly the shape it depicts, rather than the name-based block voicing `chord_pitches` returns. Fretted instruments return one pitch per non-muted string in string order; keyboard instruments return the highlighted keys. `defines` is the same `ChordDefine` list `chord_diagram_svg_with_defines` consults. `None` when no diagram is available. |
| `chord_staff_notes(chord)` | `list[StaffNote] \| None` | Constituent tones of a chord spelled for staff notation, ascending by pitch (a slash bass sorts first). Each tone is spelled diatonically from the chord's structure so it lands on its conventional staff line (e.g. `Ebm7` → E♭ G♭ B♭ D♭, not D♯ F♯ A♯ C♯). `None` when the chord is not parseable. |
| `key_scale_pitches(key)` | `list[int] \| None` | Ascending one-octave scale of a musical key as MIDI note numbers — the movable-do "do re mi fa sol la ti do". Major keys yield the major scale; minor keys the natural-minor scale. Eight notes; `None` when the key is not parseable. |
| `key_tonic_triad(key)` | `list[int] \| None` | Tonic triad of a musical key as MIDI note numbers (the "do mi sol" chord). Major / minor per the key; extensions on the spelling are ignored. Three notes; `None` when the key is not parseable. |

`StaffNote` exposes `letter: str` (`"A"`–`"G"`), `accidental: int`
(signed semitone offset from the letter, `-3..=3`), `octave: int`
(scientific-pitch-notation octave, middle C = C4), and `midi: int`
(absolute MIDI note number).

### Utility

| Function | Returns | Description |
|----------|---------|-------------|
| `version()` | `str` | Library version string |

```python
print(chordsketch.version())  # e.g. "0.3.0"
```

## Options

```python
# Transpose up 2 semitones with the ukulele preset
html = chordsketch.parse_and_render_html(source, "ukulele", 2)

# Inline RRJSON configuration
html = chordsketch.parse_and_render_html(
    source,
    '{"settings": {"notation": "solfege"}}',
    None,
)
```

## Error handling

Functions raise `chordsketch.ChordSketchError` on invalid configuration:

```python
try:
    html = chordsketch.parse_and_render_html(source, "{ bad json !!!", None)
except chordsketch.ChordSketchError as e:
    print(e)
```

`ChordSketchError` has three variants:
- `NoSongsFound` — the input produced no parseable songs (rare with lenient parsing)
- `InvalidConfig(reason)` — the `config_json` argument is not a known preset and not valid RRJSON, or `transpose` is outside the `i8` range, or `chord_diagram_svg` was called with an unsupported instrument
- `ConversionFailed(reason)` — a ChordPro ↔ iReal Pro conversion failed (`convert_chordpro_to_irealb`, `convert_irealb_to_chordpro_text`, `render_ireal_*`, `parse_irealb`, `serialize_irealb`)

Parse errors in the ChordPro input are **not** raised — the renderer is lenient
and produces a best-effort result. Call `validate()` to surface diagnostics.

## Links

- **Project**: https://github.com/koedame/chordsketch
- **Playground**: https://chordsketch.koeda.me
- **Issues**: https://github.com/koedame/chordsketch/issues

## License

MIT

