{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://achordeon.eu/achordeon.schema.json",
  "title": "Achordeon import file",
  "description": "A file Achordeon can import: songs, and optionally songbooks that order them.\n\nWrite one of these to hand somebody a song. Achordeon reads it from the file\npicker, from a file dropped anywhere on the page, or out of a link fragment\n(append \"#j1=\" + encodeURIComponent(JSON.stringify(file)) to https://achordeon.eu/app/).\n\nOnly `schemaVersion` and `data` are required, and per song only `name` and\n`content`. Everything else on this page is written by Achordeon itself and\ndescribed so that a real export also validates — leave those fields out.\n\nA song is written in Achordeon markup, not ChordPro and not chords-above-lyrics.\nSee the `content` property for the whole of it.",
  "type": "object",
  "required": [
    "schemaVersion",
    "data"
  ],
  "properties": {
    "app": {
      "type": "string",
      "const": "https://achordeon.eu/app/",
      "description": "Where the app that opens this file lives. Read by nothing — it is a line for a person who finds the file on a disk two years from now. Include it if you like."
    },
    "schemaVersion": {
      "type": "integer",
      "const": 1,
      "description": "The record shape this file holds. Write this number. A file made with an older number is migrated forward on import; a newer one is refused, because guessing a down-migration is how a library gets quietly mangled."
    },
    "deviceId": {
      "type": "string",
      "description": "Which device produced the file. Feeds sync; an import means nothing by it. Omit it, or write a readable name of your own — anything that is not a uuid can never collide with a real device."
    },
    "updatedAt": {
      "type": "integer",
      "description": "Epoch milliseconds the file was produced. Feeds sync; an import means nothing by it."
    },
    "data": {
      "type": "object",
      "required": [
        "songs",
        "songbooks"
      ],
      "description": "What the file carries. Both arrays must be present; either may be empty.",
      "properties": {
        "songs": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/song"
          },
          "description": "The songs. An empty array is a valid file."
        },
        "songbooks": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/songbook"
          },
          "description": "Ordered collections of the songs above. An empty array is a valid file. A songbook is always imported as a NEW songbook, never merged into one that exists."
        }
      }
    }
  },
  "$defs": {
    "song": {
      "type": "object",
      "required": [
        "name",
        "content"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "The library label — what the song is called in the list. Not the printed title, which is read out of `content`. They are usually the same and do not have to be."
        },
        "content": {
          "type": "string",
          "description": "The song itself, in Achordeon markup. One string, newline-separated.\n\nCHORDS. `[C]` before the character it sits over: `And the [G]trees are` prints G above the t of trees.\nSeveral chords in one bracket share a spot: `[Am G]`. A bracket at end of line hangs past the words.\nA bracket that is not a chord is kept and printed verbatim, never transposed: `[x2]`, `[N.C.]`, `[Solo]`.\nA line that has brackets and no words is a chord row and prints in the line: `[G] [C] [D]`.\nGerman H is B natural; the printed spelling is the `notation` setting, not the source.\n\nTITLE and SUBTITLE. `* Title` and `** Subtitle`, at column 0, marker then a space.\nThe rest of the line is plain print text — no chords, no label, no escapes. Last one wins.\n\nBLOCKS. A blank line separates blocks. Consecutive blank lines count as one.\n\nLABELS. `Verse: first words` names the block it opens; a label further into an open\nblock annotates that line only. The delimiter is a colon followed by a space or end of line —\nso `12:30` and `http://x` are ordinary lyrics and need no escaping.\n\nEMPHASIS. `*italic*`, `**bold**`. An asterisk with nothing to match it prints as itself.\n\nESCAPES. `\\[`, `\\]`, `\\:`, `\\*`, `\\\\` print the character. `\\ ` keeps a leading space.\n\nNOT VALID HERE: ChordPro directives (`{title: …}`, `{start_of_chorus}`), a row of chords\nwritten above the words on its own line, HTML, or Markdown headings. Fold chord rows into\nbrackets before writing them here."
        },
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The song’s identity, honoured when present and minted when absent.\nMint a FRESH uuid per song. Reusing one — the RFC 4122 example uuid especially —\nclaims the identity of whatever song already has it, and the import offers to replace it.\nKeep the same id across versions of the SAME song, and a re-import replaces rather than duplicates."
        },
        "settings": {
          "type": "object",
          "description": "Render overrides for this song alone, sparse — write only the keys you mean, or leave the object out. Anything absent falls back to the songbook’s value and then the library’s.",
          "properties": {
            "scale": {
              "anyOf": [
                {
                  "enum": [
                    "auto"
                  ]
                },
                {
                  "type": "number"
                }
              ],
              "default": "auto",
              "description": "TypeScript type: number | 'auto'"
            },
            "columns": {
              "type": "number",
              "default": 1
            },
            "titlePosition": {
              "enum": [
                "top",
                "left"
              ],
              "default": "top"
            },
            "titleLayout": {
              "enum": [
                "stacked",
                "inline"
              ],
              "default": "stacked"
            },
            "titleFont": {
              "enum": [
                "body",
                "serif",
                "display",
                "script"
              ],
              "default": "body"
            },
            "aspectRatio": {
              "anyOf": [
                {
                  "enum": [
                    "A4"
                  ]
                },
                {
                  "type": "number"
                },
                {
                  "type": "string"
                }
              ],
              "default": "A4",
              "description": "TypeScript type: 'A4' | number | `${number}:${number}` | `${number}/${number}`"
            },
            "padding": {
              "type": "number",
              "default": 1.25
            },
            "blockGap": {
              "type": "number",
              "default": 0.8
            },
            "contentX": {
              "enum": [
                "left",
                "center",
                "right"
              ],
              "default": "center"
            },
            "contentY": {
              "enum": [
                "top",
                "middle",
                "bottom"
              ],
              "default": "middle"
            },
            "chordColor": {
              "type": "string",
              "default": "#9f1212",
              "description": "TypeScript type: `#${string}`"
            },
            "chordSize": {
              "type": "number",
              "default": 1
            },
            "notation": {
              "enum": [
                "english",
                "german"
              ],
              "default": "english"
            }
          }
        },
        "favorite": {
          "type": "boolean",
          "description": "Written by Achordeon. Omit it; it defaults to false."
        },
        "createdAt": {
          "type": "integer",
          "description": "Written by Achordeon (epoch ms). Omit it; the import stamps the time it ran."
        },
        "updatedAt": {
          "type": "integer",
          "description": "Written by Achordeon (epoch ms). Omit it; the import stamps the time it ran."
        },
        "deletedAt": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Written by Achordeon. A tombstone, so a sync can propagate a delete. Omit it — an import drops every song that carries one."
        },
        "cache": {
          "type": "object",
          "description": "Written by Achordeon, derived from `content`. Omit it — an import re-reads the title and subtitle off the content and never believes a file’s copy.",
          "properties": {
            "title": {
              "type": "string"
            },
            "subtitle": {
              "type": "string"
            }
          }
        }
      }
    },
    "songbook": {
      "type": "object",
      "required": [
        "name",
        "entries"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "The library label for the book."
        },
        "entries": {
          "type": "array",
          "items": {
            "type": "string",
            "format": "uuid"
          },
          "description": "Song ids, in the order they appear in the book. The order IS the book. An id may repeat — the same song can hold two slots. An id that matches no song in this file and none in the library is dropped, so give every song here an explicit `id`."
        },
        "title": {
          "type": "string",
          "description": "Title-page text. Authored, never parsed — a book has no `content`."
        },
        "subtitle": {
          "type": "string",
          "description": "Title-page subtitle."
        },
        "author": {
          "type": "string",
          "description": "Title-page author line."
        },
        "settings": {
          "type": "object",
          "description": "Render overrides for every song in this book, sparse. A song’s own setting still wins.",
          "properties": {
            "titleFont": {
              "enum": [
                "body",
                "serif",
                "display",
                "script"
              ],
              "default": "body"
            },
            "blockGap": {
              "type": "number",
              "default": 0.8
            },
            "chordColor": {
              "type": "string",
              "default": "#9f1212",
              "description": "TypeScript type: `#${string}`"
            },
            "chordSize": {
              "type": "number",
              "default": 1
            },
            "notation": {
              "enum": [
                "english",
                "german"
              ],
              "default": "english"
            }
          }
        },
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "Optional and rarely worth writing: a songbook is ALWAYS imported as a new book, so its id is minted on the way in whatever the file says."
        },
        "createdAt": {
          "type": "integer",
          "description": "Written by Achordeon (epoch ms). Omit it."
        },
        "updatedAt": {
          "type": "integer",
          "description": "Written by Achordeon (epoch ms). Omit it."
        },
        "deletedAt": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Written by Achordeon. Omit it."
        }
      }
    }
  },
  "examples": [
    {
      "app": "https://achordeon.eu/app/",
      "schemaVersion": 1,
      "data": {
        "songs": [
          {
            "id": "9f6b2c14-3d5e-4a71-9c08-1b2e5d7a4f36",
            "name": "Wild Mountain Thyme",
            "content": "* Wild Mountain Thyme\n** Traditional\n\nVerse: Oh the [G]summer time is [C]coming\nAnd the [G]trees are sweetly [D]blooming\n\nR: And we’ll [G]all go [C]together\n[G]To pull [D]wild mountain [G]thyme"
          }
        ],
        "songbooks": []
      }
    }
  ]
}
