Cinema Designer design file format

A Cinema Designer room as one plain JSON file: what every field means, the units, how versions work, and how the designer opens a file it cannot hold exactly.

Published

What a design file is

A design file holds one Cinema Designer room: the room, the screen, the seats, the speakers, the amplifiers, the projector, the door and the treatment. It is one JSON object in UTF-8, named <name>.ohav.json. The design inside it is exactly the object the designer holds, so a file, a saved room and a share link carry the same thing.

In the designer, open the room menu and use Download design file to keep a copy, or Open design file… to load one. You can also drop a file anywhere on the designer. A file opens as a new unsaved design; saved rooms are never changed by opening a file.

The format is described by a JSON Schema (2020-12): https://openhomeav.com/schema/design/v1.0.json. Any JSON Schema validator can check a file against it.

The envelope

The top level of the file, in this order. Only format, version and design are required.

KeyTypeRequiredMeaning
$schematextnoURL of the schema for this exact format version, for external validators.
format"openhomeav-design"yesAlways "openhomeav-design". Anything else is not a design file.
versiontextyesFormat version as "MAJOR.MINOR". A minor adds optional fields only; a major changes a meaning. This schema reads 1.x.
nametextnoRoom name, 1 to 80 characters, no control characters. Pre-fills Save room when the file is opened.
generatorobjectnoThe tool that wrote the file.
createdtextnoWhen the file was written, ISO 8601 (UTC, e.g. 2026-09-27T10:00:00Z). Ignored when comparing designs.
sourceobjectnoFree-form provenance, for example which CAD variables produced which block. Shown when the file is opened; never stored by the designer.
designobjectyesThe design, exactly the object the Cinema Designer holds. Absent optional blocks mean what they mean in the designer today (stated per field).
x-…anynoExtension data for other tools. Listed when the file is opened as not used by the designer; never stored.

The designer never writes a room id, an access link or a save revision into a file. created is ignored when two designs are compared.

Units and coordinates

  • SI units only: lengths in metres, angles in degrees, power in watts, impedance in ohms, levels in dB, delay in milliseconds, light in lumens.
  • design.units is the display preference (metric or imperial). It never changes the unit of a stored number.
  • The origin is the front-left floor corner. x runs across the room, left to right facing the screen; y runs into the room from the screen wall; z runs up from the floor.
  • Numbers keep full double precision in files. Share links round to 6 decimals.

Design fields

units, room, screen and rows are required. Every other field is optional, and leaving it out means what the description says. Bands such as “2 to 15” are what the designer holds: a value outside the band still opens, clamped, and the import report says so. A value of the wrong type (text where a number belongs) or a missing required field stops the file from opening.

Catalogue ids (speaker models, amplifiers, projectors, screen materials, panel types, layouts) are plain text, so a new product in the catalogue never needs a new format version. An id the designer does not know is reported when the file opens.

units (required)

FieldTypeRequiredMeaning
units"metric" | "imperial"yesDisplay preference only: "metric" or "imperial". Every stored number stays SI (metres, degrees) either way. An unknown value opens as metric.

room (required)

FieldTypeRequiredMeaning
roomobjectyesRoom interior dimensions (m). Each is 2 to 15 m; values outside are clamped.
room.widthnumberyesAcross the room, along x (m). 2 to 15.
room.lengthnumberyesScreen wall to back wall, along y (m). 2 to 15.
room.heightnumberyesFloor to ceiling, along z (m). 2 to 15.

screen (required)

FieldTypeRequiredMeaning
screenobjectyesThe screen on the front wall. Give exactly one of width or diagonal.
screen.widthnumbernoImage width (m). 0.5 m to the room width.
screen.diagonalnumbernoImage diagonal (m), instead of width. 0.5 m to the room width.
screen.aspectnumberyesWidth over height as a number, e.g. 1.777778 for 16:9 or 2.4. 1.0 to 3.0.
screen.bottom_znumberyesHeight of the image's bottom edge above the floor (m). 0 to the room height.
screen.center_xnumberyesHorizontal position of the image centre on the front wall (m). 0 to the room width.
screen.y_offsetnumbernoHow 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.
screen.materialidnoopenavdb screen material id. Absent: none or unknown. An unknown id is dropped on import.
screen.gainnumbernoScreen gain, overriding the material's. 0.05 to 5.

rows (required)

