UNPKG

sony9pin-nodejs

Version:

Node.js controller for Sony 9-pin (RS-422) VTR decks via serial (COM port)

167 lines (151 loc) 7.85 kB
// Odetics protocol helper over Sony 9-pin framing (superset extensions) // This class mirrors BlackmagicAMP approach: provide a 1:1 raw sender and // thin helpers for well-known operations once command IDs are confirmed. // Until command IDs are populated from docs, use .send/.raw to issue packets. export class Odetics { constructor(vtr) { this.vtr = vtr; } // 1:1 sender send(cmd1, cmd2, data = []) { return this.vtr.sendCommand(cmd1, cmd2, data); } raw(cmd1, cmd2, data = []) { return this.send(cmd1, cmd2, data); } // Utilities timecodeAuto() { return this.vtr.currentTimeSense(0x03); } async pollTimecode({ intervalMs = 250, durationMs = 3000 } = {}) { const endAt = Date.now() + durationMs; while (Date.now() < endAt) { await this.timecodeAuto(); await new Promise((r) => setTimeout(r, intervalMs)); } } // ---- helpers (internal) ---- #bcd(n) { const v = Math.max(0, Math.min(99, n | 0)); const tens = Math.floor(v / 10); const ones = v % 10; return ((tens & 0x0F) << 4) | (ones & 0x0F); } #packTc({ hh, mm, ss, ff }) { return [this.#bcd(ff), this.#bcd(ss), this.#bcd(mm), this.#bcd(hh)]; } #lsmIdToBytes(id) { // EVS LSM ID is 8 ASCII bytes with last byte as a blank (space) // Examples: '114A/00 ' or '013C/00 ' let s = String(id ?? '').toUpperCase().trim() // Ensure trailing space and exact length of 8 if (!s.endsWith(' ')) s += ' ' if (s.length < 8) s = s.padEnd(8, ' ') else if (s.length > 8) s = s.slice(0, 8) const out = [] for (let i = 0; i < s.length; i++) out.push(s.charCodeAt(i) & 0xFF) return out } // ---- Odetics helpers (from Commands.csv) ---- // A0 01 Auto Skip (TSS manual notes as Odetics AUTO SKIP) autoSkip(deltaClips) { return this.send(0xA0, 0x01, [(deltaClips | 0) & 0xFF]); } // A0 06 PreviewInReset previewInReset() { return this.send(0xA0, 0x06); } // A0 07 PreviewOutReset previewOutReset() { return this.send(0xA0, 0x07); } // A0 14 ListFirstID (reply 80 14 = no clip, 88 14 = first ID) listFirstId() { return this.send(0xA0, 0x14); } // A0 15 ListNextID (odetics flavor) listNextId() { return this.send(0xA0, 0x15); } // A0 1C LongestContiguousAvailableStorage longestContiguousAvailableStorage() { return this.send(0xA0, 0x1C); } // A0 21 DeviceIDRequest deviceIdRequest() { return this.send(0xA0, 0x21); } // A8 11 EraseSegment (often NAK on devices that do not implement) eraseSegment() { return this.send(0xA8, 0x11); } // A8 16 ListClipTc (returns first frame TC and duration) listClipTc() { return this.send(0xA8, 0x16); } // A8 17 ListClipTc (EVS) – returns TC + machine number listClipTcEVS() { return this.send(0xA8, 0x17); } // A8 18 IDStatusRequest idStatusRequest() { return this.send(0xA8, 0x18); } // A8 20 SetDeviceID (expects payload bytes for device id) setDeviceId(...bytes) { return this.send(0xA8, 0x20, bytes); } // AX 02 RecordCueUpWithData (variant nibble). Provide explicit form // cmd1Variant: 0xA0..0xAF, payload as needed by device recordCueUpWithData(cmd1Variant = 0xA0, ...data) { return this.send(cmd1Variant & 0xFF, 0x02, data); } // AX 04 PreviewInPreset previewInPreset(cmd1Variant = 0xA0, ...data) { return this.send(cmd1Variant & 0xFF, 0x04, data); } // AX 05 PreviewOutPreset previewOutPreset(cmd1Variant = 0xA0, ...data) { return this.send(cmd1Variant & 0xFF, 0x05, data); } // AX 10 EraseID eraseId(cmd1Variant = 0xA0, ...idBytes) { return this.send(cmd1Variant & 0xFF, 0x10, idBytes); } // ---- CueUpWithData variants (EVS specifics) ---- // 24.31 Cue by timecode only cueByTimecode(tc /* {hh,mm,ss,ff} */) { return this.send(0x24, 0x31, this.#packTc(tc)); } // 28.31 Load and cue by LSM ID (string like '114A/00') loadAndCueById(lsmId /* string */) { return this.send(0x28, 0x31, this.#lsmIdToBytes(lsmId)); } // 2C.31 Load by LSM ID and cue by timecode loadByIdAndCueByTimecode(lsmId /* string */, tc /* {hh,mm,ss,ff} */) { return this.send(0x2C, 0x31, [...this.#lsmIdToBytes(lsmId), ...this.#packTc(tc)]); } // B0 00 GetEvent (90 00 => no event, 9X 00 => event) getEvent() { return this.send(0xB0, 0x00); } // B1 01 SetTargetMachine (expects payload) setTargetMachine(...bytes) { return this.send(0xB1, 0x01, bytes); } // B8 02 SetIdForData (store clip ID for BC 02) setIdForData(...idBytes) { return this.send(0xB8, 0x02, idBytes); } // BC 02 SetData (associate data with previously stored ID) setData(...dataBytes) { return this.send(0xBC, 0x02, dataBytes); } // B8 03 GetData (reply 9C 03 ...) getData(...idBytes) { return this.send(0xB8, 0x03, idBytes); } // BX 04 MakeClip – requires variant nibble makeClip(cmd1Variant = 0xB0, ...bytes) { return this.send(cmd1Variant & 0xFF, 0x04, bytes); } // BA 05 SetIDEVSStatus setIDEVSStatus(...bytes) { return this.send(0xBA, 0x05, bytes); } // B8 06 ListClipProtectTC listClipProtectTC(...idBytes) { return this.send(0xB8, 0x06, idBytes); } // B9 07 GetKeyword getKeyword(...idBytes) { return this.send(0xB9, 0x07, idBytes); } // B8 08 SetKeyword 1 (set clip ID used by BD 08) setKeyword1(...idBytes) { return this.send(0xB8, 0x08, idBytes); } // BD 08 SetKeyword 2 (set keyword on clip ID) setKeyword2(...bytes) { return this.send(0xBD, 0x08, bytes); } // B9 09 04 ID LSM => ID Louth (data first byte 0x04 + payload as device expects) idLsmToLouth(...bytes) { return this.send(0xB9, 0x09, [0x04, ...bytes]); } // B9 09 05 ID Louth => ID LSM idLouthToLsm(...bytes) { return this.send(0xB9, 0x09, [0x05, ...bytes]); } // B9 0A NetMoveClipIdVDCP netMoveClipIdVDCP(...bytes) { return this.send(0xB9, 0x0A, bytes); } // B9 0B 53 NetMoveClipIdLsm 1 (source) netMoveClipIdLsm1(...bytes) { return this.send(0xB9, 0x0B, [0x53, ...bytes]); } // B9 0B 54 NetMoveClipIdLsm 2 (target) netMoveClipIdLsm2(...bytes) { return this.send(0xB9, 0x0B, [0x54, ...bytes]); } // B8 0C NetCopyClipIdVDCP 1 (source) netCopyClipIdVDCP1(...bytes) { return this.send(0xB8, 0x0C, bytes); } // B9 0C NetCopyClipIdVDCP 2 (target) netCopyClipIdVDCP2(...bytes) { return this.send(0xB9, 0x0C, bytes); } // B9 0D 53 NetCopyClipIdLsm 1 (source) netCopyClipIdLsm1(...bytes) { return this.send(0xB9, 0x0D, [0x53, ...bytes]); } // B9 0D 54 NetCopyClipIdLsm 2 (target) netCopyClipIdLsm2(...bytes) { return this.send(0xB9, 0x0D, [0x54, ...bytes]); } // B0 0E GetFirstMachine getFirstMachine() { return this.send(0xB0, 0x0E); } // B0 0F GetNextMachine getNextMachine() { return this.send(0xB0, 0x0F); } // B4 10 SetOptions setOptions(...bytes) { return this.send(0xB4, 0x10, bytes); } // B0 11 GetOptions getOptions(...bytes) { return this.send(0xB0, 0x11, bytes); } // BX 12 SetInOut (update short in/out). Variant nibble. setInOut(cmd1Variant = 0xB0, ...bytes) { return this.send(cmd1Variant & 0xFF, 0x12, bytes); } // B8 13 Live (go live on given camera) live(...bytes) { return this.send(0xB8, 0x13, bytes); } // ---- Additional from TSS manual ---- // CX 01 Jump Forward X Frames (variant nibble Cx) jumpForwardFrames(frames /* 0..255 */, cmd1Variant = 0xC0) { return this.send(cmd1Variant & 0xFF, 0x01, [frames & 0xFF]); } // CX 02 Jump Back X Frames (variant nibble Cx) jumpBackFrames(frames /* 0..255 */, cmd1Variant = 0xC0) { return this.send(cmd1Variant & 0xFF, 0x02, [frames & 0xFF]); } // CX 03 Get Loaded ID (variant nibble Cx) getLoadedId(cmd1Variant = 0xC0, ...bytes) { return this.send(cmd1Variant & 0xFF, 0x03, bytes); } // EVS: BX 09 YY general information selector info(cmd1Variant = 0xB0, selector = 0x01, ...data) { return this.send(cmd1Variant & 0xFF, 0x09, [selector & 0xFF, ...data]); } // EVS: B1.09.01 ActiveIDRequest activeIdRequest() { return this.send(0xB1, 0x09, [0x01]); } }