{
  "@context": "https://schema.org",
  "@type": "Dataset",
  "name": "Multilarm feature matrix",
  "description": "A machine-readable statement of what Multilarm does, what it partly does, what it does not do, and what it has deliberately declined to do. Every shipped or partial entry names the code path or document that proves it. Entries marked \"absent\" are published deliberately: a feature list that omits its own gaps cannot be checked, and one that admits them can.",
  "url": "https://multilarm.com/features.json",
  "license": "https://multilarm.com/terms.html",
  "creator": {
    "@type": "Organization",
    "name": "Multilarm",
    "url": "https://multilarm.com"
  },
  "isAccessibleForFree": true,
  "encodingFormat": "application/json",
  "dateModified": "2026-09-23",
  "about": {
    "@type": "SoftwareApplication",
    "name": "Multilarm",
    "url": "https://multilarm.com",
    "applicationCategory": "BusinessApplication",
    "operatingSystem": "Windows, Linux, Raspberry Pi OS, macOS"
  },
  "legend": {
    "shipped": "Built, released and in use.",
    "partial": "Built, but with a stated limit — read the note before relying on it.",
    "absent": "Not built. Named here so the answer is unambiguous.",
    "declined": "Deliberately not built. The note says why."
  },
  "summary": {
    "total": 89,
    "shipped": 64,
    "partial": 10,
    "absent": 9,
    "declined": 6,
    "note": "Counts are of published rows, not of everything the product does. A high \"absent\" count is a sign the list is honest, not that the product is thin."
  },
  "categories": [
    {
      "id": "scheduling",
      "name": "Scheduling"
    },
    {
      "id": "audio",
      "name": "Audio engine"
    },
    {
      "id": "announce",
      "name": "Announcements and speech"
    },
    {
      "id": "rooms",
      "name": "Rooms and distribution"
    },
    {
      "id": "music",
      "name": "Music library and rotation"
    },
    {
      "id": "listening",
      "name": "Listening and sensing"
    },
    {
      "id": "emergency",
      "name": "Emergency and mass notification"
    },
    {
      "id": "integration",
      "name": "Integration and openness"
    },
    {
      "id": "ops",
      "name": "Operations and assurance"
    },
    {
      "id": "platform",
      "name": "Platform and licensing"
    }
  ],
  "features": [
    {
      "id": "schedule-per-date",
      "category": "scheduling",
      "name": "Per-date and recurring schedules",
      "summary": "Event times per date, with daily, weekly and monthly recurrence.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "Multilarm/ScheduleModel.vb",
        "Multilarm/Program.vb#MAConfig.DateAndTimeData",
        "https://multilarm.com/docs.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "schedule-rules-overlay",
      "category": "scheduling",
      "name": "Date-scoped rule overlays that revert on their own",
      "summary": "Exam fortnights, term dates and one-off changes are written as rules over the timetable rather than edits to it, so the ordinary schedule returns with no second action.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "Multilarm/SlotRules.vb",
        "https://multilarm.com/solutions/exam-period-bell-schedules"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "schedule-alternate-config",
      "category": "scheduling",
      "name": "Alternate configuration by date",
      "summary": "A wholly different day — study leave, holiday, Ramadan — switches to another config file at midnight.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "Multilarm/Program.vb#MAConfig.AlternateConfig"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "schedule-bells",
      "category": "scheduling",
      "name": "Relay-driven bell schedules",
      "summary": "Physical bells and sounders on their own schedule, driven from relay channels.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "Multilarm/BellScheduler.vb",
        "Multilarm/RelayController.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "schedule-calendar-import",
      "category": "scheduling",
      "name": "Calendar (.ics) import",
      "summary": "A term calendar can be imported once from an .ics file in the Config Generator.",
      "state": "partial",
      "note": "One-shot import. Live two-way sync with Google, Outlook or CalDAV is not built — see calendar-sync.",
      "since": "1.0",
      "evidence": [
        "Web/multilarmconfiggenerator.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "calendar-sync",
      "category": "integration",
      "name": "Live calendar sync (Google / Outlook / CalDAV)",
      "summary": "Subscribe to .ics calendar addresses and announce events as they come round, alongside the existing one-shot .ics file import.",
      "state": "shipped",
      "note": "Private .ics subscription URLs rather than OAuth: no mailbox access, no cloud app registration, revocable by the customer. A feed can only say something or run a scene -- there is no stop or volume action -- and announcements are queued behind whatever is playing.",
      "since": "1.2026.901",
      "evidence": [
        "Multilarm/CalendarSync.vb",
        "Multilarm/MultilarmTests/CalendarSyncTests.vb"
      ],
      "verified": "2026-09-01"
    },
    {
      "id": "audio-engine-native",
      "category": "audio",
      "name": "In-house cross-platform native audio engine",
      "summary": "Decode, record, stream, mirror, scan and mix — built on miniaudio and libFLAC, with no GPL dependency anywhere in the chain.",
      "state": "shipped",
      "since": "1.4.0",
      "evidence": [
        "MultilarmAudio/",
        "Multilarm/MaEngine.vb",
        "THIRD_PARTY_NOTICES.txt"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-priority-mixer",
      "category": "audio",
      "name": "Four-channel priority mixer with ducking",
      "summary": "Emergency, announcement, secondary and background each hold their own channel. A higher priority ducks or pre-empts what is below it and the lower channel resumes where it was — it is not paused and restarted.",
      "state": "shipped",
      "since": "1.4.0",
      "evidence": [
        "Multilarm/MaEngine.vb",
        "Multilarm/ManagedPlayer.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-fade-crossfade",
      "category": "audio",
      "name": "Fade in, fade out and crossfade",
      "summary": "Per-event fade timings, and crossfade between background tracks.",
      "state": "shipped",
      "since": "1.4.0",
      "evidence": [
        "Multilarm/MaEngine.vb",
        "Multilarm/Program.vb#MAConfig.AlarmFadeInMs"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-device-mirroring",
      "category": "audio",
      "name": "Simultaneous output to several sound devices on one host",
      "summary": "The same audio to more than one output card or USB device at once.",
      "state": "shipped",
      "since": "1.4.0",
      "evidence": [
        "Multilarm/MaEngine.vb",
        "Multilarm/Program.vb#MAConfig.MirrorDevices"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-loudness-tools",
      "category": "audio",
      "name": "Loudness analysis and batch normalisation",
      "summary": "Measure loudness across a folder of announcements and normalise them so one is not twice as loud as the next.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "Multilarm/AudioAnalysis.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-silence-scan",
      "category": "audio",
      "name": "Silence scanning and trimming",
      "summary": "Finds leading and trailing silence in source files, so an announcement does not begin with two seconds of nothing.",
      "state": "shipped",
      "since": "1.4.0",
      "evidence": [
        "Multilarm/MaSilenceScanner.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-dsp",
      "category": "audio",
      "name": "Per-output DSP: parametric EQ, delay, limiter",
      "summary": "Correcting a room, time-aligning distributed speaker runs, and preventing a clipped announcement.",
      "state": "absent",
      "note": "Planned. The engine currently offers gain, fade and crossfade only. To be fixed in the C engine first.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "audio-sound-masking",
      "category": "audio",
      "name": "Sound masking / focus audio",
      "summary": "Generated masking noise on a schedule -- pink, brown, white or hush -- for open-plan offices, clinics and study spaces.",
      "state": "shipped",
      "note": "The noise is computed on the device rather than played from a sample library, so nothing is licensed and nothing is downloaded. The level is capped at 0.6 deliberately: masking sits under the conversation it covers. The radio always wins -- masking starts only when the background channel is genuinely idle and never stops anything -- and the status carries radioHasChannel so \"not scheduled\" and \"the radio has the channel\" are distinguishable rather than both looking like a fault.",
      "since": "1.2026.901",
      "evidence": [
        "Multilarm/SoundMasking.vb",
        "Multilarm/SignalGen.vb",
        "Multilarm/MultilarmTests/SoundMaskingTests.vb"
      ],
      "verified": "2026-09-01"
    },
    {
      "id": "chime-library",
      "category": "audio",
      "name": "Bundled chime and tone library",
      "summary": "Twelve chimes and signal tones generated beside the configuration on first run, ready to use as an announcement chime or a bell.",
      "state": "shipped",
      "note": "Synthesised from sine waves and decay envelopes, not sampled. A bundled \"royalty-free\" sample pack carries redistribution terms a customer installing an MSI never sees and cannot re-audit; generated tones have no third party in them at all, and add megabytes rather than tens of megabytes to the installer. An existing file is never overwritten, so a replaced chime survives every restart, and deleting one re-renders it.",
      "since": "1.2026.901",
      "evidence": [
        "Multilarm/ToneLibrary.vb",
        "Multilarm/SignalGen.vb",
        "Multilarm/MultilarmTests/SoundMaskingTests.vb"
      ],
      "verified": "2026-09-01"
    },
    {
      "id": "audio-output-dsp",
      "category": "audio",
      "name": "Output tuning per speaker output (EQ, delay, limiter)",
      "summary": "Parametric EQ, speaker time-alignment delay and a peak limiter on every physical output, built in - the work an external rack DSP is normally bought for.",
      "state": "shipped",
      "note": "One independent chain per output: pre-gain, up to 8 cookbook biquads (peak/lowshelf/highshelf/lowpass/highpass), a 0-500 ms delay line, a peak limiter with a 1.5 ms attack, and a hard ceiling behind it. Delays are positional, so a zone closer to the listener can be held back rather than arriving as an echo. Off out of the box, and off is a true bypass: with the chain disabled every sample is bit-identical, asserted by an automated gate rather than assumed. The Rooms multi-room tap is taken before the chain, so a remote room hears programme audio and not this building’s amplifier correction. Measured on the bench: pre-gain within 0.001 dB, delay sample-exact, EQ centre gain within 0.008 dB, limiter holding -6.00 dBFS at -5.985 dBFS, and 0.0104 % THD added holding a 1 kHz tone 2.9 dB down. Tone shaping and driver protection only - it can attenuate, delay and limit audio and can never start, stop or silence anything.",
      "since": "1.2026.831",
      "evidence": [
        "MultilarmAudio/src/multilarm_audio.c",
        "MultilarmAudio/test/smoke_dsp.c",
        "Multilarm/MaOutputDsp.vb",
        "Multilarm/ConfigSchema.vb#DspEnabled"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "tts-offline",
      "category": "announce",
      "name": "Fully offline neural text-to-speech",
      "summary": "Kokoro 82M ONNX with dictionary-only grapheme-to-phoneme. No cloud service, no per-character billing, and no audio leaves the building.",
      "state": "shipped",
      "since": "1.2026.5",
      "evidence": [
        "MultilarmTTS/",
        "Multilarm/MultilarmTTSBridge.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "tts-languages",
      "category": "announce",
      "name": "Neural voices in languages other than English",
      "summary": "Spoken announcements generated in other languages.",
      "state": "declined",
      "note": "The bundled voice model is en-US and en-GB only. We will not claim otherwise, because a mispronounced evacuation instruction is a safety problem rather than a cosmetic one. Non-English announcements are served by pre-recorded phrase packs instead, in a real human voice -- see phrase-packs, which is shipped.",
      "evidence": [],
      "verified": "2026-09-02"
    },
    {
      "id": "supervision",
      "category": "ops",
      "name": "Endpoint supervision, nightly self-test and PA Health Report",
      "summary": "A silent nightly self-test of the relays, rooms, devices, files and speech engine, and a weekly printable health report.",
      "state": "shipped",
      "note": "A relay board whose USB adapter has been unplugged still \"rings\" the bell: the schedule fires, the log records it, and no metal moves. Once a night Multilarm checks the output device, free space, every configured audio path, the speech engine, the relay board and every room following this device. Failures are raised as faults, which already travel out over e-mail, Telegram and webhooks. The test is silent -- the relay check re-asserts each channel to the state it is already in, a genuine end-to-end serial write that changes no contacts and never pulses the bell. An optional amplifier loopback plays a short tone and listens for it returning on a capture input wired from the speaker line; it is off by default and records \"not run\" rather than \"passed\" when it cannot run. Once a week the runs are rendered into a printable PA Health Report next to the config.",
      "since": "1.2026.902",
      "evidence": [
        "Multilarm/Supervision.vb",
        "Multilarm/MultilarmTests/SupervisionTests.vb"
      ],
      "verified": "2026-09-02"
    },
    {
      "id": "routines-timers",
      "category": "scheduling",
      "name": "Timers, break reminders and daily routines",
      "summary": "Countdown timers and recurring reminders in plain words, on top of the scheduler that was already there.",
      "state": "shipped",
      "note": "A timer is a one-shot countdown started by hand -- a class timer, an exam, a break -- that announces itself when it runs out. A routine recurs, either at a time of day or every so many minutes inside a window, with an optional day list; interval routines are anchored to the start of their window rather than to boot time, so a restart does not shift the day. Eight presets ship with it (break reminders, hourly stand-up, focus blocks, wake, lunch, end of day, wind-down, lights out). A running timer survives a reboot; one that elapsed while the machine was off is discarded rather than fired. Routines can only start audio and always queue at priority 3, so a reminder can never displace an announcement, an alarm or an emergency.",
      "since": "1.2026.902",
      "evidence": [
        "Multilarm/RoutineStore.vb",
        "Multilarm/MultilarmTests/RoutineStoreTests.vb"
      ],
      "verified": "2026-09-02"
    },
    {
      "id": "phrase-packs",
      "category": "announce",
      "name": "Pre-recorded phrase packs (.mlpack)",
      "summary": "A documented pack format so a site can hold announcements in any language, in a real human voice, without any claim about speech synthesis.",
      "state": "shipped",
      "note": "A .mlpack is a plain zip of recordings plus a manifest with an English gloss for each phrase, so an operator who does not read the language can still pick the right one. It can be handed over on a USB stick or passed between sites -- no account and no download service. A phrase can only start audio, never stop it, and plays at priority 1 or 3 only, exactly as speech does; /api/phrasepacks/play takes a list so an evacuation instruction can run in two or three languages as one announcement. Untrusted archives are validated entry by entry and staged before they are visible, and a pack missing any named recording is refused whole rather than installed in part.",
      "since": "1.2026.902",
      "evidence": [
        "Multilarm/PhrasePackStore.vb",
        "Multilarm/MultilarmTests/PhrasePackTests.vb",
        "ops/make-phrase-pack.ps1"
      ],
      "verified": "2026-09-02"
    },
    {
      "id": "pronunciation-editor",
      "category": "announce",
      "name": "In-app pronunciation editor",
      "summary": "Correct how the voice says a name or a place, from the console, without editing a dictionary file by hand.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "MultilarmTTS/",
        "Multilarm/Multilarm_Help.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "ptt-live-mic",
      "category": "announce",
      "name": "Live microphone paging (push to talk)",
      "summary": "Speak through the building's speakers from the console or the app, with sub-second latency, and have it obey the same priority and ducking rules as everything else.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/LivePassthrough.vb",
        "Multilarm/Intercom.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "talkback",
      "category": "announce",
      "name": "Talkback / call-in from a station",
      "summary": "A channel opened the other way — somebody at the device rings the operator, who can answer and hear the room. A call can only ever be started at the device (its /talkback page on the local network, or Ctrl+W at the console); there is no command that opens a microphone in a building from outside it. An audible chime plays on answer, the call ends itself after five minutes, and no audio is recorded or stored.",
      "state": "shipped",
      "since": "1.2026.831",
      "evidence": [
        "Multilarm/Talkback.vb",
        "Web/Talkback/MultilarmTalkback.html",
        "Web/Cloud/api/talkback-push.php"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "scheduled-recording",
      "category": "announce",
      "name": "Scheduled recording",
      "summary": "Record the room on a schedule, with automatic retention.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Multilarm/ScheduledRecorder.vb",
        "Multilarm/MaRecording.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "zones",
      "category": "rooms",
      "name": "Zones and zone-targeted announcements",
      "summary": "Named areas, driven by relay channels, so an announcement reaches the workshop and not the exam hall.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/RelayController.vb",
        "Multilarm/Program.vb#MAConfig.ZoneRules"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "zone-intercom",
      "category": "rooms",
      "name": "Zone-targeted intercom",
      "summary": "Live speech into one zone rather than the whole site.",
      "state": "partial",
      "note": "Built, and its refusal path and whole-site path are tested. Zone targeting itself is unverified — it needs an eight-channel relay board we do not yet own.",
      "evidence": [
        "Multilarm/Intercom.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "rooms-sync",
      "category": "rooms",
      "name": "Synchronised playback across separate machines",
      "summary": "Several hosts playing the same audio in phase, so a corridor and the room it opens onto do not echo.",
      "state": "shipped",
      "note": "One device is the sender; any number of others follow it while keeping their own schedule, emergency button and settings. Rooms are matched to each other to within a few milliseconds on an ordinary network - and `multilarm --rooms-probe` measures that on your network and prints the numbers, rather than asking you to take ours on trust. What is not corrected is the delay your own sound card and amplifier add after Multilarm hands the audio over.",
      "since": "1.2026.830",
      "evidence": [
        "Multilarm/RoomSync.vb",
        "Multilarm/ConfigSchema.vb#RoomsRole",
        "MultilarmAudio/src/multilarm_audio.c#ml_tap_read"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "rooms-receiver",
      "category": "rooms",
      "name": "Receiving audio from a phone or laptop (DLNA / UPnP AV)",
      "summary": "Casting to the building's speakers from a phone, tablet or PC, and still having a scheduled announcement duck it correctly.",
      "state": "shipped",
      "note": "DLNA / UPnP AV only. What a sender sends becomes the background channel, so ducking, priority, the volume profile and emergency override all apply unchanged - a phone can never make the building quieter than the schedule intends. Off out of the box: nothing is advertised and no port is opened until CastEnabled is set. DLNA has no authentication of any kind, which is a fact about the standard rather than a gap here, so CastAllowFrom limits who may send. AirPlay 2, Bluetooth A2DP and Spotify Connect are separate rows below and are deliberately not supported.",
      "since": "1.2026.831",
      "evidence": [
        "Multilarm/CastReceiver.vb",
        "Multilarm/ConfigSchema.vb#CastEnabled"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "rooms-airplay",
      "category": "rooms",
      "name": "AirPlay 2 receiver",
      "summary": "Appearing in an iPhone's or a Mac's AirPlay list.",
      "state": "declined",
      "note": "Every working open implementation is GPL-licensed, which a proprietary product cannot link to, and the official route requires an Apple MFi licence. Neither is available to us, so we do not claim it. Use DLNA from an app that speaks it, or the Web Remote.",
      "evidence": [],
      "verified": "2026-08-31"
    },
    {
      "id": "rooms-bluetooth",
      "category": "rooms",
      "name": "Bluetooth A2DP receiver",
      "summary": "Pairing a phone directly with Multilarm and playing to it over Bluetooth.",
      "state": "declined",
      "note": "The Linux Bluetooth stack this needs (BlueZ) is GPL-licensed. There is a route that works and needs nothing from us: pair the phone with the OPERATING SYSTEM of the machine running Multilarm and take the paired audio in on the sound card's line input, which Multilarm treats as an ordinary input.",
      "evidence": [],
      "verified": "2026-08-31"
    },
    {
      "id": "rooms-spotify",
      "category": "rooms",
      "name": "Spotify Connect",
      "summary": "Appearing as a device in the Spotify app.",
      "state": "declined",
      "note": "Requires a commercial Spotify SDK licence we do not hold. Spotify's own app can usually cast to a DLNA renderer through the operating system instead, which this device is.",
      "evidence": [],
      "verified": "2026-08-31"
    },
    {
      "id": "presence-routing",
      "category": "rooms",
      "name": "Presence-aware routing",
      "summary": "Announce only where someone is, using devices already seen on the network — no sensors, no cameras, no app on anyone's phone.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Multilarm/Presence.vb",
        "MultilarmNetmon/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "internet-radio",
      "category": "rooms",
      "name": "Internet radio and background music scheduling",
      "summary": "Background audio from a folder or a stream, on its own time ranges, ducked automatically under anything more important.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "Multilarm/VirtualRadio.vb",
        "Multilarm/MaIcyClient.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "listen-room-level",
      "category": "listening",
      "name": "Room level measurement",
      "summary": "Measures how loud the room is. Clips are measured and discarded immediately — no audio is kept or sent anywhere — and the whole feature is off until somebody turns it on.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Multilarm/Listener.vb",
        "Multilarm/Program.vb#MAConfig.ListenEnabled"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "adaptive-gain",
      "category": "listening",
      "name": "Adaptive announcement loudness",
      "summary": "Raise an announcement in a noisy room, lower it in a quiet one, so it is always intelligible and never startling.",
      "state": "partial",
      "note": "Built and configurable. Never soaked against a real microphone — the production device has no capture hardware.",
      "evidence": [
        "Multilarm/Listener.vb",
        "Multilarm/Program.vb#MAConfig.AdaptiveGainEnabled"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "smoke-alarm-detect",
      "category": "listening",
      "name": "Smoke alarm detection",
      "summary": "Recognises a sounding smoke alarm and can act on it. This is a convenience, not a fire detection system.",
      "state": "partial",
      "note": "Implemented without a downloaded model. Unverified against a real sounder.",
      "evidence": [
        "Multilarm/Listener.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "sound-classes",
      "category": "listening",
      "name": "Broader sound-event classes (glass, doorbell, shouting, machine stop)",
      "summary": "Recognising a wider set of sounds and reacting to them.",
      "state": "absent",
      "note": "Legally clear to build; held on a decision about whether to bundle a multi-megabyte model into every install.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "emergency-chain",
      "category": "emergency",
      "name": "Emergency chain with pre-emption",
      "summary": "A physical button or a network trigger pre-empts everything playing, suppresses the schedule, drives the emergency relay, and writes an audit entry.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/EmergencyInputMonitor.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "not-fire-alarm",
      "category": "emergency",
      "name": "Certified fire or voice alarm equipment",
      "summary": "Equipment approved to a fire detection or voice alarm standard.",
      "state": "declined",
      "note": "Multilarm is not fire-alarm equipment and is not certified to any fire-alarm standard. Its emergency voice alerts complement a certified system; they do not replace one. This row exists so the answer is unambiguous.",
      "evidence": [
        "https://multilarm.com/faq/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "cap-ingest",
      "category": "emergency",
      "name": "CAP / public alert feed ingestion",
      "summary": "Polls a Common Alerting Protocol feed and acts on matching alerts.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "Multilarm/CapPoller.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "notify-multichannel",
      "category": "emergency",
      "name": "Multi-channel person notification",
      "summary": "Email, mobile push, Telegram, Slack/Teams/Discord and SMS delivery alongside the audio.",
      "state": "partial",
      "note": "Email, push and Telegram are proven end to end. The Slack/Teams/Discord channel and the SMS path are built and deployed but have never carried a real message — both are bring-your-own-credentials and nobody has supplied any yet.",
      "evidence": [
        "Web/cloud/alerts-core.php",
        "Web/cloud/smtp.php",
        "AndroidFrontend/"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "incident-workflow",
      "category": "emergency",
      "name": "Incident templates, escalation and acknowledgement",
      "summary": "Pre-authored incidents, recipient groups, escalation when nobody acknowledges, and tracked confirmation.",
      "state": "absent",
      "note": "Planned. The delivery rails exist; the state machine does not.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "drill-mode",
      "category": "emergency",
      "name": "Drill mode",
      "summary": "Run the whole chain, log everything, and mark every delivery as a drill.",
      "state": "absent",
      "note": "Planned.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "after-action-report",
      "category": "emergency",
      "name": "After-action report",
      "summary": "Who was notified, when, on which channel, who acknowledged, and what played in which zone at what time.",
      "state": "absent",
      "note": "Planned. The audit ledger already holds the underlying record.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "desktop-alert",
      "category": "emergency",
      "name": "Desktop alert takeover",
      "summary": "A full-screen or banner alert on staff computers, acknowledged from the popup.",
      "state": "absent",
      "note": "Planned, extending the existing tray application.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "visual-display",
      "category": "emergency",
      "name": "Visual display companion",
      "summary": "A browser page on any screen showing the current notice, alert or countdown.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "Web/Display/MultilarmDisplay.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "signage-feed",
      "category": "integration",
      "name": "Digital signage feed",
      "summary": "Any signage player with an RSS or ticker widget — Xibo, Yodeck, ScreenCloud, Rise Vision, OptiSigns — can pull a device's emergency, notice and next event.",
      "state": "shipped",
      "note": "Reuses the display's existing public read-only token, so one Rotate link revokes the screen and the feed together. RSS, JSON and plain-text forms; every format is parser-checked by ops/test-signage-feed.py.",
      "since": "1.2026.831",
      "evidence": [
        "Web/cloud/api/signage.php",
        "ops/test-signage-feed.py"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "rest-api",
      "category": "integration",
      "name": "REST API",
      "summary": "Device routes for triggering, status, configuration and control, with tokens and rate limits.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/WebRemote.vb",
        "Multilarm/TriggerStore.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "mqtt",
      "category": "integration",
      "name": "MQTT bridge",
      "summary": "Subscribe to triggers and publish state on a broker.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "Multilarm/MqttBridge.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "webhooks",
      "category": "integration",
      "name": "Inbound and outbound webhooks",
      "summary": "Call in to fire something; call out when something happens.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/WebhookDispatcher.vb",
        "Multilarm/TriggerStore.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "cli",
      "category": "integration",
      "name": "Command-line interface",
      "summary": "Scriptable control of a local or remote device.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Multilarm/CommandLine.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "openapi",
      "category": "integration",
      "name": "OpenAPI description",
      "summary": "A machine-readable description of the device API.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Web/api/multilarm-openapi.json"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "home-assistant",
      "category": "integration",
      "name": "Home Assistant integration",
      "summary": "Multilarm as an entity in Home Assistant.",
      "state": "partial",
      "note": "Written and validated, but never run against a live Home Assistant host. Until it has been, we will not claim it works.",
      "evidence": [
        "Integrations/homeassistant"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "docker",
      "category": "integration",
      "name": "Docker image",
      "summary": "Running the device software in a container.",
      "state": "partial",
      "note": "Dockerfile written, never built and run.",
      "evidence": [
        "Integrations/docker"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "config-templates",
      "category": "ops",
      "name": "Multi-site config templates and group push",
      "summary": "Save a partial set of settings and push it to up to 64 sites at once; each site keeps everything the template does not name.",
      "state": "shipped",
      "note": "Identity fields and the cloud token are refused, passwords are encrypted at rest and never returned to the browser, and per-site settings are flagged before a push. Each device reports how many settings it applied.",
      "since": "1.2026.901",
      "evidence": [
        "Web/Cloud/api/config-templates.php",
        "Web/Cloud/config-templates-core.php",
        "ops/test-config-templates.py"
      ],
      "verified": "2026-09-01"
    },
    {
      "id": "node-red",
      "category": "integration",
      "name": "Node-RED nodes",
      "summary": "speak, scene and status nodes that drive a device from a Node-RED flow over its local Web Remote API.",
      "state": "shipped",
      "note": "Installed from disk, not published to npm. There is deliberately no stop/volume/mute node: a flow runs unattended and no automation should be able to silence a live building. A device reply of HTTP 200 carrying an error body is treated as a failure, not a delivery.",
      "since": "1.2026.831",
      "evidence": [
        "Integrations/node-red",
        "ops/test-node-red-nodes.mjs"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "sip-paging",
      "category": "integration",
      "name": "SIP / phone-system paging",
      "summary": "Dial an extension and speak through the building.",
      "state": "partial",
      "note": "Built and shipped as a separately licensed add-on. Never tested against a real PBX.",
      "evidence": [
        "MultilarmSip/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "android-app",
      "category": "integration",
      "name": "Mobile application",
      "summary": "Control, alerts and push-to-talk from a phone. A native Android app, plus an installable web app (PWA) that covers iPhone, iPad and desktop from the same console.",
      "state": "shipped",
      "note": "The installable web app deliberately does not work offline: a control screen showing remembered state is worse than one that says it cannot reach the cloud. Devices never depend on either — schedules run on the device.",
      "since": "1.2026.831",
      "evidence": [
        "AndroidFrontend/",
        "Web/Cloud/app/manifest.webmanifest",
        "Web/Cloud/sw.js"
      ],
      "verified": "2026-08-31"
    },
    {
      "id": "audit-ledger",
      "category": "ops",
      "name": "Audit ledger",
      "summary": "What played, what was triggered, by whom, and when.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/LogWriter.vb",
        "Web/cloud/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "self-diagnostics",
      "category": "ops",
      "name": "On-device diagnostics battery",
      "summary": "A numbered battery of checks the device runs on itself, readable from the console and bundled for support.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "Multilarm/Diagnostics.vb",
        "Multilarm/DiagBundle.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "network-monitor",
      "category": "ops",
      "name": "Network monitor",
      "summary": "Watches what is on the local network, with an offline vendor database — no lookup service, no data leaving the site.",
      "state": "shipped",
      "since": "1.2026.8",
      "evidence": [
        "MultilarmNetmon/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "endpoint-supervision",
      "category": "ops",
      "name": "Speaker and amplifier supervision",
      "summary": "Knowing that the amplifier is on and the speakers are working, without a person walking the corridor.",
      "state": "absent",
      "note": "Planned. This is the headline feature of every professional paging ecosystem and no software product offers it.",
      "evidence": [],
      "verified": "2026-08-30"
    },
    {
      "id": "signed-updates",
      "category": "ops",
      "name": "Signed, self-reverting updates",
      "summary": "Updates are signature-checked, and a bad build is reverted by a watchdog rather than needing a site visit.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "Multilarm/SelfUpdater.vb",
        "Multilarm/UpdateSignature.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "published-benchmarks",
      "category": "ops",
      "name": "Published, reproducible benchmarks",
      "summary": "Performance figures with the device, the method, the date and the harness that produced them, so anyone can re-run them.",
      "state": "partial",
      "note": "The harness ships at ops/bench/bench.mjs and Web/benchmarks.json publishes every figure with its device, method and date. Partial because no harness run has been committed yet: the published figures currently rest on their documented method, and the file says so.",
      "evidence": [
        "ops/bench/bench.mjs",
        "ops/build-benchmarks-json.mjs",
        "Web/benchmarks.json"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "offline-operation",
      "category": "platform",
      "name": "Works with no internet connection",
      "summary": "Scheduling, announcements, speech, zones, emergency and the local console all work with the network cable out. Internet is needed only for the optional cloud dashboard and cloud-side integrations.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "https://multilarm.com/faq/"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "no-inbound-ports",
      "category": "platform",
      "name": "Remote control without opening a firewall port",
      "summary": "The device polls outbound over HTTPS. Nothing inbound needs to be opened.",
      "state": "shipped",
      "since": "1.2026.6",
      "evidence": [
        "Multilarm/CloudRelay.vb"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "perpetual-licence",
      "category": "platform",
      "name": "One-off perpetual licence",
      "summary": "Institutional and commercial use is a one-off perpetual licence, not a subscription. Only the optional Cloud Relay is billed monthly. The software does not stop working if you stop paying.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "https://multilarm.com/pricing.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "free-personal-use",
      "category": "platform",
      "name": "Free for individual, non-commercial personal use",
      "summary": "A person running it at home does not pay and is not on a countdown.",
      "state": "shipped",
      "since": "1.0",
      "evidence": [
        "https://multilarm.com/pricing.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "cross-platform",
      "category": "platform",
      "name": "Windows, Raspberry Pi and 32/64-bit ARM Linux",
      "summary": "Including 32-bit and 64-bit ARM, so a forty-pound Raspberry Pi is a supported deployment and not a hobby port. No x86-64 Linux or macOS build is published.",
      "state": "partial",
      "note": "Published builds are win-x64, linux-arm64 and linux-arm, which is what /multilarm/latest.json actually serves. There is deliberately no x86-64 Linux build and no macOS build: the macOS native audio library has never been compiled because we do not own a Mac, and desktop-Linux x64 has not been asked for. Nothing published claims either until one is built, tested and benchmarked.",
      "evidence": [
        "https://multilarm.com/downloads.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "apt-repo",
      "category": "platform",
      "name": "Signed apt repository and one-command install",
      "summary": "A signed Debian/Raspberry Pi OS repository, so Multilarm installs and updates with ordinary apt commands -- or with a single line.",
      "state": "shipped",
      "note": "curl -fsSL https://multilarm.com/install.sh | sh adds the signing key, adds the repository and installs the package; after that upgrades ride the machine's ordinary apt upgrade. Packages are signed and built for arm64 and armhf. The installer prints every command before running it, never executes anything it downloads, and refuses with a plain reason on an architecture we do not build rather than adding a repository that will never hold a package. Settings live in ~/.local/share/multilarm and survive removal. Multilarm is not part of Debian or Raspberry Pi OS -- the repository is operated by us.",
      "since": "1.2026.815",
      "evidence": [
        "https://multilarm.com/install.sh",
        "Web/install.sh",
        "ops/test-install-script.mjs"
      ],
      "verified": "2026-09-02"
    },
    {
      "id": "install-shapes",
      "category": "platform",
      "name": "Four install shapes",
      "summary": "Windows installer and service, Debian package, appliance install, and a ready-made Raspberry Pi card image.",
      "state": "shipped",
      "since": "1.2026.7",
      "evidence": [
        "https://multilarm.com/downloads.html"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "open-source",
      "category": "platform",
      "name": "Open source",
      "summary": "The source code of the product published under an open licence.",
      "state": "declined",
      "note": "Multilarm is proprietary software. Third-party components are listed with their licences in THIRD_PARTY_NOTICES.txt, and there is no GPL component anywhere in the audio chain.",
      "evidence": [
        "THIRD_PARTY_NOTICES.txt"
      ],
      "verified": "2026-08-30"
    },
    {
      "id": "media-library",
      "category": "music",
      "name": "Music library with tag reading and audio analysis",
      "summary": "Indexes one or more folders, reading title, artist, album, genre, year and BPM from ID3v1, ID3v2, FLAC, Ogg, MP4 and WAV tags, alongside duration, sample rate, channels and bitrate. Embedded cover art is located during the scan (offset, length and type) and served on demand from the audio file itself, so nothing is extracted or cached. The customer's files are never modified — corrections are stored as overrides in our own index.",
      "state": "shipped",
      "note": "Art is deliberately not decoded during a scan: a five-thousand file scan that pulled every JPEG out would spend most of its time on pictures nobody asked to see, and a thumbnail cache is one more thing to go stale when a customer re-tags. ID3v2 (APIC and v2.2 PIC), FLAC METADATA_BLOCK_PICTURE and MP4 covr are served; Ogg Vorbis art is detected but not served because it is base64 inside a comment. A declared MIME type is only a fallback — the first bytes of the image win, because tags lie.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/MediaLibrary.vb#ReadArt",
        "Multilarm/MediaTags.vb",
        "Multilarm/WebRemote.vb#/api/library/art"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "media-library-reports",
      "category": "music",
      "name": "Library health reports: unreadable, missing metadata, clipped, duplicated",
      "summary": "The four questions an operator actually asks about a folder of audio, answered from the index rather than by listening to it. WAV files are measured for peak, RMS, crest factor, clipping and leading or trailing silence.",
      "state": "shipped",
      "since": "1.2026.913",
      "evidence": [
        "Multilarm/MediaLibrary.vb#ReportsJson",
        "Multilarm/WebRemote.vb#/api/library/reports"
      ],
      "verified": "2026-09-13"
    },
    {
      "id": "playlists-smart",
      "category": "music",
      "name": "Static and smart playlists, nested and freezable",
      "summary": "A static playlist is a running order; a smart playlist is a rule set resolved live across thirteen fields with ten operators. Playlists nest six deep, a save that would make one contain itself is refused, entries can be marked play-once so they are spent after they go out, a list can be capped by count or by duration, and a smart playlist can be frozen so a rehearsed running order cannot change underneath the people rehearsing it.",
      "state": "shipped",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/PlaylistStore.vb",
        "Multilarm/WebRemote.vb#/api/playlists"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "rotation-engine",
      "category": "music",
      "name": "Weighted rotation with separation rules and a dead-air guard",
      "summary": "Chooses the next track across several playlists by weight, honouring no-same-track, no-same-artist, no-same-album and no-repeat-within-N-tracks windows, with interrupting playlists on a count or a clock for the jingle or the hourly reminder, and an optional safety folder read directly off disk for the case where the library index itself has gone.",
      "state": "shipped",
      "note": "The dead-air guard outranks every other rule: if the separation windows leave nothing to play, the engine relaxes them in a fixed order — album window first as the weakest, then artist, then track, then all repeat rules, least-recently-played first — and logs every relaxation. Below that sits the safety folder, which does not use the library index at all, because a wiped index is the failure it exists for. Silence on a PA is a fault; a repeated artist is not.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/RotationEngine.vb",
        "Multilarm/ManagedPlayer.vb#BgNextProvider",
        "Multilarm/VirtualRadio.vb"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "now-playing",
      "category": "music",
      "name": "Now playing, with a public on-air page",
      "summary": "One announcement point feeds the play log, the outbound webhooks, the operator dashboard and a self-contained public page at /onair, so no two surfaces can disagree about what is on air. The public feed carries metadata only — never a file path, folder or machine name.",
      "state": "shipped",
      "since": "1.2026.913",
      "evidence": [
        "Multilarm/NowPlaying.vb",
        "Multilarm/WebRemote.vb#/api/nowplaying"
      ],
      "verified": "2026-09-13"
    },
    {
      "id": "play-log",
      "category": "music",
      "name": "Play log with reporting-return exports",
      "summary": "Everything that goes out is written to a monthly CSV, exportable as plain columns or in the shape PRS, PPL and SoundExchange returns ask for. Announcements are excluded from the music totals and unknown fields are left blank rather than invented.",
      "state": "shipped",
      "note": "It produces the paperwork and nothing more. Multilarm offers no view on whether a music licence is needed, which one, or what it costs — that is between the customer and the licensing society.",
      "since": "1.2026.913",
      "evidence": [
        "Multilarm/PlayLog.vb",
        "Multilarm/WebRemote.vb#/api/playlog"
      ],
      "verified": "2026-09-13"
    },
    {
      "id": "podcast-ingest",
      "category": "music",
      "name": "Podcast ingest into the library",
      "summary": "Subscribe to an RSS feed and new episodes are downloaded and indexed automatically, so a daily bulletin or a weekly talk reaches a playlist without anybody copying files. Nothing downloaded plays by itself.",
      "state": "shipped",
      "since": "1.2026.913",
      "evidence": [
        "Multilarm/PodcastFeeds.vb",
        "Multilarm/WebRemote.vb#/api/podcasts"
      ],
      "verified": "2026-09-13"
    },
    {
      "id": "podcast-publish",
      "category": "music",
      "name": "Publishing recordings as a podcast feed",
      "summary": "Scheduled recordings are offered at /podcast.xml as an RSS 2.0 feed with iTunes tags, so an assembly or a talk can be handed to whoever missed it in any podcast app.",
      "state": "shipped",
      "note": "The generator parses its own output before serving it and refuses to publish a document that does not load, because we have served invalid generated XML for months before without noticing.",
      "since": "1.2026.913",
      "evidence": [
        "Multilarm/PodcastFeeds.vb#BuildFeedXml",
        "Multilarm/ScheduledRecorder.vb"
      ],
      "verified": "2026-09-13"
    },
    {
      "id": "lan-listen",
      "category": "music",
      "name": "LAN listen endpoint",
      "summary": "An optional endpoint so somebody on the same network can hear what the PA is playing in a browser with nothing to install: 16-bit PCM in an open-ended WAV stream on three bandwidth tiers, up to 32 listeners, with a listen key checked in constant time.",
      "state": "shipped",
      "note": "This is a LAN convenience, not internet streaming. At roughly 1.5 Mbit/s per listener on the full mount (1.54 measured on the reference Pi at 48 kHz) it is LAN quality at LAN scale; a mono mount halves that and a low-bandwidth mount quarters it, all from one read of the master tap, and a mount nobody is on is never encoded. It refuses to bind the network without a listen key, and refuses to start at all while multi-room sync is running, because both need the audio engine's single master tap.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/BroadcastServer.vb",
        "Multilarm/BroadcastServer.vb#EncodeLow",
        "Multilarm/WebRemote.vb#/api/broadcast/status"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "listen-fallback",
      "category": "music",
      "name": "Silence fallback for listeners",
      "summary": "A short WAV looped to listeners while the programme is quiet, so a gap sounds like a hold loop rather than a dead line.",
      "state": "shipped",
      "note": "On the wire only: the building itself stays silent, because the engine decodes a file by playing it and playing this one would put it through the speakers. That is also why it is 16-bit WAV only and has to match the engine's sample rate and channel count exactly - a mismatch is refused with a note in the status rather than resampled into something that sounds wrong. Capped at 60 seconds, and every gap restarts the loop from the top rather than resuming mid-word.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/BroadcastServer.vb#LoadFallback",
        "Multilarm/BroadcastServer.vb#FillFromFallback"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "listener-stats",
      "category": "music",
      "name": "Listener analytics",
      "summary": "Connections, peak, listening time and a unique-today figure, bucketed by hour and by day, with the last fifty sessions listed.",
      "state": "shipped",
      "note": "Counts, not identities. No address is ever written to disk and nothing is sent off the device; the play log is deliberately not joined to it. The unique-today figure uses a salted SHA-256 fingerprint held in memory for the current day only - enough to stop one listener counting five times. The salt sits beside the data and IPv4 is a small space, so this is not anonymisation and does not claim to be. A session is credited to the hour it started in, so a long listen is not smeared across the chart, and empty hours are filled with zeroes because a chart with holes in it reads as missing data.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/ListenStats.vb",
        "Multilarm/WebRemote.vb#/api/broadcast/stats"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "listen-embeds",
      "category": "music",
      "name": "Embeddable player and schedule",
      "summary": "A standalone player page and a weekly schedule grid, both sized to drop into an iframe on a site of your own, with an optional stylesheet.",
      "state": "shipped",
      "note": "The schedule is derived from the music slots that actually drive the rotation, so it cannot drift from what plays - this endpoint owns no schedule of its own. Neither page can start, stop or set a volume. The stylesheet is served without a listen key because an iframed player cannot send one on a link element; an unreadable or oversized file yields an empty sheet rather than a broken page.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/BroadcastServer.vb#PlayerPage",
        "Multilarm/BroadcastServer.vb#ScheduleJson"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "listen-again",
      "category": "music",
      "name": "Listen again (on-demand playback)",
      "summary": "Finished recordings and library tracks played back in a browser, served from where the files already sit, with seeking.",
      "state": "shipped",
      "note": "A single byte range is honoured and answered with a 206 and a Content-Range, which is what a browser needs in order to seek; the multi-range form is not served, and a malformed or unsatisfiable range gets the whole file rather than an error, because a player that cannot seek is a far smaller problem than one that will not play. A requested recording is matched against the folder listing rather than joined onto a path, so a caller cannot walk out of the folder. Nothing is copied, transcoded or cached.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/WebRemote.vb#SendFileRanged",
        "Multilarm/WebRemote.vb#/api/recordings"
      ],
      "verified": "2026-09-14"
    },
    {
      "id": "icecast-source",
      "category": "music",
      "name": "Icecast or SHOUTcast source output",
      "summary": "Encoding the programme output to a lossy codec and pushing it to an Icecast or SHOUTcast server for public internet listeners.",
      "state": "absent",
      "note": "The in-house audio engine encodes WAV and FLAC only, so there is no Opus, Vorbis, MP3 or AAC encoder to feed a streaming server with. Adding one across five build targets is a licensing and maintenance decision that has deliberately not been taken. If you are running an internet radio station, LibreTime or AzuraCast is the right answer and this is not.",
      "evidence": [],
      "verified": "2026-09-13"
    },
    {
      "id": "web-dj",
      "category": "music",
      "name": "Live DJ ingest from a remote client",
      "summary": "Remote presenters connecting with Icecast or SHOUTcast source clients, or from a browser, and taking over the output for a live show.",
      "state": "absent",
      "note": "Out of scope for now. Multilarm has live microphone passthrough for somebody standing at the machine, and remote playback control over its API, but it does not accept a live audio feed from a remote DJ. That is a radio-station requirement rather than a building-audio one.",
      "evidence": [],
      "verified": "2026-09-13"
    },
    {
      "id": "music-slots",
      "category": "music",
      "name": "Music slots (dayparting) with autoload, idents and linked repeats",
      "summary": "Between these clock times, on these days, the rotation draws from these playlists — then hands back. Slots wrap midnight, resolve ahead of air, can open and close with their own ident playlist, can share one running order under a link key so a repeat genuinely repeats, and can refuse to start a track that would run past the slot's end.",
      "state": "shipped",
      "note": "A slot chooses and nothing more: it does not play, stop or set a volume, and no path through it silences a building. A misconfigured slot, an empty playlist or a deleted one yields the ordinary rotation. Overrun never outranks the dead-air guard — if nothing is short enough to finish in time, the slot runs long.",
      "since": "1.2026.914",
      "evidence": [
        "Multilarm/MusicSlots.vb",
        "Multilarm/RotationEngine.vb#NextTrack",
        "Multilarm/WebRemote.vb#/api/musicslots"
      ],
      "verified": "2026-09-14"
    }
  ]
}