FieldTypeRequiredMeaning
rowslist of objectsyesSeating rows, front to back. 1 to 8 rows.
rows[].ynumberyesRow depth from the front wall (m), not from the screen. At least 0.3, at most the room length.
rows[].eye_znumberyesSeated eye height above the floor (m). 0.1 to the room height.
rows[].head_znumberyesSeated head-top height above the floor (m), for sightlines. At least eye_z, at most the room height.
rows[].seat_xlist of numbersyesAcross-room position of each seat (m). 1 to 20 seats; each kept 0.3 m off the side walls.
rows[].risernumbernoHeight of the floor the row stands on (m). 0 to the lower of eye_z and 1.2. Absent: the historical eye-height convention.
rows[].seatobjectnoThe seat type drawn around the listeners. Never used by the scores. Absent: cinema seating, not reclining.
rows[].seat.kind"sofa" | "cinema"yessofa or cinema. An unknown kind drops the seat type.
rows[].seat.widthnumbernoSofa overall width (m). 0.5 to 8.
rows[].seat.depthnumbernoSofa depth (m). 0.4 to 2.
rows[].seat.back_heightnumbernoSofa back height above the seat floor (m). 0.3 to 1.5.
rows[].seat.reclinertrue / falsenoCinema seating reclines.
rows[].seat.footresttrue / falsenoCinema recliner with a footrest (only with recliner).
rows[].seat.high_backtrue / falsenoCinema seat with a high back.
rows[].seat.seat_widthnumbernoCinema chair width per seat (m). 0.4 to 1.2.

seat_centering

FieldTypeRequiredMeaning
seat_centering"mlp" | "row"no"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".

mlp_seat

FieldTypeRequiredMeaning
mlp_seatobjectnoThe 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.
mlp_seat.rowwhole numberyesRow index, from 0.
mlp_seat.seatwhole numberyesSeat index in that row, from 0.

layout

FieldTypeRequiredMeaning
layoutidnoChannel 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.

speakers

FieldTypeRequiredMeaning
speakerslist of objectsnoPlaced speakers, one per channel. At most 32; at most 4 subwoofers are carried (LFE, SW, SW1 to SW4). Absent: no speakers.
speakers[].channeltextyesChannel 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.
speakers[].xnumberyesAcoustic centre across the room (m). Kept inside the room.
speakers[].ynumberyesAcoustic centre from the front wall (m). Kept inside the room.
speakers[].znumberyesAcoustic centre above the floor (m). Kept inside the room.
speakers[].archetypeidyesSpeaker 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.
speakers[].presetidnoopenavdb speaker id, when a real speaker is picked. An unknown id is dropped and the speaker is shown as its type.
speakers[].paramsobjectnoOverrides on top of the type or catalogue values. An override outside its band is clamped.
speakers[].params.sensitivitynumbernoSensitivity (dB SPL at 2.83 V, 1 m). 80 to 110.
speakers[].params.coverageHDegnumbernoHorizontal coverage (degrees). 10 to 360.
speakers[].params.coverageVDegnumbernoVertical coverage (degrees). 10 to 360.
speakers[].params.maxSplnumbernoMaximum output (dB SPL at 1 m). 90 to 160.
speakers[].params.impedancenumbernoNominal impedance (ohms). 2 to 16.
speakers[].params.lfExtensionHznumbernoLow-frequency extension (Hz). 15 to 200.
speakers[].aimobjectnoAim intent, resolved to a direction when drawn, so it survives room and seat edits. Absent: toward the main listening position. Subwoofers carry no aim.
speakers[].aim.mode"normal" | "mlp" | "mount"yesnormal: straight out of its surface. mlp: toward the main listening position. mount: from the mounting (see mount). An unknown mode drops the aim.
speakers[].aim.offsetAzDegnumbernoHorizontal rotation from the base direction (degrees). -90 to 90.
speakers[].aim.offsetElDegnumbernoVertical rotation from the base direction (degrees). -90 to 90.
speakers[].mountobjectnoHow 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.
speakers[].mount.surface"wall" | "ceiling" | "stand" | "in_wall"yeswall, ceiling, stand or in_wall. An unknown surface drops the mount.
speakers[].mount.cabinetAngleDegnumberyesBuilt-in baffle angle of a wedge cabinet (degrees). 0 to 60.
speakers[].mount.facing"down" | "inward"yesWalls only: whether the wedge tilts down or toward the room (inward). A ceiling wedge always tilts toward the seats.
speakers[].mount.bracketYawDegnumberyesBracket yaw (degrees), + toward the main listening position's side. -60 to 60.
speakers[].mount.bracketPitchDegnumberyesBracket pitch (degrees), + down on walls, + toward the seats on the ceiling. -60 to 60.
speakers[].bodyobjectnoManual cabinet dimensions (m), separate from the acoustic parameters.
speakers[].body.widthnumberyesCabinet width (m). 0.05 to 3.
speakers[].body.heightnumberyesCabinet height (m). 0.02 to 3.
speakers[].body.depthnumberyesCabinet depth (m). 0.02 to 3.
speakers[].body.yawnumbernoCabinet rotation about the vertical (degrees). -180 to 180.
speakers[].followtrue / falsenotrue: re-placed at target angles whenever the room, rows, seats or main listening position change. false or absent: stays where it is.
speakers[].ampPowernumbernoThis speaker's amplifier power (W), overriding the global amp. 1 to 2000.
speakers[].ampIdtextnoId of the amplifier in amps that drives this speaker; wins over ampPower. Dropped when it names no amplifier.

