#!/usr/bin/env python3 # -*- coding: utf-8 -*- """OscCommentMacroListener.py: Hog 4 comment macro antlr4 listener for OSC.""" __author__ = "Kevin Matz" __copyright__ = "Copyright 2018, Company 235, LLC" __credits__ = ["Kevin Matz"] __license__ = "MIT" __version__ = "0.0.1" __maintainer__ = "Kevin Matz" __email__ = "kevin@company235.com" __status__ = "Prototype" import antlr4 from CommentMacroParser import CommentMacroParser from CommentMacroListener import CommentMacroListener from pythonosc import osc_message_builder from pythonosc import udp_client from time import sleep def num(string): try: num = int(string) return num except ValueError: num = float(string) return num def _master_go(self, ctx): if ctx.number is not None: print("GO MASTER doesn't support goto. " + "Cue number " + str(ctx.number.value) + " will be ignored.") if (len(ctx.master.targets) == 0): print("Main GO") self.button_press(ctx.device, "/hog/hardware/maingo") return 1 else: for i in ctx.master.targets: if isinstance(i, int) is not True: print("GO MASTER macro targets must be intigers. " + str(i) + " is not an intigers.") continue if (i < 0): print("Master " + str(i) + " should have been greater than 0.") continue master = str(i) print("GO on master " + master) self.button_press(ctx.device, "/hog/hardware/go/" + master) return 1 def _master_halt(self, ctx): if (len(ctx.master.targets) == 0): print("Main HALT") self.button_press(ctx.device, "/hog/hardware/mainhalt") return 1 else: for i in ctx.master.targets: if isinstance(i, int) is not True: print("GO MASTER macro targets must be intigers. " + str(i) + " is not an intigers.") continue if (i < 0): print("Master " + str(i) + " should have been greater than 0.") continue master = str(i) print("HALT on master " + master) self.button_press(ctx.device, "/hog/hardware/pause/" + master) return 1 def _master_fade(self, ctx): level = ctx.number.value if (level < 0 or level > 100): print("Level must be between 0 and 100.") return -1 if (len(ctx.master.targets) == 0): print("MASTER FADE doesn't support unspecified current master.") return -1 else: for i in ctx.master.targets: if isinstance(i, int) is not True: print("FADE MASTER macro targets must be intigers. " + str(i) + " is not an intigers.") continue if (i < 0): print("Master " + str(i) + " should have been greater than 0.") continue master = str(i) print("Fade Master "+master+" to "+str(level)+"%") level *= 255 / 100 # percent in Macro, 0>255 in OSC self.send_message(ctx.device, "/hog/hardware/fader/"+master, level) return 1 def _master_fade_grand(self, ctx): level = ctx.number.value if (level < 0 or level > 100): print("Level must be between 0 and 100.") return -1 print("Fading Grand Master to " + str(level) + "%") level *= 255 / 100 # percent in Macro, 0>255 in OSC self.send_message(ctx.device, "/hog/hardware/fader/0", level) return 1 def _master_choose(self, ctx): if (ctx.number.value < 0): print("Master must be greater than 0.") return -1 master = str(ctx.number.value) print("Choose Master " + master) self.button_press(ctx.device, "/hog/hardware/choose/" + master) return 1 def _list_go(self, ctx): for i in ctx.targets: list = str(i) if ctx.number.value is not None: list += "." + str(ctx.number.value) print("Go on List " + list) self.send_message(ctx.device, "/hog/playback/go/0", list) return 1 def _list_halt(self, ctx): for i in ctx.targets: print("Halting List " + str(i)) self.send_message(ctx.device, "/hog/playback/halt/0", i) return 1 def _list_release(self, ctx): for i in ctx.targets: print("Releasing List " + str(i)) self.send_message(ctx.device, "/hog/playback/release/0", i) return 1 def _scene_go(self, ctx): for i in ctx.targets: print("Go on Scene " + str(i)) self.send_message(ctx.device, "/hog/playback/go/1", i) return 1 def _scene_halt(self, ctx): for i in ctx.targets: print("Halt Scene " + str(i)) self.send_message(ctx.device, "/hog/playback/halt/1", i) return 1 def _scene_release(self, ctx): for i in ctx.targets: print("Release Scene " + str(i)) self.send_message(ctx.device, "/hog/playback/release/1", i) return 1 command = {"GM": _master_go, "HM": _master_halt, "FM": _master_fade, "FGM": _master_fade_grand, "CM": _master_choose, "GL": _list_go, "HL": _list_halt, "RL": _list_release, "GS": _scene_go, "HS": _scene_halt, "RS": _scene_release } # https://raw.githubusercontent.com/jszheng/py3antlr4book/master/bin/pygrun # this is a python version of TestRig def beautify_lisp_string(in_string): __author__ = 'jszheng' indent_size = 2 add_indent = ' ' * indent_size out_string = in_string[0] # no indent for 1st ( indent = '' for i in range(1, len(in_string) - 1): if in_string[i] == '(' and in_string[i + 1] != ' ': indent += add_indent out_string += "\n" + indent + '(' elif in_string[i] == ')': out_string += ')' if len(indent) > 0: indent = indent.replace(add_indent, '', 1) else: out_string += in_string[i] return out_string class OscCommentMacroListener(CommentMacroListener): def __init__(self, parser, server): self.parser = parser self.server = server def button_press(self, device, path, delay=0.05): self.send_message(device, path, 1) # button down sleep(delay) self.send_message(device, path, 0) # button up def send_message(self, device, path, arg): if device is None: osc = list(self.server.values())[0] else: if (device.type.getText() != 'h'): print("Only Hog type devices are curently supported.") print("WARNIN: macro discarded!") return -1 else: try: osc = self.server[device.number.value] except KeyError: print("Net# "+str(device.number.value)+" not configured.") print("WARNING: macro discarded!") return -1 osc.send_message(path, arg) return 1 def exitDevice(self, ctx: CommentMacroParser.DeviceContext): if isinstance(ctx.parentCtx, CommentMacroParser.MacroContext): ctx.parentCtx.device = ctx def enterMacro(self, ctx: CommentMacroParser.MacroContext): ctx.device = None ctx.number = None ctx.master = None ctx.targets = [] ctx.time = None def exitMacro(self, ctx: CommentMacroParser.MacroContext): # print the lisp tree of this macro lisp_tree_str = ctx.toStringTree(recog=self.parser) print(beautify_lisp_string(lisp_tree_str)) # execute macro from name name = ctx.children[0].getText() try: command[name](self, ctx) except KeyError: print(name + " macro is not compatable with OSC.") return -1 print("Exiting Macro") def enterMaster(self, ctx: CommentMacroParser.MasterContext): ctx.targets = [] def exitMaster(self, ctx: CommentMacroParser.MasterContext): ctx.targets = set(ctx.targets) # no duplicates if isinstance(ctx.parentCtx, CommentMacroParser.MacroContext): ctx.parentCtx.master = ctx def exitNumber(self, ctx: CommentMacroParser.NumberContext): ctx.value = num(ctx.getText()) if isinstance(ctx.parentCtx, CommentMacroParser.TargetContext): ctx.parentCtx.targets.append(ctx.value) if isinstance(ctx.parentCtx, CommentMacroParser.MacroContext): ctx.parentCtx.number = ctx if isinstance(ctx.parentCtx, CommentMacroParser.DeviceContext): ctx.parentCtx.number = ctx def exitSpan(self, ctx: CommentMacroParser.SpanContext): number1 = ctx.children[0].value number2 = ctx.children[2].value if (isinstance(number1, int) and isinstance(number2, int)): if isinstance(ctx.parentCtx, CommentMacroParser.TargetContext): minimum = min(number1, number2) maximum = max(number1, number2) for i in (range(minimum, maximum + 1)): ctx.parentCtx.targets.append(i) else: print("ERROR: Spans must be ranged with intigers.") ctx.parentCtx.targets.append(-1) def enterTarget(self, ctx: CommentMacroParser.TargetContext): ctx.targets = [] def exitTarget(self, ctx: CommentMacroParser.TargetContext): ctx.target = set(ctx.targets) # no duplicates ctx.parentCtx.targets.extend(ctx.targets) def exitNodeType(self, ctx: CommentMacroParser.NodeTypeContext): if isinstance(ctx.parentCtx, CommentMacroParser.DeviceContext): ctx.parentCtx.type = ctx