{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openhomeav.com/schema/design/v1.0.json",
  "$comment": "SPDX-License-Identifier: CC0-1.0. OpenHomeAV design file format 1.0 (R34). Immutable once released: a later minor gets its own file. Documentation: https://openhomeav.com/designer/file-format/",
  "title": "OpenHomeAV design file 1.0",
  "description": "A Cinema Designer room as one JSON object: a small envelope around the design. Every length is in metres and every angle in degrees. Origin at the front-left floor corner; x runs across the room (left to right facing the screen), y into the room from the screen wall, z up. What every field means, with examples: https://openhomeav.com/designer/file-format/",
  "type": "object",
  "required": ["format", "version", "design"],
  "properties": {
    "$schema": {
      "description": "URL of the schema for this exact format version, for external validators.",
      "type": "string"
    },
    "format": {
      "description": "Always \"openhomeav-design\". Anything else is not a design file.",
      "const": "openhomeav-design"
    },
    "version": {
      "description": "Format version as \"MAJOR.MINOR\". A minor adds optional fields only; a major changes a meaning. This schema reads 1.x.",
      "type": "string",
      "pattern": "^1\\.(0|[1-9][0-9]*)$"
    },
    "name": {
      "description": "Room name, 1 to 80 characters, no control characters. Pre-fills Save room when the file is opened.",
      "type": "string",
      "pattern": "^[^\\u0000-\\u001f]{1,80}$"
    },
    "generator": {
      "description": "The tool that wrote the file.",
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": { "description": "Tool name.", "type": "string" },
        "version": { "description": "Tool version or build id.", "type": "string" },
        "url": { "description": "Where the tool lives.", "type": "string" }
      },
      "additionalProperties": false
    },
    "created": {
      "description": "When the file was written, ISO 8601 (UTC, e.g. 2026-09-27T10:00:00Z). Ignored when comparing designs.",
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:?[0-9]{2})?)?$"
    },
    "source": {
      "description": "Free-form provenance, for example which CAD variables produced which block. Shown when the file is opened; never stored by the designer.",
      "type": "object"
    },
    "design": { "$ref": "#/$defs/design" }
  },
  "patternProperties": {
    "^x-": { "description": "Extension data for other tools. Listed on import as not used by the designer; never stored." }
  },
  "additionalProperties": false,
  "$defs": {
    "id": {
      "description": "A catalogue or data id: letters, digits, dot, underscore, plus or hyphen.",
      "type": "string",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$"
    },
    "design": {
      "description": "The design, exactly the object the Cinema Designer holds. Absent optional blocks mean what they mean in the designer today (stated per field).",
      "type": "object",
      "required": ["units", "room", "screen", "rows"],
      "properties": {
        "units": {
          "description": "Display preference only: \"metric\" or \"imperial\". Every stored number stays SI (metres, degrees) either way. An unknown value opens as metric.",
          "enum": ["metric", "imperial"]
        },
        "room": {
          "description": "Room interior dimensions (m). Each is 2 to 15 m; values outside are clamped.",
          "type": "object",
          "required": ["width", "length", "height"],
          "properties": {
            "width": { "description": "Across the room, along x (m). 2 to 15.", "type": "number" },
            "length": { "description": "Screen wall to back wall, along y (m). 2 to 15.", "type": "number" },
            "height": { "description": "Floor to ceiling, along z (m). 2 to 15.", "type": "number" }
          },
          "additionalProperties": false
        },
        "screen": {
          "description": "The screen on the front wall. Give exactly one of width or diagonal.",
          "type": "object",
          "required": ["aspect", "bottom_z", "center_x"],
          "properties": {
            "width": { "description": "Image width (m). 0.5 m to the room width.", "type": "number" },
            "diagonal": { "description": "Image diagonal (m), instead of width. 0.5 m to the room width.", "type": "number" },
            "aspect": { "description": "Width over height as a number, e.g. 1.777778 for 16:9 or 2.4. 1.0 to 3.0.", "type": "number" },
            "bottom_z": { "description": "Height of the image's bottom edge above the floor (m). 0 to the room height.", "type": "number" },
            "center_x": { "description": "Horizontal position of the image centre on the front wall (m). 0 to the room width.", "type": "number" },
            "y_offset": { "description": "How far the screen plane sits forward of the front wall (m). Absent or 0: flush on the wall. Kept at least 0.3 m in front of the nearest row.", "type": "number" },
            "material": { "description": "openavdb screen material id. Absent: none or unknown. An unknown id is dropped on import.", "$ref": "#/$defs/id" },
            "gain": { "description": "Screen gain, overriding the material's. 0.05 to 5.", "type": "number" }
          },
          "oneOf": [{ "required": ["width"] }, { "required": ["diagonal"] }],
          "additionalProperties": false
        },
        "rows": {
          "description": "Seating rows, front to back. 1 to 8 rows.",
          "type": "array",
          "minItems": 1,
          "maxItems": 8,
          "items": { "$ref": "#/$defs/row" }
        },
        "seat_centering": {
          "description": "\"mlp\": the main listening seat sits on the screen axis and the whole block shifts with it. \"row\": every row is symmetric about the screen centre. Absent: \"mlp\".",
          "enum": ["mlp", "row"]
        },
        "mlp_seat": {
          "description": "The main listening position, chosen by the user. Absent: the front-row seat nearest the screen axis. Must name an existing seat, else it is dropped.",
          "type": "object",
          "required": ["row", "seat"],
          "properties": {
            "row": { "description": "Row index, from 0.", "type": "integer" },
            "seat": { "description": "Seat index in that row, from 0.", "type": "integer" }
          },
          "additionalProperties": false
        },
        "layout": {
          "description": "Channel layout id: 5.1, 7.1, 5.1.2, 5.1.4, 7.1.4 or 9.1.6 at 1.0. Absent: 5.1. An unknown id is dropped.",
          "$ref": "#/$defs/id"
        },
        "speakers": {
          "description": "Placed speakers, one per channel. At most 32; at most 4 subwoofers are carried (LFE, SW, SW1 to SW4). Absent: no speakers.",
          "type": "array",
          "maxItems": 32,
          "items": { "$ref": "#/$defs/speaker" }
        },
        "amp": {
          "description": "The global amplifier and room gain, used by every speaker with no amplifier of its own.",
          "type": "object",
          "required": ["power", "rated_ohms", "room_gain"],
          "properties": {
            "power": { "description": "Rated power per channel (W) at rated_ohms. 1 to 2000.", "type": "number" },
            "rated_ohms": { "description": "Impedance the power is rated at (ohms). 2 to 16.", "type": "number" },
            "room_gain": { "description": "Broadband room gain added to every level (dB). -12 to 12; 0 by design.", "type": "number" }
          },
          "additionalProperties": false
        },
        "amps": {
          "description": "The amplifiers in the design. At most 8 are carried, and only when the design has speakers. Absent or empty: every speaker uses ampPower or the global amp.",
          "type": "array",
          "items": { "$ref": "#/$defs/ampUnit" }
        },
        "bass": {
          "description": "Subwoofer signal settings for the bass view. Absent: unity gain, no delay, normal polarity, loss rate 20.",
          "type": "object",
          "required": ["lossRate", "sources"],
          "properties": {
            "lossRate": { "description": "Uniform room loss rate assumed by the bass model. 5 to 80.", "type": "number" },
            "sources": {
              "description": "Per subwoofer channel (LFE, SW, SW1 to SW4): gain, delay and polarity. Other keys are dropped.",
              "type": "object",
              "additionalProperties": { "$ref": "#/$defs/bassSource" }
            }
          },
          "additionalProperties": false
        },
        "projector": {
          "description": "The projector. Absent: no projector.",
          "type": "object",
          "required": ["throw_ratio_min", "throw_ratio_max", "lens_y"],
          "properties": {
            "throw_ratio_min": { "description": "Shortest throw ratio (throw distance over image width). 0.5 to 5. Equal to the maximum for a fixed lens.", "type": "number" },
            "throw_ratio_max": { "description": "Longest throw ratio. 0.5 to 5.", "type": "number" },
            "lens_y": { "description": "Lens distance from the front wall (m). At least 0.5, and a catalogue projector's body stays off the back wall.", "type": "number" },
            "product": { "description": "openavdb projector id; throw ratios, shift and lumens then come from the catalogue. An unknown id is dropped.", "$ref": "#/$defs/id" },
            "lens_z": { "description": "Lens centre height above the floor (m). 0 to the room height.", "type": "number" },
            "lens_x": { "description": "Lens centre across the room (m). Absent: the screen's center_x.", "type": "number" },
            "shift_v": { "description": "Vertical lens shift, % each way (custom projectors). 0 to 300.", "type": "number" },
            "shift_h": { "description": "Horizontal lens shift, % each way (custom projectors). 0 to 300.", "type": "number" },
            "lumens": { "description": "Light output (lm), custom or overriding the catalogue. 1 to 100000.", "type": "number" },
            "lumens_pick": { "description": "Index into the catalogue projector's measured modes. Dropped when it names no mode.", "type": "integer" }
          },
          "additionalProperties": false
        },
        "door": {
          "description": "The room door, for practical checks only (swing, seat clearance). Absent: no door.",
          "type": "object",
          "required": ["wall", "offset", "width", "hinge", "swing"],
          "properties": {
            "wall": { "description": "Which wall: front (the screen wall), back, left or right.", "enum": ["front", "back", "left", "right"] },
            "offset": { "description": "Hinge-side edge along the wall from its origin corner (m): front-left for front and back walls (along x), front corner for left and right walls (along y).", "type": "number" },
            "width": { "description": "Door width (m). At least 0.6, at most the wall.", "type": "number" },
            "hinge": { "description": "Hinge side: left or right.", "enum": ["left", "right"] },
            "swing": { "description": "Opens into the room (in) or out.", "enum": ["in", "out"] }
          },
          "additionalProperties": false
        },
        "panels": {
          "description": "Acoustic treatment panels. At most 200. Absent: an untreated room; an empty list is a treated room with no panels.",
          "type": "array",
          "maxItems": 200,
          "items": { "$ref": "#/$defs/panel" }
        }
      },
      "additionalProperties": false
    },
    "row": {
      "description": "One seating row. Eye and head heights are explicit; a riser is expressed by raising them.",
      "type": "object",
      "required": ["y", "eye_z", "head_z", "seat_x"],
      "properties": {
        "y": { "description": "Row depth from the front wall (m), not from the screen. At least 0.3, at most the room length.", "type": "number" },
        "eye_z": { "description": "Seated eye height above the floor (m). 0.1 to the room height.", "type": "number" },
        "head_z": { "description": "Seated head-top height above the floor (m), for sightlines. At least eye_z, at most the room height.", "type": "number" },
        "seat_x": {
          "description": "Across-room position of each seat (m). 1 to 20 seats; each kept 0.3 m off the side walls.",
          "type": "array",
          "minItems": 1,
          "maxItems": 20,
          "items": { "type": "number" }
        },
        "riser": { "description": "Height of the floor the row stands on (m). 0 to the lower of eye_z and 1.2. Absent: the historical eye-height convention.", "type": "number" },
        "seat": { "$ref": "#/$defs/seat" }
      },
      "additionalProperties": false
    },
    "seat": {
      "description": "The seat type drawn around the listeners. Never used by the scores. Absent: cinema seating, not reclining.",
      "type": "object",
      "required": ["kind"],
      "properties": {
        "kind": { "description": "sofa or cinema. An unknown kind drops the seat type.", "enum": ["sofa", "cinema"] },
        "width": { "description": "Sofa overall width (m). 0.5 to 8.", "type": "number" },
        "depth": { "description": "Sofa depth (m). 0.4 to 2.", "type": "number" },
        "back_height": { "description": "Sofa back height above the seat floor (m). 0.3 to 1.5.", "type": "number" },
        "recliner": { "description": "Cinema seating reclines.", "type": "boolean" },
        "footrest": { "description": "Cinema recliner with a footrest (only with recliner).", "type": "boolean" },
        "high_back": { "description": "Cinema seat with a high back.", "type": "boolean" },
        "seat_width": { "description": "Cinema chair width per seat (m). 0.4 to 1.2.", "type": "number" }
      },
      "additionalProperties": false
    },
    "speaker": {
      "description": "One placed speaker.",
      "type": "object",
      "required": ["channel", "x", "y", "z", "archetype"],
      "properties": {
        "channel": { "description": "Channel code, unique in the design: L, C, R, Lw, Rw, Ls, Rs, Lrs, Rrs, Ltf, Rtf, Ltm, Rtm, Ltr, Rtr, and the subwoofers LFE, SW, SW1 to SW4. Other codes are dropped.", "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9]{0,7}$" },
        "x": { "description": "Acoustic centre across the room (m). Kept inside the room.", "type": "number" },
        "y": { "description": "Acoustic centre from the front wall (m). Kept inside the room.", "type": "number" },
        "z": { "description": "Acoustic centre above the floor (m). Kept inside the room.", "type": "number" },
        "archetype": { "description": "Speaker type the parameters derive from: horn_loaded_cd, waveguide_dome, direct_dome, dipole_surround or in_ceiling at 1.0. An unknown type opens as horn_loaded_cd.", "$ref": "#/$defs/id" },
        "preset": { "description": "openavdb speaker id, when a real speaker is picked. An unknown id is dropped and the speaker is shown as its type.", "$ref": "#/$defs/id" },
        "params": {
          "description": "Overrides on top of the type or catalogue values. An override outside its band is clamped.",
          "type": "object",
          "properties": {
            "sensitivity": { "description": "Sensitivity (dB SPL at 2.83 V, 1 m). 80 to 110.", "type": "number" },
            "coverageHDeg": { "description": "Horizontal coverage (degrees). 10 to 360.", "type": "number" },
            "coverageVDeg": { "description": "Vertical coverage (degrees). 10 to 360.", "type": "number" },
            "maxSpl": { "description": "Maximum output (dB SPL at 1 m). 90 to 160.", "type": "number" },
            "impedance": { "description": "Nominal impedance (ohms). 2 to 16.", "type": "number" },
            "lfExtensionHz": { "description": "Low-frequency extension (Hz). 15 to 200.", "type": "number" }
          },
          "additionalProperties": false
        },
        "aim": {
          "description": "Aim intent, resolved to a direction when drawn, so it survives room and seat edits. Absent: toward the main listening position. Subwoofers carry no aim.",
          "type": "object",
          "required": ["mode"],
          "properties": {
            "mode": { "description": "normal: straight out of its surface. mlp: toward the main listening position. mount: from the mounting (see mount). An unknown mode drops the aim.", "enum": ["normal", "mlp", "mount"] },
            "offsetAzDeg": { "description": "Horizontal rotation from the base direction (degrees). -90 to 90.", "type": "number" },
            "offsetElDeg": { "description": "Vertical rotation from the base direction (degrees). -90 to 90.", "type": "number" }
          },
          "additionalProperties": false
        },
        "mount": {
          "description": "How the cabinet sits on its surface; read only when aim.mode is mount. Absent with mode mount: the channel's surface and the model's catalogue wedge angle.",
          "type": "object",
          "required": ["surface", "cabinetAngleDeg", "facing", "bracketYawDeg", "bracketPitchDeg"],
          "properties": {
            "surface": { "description": "wall, ceiling, stand or in_wall. An unknown surface drops the mount.", "enum": ["wall", "ceiling", "stand", "in_wall"] },
            "cabinetAngleDeg": { "description": "Built-in baffle angle of a wedge cabinet (degrees). 0 to 60.", "type": "number" },
            "facing": { "description": "Walls only: whether the wedge tilts down or toward the room (inward). A ceiling wedge always tilts toward the seats.", "enum": ["down", "inward"] },
            "bracketYawDeg": { "description": "Bracket yaw (degrees), + toward the main listening position's side. -60 to 60.", "type": "number" },
            "bracketPitchDeg": { "description": "Bracket pitch (degrees), + down on walls, + toward the seats on the ceiling. -60 to 60.", "type": "number" }
          },
          "additionalProperties": false
        },
        "body": {
          "description": "Manual cabinet dimensions (m), separate from the acoustic parameters.",
          "type": "object",
          "required": ["width", "height", "depth"],
          "properties": {
            "width": { "description": "Cabinet width (m). 0.05 to 3.", "type": "number" },
            "height": { "description": "Cabinet height (m). 0.02 to 3.", "type": "number" },
            "depth": { "description": "Cabinet depth (m). 0.02 to 3.", "type": "number" },
            "yaw": { "description": "Cabinet rotation about the vertical (degrees). -180 to 180.", "type": "number" }
          },
          "additionalProperties": false
        },
        "follow": { "description": "true: re-placed at target angles whenever the room, rows, seats or main listening position change. false or absent: stays where it is.", "type": "boolean" },
        "ampPower": { "description": "This speaker's amplifier power (W), overriding the global amp. 1 to 2000.", "type": "number" },
        "ampId": { "description": "Id of the amplifier in amps that drives this speaker; wins over ampPower. Dropped when it names no amplifier.", "type": "string" }
      },
      "additionalProperties": false
    },
    "ampUnit": {
      "description": "One amplifier: a catalogue amp (product) or a manual one (power at rated_ohms, channels). Which speakers it drives is stored on the speakers (ampId).",
      "type": "object",
      "required": ["id"],
      "properties": {
        "id": { "description": "Instance id, unique in the design: 1 to 8 lower-case letters or digits (a1, a2, ...). Other ids drop the amplifier.", "type": "string" },
        "product": { "description": "openavdb amplifier id. Absent: manual. An unknown id drops the amplifier.", "$ref": "#/$defs/id" },
        "power": { "description": "Manual amp: power per channel (W) at rated_ohms. 1 to 2000. Not used with a product.", "type": "number" },
        "rated_ohms": { "description": "Manual amp: the impedance power is rated at (ohms). 2 to 16. Not used with a product.", "type": "number" },
        "channels": { "description": "Manual amp: channel count, 1 to 32, rounded. Not used with a product.", "type": "number" },
        "bridged": {
          "description": "Speaker channels driven from a bridged pair. Only channels this amp drives are kept.",
          "type": "array",
          "items": { "type": "string" }
        }
      },
      "additionalProperties": false
    },
    "bassSource": {
      "description": "One subwoofer's signal settings.",
      "type": "object",
      "required": ["gainDb", "delayMs", "polarity"],
      "properties": {
        "gainDb": { "description": "Gain (dB). -120 to 40.", "type": "number" },
        "delayMs": { "description": "Delay (ms). 0 to 1000.", "type": "number" },
        "polarity": { "description": "1 normal, -1 inverted.", "enum": [1, -1] }
      },
      "additionalProperties": false
    },
    "panel": {
      "description": "One treatment panel: a rectangle on a room surface in that surface's own (u, v) coordinates (m). Absorption and scattering come from the panel type, never from the file.",
      "type": "object",
      "required": ["id", "typeId", "surface", "u0", "u1", "v0", "v1"],
      "properties": {
        "id": { "description": "Instance id, unique in the design.", "type": "string" },
        "typeId": { "description": "Panel type id: absorber_broadband, absorber_midhigh, bass_trap, diffuser_qrd or diffuser_skyline at 1.0. An unknown type drops the panel.", "$ref": "#/$defs/id" },
        "surface": { "description": "front, back, left, right, floor or ceiling. An unknown surface drops the panel.", "enum": ["front", "back", "left", "right", "floor", "ceiling"] },
        "u0": { "description": "Rectangle start along u (m). Clamped to the surface.", "type": "number" },
        "u1": { "description": "Rectangle end along u (m). Clamped to the surface.", "type": "number" },
        "v0": { "description": "Rectangle start along v (m). Clamped to the surface.", "type": "number" },
        "v1": { "description": "Rectangle end along v (m). Clamped to the surface.", "type": "number" },
        "thickness": { "description": "Depth into the room (m). 0.01 to 0.6.", "type": "number" }
      },
      "additionalProperties": false
    }
  }
}