amp

FieldTypeRequiredMeaning
ampobjectnoThe global amplifier and room gain, used by every speaker with no amplifier of its own.
amp.powernumberyesRated power per channel (W) at rated_ohms. 1 to 2000.
amp.rated_ohmsnumberyesImpedance the power is rated at (ohms). 2 to 16.
amp.room_gainnumberyesBroadband room gain added to every level (dB). -12 to 12; 0 by design.

amps

FieldTypeRequiredMeaning
ampslist of objectsnoThe 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.
amps[].idtextyesInstance id, unique in the design: 1 to 8 lower-case letters or digits (a1, a2, ...). Other ids drop the amplifier.
amps[].productidnoopenavdb amplifier id. Absent: manual. An unknown id drops the amplifier.
amps[].powernumbernoManual amp: power per channel (W) at rated_ohms. 1 to 2000. Not used with a product.
amps[].rated_ohmsnumbernoManual amp: the impedance power is rated at (ohms). 2 to 16. Not used with a product.
amps[].channelsnumbernoManual amp: channel count, 1 to 32, rounded. Not used with a product.
amps[].bridgedlist of textsnoSpeaker channels driven from a bridged pair. Only channels this amp drives are kept.

bass

FieldTypeRequiredMeaning
bassobjectnoSubwoofer signal settings for the bass view. Absent: unity gain, no delay, normal polarity, loss rate 20.
bass.lossRatenumberyesUniform room loss rate assumed by the bass model. 5 to 80.
bass.sourcesobject keyed by channelyesPer subwoofer channel (LFE, SW, SW1 to SW4): gain, delay and polarity. Other keys are dropped.
bass.sources.<channel>.gainDbnumberyesGain (dB). -120 to 40.
bass.sources.<channel>.delayMsnumberyesDelay (ms). 0 to 1000.
bass.sources.<channel>.polarity1 | -1yes1 normal, -1 inverted.

projector

FieldTypeRequiredMeaning
projectorobjectnoThe projector. Absent: no projector.
projector.throw_ratio_minnumberyesShortest throw ratio (throw distance over image width). 0.5 to 5. Equal to the maximum for a fixed lens.
projector.throw_ratio_maxnumberyesLongest throw ratio. 0.5 to 5.
projector.lens_ynumberyesLens distance from the front wall (m). At least 0.5, and a catalogue projector's body stays off the back wall.
projector.productidnoopenavdb projector id; throw ratios, shift and lumens then come from the catalogue. An unknown id is dropped.
projector.lens_znumbernoLens centre height above the floor (m). 0 to the room height.
projector.lens_xnumbernoLens centre across the room (m). Absent: the screen's center_x.
projector.shift_vnumbernoVertical lens shift, % each way (custom projectors). 0 to 300.
projector.shift_hnumbernoHorizontal lens shift, % each way (custom projectors). 0 to 300.
projector.lumensnumbernoLight output (lm), custom or overriding the catalogue. 1 to 100000.
projector.lumens_pickwhole numbernoIndex into the catalogue projector's measured modes. Dropped when it names no mode.

door

FieldTypeRequiredMeaning
doorobjectnoThe room door, for practical checks only (swing, seat clearance). Absent: no door.
door.wall"front" | "back" | "left" | "right"yesWhich wall: front (the screen wall), back, left or right.
door.offsetnumberyesHinge-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).
door.widthnumberyesDoor width (m). At least 0.6, at most the wall.
door.hinge"left" | "right"yesHinge side: left or right.
door.swing"in" | "out"yesOpens into the room (in) or out.

