{"id":50759752,"url":"https://github.com/2bbb/bbb.dmx","last_synced_at":"2026-06-11T09:00:17.163Z","repository":{"id":363825989,"uuid":"1265103714","full_name":"2bbb/bbb.dmx","owner":"2bbb","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-10T13:56:04.000Z","size":23,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T15:13:27.529Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/2bbb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-10T13:19:24.000Z","updated_at":"2026-06-10T13:55:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/2bbb/bbb.dmx","commit_stats":null,"previous_names":["2bbb/bbb.dmx"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/2bbb/bbb.dmx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2bbb%2Fbbb.dmx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2bbb%2Fbbb.dmx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2bbb%2Fbbb.dmx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2bbb%2Fbbb.dmx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/2bbb","download_url":"https://codeload.github.com/2bbb/bbb.dmx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2bbb%2Fbbb.dmx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34190585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-11T09:00:15.128Z","updated_at":"2026-06-11T09:00:17.151Z","avatar_url":"https://github.com/2bbb.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bbb.dmx\n\nDMX utility external object suite for Max/MSP.\n\nThis repository deliberately does **not** implement DMX network output. Art-Net output belongs in `bbb.artnet`. `bbb.dmx.*` objects produce, inspect, transform, and guard DMX frame data inside Max.\n\n## Current objects\n\n- `bbb.dmx.movertrack` — converts a 3D target position into 16-bit DMX pan/tilt bytes for a moving light.\n- `bbb.dmx.fixturemap` — maps fixture parameters into a selected 512-channel DMX universe list.\n- `bbb.dmx.monitor` — stores and reports multi-universe DMX frames and changed channels.\n- `bbb.dmx.merge` — merges named layers across multiple universes with `priority`, `htp`, or `ltp` modes.\n- `bbb.dmx.fade` — fades incoming multi-universe frames over time.\n- `bbb.dmx.generator` — generates test frames such as blackout, full, range, ramp, and chase.\n- `bbb.dmx.safety` — clamps, slews, freezes, blackouts, and deadman-protects multi-universe streams.\n- `bbb.dmx.record` — records and plays back multi-universe frame snapshots.\n- `bbb.dmx.patchcheck` — validates fixture patch/profile JSON.\n- `bbb.dmx.fixtureinfo` — inspects fixture patch/profile metadata.\n\n## Build\n\n```sh\ngit submodule update --init --recursive\ncmake -B build -DBBB_DMX_BUILD_EXTERNALS=ON\ncmake --build build --config Release\nctest --test-dir build --output-on-failure\n```\n\nmacOS builds universal `.mxo` externals (`x86_64` + `arm64`). Windows builds `.mxe64` via Visual Studio 2022.\n\n## Multi-universe convention\n\nMost utility objects use this message shape:\n\n```max\nuniverse \u003cid\u003e \u003c512 byte values\u003e\n```\n\n- `id` is 1-based and sanitized to at least `1`.\n- DMX addresses are 1-based: `1..512`.\n- Byte values are clamped to `0..255`.\n- Bare `list` input, where supported, means the object's default `@universe`.\n- Multi-universe objects output the same `universe \u003cid\u003e \u003c512 byte values\u003e` format, making them chainable before a sender such as `bbb.artnet`.\n\nThis is not cosmetic. A 512-value bare list is ambiguous once a show uses more than one universe. Use explicit `universe` messages at object boundaries unless you are intentionally staying in a one-universe patch.\n\n## `bbb.dmx.movertrack`\n\n```max\n[bbb.dmx.movertrack 0. 0. 3. @pan_range 540. @tilt_range 270. @rot 0. 0. 0.]\n```\n\nInput a target position list:\n\n```max\n0. 5. 1.5\n```\n\nOutput:\n\n```text\npan_byte_1 pan_byte_2 tilt_byte_1 tilt_byte_2\n```\n\nDefault byte order is `coarsefine`.\n\nTracking modes:\n\n```max\ntracking_mode smart\ntracking_mode pan\ntracking_mode off\n```\n\n- `smart` is the default. It tries direct and pan+180/tilt-flipped candidates, rejects candidates outside the configured pan/tilt ranges, then chooses the smallest move from the previous output.\n- `pan` keeps the legacy pan-only shortest-path behavior. It can still choose an out-of-range equivalent pan and then clip, so use it only if you explicitly want that old behavior.\n- `off` disables tracking and outputs the direct pan/tilt solution. This avoids history-induced wrong turns, but it can spin through atan2 wrap points.\n\nCoordinate convention:\n\n```text\n+X = stage right / local right\n+Y = forward / pan center\n+Z = up\n```\n\nFor a ceiling-hung fixture mounted upside down with pan center facing the `y = 0` side of the room/stage, use:\n\n```max\n[bbb.dmx.movertrack 0. 3.84 2.55 @rot 180. 0. 0. @tilt_invert 0 @tilt_offset -90.]\n```\n\nThis separates the two corrections:\n\n- `@rot 180. 0. 0.` describes the physical upside-down orientation.\n- `@tilt_offset -90.` calibrates the fixture-specific tilt horizontal point.\n- Keep `@tilt_invert 0` for this installation; `@rot` already accounts for the world up/down reversal.\n\nYou can also derive offsets from a known target and a DMX value instead of hand-tuning degrees:\n\n```max\ncalibrate_pan 0. 0. 2.55 32768\ncalibrate_tilt 0. 0. 2.55 10923\n```\n\nWith two byte arguments, the current `byte_order` is used:\n\n```max\ncalibrate_tilt 0. 0. 2.55 42 171\n```\n\n### `bbb.dmx.movertrack` API quick reference\n\nConstructor arguments:\n\n```max\n[bbb.dmx.movertrack fixture_x fixture_y fixture_z]\n```\n\nAttributes:\n\n- `@fixture_x`, `@fixture_y`, `@fixture_z` — fixture world position.\n- `@pan_range`, `@tilt_range` — addressable movement ranges in degrees. Values map to `-range/2 ... +range/2`.\n- `@rot rx ry rz` — fixture-local to world rotation in degrees. Rotation order is `Rz * Ry * Rx`.\n- `@pan_offset`, `@tilt_offset` — degree offsets applied after raw angle calculation and before inversion.\n- `@pan_invert`, `@tilt_invert` — invert the calibrated pan/tilt angle.\n- `@byte_order coarsefine|finecoarse` — byte order inside each 16-bit pan/tilt value.\n- `@tracking_mode smart|pan|off` — current tracking solver. Default is `smart`.\n- `@shortest_pan 1|0` — compatibility alias: `1` selects `tracking_mode smart`, `0` selects `tracking_mode off`. Do not read it as the old pan-only solver.\n\nMessages:\n\n```max\ntarget x y z      // same as a plain x y z list\npos x y z         // update fixture position\nrange pan tilt    // update ranges\ncalibrate_pan target_x target_y target_z pan_u16\ncalibrate_tilt target_x target_y target_z tilt_u16\ncalibrate_tilt target_x target_y target_z tilt_byte_1 tilt_byte_2\nreset             // clears tracking history only\nbang              // recomputes the last target, or outputs neutral center before any target\n```\n\n`reset` does not reset attributes or fixture position. It only clears the previous pan/tilt tracking state.\n\n## `bbb.dmx.fixturemap`\n\n```max\n[bbb.dmx.fixturemap @patch patches/example.json @universe 1 @autobang 1]\n```\n\nThe left outlet outputs a 512-integer list for the selected universe: DMX channel 1 first, channel 512 last. The right outlet outputs status/error messages such as load failures and `dump` status. Fixture profiles live in `fixtures/`; show patch files live in `patches/`. Profile paths inside patch JSON are resolved relative to the patch file.\n\n`fixturemap` can load patches containing multiple universes, but each object instance outputs one selected universe. If you need a fully explicit multi-universe stream, run one `fixturemap` per universe or pass its output through `prepend universe \u003cid\u003e` before the rest of the chain.\n\nAttributes:\n\n- `@patch` — patch JSON path. Loaded on object initialization.\n- `@universe` — selected universe, starting at `1`.\n- `@autobang` — if non-zero, successful updates immediately output the full 512-channel universe. Default is `1`.\n\nLoad / inspect messages:\n\n```max\nread patches/example.json\nreload\ndump\nclear\nreset\nbang\n```\n\nParameter messages:\n\n```max\nset spot_01 dimmer 255\nset spot_01 pan 32768\nset spot_01 pan_tilt 32768 32768\nnset spot_01 dimmer 1.0\nptbytes spot_01 127 255 127 255\n```\n\nRaw channel messages for testing or emergency overrides:\n\n```max\nchannel 512 255\nchannels 1 255 2 128 3 0\n```\n\nMovertrack integration is intentionally byte-tuple based for now:\n\n```max\n[bbb.dmx.movertrack ...]\n|\n[prepend ptbytes spot_01]\n|\n[bbb.dmx.fixturemap @patch patches/example.json]\n```\n\n`ptbytes` accepts `pan_byte_1 pan_byte_2 tilt_byte_1 tilt_byte_2` and converts them through the target fixture profile's pan/tilt byte-order metadata.\n\n## Frame utilities\n\n### `bbb.dmx.monitor`\n\n```max\n[bbb.dmx.monitor @universe 1 @changed_only 0]\n```\n\nInput:\n\n```max\nuniverse 1 \u003c512 values\u003e\nchannel 1 42 255\nchannels 1 1 255 2 128\nbang\nbangall\ndump\nclear\n```\n\nOutput is either full `universe \u003cid\u003e ...` frames or `changed \u003cid\u003e address value ...` when `@changed_only 1`.\n\n### `bbb.dmx.merge`\n\n```max\n[bbb.dmx.merge @mode priority]\n```\n\nMessages:\n\n```max\nuniverse layer_a 1 \u003c512 values\u003e\nlayer layer_b 2 \u003c512 values\u003e\npriority layer_a 10\nchannel layer_a 1 42 255\nchannels layer_b 2 1 255 2 128\nclear layer_a\nclear all\nbangall\n```\n\nModes:\n\n- `priority` — highest layer priority wins per channel.\n- `htp` — highest byte value wins per channel.\n- `ltp` — most recently updated layer wins per channel.\n\n### `bbb.dmx.fade`\n\n```max\n[bbb.dmx.fade @time_ms 1000 @fps 30]\n```\n\nMessages:\n\n```max\nuniverse 1 1000 \u003c512 target values\u003e\nchannel 1 42 255 500\nchannels 1 750 1 255 2 128\nstop\nclear\nbangall\n```\n\nThe object outputs interpolated `universe \u003cid\u003e ...` frames from a main-thread timer.\n\n### `bbb.dmx.record`\n\n```max\n[bbb.dmx.record @fps 30 @loop 0]\n```\n\nMessages:\n\n```max\nrecord 1\nuniverse 1 \u003c512 values\u003e\nuniverse 2 \u003c512 values\u003e\nrecord 0\nplay 1\nplay 0\nframe 0\nbangall\nwrite /tmp/show.dmxrec\nread /tmp/show.dmxrec\nclear\n```\n\nRecording stores snapshots of the full known frame set, so multiple universes are preserved per recorded frame.\n\n## Test, safety, and inspection utilities\n\n### `bbb.dmx.generator`\n\n```max\n[bbb.dmx.generator]\n```\n\nMessages:\n\n```max\nblackout 1\nfull 2\nall 1 64\nrange 1 1 16 255\nramp 1 1 512 0 255\nchase 1 42 255\n```\n\n### `bbb.dmx.safety`\n\n```max\n[bbb.dmx.safety @max_value 255 @max_delta 255 @timeout_ms 0 @blackout 0 @freeze 0]\n```\n\nMessages:\n\n```max\nuniverse 1 \u003c512 values\u003e\nchannel 1 42 255\nchannels 1 1 255 2 128\nblackout 1\nfreeze 1\nbangall\n```\n\n- `@max_value` clamps every output byte.\n- `@max_delta` limits per-update channel jumps.\n- `@timeout_ms` blackouts if no input arrives within the configured interval.\n- `@blackout` forces zero output.\n- `@freeze` holds the last safe frame.\n\n### `bbb.dmx.patchcheck`\n\n```max\n[bbb.dmx.patchcheck @patch patches/example.json]\n```\n\nMessages:\n\n```max\nread patches/example.json\nbang\n```\n\nOutput:\n\n```text\nok fixtures \u003ccount\u003e universes \u003cid...\u003e\nerror \u003cmessage\u003e\n```\n\n### `bbb.dmx.fixtureinfo`\n\n```max\n[bbb.dmx.fixtureinfo @patch patches/example.json]\n```\n\nMessages:\n\n```max\nbang\nlistfixtures\nfixture spot_01\nlistparams spot_01\nparam spot_01 pan\ndump\n```\n\nTypical output selectors are `summary`, `fixture`, `param`, and `error`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2bbb%2Fbbb.dmx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F2bbb%2Fbbb.dmx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2bbb%2Fbbb.dmx/lists"}