-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdmx-output.js
More file actions
214 lines (188 loc) · 5.52 KB
/
Copy pathdmx-output.js
File metadata and controls
214 lines (188 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// DMX Output via Web Serial API
// Supports: Raw USB-RS485 adapters and ENTTEC DMX USB Pro
// DMX512 spec: 250kbaud, 8N2, ~44 Hz max for full 512-ch universe; default 40 Hz
const DMX_UNIVERSE_SIZE = 512;
const DMX_DEFAULT_HZ = 20;
const DMX_BREAK_MS = 1; // DMX break >= 88µs; 1ms is safe and within spec
// Protocol modes
const DMX_MODE_RAW = "raw"; // USB-to-RS485 adapter (250000 baud, manual break)
const DMX_MODE_ENTTEC = "enttec"; // ENTTEC DMX USB Pro (57600 baud, framed packets)
let dmxPort = null;
let dmxWriter = null;
let dmxRunning = false;
let dmxIntervalId = null;
let dmxHz = DMX_DEFAULT_HZ;
let dmxMode = DMX_MODE_RAW;
let dmxSending = false; // lock to prevent overlapping serial writes
// --- Raw DMX (USB-to-RS485) ---
function buildRawDmxFrame(channelData) {
// Start code (0x00) + 512 channel bytes
const frame = new Uint8Array(1 + channelData.length);
frame[0] = 0x00; // DMX start code
frame.set(channelData, 1);
return frame;
}
async function sendRawDmxFrame(data) {
// 1. Assert BREAK (TX held low >= 88µs)
await dmxPort.setSignals({ break: true });
await sleep(DMX_BREAK_MS);
// 2. Release BREAK → MAB (Mark After Break, TX goes high >= 8µs)
await dmxPort.setSignals({ break: false });
// 3. Send start code + channel data
const frame = buildRawDmxFrame(data);
await dmxWriter.write(frame);
}
// --- ENTTEC DMX USB Pro ---
function buildEnttecDmxPacket(channelData) {
const dmxLen = channelData.length + 1; // +1 for DMX start code
const packet = new Uint8Array(4 + dmxLen + 1);
packet[0] = 0x7e; // Start
packet[1] = 0x06; // Label: Send DMX
packet[2] = dmxLen & 0xff; // Length LSB
packet[3] = (dmxLen >> 8) & 0xff; // Length MSB
packet[4] = 0x00; // DMX start code
packet.set(channelData, 5);
packet[4 + dmxLen] = 0xe7; // End
return packet;
}
async function sendEnttecDmxFrame(data) {
const packet = buildEnttecDmxPacket(data);
await dmxWriter.write(packet);
}
// --- Shared ---
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getCurrentSceneDmxData() {
if (!proFileData) return new Uint8Array(DMX_UNIVERSE_SIZE);
const dmx = new Uint8Array(DMX_UNIVERSE_SIZE);
for (let s = 0; s < SCANNERS_PER_SCENE; s++) {
for (let ch = 0; ch < CHANNELS_PER_SCANNER; ch++) {
const dmxChannel = s * CHANNELS_PER_SCANNER + ch;
dmx[dmxChannel] =
proFileData[getSceneChannelOffset(currentSceneIndex, s, ch)];
}
}
return dmx;
}
async function dmxConnect() {
if (!("serial" in navigator)) {
showError("Web Serial API not supported. Use Chrome or Edge.");
return;
}
try {
dmxPort = await navigator.serial.requestPort();
if (dmxMode === DMX_MODE_RAW) {
await dmxPort.open({
baudRate: 250000,
dataBits: 8,
stopBits: 2,
parity: "none",
flowControl: "none",
});
} else {
await dmxPort.open({
baudRate: 57600,
dataBits: 8,
stopBits: 1,
parity: "none",
flowControl: "none",
});
}
dmxWriter = dmxPort.writable.getWriter();
dmxStart();
updateDmxButton(true);
} catch (err) {
if (err.name !== "NotFoundError") {
showError(`DMX connection failed: ${err.message}`);
}
dmxPort = null;
dmxWriter = null;
}
}
async function dmxDisconnect() {
dmxStop();
try {
if (dmxWriter) {
await dmxWriter.releaseLock();
dmxWriter = null;
}
if (dmxPort) {
await dmxPort.close();
dmxPort = null;
}
} catch (err) {
console.warn("DMX disconnect:", err);
}
updateDmxButton(false);
}
function dmxStart() {
if (dmxRunning) return;
dmxRunning = true;
dmxIntervalId = setInterval(dmxSendFrame, 1000 / dmxHz);
}
function dmxStop() {
dmxRunning = false;
if (dmxIntervalId !== null) {
clearInterval(dmxIntervalId);
dmxIntervalId = null;
}
}
async function dmxSendFrame() {
if (!dmxWriter || !dmxRunning || dmxSending) return;
dmxSending = true;
try {
// Use operator overrides when panel is open (master dimmer, strobe, blackout etc.)
const data =
typeof getOperatorDmxData === "function" && operatorPanelOpen
? getOperatorDmxData().slice()
: getCurrentSceneDmxData().slice();
if (dmxMode === DMX_MODE_RAW) {
await sendRawDmxFrame(data);
} else {
await sendEnttecDmxFrame(data);
}
} catch (err) {
console.error("DMX send error:", err);
dmxDisconnect();
showError(`DMX output lost: ${err.message}`);
} finally {
dmxSending = false;
}
}
function toggleDmxOutput() {
if (dmxPort && dmxRunning) {
dmxDisconnect();
} else {
dmxConnect();
}
}
function setDmxMode(mode) {
const wasRunning = dmxRunning;
if (wasRunning) dmxDisconnect();
dmxMode = mode;
updateDmxModeButton();
}
function toggleDmxMode() {
setDmxMode(dmxMode === DMX_MODE_RAW ? DMX_MODE_ENTTEC : DMX_MODE_RAW);
}
function updateDmxModeButton() {
const btn = document.getElementById("dmxModeBtn");
if (!btn) return;
btn.textContent = dmxMode === DMX_MODE_RAW ? "📡 Raw RS485" : "📡 ENTTEC Pro";
btn.title =
dmxMode === DMX_MODE_RAW
? "Mode: Raw USB-to-RS485 (250kbaud). Click to switch to ENTTEC Pro."
: "Mode: ENTTEC DMX USB Pro (57600 baud). Click to switch to Raw RS485.";
}
function updateDmxButton(connected) {
const btn = document.getElementById("dmxPlayBtn");
if (!btn) return;
if (connected) {
btn.classList.add("dmx-active");
btn.textContent = "🔴 DMX Stop";
} else {
btn.classList.remove("dmx-active");
btn.textContent = "▶️ DMX Output";
}
}