panels

FieldTypeRequiredMeaning
panelslist of objectsnoAcoustic treatment panels. At most 200. Absent: an untreated room; an empty list is a treated room with no panels.
panels[].idtextyesInstance id, unique in the design.
panels[].typeIdidyesPanel type id: absorber_broadband, absorber_midhigh, bass_trap, diffuser_qrd or diffuser_skyline at 1.0. An unknown type drops the panel.
panels[].surface"front" | "back" | "left" | "right" | "floor" | "ceiling"yesfront, back, left, right, floor or ceiling. An unknown surface drops the panel.
panels[].u0numberyesRectangle start along u (m). Clamped to the surface.
panels[].u1numberyesRectangle end along u (m). Clamped to the surface.
panels[].v0numberyesRectangle start along v (m). Clamped to the surface.
panels[].v1numberyesRectangle end along v (m). Clamped to the surface.
panels[].thicknessnumbernoDepth into the room (m). 0.01 to 0.6.

Versions

  • version is "MAJOR.MINOR". This page describes 1.0.
  • A minor version only adds optional fields or new allowed values. Every older file stays valid and means the same.
  • A major version changes a meaning, a unit or a required field, or removes something. The designer converts files of an older major when it opens them.
  • Each version has its own schema file that never changes once released, and /schema/design/v1.json is always the latest 1.x.
  • A file from a newer minor opens; fields this designer does not know are listed as not used. A file from a newer major does not open: reload to get the latest designer.
  • One major number is shared by design files, saved rooms and share links, so everything saved today is design format 1.0.

What the designer accepts

  • A design file, as above.
  • A bare design object (the contents of design on its own).
  • An object with the design under config.

The last two open as format 1.0, with the note “Opened without a format header”. Files over 1 MB are not read.

How opening a file reports changes

When the designer cannot hold a file exactly as written, it shows every change before opening and asks Open with these changes or Cancel. Nothing a file carries is dropped without being listed.

KindMeansExample
ClampedA value outside the designer’s band, moved to the nearest edgeroom height 16 m to 15 m
Not carried by the designerMore of something than the designer holds2 of 6 subs (the designer holds 4): SW, SW1
DroppedAn id the designer does not know, a duplicate, or a reference to nothingUnknown catalogue id, speaker shown as its type: Rtf acme-x1
Set to the designer’s defaultA value outside a closed set, replaced by the defaultdoor wall ceiling to back
Not used by this designerA field this version does not definedesign.room.shape

A file that cannot be opened at all (not JSON, not a design, a wrong type, too many rows or speakers) is refused with the reason and the path of each problem, and nothing on screen changes.

Examples

The smallest useful file: one row with one seat, no speakers.

{
  "$schema": "https://openhomeav.com/schema/design/v1.0.json",
  "format": "openhomeav-design",
  "version": "1.0",
  "name": "Small room",
  "design": {
    "units": "metric",
    "room": {
      "width": 4,
      "length": 5.5,
      "height": 2.5
    },
    "screen": {
      "width": 2.2,
      "aspect": 1.7777777777777777,
      "bottom_z": 0.6,
      "center_x": 2
    },
    "rows": [
      {
        "y": 3.2,
        "eye_z": 1.05,
        "head_z": 1.25,
        "seat_x": [
          2
        ]
      }
    ]
  }
}

The designer’s default design, as its Download writes it (without created).

