Watch all six MIDI links from the checklist, including ProPresenter -> Lighting
Adds Tracks -> Livebox, Tracks -> Studio B and ProPresenter -> Lighting
(read on the ProPresenter Mac over SSH), each with the hold-to-reconnect fix.
coremidi.py now recognises the network MIDI driver on macOS 26.2 too
("AppleMIDIRTPDriver"; 26.5 calls it "AppleMIDINetworkDriver"), which is why
the ProPresenter Mac's session looked empty before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ There are two parts:
|
|||||||
|--------|-------|-------|-------|-------|-------|-------|-------|-------|
|
|--------|-------|-------|-------|-------|-------|-------|-------|-------|
|
||||||
| row 0 | UP | **ALL** | REC | AUDIO | RF | SHOW | VIDEO | STREAM |
|
| row 0 | UP | **ALL** | REC | AUDIO | RF | SHOW | VIDEO | STREAM |
|
||||||
| row 1 | page | HD1 | HD2 | HD3 | HD4 | HD5 | HD6 | TASCAM |
|
| row 1 | page | HD1 | HD2 | HD3 | HD4 | HD5 | HD6 | TASCAM |
|
||||||
| row 2 | DOWN | X32 | DANTE | MIDI | APPS | | | |
|
| row 2 | DOWN | X32 | DANTE | MIDI | All six network MIDI links on the FOH / Studio B checklist, each named when it drops: **LIGHT PC** (Ingest → Lighting), **SUPERRACK** (Ingest → Livebox), **FX LIGHTS** (Tracks → Lighting, the FX lighting cues), **TRK LIVEBOX** (Tracks → Livebox), **TRK STUDIO B** (Tracks → Studio B), **PROP LIGHTS** (ProPresenter → Lighting). NA Ingest's own session is read locally; NA Tracks' and NA ProP's over SSH every 5 s. Also: the Lighting PC and NA Tracks MIDI sessions still announced on the network. A clean disconnect shows within about 10 s; a crash or pulled cable takes about a minute (macOS's own timeout, measured). Hold 2 s reconnects whichever links dropped. |
|
||||||
| row 3 | ALARMS | ATEM | OBS | RESI | NET | INGEST | LED | ARENA |
|
| row 3 | ALARMS | ATEM | OBS | RESI | NET | INGEST | LED | ARENA |
|
||||||
|
|
||||||
Row 0 holds the group buttons. Each one shows the first problem in its group, plus "+N" if there are more:
|
Row 0 holds the group buttons. Each one shows the first problem in its group, plus "+N" if there are more:
|
||||||
|
|||||||
@@ -100,6 +100,9 @@
|
|||||||
{ "type": "midi_peer", "session": "NA Ingest", "peer": "Lighting PC", "tag": "LIGHT PC", "fix": "midi_reconnect", "address": "10.1.10.206:5004" },
|
{ "type": "midi_peer", "session": "NA Ingest", "peer": "Lighting PC", "tag": "LIGHT PC", "fix": "midi_reconnect", "address": "10.1.10.206:5004" },
|
||||||
{ "type": "midi_peer", "session": "NA Ingest", "peer": "SuperRack-LiveBox", "tag": "SUPERRACK", "fix": "midi_reconnect", "address": "10.1.13.140:5004" },
|
{ "type": "midi_peer", "session": "NA Ingest", "peer": "SuperRack-LiveBox", "tag": "SUPERRACK", "fix": "midi_reconnect", "address": "10.1.13.140:5004" },
|
||||||
{ "type": "midi_peer", "host": "tracks", "session": "NA Tracks", "peer": "Lighting PC", "tag": "FX LIGHTS", "fix": "midi_reconnect", "address": "10.1.10.206:5004" },
|
{ "type": "midi_peer", "host": "tracks", "session": "NA Tracks", "peer": "Lighting PC", "tag": "FX LIGHTS", "fix": "midi_reconnect", "address": "10.1.10.206:5004" },
|
||||||
|
{ "type": "midi_peer", "host": "tracks", "session": "NA Tracks", "peer": "SuperRack-LiveBox", "tag": "TRK LIVEBOX", "fix": "midi_reconnect", "address": "10.1.13.140:5004" },
|
||||||
|
{ "type": "midi_peer", "host": "tracks", "session": "NA Tracks", "peer": "Studio B Mac Studio", "tag": "TRK STUDIO B", "fix": "midi_reconnect", "address": "\tStudio B Mac Studio\tlocal." },
|
||||||
|
{ "type": "midi_peer", "host": "prop", "session": "NA ProP", "peer": "Lighting PC", "tag": "PROP LIGHTS", "fix": "midi_reconnect", "address": "10.1.10.206:5004" },
|
||||||
{ "type": "mdns", "instance": "Lighting PC", "name": "LIGHT PC MIDI" },
|
{ "type": "mdns", "instance": "Lighting PC", "name": "LIGHT PC MIDI" },
|
||||||
{ "type": "mdns", "instance": "NA Tracks", "name": "TRACKS MIDI" },
|
{ "type": "mdns", "instance": "NA Tracks", "name": "TRACKS MIDI" },
|
||||||
{ "type": "midi_rx", "source": "Network NA Ingest Listen", "name": "TRACKS IN" }
|
{ "type": "midi_rx", "source": "Network NA Ingest Listen", "name": "TRACKS IN" }
|
||||||
|
|||||||
+14
-18
@@ -54,6 +54,10 @@ _cf.CFPropertyListCreateWithData.restype = ctypes.c_void_p
|
|||||||
_cf.CFPropertyListCreateWithData.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong,
|
_cf.CFPropertyListCreateWithData.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong,
|
||||||
ctypes.c_void_p, ctypes.c_void_p]
|
ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
# Apple's network MIDI driver: "AppleMIDIRTPDriver" on macOS 26.2 (ProPresenter Mac),
|
||||||
|
# "AppleMIDINetworkDriver" on 26.5 (NA Ingest, NA Tracks).
|
||||||
|
NETWORK_DRIVERS = ("com.apple.AppleMIDINetworkDriver", "com.apple.AppleMIDIRTPDriver")
|
||||||
|
|
||||||
# One lock around every CoreMIDI call: concurrent first calls from two threads
|
# One lock around every CoreMIDI call: concurrent first calls from two threads
|
||||||
# (session reads vs. MIDIClientCreate) intermittently fail with -2.
|
# (session reads vs. MIDIClientCreate) intermittently fail with -2.
|
||||||
_lock = threading.RLock()
|
_lock = threading.RLock()
|
||||||
@@ -109,7 +113,7 @@ def _session_entities():
|
|||||||
out = []
|
out = []
|
||||||
for i in range(_cm.MIDIGetNumberOfDevices()):
|
for i in range(_cm.MIDIGetNumberOfDevices()):
|
||||||
dev = _cm.MIDIGetDevice(i)
|
dev = _cm.MIDIGetDevice(i)
|
||||||
if _string_prop(dev, "driver") != "com.apple.AppleMIDINetworkDriver":
|
if _string_prop(dev, "driver") not in NETWORK_DRIVERS:
|
||||||
continue
|
continue
|
||||||
for j in range(_cm.MIDIDeviceGetNumberOfEntities(dev)):
|
for j in range(_cm.MIDIDeviceGetNumberOfEntities(dev)):
|
||||||
ent = _cm.MIDIDeviceGetEntity(dev, j)
|
ent = _cm.MIDIDeviceGetEntity(dev, j)
|
||||||
@@ -151,23 +155,15 @@ def sessions():
|
|||||||
"""{session name: {"port": int, "peers": [{"name", "address", "state"}]}}"""
|
"""{session name: {"port": int, "peers": [{"name", "address", "state"}]}}"""
|
||||||
found = {}
|
found = {}
|
||||||
with _lock:
|
with _lock:
|
||||||
for i in range(_cm.MIDIGetNumberOfDevices()):
|
for ent, sess in _session_entities():
|
||||||
dev = _cm.MIDIGetDevice(i)
|
name = sess.get("dns-sd-name") or _string_prop(ent, "name") or "?"
|
||||||
if _string_prop(dev, "driver") != "com.apple.AppleMIDINetworkDriver":
|
found[name] = {
|
||||||
continue
|
"port": sess.get("port"),
|
||||||
for j in range(_cm.MIDIDeviceGetNumberOfEntities(dev)):
|
"peers": [
|
||||||
ent = _cm.MIDIDeviceGetEntity(dev, j)
|
{"name": p.get("name", ""), "address": p.get("address", ""), "state": p.get("state", "")}
|
||||||
sess = _dict_prop(ent, "apple.midirtp.session")
|
for p in sess.get("peers", [])
|
||||||
if sess is None:
|
],
|
||||||
continue
|
}
|
||||||
name = sess.get("dns-sd-name") or _string_prop(ent, "name") or "?"
|
|
||||||
found[name] = {
|
|
||||||
"port": sess.get("port"),
|
|
||||||
"peers": [
|
|
||||||
{"name": p.get("name", ""), "address": p.get("address", ""), "state": p.get("state", "")}
|
|
||||||
for p in sess.get("peers", [])
|
|
||||||
],
|
|
||||||
}
|
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ class TestChecks(unittest.TestCase):
|
|||||||
self.assertEqual(engine.step(self.snap, self.now)["midi"]["x"], "MIDI\nOK") # not read yet: no verdict
|
self.assertEqual(engine.step(self.snap, self.now)["midi"]["x"], "MIDI\nOK") # not read yet: no verdict
|
||||||
self.snap.remote_midi["tracks"] = {"NA Tracks": {"port": 5004, "peers": [
|
self.snap.remote_midi["tracks"] = {"NA Tracks": {"port": 5004, "peers": [
|
||||||
{"name": "SuperRack-LiveBox", "address": "", "state": "running"},
|
{"name": "SuperRack-LiveBox", "address": "", "state": "running"},
|
||||||
|
{"name": "Studio B Mac Studio", "address": "", "state": "running"},
|
||||||
{"name": "Lighting PC", "address": "", "state": "running"}]}}
|
{"name": "Lighting PC", "address": "", "state": "running"}]}}
|
||||||
self.assertEqual(engine.step(self.snap, self.now)["midi"]["x"], "MIDI\nOK")
|
self.assertEqual(engine.step(self.snap, self.now)["midi"]["x"], "MIDI\nOK")
|
||||||
self.snap.remote_midi["tracks"]["NA Tracks"]["peers"].pop()
|
self.snap.remote_midi["tracks"]["NA Tracks"]["peers"].pop()
|
||||||
@@ -265,7 +266,7 @@ class TestChecks(unittest.TestCase):
|
|||||||
self.assertEqual(r.returncode, 0, r.stderr)
|
self.assertEqual(r.returncode, 0, r.stderr)
|
||||||
self.assertIsInstance(sp.parse_remote_midi(r.stdout), dict)
|
self.assertIsInstance(sp.parse_remote_midi(r.stdout), dict)
|
||||||
self.assertIsNone(sp.parse_remote_midi("ssh: connect to host timed out"))
|
self.assertIsNone(sp.parse_remote_midi("ssh: connect to host timed out"))
|
||||||
self.assertEqual(sp.remote_midi_hosts(CONFIG), ["tracks"])
|
self.assertEqual(sp.remote_midi_hosts(CONFIG), ["prop", "tracks"])
|
||||||
|
|
||||||
def test_midi_peer_joining_is_not_an_error(self):
|
def test_midi_peer_joining_is_not_an_error(self):
|
||||||
for p in self.snap.midi["NA Ingest"]["peers"]:
|
for p in self.snap.midi["NA Ingest"]["peers"]:
|
||||||
@@ -534,10 +535,12 @@ class TestFixes(unittest.TestCase):
|
|||||||
self.assertTrue(CONFIG["fixes"][fix_id]["run"].startswith("tasklist "), fix_id)
|
self.assertTrue(CONFIG["fixes"][fix_id]["run"].startswith("tasklist "), fix_id)
|
||||||
|
|
||||||
def test_midi_reconnect_where_the_peer_was_last_seen(self):
|
def test_midi_reconnect_where_the_peer_was_last_seen(self):
|
||||||
tracks = {"NA Tracks": {"port": 5004, "peers": [
|
others = [{"name": "SuperRack-LiveBox", "address": "10.1.13.140:5004", "state": "running"},
|
||||||
|
{"name": "Studio B Mac Studio", "address": "\tStudio B Mac Studio\tlocal.", "state": "running"}]
|
||||||
|
tracks = {"NA Tracks": {"port": 5004, "peers": others + [
|
||||||
{"name": "Lighting PC", "address": "10.1.10.77:5004", "state": "running"}]}}
|
{"name": "Lighting PC", "address": "10.1.10.77:5004", "state": "running"}]}}
|
||||||
sp.remember_peers(self.snap, "tracks", tracks) # seen at a new address earlier
|
sp.remember_peers(self.snap, "tracks", tracks) # seen at a new address earlier
|
||||||
tracks["NA Tracks"]["peers"] = []
|
tracks["NA Tracks"]["peers"] = list(others)
|
||||||
self.snap.remote_midi["tracks"] = tracks
|
self.snap.remote_midi["tracks"] = tracks
|
||||||
self.snap.midi["NA Ingest"]["peers"] = [p for p in self.snap.midi["NA Ingest"]["peers"]
|
self.snap.midi["NA Ingest"]["peers"] = [p for p in self.snap.midi["NA Ingest"]["peers"]
|
||||||
if p["name"] != "Lighting PC"]
|
if p["name"] != "Lighting PC"]
|
||||||
|
|||||||
Reference in New Issue
Block a user