{
  "$schema": "https://openhomeav.com/schema/design/v1.0.json",
  "format": "openhomeav-design",
  "version": "1.0",
  "name": "Cinema design",
  "generator": {
    "name": "OpenHomeAV Cinema Designer",
    "url": "https://openhomeav.com/designer/"
  },
  "design": {
    "units": "metric",
    "room": {
      "width": 5.5,
      "length": 7.5,
      "height": 2.8
    },
    "screen": {
      "width": 3,
      "aspect": 1.7777777777777777,
      "bottom_z": 0.7,
      "center_x": 2.75
    },
    "rows": [
      {
        "y": 3.6,
        "eye_z": 1.1,
        "head_z": 1.3,
        "seat_x": [
          1.85,
          2.75,
          3.65
        ]
      },
      {
        "y": 4.9,
        "eye_z": 1.55,
        "head_z": 1.75,
        "seat_x": [
          2.3,
          3.2
        ]
      }
    ],
    "seat_centering": "mlp",
    "layout": "5.1",
    "speakers": [
      {
        "channel": "L",
        "x": 1.15,
        "y": 0.1,
        "z": 1.1,
        "archetype": "horn_loaded_cd",
        "aim": {
          "mode": "mlp"
        },
        "follow": true
      },
      {
        "channel": "C",
        "x": 2.75,
        "y": 0.1,
        "z": 1.1,
        "archetype": "horn_loaded_cd",
        "aim": {
          "mode": "mlp"
        },
        "follow": true
      },
      {
        "channel": "R",
        "x": 4.35,
        "y": 0.1,
        "z": 1.1,
        "archetype": "horn_loaded_cd",
        "aim": {
          "mode": "mlp"
        },
        "follow": true
      },
      {
        "channel": "Ls",
        "x": 0.1,
        "y": 4,
        "z": 1.1,
        "archetype": "dipole_surround",
        "aim": {
          "mode": "mlp"
        },
        "follow": true
      },
      {
        "channel": "Rs",
        "x": 5.4,
        "y": 4,
        "z": 1.1,
        "archetype": "dipole_surround",
        "aim": {
          "mode": "mlp"
        },
        "follow": true
      },
      {
        "channel": "LFE",
        "x": 0.25,
        "y": 0.25,
        "z": 0,
        "archetype": "horn_loaded_cd"
      }
    ],
    "amp": {
      "power": 150,
      "rated_ohms": 8,
      "room_gain": 0
    },
    "door": {
      "wall": "back",
      "offset": 0.3,
      "width": 0.8,
      "hinge": "left",
      "swing": "in"
    }
  }
}

Write it from OpenSCAD

A small OpenSCAD library, openhomeav_design.scad (MIT licence, in the openscad/ folder of the OpenHomeAV source), writes this file from a CAD model. You declare the room once, in one ohav_design(...) call that uses the variables and functions your model already has. The library echoes the whole file as one line; a command-line tool turns that line into the file and checks it with the designer’s own reader. It works with OpenSCAD 2021.01 and later.

  1. Put use <openhomeav_design.scad> at the top of a model file that includes your CAD.
  2. Call ohav_design(...) once, as in the example below.
  3. Run node tools/scad-to-design.mjs model.scad -o room.ohav.json, then open the file in the designer.
FunctionWrites
ohav_design(name, unit, frame, room, screen, rows, mlp, layout, speakers, amps, projector, door, bass, panels, source)The file. mlp = [row, seat] picks the main listening position; source lists where each block came from
ohav_room(width, length, height)room
ohav_screen(width or diagonal, aspect, bottom_z, center_x, y_offset, material, gain)screen
ohav_row(y, eye_z, head_z, seat_x, riser, seat), ohav_sofa(...), ohav_cinema_seat(...)One entry of rows and its seat type
ohav_speaker(channel, pos, model, archetype, aim, amp, body), ohav_sub(...)One entry of speakers; pos is a point in your model
ohav_aim_mlp(az, el), ohav_aim_normal(az, el)Aim at the main listening position, or along the wall’s normal, with optional offsets
ohav_aim_axis(dir)A driver axis from your model, turned into offsets from the direction to the main listening position
ohav_aim_mount(surface, cabinet, facing, yaw, pitch)The axis the hardware gives: a wedge’s built-in angle and a bracket
ohav_body(width, height, depth, yaw)A speaker’s cabinet size
ohav_amp(id, product, ...)One entry of amps; a speaker names it with amp
ohav_projector(product, lens, ...)projector; a catalogue projector takes its throw ratios from the catalogue
ohav_door(wall, offset, width, hinge, swing), ohav_bass(sources, loss_rate), ohav_panel(surface, type, u0, u1, v0, v1, thickness)door, bass, one entry of panels

Units and axes. unit ("m", "cm", "mm" or "in") is the unit of every length you pass; the file is always in metres and degrees. Arguments are named in the designer’s frame (center_x is across the room, y and y_offset are depths from the screen wall). frame maps points and directions from your model: "x_across" (the default, your X runs across the room) or "x_depth" (your X runs from the screen wall into the room and your Y across it).

Numbers and text. Numbers are written to 6 significant digits, about 10 micrometres at room scale. Text is escaped for JSON by the library. An infinite or undefined number stops the run with the name of the field, and so does a channel used twice.

Driver axes. The file has no axis aim. The tool turns each ohav_aim_axis into offsets from the direction to the main listening position, rounded to 0.01 degrees, with the designer’s own aim rule. The offsets then follow the listening position if you move the seats in the designer.

Nothing changed silently. The tool refuses any design the designer would change when opening it (a value outside a band, an unknown catalogue id, a fifth subwoofer) and prints the reason, so a file it writes always opens exactly as declared.

The example model (openscad/examples/simple_room.scad), a 5.1.2 room in millimetres:

// SPDX-License-Identifier: MIT
// ============================================================================
// simple_room.scad: a 5.1.2 room declared with openhomeav_design.scad (R34b).
// ============================================================================
// Write its design file:
//   node tools/scad-to-design.mjs openscad/examples/simple_room.scad -o simple_room.ohav.json
// The committed openscad/examples/simple_room.ohav.json is that output.
//
// Lengths in millimetres; the model's X runs across the room (the default
// frame), so points pass through as [across, into the room, up].
// ============================================================================

use <../openhomeav_design.scad>

// The model's own named variables, as any CAD model has them.
room_w = 4200;
room_l = 6000;
room_h = 2600;
screen_w = 2600;
screen_bottom = 500;
screen_depth = 100;            // screen surface forward of the front wall
row_y = 3600;
ear_z = 1100;
seats = [1500, 2100, 2700];
mlp = [2100, 3600, 1100];      // the middle seat
wedge_angle = 30;

// A front left speaker toed in by eye, not aimed at the seat: its driver
// axis becomes an aim offset from the MLP direction in the file.
front_left = [900, 150, 1000];
front_left_axis = [0.25, 1, -0.02];

ohav_design(
    name     = "Simple room",
    unit     = "mm",
    room     = ohav_room(width = room_w, length = room_l, height = room_h),
    screen   = ohav_screen(width = screen_w, aspect = 16 / 9, bottom_z = screen_bottom,
                           center_x = room_w / 2, y_offset = screen_depth),
    rows     = [ohav_row(y = row_y, eye_z = ear_z, head_z = ear_z + 200, seat_x = seats,
                         seat = ohav_sofa(width = 2200, depth = 950, back_height = 800))],
    mlp      = [0, 1],
    layout   = "5.1.2",
    speakers = [
        ohav_speaker("L", pos = front_left, model = "ascendo-the6-passive-on-wall",
                     archetype = "waveguide_dome", aim = ohav_aim_axis(front_left_axis), amp = "a1",
                     body = ohav_body(260, 440, 94)),
        ohav_speaker("C", pos = [room_w / 2, 150, 1000], model = "ascendo-the6-passive-on-wall",
                     archetype = "waveguide_dome", aim = ohav_aim_mlp(), amp = "a1"),
        ohav_speaker("R", pos = [room_w - 900, 150, 1000], model = "ascendo-the6-passive-on-wall",
                     archetype = "waveguide_dome", aim = ohav_aim_mlp(az = -2.5), amp = "a1"),
        ohav_speaker("Ls", pos = [60, 3900, 1300], aim = ohav_aim_normal(), amp = "a1"),
        ohav_speaker("Rs", pos = [room_w - 60, 3900, 1300], aim = ohav_aim_normal(), amp = "a1"),
        ohav_speaker("Ltm", pos = [1300, 3300, room_h - 100], model = "ascendo-the6-passive-wedge30",
                     archetype = "waveguide_dome", aim = ohav_aim_mount("ceiling", cabinet = wedge_angle), amp = "a1"),
        ohav_speaker("Rtm", pos = [room_w - 1300, 3300, room_h - 100], model = "ascendo-the6-passive-wedge30",
                     archetype = "waveguide_dome", aim = ohav_aim_mount("ceiling", cabinet = wedge_angle), amp = "a1"),
        ohav_sub("LFE", pos = [400, 400, 0])
    ],
    amps     = [ohav_amp("a1", product = "denon-avr-x3800h")],
    door     = ohav_door("right", offset = 4600, width = 900, hinge = "left", swing = "in"),
    panels   = [ohav_panel("left", "absorber_broadband", u0 = 3000, u1 = 4200, v0 = 800, v1 = 1800, thickness = 100)],
    source   = [["room", "simple_room.scad room_w, room_l, room_h"],
                ["speakers", "front_left_axis is a driver axis; the rest aim at the MLP or the wall normal"]]
);

Licence

The format, its JSON Schema and this page are dedicated to the public domain under CC0 1.0. Write the file from any tool.