From 1087ebd08cf3eba47a71d39fba4d277ce82a5ec4 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Fri, 21 Feb 2020 16:06:27 +0000 Subject: [PATCH 1/6] tidy up the modules a little --- plugins/ManipulatePlugin.py | 34 +++++++++------------------ plugins/WJSendPlugin.py | 46 +++++++++++-------------------------- 2 files changed, 24 insertions(+), 56 deletions(-) diff --git a/plugins/ManipulatePlugin.py b/plugins/ManipulatePlugin.py index a298c41..8aab1fb 100644 --- a/plugins/ManipulatePlugin.py +++ b/plugins/ManipulatePlugin.py @@ -1,5 +1,5 @@ import data_centre.plugin_collection -from data_centre.plugin_collection import ActionsPlugin, DisplayPlugin#, SequencePlugin +from data_centre.plugin_collection import ActionsPlugin, DisplayPlugin, ModulationReceiverPlugin#, SequencePlugin #import math from math import sin, cos, tan, log, exp, pi @@ -41,12 +41,13 @@ TODO: >> ?? invert|set_the_shader_param_0_layer_>>print_arguments>>set_variab """ -class ManipulatePlugin(ActionsPlugin,DisplayPlugin): +class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): disabled = False def __init__(self, plugin_collection): super().__init__(plugin_collection) + # ActionsPlugin methods @property def parserlist(self): return [ @@ -58,6 +59,7 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin): ( r"^(.*)>\&(.*)$", self.run_multi ), # pick up piped commands that duplicate a chain of values last ] + # DisplayPlugin methods def show_plugin(self, display, display_mode): from tkinter import Text, END #super(DisplayPlugin).show_plugin(display, display_mode) @@ -67,10 +69,10 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin): for key,value in self.variables.items(): display.display_text.insert(END, "\t" + key + "\t{:03.2f}\n".format(value)) - def get_display_modes(self): return ["MANIPULA","NAV_MANI"] #"NAV_MANIPULATE"] + # Actions def run_multi(self, action1, action2, value): print("ManipulatePlugin>> multi-running '%s' and '%s' with value %s" % (action1, action2, value)) self.pc.actions.call_method_name(action1, value) @@ -106,25 +108,11 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin): action, self.variables.get(var_name)# + list(args) ) - """def pipe2(self, left, right1, separator, right2, tail, value): - self.pc.actions.call_method_name( - left + separator + right1, value - ) - self.pc.actions.call_method_name( - left + separator + right2, value - ) - self.pc.actions.call_method_name( - tail, value - )""" - """def pipe(self, left, right, value): - # ?? - print("ManipulatePlugin>> pipe calling left '%s' and right '%s', both with value '%s'" % (left, right, value)) - self.pc.actions.call_method_name( - left, value - ) - - self.pc.actions.call_method_name( - right, value - )""" + # ModulationReceiverPlugin methods + # methods for ModulationReceiverPlugin - receives changes to the in-built modulation levels (-1 to +1) + def set_modulation_value(self, param, value): + # take modulation value and throw it to local parameter + print("||||| ManipulatePlugin received set_modulation_value for param %s with value %s!" % (param, value)) + self.set_variable("MODVALUE%s" % ('ABCD'[param]), value) diff --git a/plugins/WJSendPlugin.py b/plugins/WJSendPlugin.py index c38cb81..16eda31 100644 --- a/plugins/WJSendPlugin.py +++ b/plugins/WJSendPlugin.py @@ -37,10 +37,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei print ("WJSendPlugin is disabled, not opening serial") return - #self.open_serial() - #print ("starting refresh?") self.pc.actions.tk.after(500, self.refresh) - #tk.after(500, self.refresh) # methods/vars for AutomationSourcePlugin # a lot of the nitty-gritty handled in parent class, these are for interfacing to the plugin @@ -67,7 +64,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei # experimental & hardcoded ! def set_modulation_value(self, param, value): # take modulation value and throw it to local parameter - print("||||| wjsend received set_modulation_value for param %s with value %s!" % (param, value)) + print("||||| WJSendPlugin received set_modulation_value for param %s with value %s!" % (param, value)) if param==0: self.set_mix((0.5+value)/2) elif param==1: @@ -77,7 +74,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei elif param==3: self.set_back_colour('x', 0.5+value) else: - print("unknown param %s!" % param) + print("\tunknown param %s!" % param) #methods for DisplayPlugin def show_plugin(self, display, display_mode): @@ -85,15 +82,15 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei #super(DisplayPlugin).show_plugin(display, display_mode) #print("show plugin?") display.display_text.insert(END, '{} \n'.format(display.body_title)) - display.display_text.insert(END, "test from WJSendPlugin!\n\n") + display.display_text.insert(END, "WJSendPlugin status!\n\n") for queue, last in self.last.items(): - display.display_text.insert(END, "last %s:\t%s\n" % (queue,self.last.get(queue))) + display.display_text.insert(END, "%s:\t%s\n" % (queue,self.last.get(queue)[1])) def get_display_modes(self): return ["WJMXSEND","NAV_WJMX"] - # methods for SerialPlugin (todo!) + # methods for SerialPlugin (todo!) and serial command queueing def open_serial(self, port='/dev/ttyUSB0', baudrate=9600): if self.ser is not None: self.ser.close() @@ -111,25 +108,22 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei timeout=None #timeout ) - print ("starting refresh?") - - #self.pc.midi_input.root.after(500, self.refresh) except Exception as e: - print ("open_serial failed: " + str(type(e))) + print ("WJSendPlugin>> open_serial failed: " + str(type(e))) self.disabled = True import traceback traceback.print_exc() def send_serial_string(self, string): try: - print("sending string %s " % string) + print("WJSendPlugin>> sending string %s " % string) output = b'\2' + string.encode('ascii') + b'\3' self.ser.write(output) #.encode()) #print("sent string '%s'" % output) #.encode('ascii')) #if 'S' in string: # self.get_device_status() except Exception as e: - print("%s: send_serial_string failed for '%s'" % (e,string)) #.encode() + print("\t%s: send_serial_string failed for '%s'" % (e,string)) #.encode() queue = {} def refresh(self): @@ -142,7 +136,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei self.send_buffered(queue, command[0], command[1]) #self.queue.clear() except Exception: - print ("!!! CAUGHT EXCEPTION running queue !!!") + print ("WJSendPlugin>>> !!! CAUGHT EXCEPTION running queue %s!!!" % queue) import traceback print(traceback.format_exc()) finally: @@ -157,20 +151,12 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei last = {} def send_buffered(self, queue, form, args, record = True): - """# TODO: remove this crap when i'm sure the bug has been fixed - if output is not None and (type(output) is dict or 'WJSendPlugin' in output): - print ("\n\n\ncaught fucker?") - import traceback - traceback.print_stack() - quit()""" - if self.last.get(queue)!=(form,args): - print("send_buffered attempting to parse queue\t%s with form\t'%s' and args\t%s" % (queue, form, args)) + #print("WJSendPlugin>> send_buffered attempting to parse queue\t%s with form\t'%s' and args\t%s" % (queue, form, args)) output = form.format(*args) self.send_serial_string(output) self.last[queue] = (form,args) #output if record: - print("### send_buffered is setting last_record[%s] to %s" % (queue,output)) self.last_record[queue] = (form,args)#output def send_append(self, command, value): @@ -210,9 +196,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei elif dim=='y': self.colour_y = int(255*value) - #output = "VCC:{}{:02X}{:02X}".format(chan, self.colour_x, self.colour_y) - # could store format string + values, then interpolate over those values - self.send('VCC', "VCC:{}{:02X}{:02X}", [chan, self.colour_x, self.colour_y]) #output) + self.send('VCC', "VCC:{}{:02X}{:02X}", [chan, self.colour_x, self.colour_y]) # RGB control of matte colour! back_colour_x = 127 @@ -227,7 +211,6 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei elif dim=='z': self.back_colour_z = int(255*value) - #output = "VBM:{:02X}{:02X}{:02X}".format(self.back_colour_x,self.back_colour_y,self.back_colour_z) self.send('VBM', "VBM:{:02X}{:02X}{:02X}", [ self.back_colour_x,self.back_colour_y,self.back_colour_z ]) # this doesnt seem to work on WJ-MX30 at least, or maybe i dont know how to get it into the right mode? @@ -244,7 +227,6 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei elif dim=='z': self.back_wash_colour_z = int(255*value) - #output = "VBW:{:02X}{:02X}{:02X}".format(self.back_wash_colour_x,self.back_wash_colour_y,self.back_wash_colour_z) self.send('VBW', "VBW:{:02X}{:02X}{:02X}", [ self.back_wash_colour_x,self.back_wash_colour_y,self.back_wash_colour_z ] ) # positioner joystick @@ -256,11 +238,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei elif dim=='x': # yes, x is really y! self.position_y = int(255*value) - #output = "VPS:{}{:02X}{:02X}".format(mode,self.position_x,self.position_y) - self.send('VPS:{}'.format(mode), "VPS:{}{:02X}{:02X}", [ mode,self.position_x,self.position_y ])#output) + self.send('VPS:{}'.format(mode), "VPS:{}{:02X}{:02X}", [ mode,self.position_x,self.position_y ]) # wipe / mix level def set_mix(self, value): - #output = "VMM:{:04X}".format(int(255*255*value)) - self.send('VMM', "VMM:{:02X}", [ int(255*value) ])#output) + self.send('VMM', "VMM:{:02X}", [ int(255*value) ]) From 70dbc4e9545b11248f350448cc1c6b45ccabdba2 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Fri, 21 Feb 2020 16:49:58 +0000 Subject: [PATCH 2/6] tidy up LFOModulation to make it easier to add selectable formulas later --- plugins/LFOModulation.py | 56 ++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/plugins/LFOModulation.py b/plugins/LFOModulation.py index 3b6865e..a51655d 100644 --- a/plugins/LFOModulation.py +++ b/plugins/LFOModulation.py @@ -1,12 +1,17 @@ +import math import data_centre.plugin_collection from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, DisplayPlugin class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): disabled = False + MAX_LFOS = 4 + + # active = True (toggle_lfo_active) to enable sending of modulation active = False - level = [0.0, 0.0, 0.0, 0.0] + # for keeping track of LFO levels + level = [0.0]*MAX_LFOS #, 0.0, 0.0, 0.0] stop_flag = False pause_flag = False @@ -17,6 +22,8 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): self.pc.shaders.root.after(1000, self.run_automation) + + # DisplayPlugin methods def get_display_modes(self): return ['LFOMODU','NAV_LFO'] @@ -24,18 +31,23 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): from tkinter import Text, END #super(DisplayPlugin).show_plugin(display, display_mode) display.display_text.insert(END, '{} \n'.format(display.body_title)) - display.display_text.insert(END, "test from LFOModulationPlugin!\n") + display.display_text.insert(END, "LFOModulationPlugin ") - display.display_text.insert(END, "\tACTIVE\n" if self.active else "not active\n") + display.display_text.insert(END, "ACTIVE\n" if self.active else "not active\n\n") - for i,value in enumerate(self.level): - display.display_text.insert(END, "{} level: {:03.2f}%\n".format(i,value)) + for lfo,value in enumerate(self.level): + display.display_text.insert(END, "lfo {} level: {:03.2f}%\t".format(lfo,value)) + display.display_text.insert(END, "%s\n" %self.last_lfo_status[lfo]) + display.display_text.insert(END, "\t%s\n" % self.formula[lfo]) + + # ActionsPlugin methods @property def parserlist(self): return [ ( r"^set_lfo_modulation_([0-3])_level$", self.set_lfo_modulation_level ), ( r"^toggle_lfo_active$", self.toggle_lfo_active ) + # TODO: changing formulas and LFO modes, speed ] def set_lfo_modulation_level(self, slot, value): @@ -44,6 +56,32 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): def toggle_lfo_active(self): self.active = not self.active + # Formula handling for generating automation + # mapping 0-3 to match the LFO + # TODO: save & load this to config file, make editable + formula = [ + "f_sin", + "f_inverted_sin", + "f_sin", + "f_inverted_sin" + ] + + # run the formula for the stored lfo configuration + last_lfo_status = [None]*MAX_LFOS # for displaying status + def getLFO(self, position, lfo): + lfo_value = getattr(self,self.formula[lfo])(position, self.level[lfo]) + self.last_lfo_status[lfo] = " output {:03.2f}\tat T{:03.2f}".format(lfo_value*100.0, position) + return lfo_value + + # built-in waveshapes + # todo: more of the these, and better! + def f_sin(self, position, level): + return 0.5+(0.5*level * math.sin(position*math.pi)) + + def f_inverted_sin(self, position, level): + return self.f_sin(1.0-position, 1.0-level) + + # SequencePlugin methods def run_sequence(self, position): import time now = time.time() @@ -53,10 +91,6 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): if not self.active: return - #print("run_automation position %s!"%position) - import math - self.pc.actions.call_method_name("modulate_param_0_to_amount_continuous", 0.5+(0.5*self.level[0] * math.sin(position*math.pi))) #(now*100)%300) - self.pc.actions.call_method_name("modulate_param_1_to_amount_continuous", 0.5+(0.5*self.level[1] * math.cos(position*math.pi)/math.pi)) - self.pc.actions.call_method_name("modulate_param_2_to_amount_continuous", 0.5+(0.5*self.level[2] * math.atan(position*math.pi)/math.pi)) - self.pc.actions.call_method_name("modulate_param_3_to_amount_continuous", 0.5+(0.5*self.level[3] * math.sin(math.sin(position*math.pi)*math.pi))) + for i in range(0,4): + self.pc.actions.call_method_name("modulate_param_%s_to_amount_continuous"%i, self.getLFO(position, i)) From ebd6859490e081b83b7cd63aab8eb955a5268bc9 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Fri, 21 Feb 2020 19:54:26 +0000 Subject: [PATCH 3/6] tidied up + apparently fixed laggy response by sorting the order that i sent commands to the wjmx --- plugins/LFOModulation.py | 2 +- plugins/WJSendPlugin.py | 51 +++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/plugins/LFOModulation.py b/plugins/LFOModulation.py index a51655d..42046ce 100644 --- a/plugins/LFOModulation.py +++ b/plugins/LFOModulation.py @@ -33,7 +33,7 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): display.display_text.insert(END, '{} \n'.format(display.body_title)) display.display_text.insert(END, "LFOModulationPlugin ") - display.display_text.insert(END, "ACTIVE\n" if self.active else "not active\n\n") + display.display_text.insert(END, "ACTIVE\n\n" if self.active else "not active\n\n") for lfo,value in enumerate(self.level): display.display_text.insert(END, "lfo {} level: {:03.2f}%\t".format(lfo,value)) diff --git a/plugins/WJSendPlugin.py b/plugins/WJSendPlugin.py index 16eda31..d164a24 100644 --- a/plugins/WJSendPlugin.py +++ b/plugins/WJSendPlugin.py @@ -6,6 +6,7 @@ import threading class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationReceiverPlugin, AutomationSourcePlugin): disabled = False#True + DEBUG = False#True ser = None # from http://depot.univ-nc.nc/sources/boxtream-0.9999/boxtream/switchers/panasonic.py """serial.Serial(device, baudrate=9600, @@ -62,27 +63,26 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei # methods for ModulationReceiverPlugin - receives changes to the in-built modulation levels (-1 to +1) # experimental & hardcoded ! + # TODO: make this not hardcoded and configurable mapping modulation to parameters, preferably on-the-fly.. def set_modulation_value(self, param, value): # take modulation value and throw it to local parameter - print("||||| WJSendPlugin received set_modulation_value for param %s with value %s!" % (param, value)) + if self.DEBUG: print("||||| WJSendPlugin received set_modulation_value for param %s with value %s!" % (param, value)) if param==0: self.set_mix((0.5+value)/2) elif param==1: - self.set_colour('T', 'x', 0.5+value) + self.set_colour('T', 'x', (0.5+value)/2) elif param==2: - self.set_colour('T', 'y', 0.5+value) + self.set_colour('T', 'y', (0.5+value)/2) elif param==3: - self.set_back_colour('x', 0.5+value) + self.set_back_colour('x', (0.5+value)/2) else: - print("\tunknown param %s!" % param) + if self.DEBUG: print("\tunknown param %s!" % param) #methods for DisplayPlugin def show_plugin(self, display, display_mode): from tkinter import Text, END - #super(DisplayPlugin).show_plugin(display, display_mode) - #print("show plugin?") display.display_text.insert(END, '{} \n'.format(display.body_title)) - display.display_text.insert(END, "WJSendPlugin status!\n\n") + display.display_text.insert(END, "WJSendPlugin status\n\n") for queue, last in self.last.items(): display.display_text.insert(END, "%s:\t%s\n" % (queue,self.last.get(queue)[1])) @@ -90,7 +90,8 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei def get_display_modes(self): return ["WJMXSEND","NAV_WJMX"] - # methods for SerialPlugin (todo!) and serial command queueing + + # methods for SerialPlugin (TODO: if this needs generalising out!) and serial command queueing def open_serial(self, port='/dev/ttyUSB0', baudrate=9600): if self.ser is not None: self.ser.close() @@ -116,23 +117,24 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei def send_serial_string(self, string): try: - print("WJSendPlugin>> sending string %s " % string) + if self.DEBUG: print("WJSendPlugin>> sending string %s " % string) output = b'\2' + string.encode('ascii') + b'\3' self.ser.write(output) #.encode()) #print("sent string '%s'" % output) #.encode('ascii')) #if 'S' in string: # self.get_device_status() except Exception as e: - print("\t%s: send_serial_string failed for '%s'" % (e,string)) #.encode() + print("\t%s: send_serial_string failed for '%s'" % (e,string)) queue = {} + # send the queued commands to WJMX def refresh(self): - #print("refresh called!") if not self.ser or self.ser is None: self.open_serial() try: - for queue, command in self.queue.items(): + # sorting the commands that are sent seems to fix jerk and lag that is otherwise pretty horrendous + for queue, command in sorted(self.queue.items()): self.send_buffered(queue, command[0], command[1]) #self.queue.clear() except Exception: @@ -151,24 +153,25 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei last = {} def send_buffered(self, queue, form, args, record = True): + # only send if new command is different to the last one we sent if self.last.get(queue)!=(form,args): #print("WJSendPlugin>> send_buffered attempting to parse queue\t%s with form\t'%s' and args\t%s" % (queue, form, args)) output = form.format(*args) self.send_serial_string(output) - self.last[queue] = (form,args) #output + self.last[queue] = (form,args) if record: - self.last_record[queue] = (form,args)#output + self.last_record[queue] = (form,args) def send_append(self, command, value): - # append value to the command as a hex value - self.send(command.split(':')[0], "{}{:02X}", [ command,int(255*value) ]) + # append value to the command as a hex value - for sending commands that aren't preprogrammed + self.send(command.split(':')[0], "{}{:02X}", [ command, int(255*value) ]) def send_append_pad(self, pad, command, value): - # append value, padded to length - self.send(command.split(':')[0], "{}{:0%iX}"%pad, [ command,int(255*value) ]) + # append value, padded to length - for sending commands that aren't preprogrammed + self.send(command.split(':')[0], "{}{:0%iX}"%pad, [ command, int(255*value) ]) - # methods for ActionPlugin + # methods for ActionPlugin - preprogrammed parameters @property def parserlist(self): return [ @@ -183,9 +186,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei ( r"^wj_send_append_([:0-9a-zA-Z]*)$", self.send_append ), ] - # methods for handling some Panasonic control settings - # todo: come up with a way to represent this programmatically - # todo: add more! + # methods for handling some Panasonic control settings as parameters + # TODO: come up with a way to represent this programmatically + # TODO: add more! colour_x = 127 colour_y = 127 @@ -214,7 +217,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei self.send('VBM', "VBM:{:02X}{:02X}{:02X}", [ self.back_colour_x,self.back_colour_y,self.back_colour_z ]) # this doesnt seem to work on WJ-MX30 at least, or maybe i dont know how to get it into the right mode? - # todo: replace with downstream key control + # TODO: replace with downstream key control back_wash_colour_x = 127 back_wash_colour_y = 127 back_wash_colour_z = 127 From a055c5585c36d665891b0d33214acd93cd100572 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Fri, 21 Feb 2020 19:55:28 +0000 Subject: [PATCH 4/6] improvements to LFOModulation plugin - speed control, tidy up --- .../midi_action_mapping_APC Key 25.json | 3 ++- plugins/LFOModulation.py | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/json_objects/midi_action_mapping_APC Key 25.json b/json_objects/midi_action_mapping_APC Key 25.json index c895c0e..2ccd933 100644 --- a/json_objects/midi_action_mapping_APC Key 25.json +++ b/json_objects/midi_action_mapping_APC Key 25.json @@ -68,7 +68,8 @@ "control_change 52": { "DEFAULT": ["set_the_shader_param_0_layer_offset_1_continuous","set_param_0_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_speed_position_continuous"], - "NAV_WJMX": ["wj_set_mix","wj_send_append_pad_2_VCG:T"] + "NAV_WJMX": ["wj_set_mix","wj_send_append_pad_2_VCG:T"], + "NAV_LFO": ["set_lfo_speed"] }, "control_change 53": { "DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous","set_param_1_layer_offset_0_modulation_level_continuous"], diff --git a/plugins/LFOModulation.py b/plugins/LFOModulation.py index 42046ce..45b304a 100644 --- a/plugins/LFOModulation.py +++ b/plugins/LFOModulation.py @@ -33,7 +33,9 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): display.display_text.insert(END, '{} \n'.format(display.body_title)) display.display_text.insert(END, "LFOModulationPlugin ") - display.display_text.insert(END, "ACTIVE\n\n" if self.active else "not active\n\n") + display.display_text.insert(END, "ACTIVE" if self.active else "not active") + + display.display_text.insert(END, "\tSpeed: {:03.2f}\n\n".format(self.speed)) for lfo,value in enumerate(self.level): display.display_text.insert(END, "lfo {} level: {:03.2f}%\t".format(lfo,value)) @@ -46,13 +48,17 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): def parserlist(self): return [ ( r"^set_lfo_modulation_([0-3])_level$", self.set_lfo_modulation_level ), - ( r"^toggle_lfo_active$", self.toggle_lfo_active ) + ( r"^toggle_lfo_active$", self.toggle_lfo_active ), + ( r"^set_lfo_speed", self.set_lfo_speed ) # TODO: changing formulas and LFO modes, speed ] def set_lfo_modulation_level(self, slot, value): self.level[slot] = value + def set_lfo_speed(self, speed): + self.speed = -4*(0.5-(speed)) + def toggle_lfo_active(self): self.active = not self.active @@ -61,16 +67,17 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): # TODO: save & load this to config file, make editable formula = [ "f_sin", - "f_inverted_sin", + "f_double_sin", "f_sin", - "f_inverted_sin" + "f_double_sin" ] # run the formula for the stored lfo configuration last_lfo_status = [None]*MAX_LFOS # for displaying status + #lfo_speed = [1.0]*MAX_LFOS def getLFO(self, position, lfo): lfo_value = getattr(self,self.formula[lfo])(position, self.level[lfo]) - self.last_lfo_status[lfo] = " output {:03.2f}\tat T{:03.2f}".format(lfo_value*100.0, position) + self.last_lfo_status[lfo] = " sent {:03.1f}%".format(lfo_value*100.0) #, position*self.lfo_speed[lfo]) return lfo_value # built-in waveshapes @@ -78,8 +85,8 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): def f_sin(self, position, level): return 0.5+(0.5*level * math.sin(position*math.pi)) - def f_inverted_sin(self, position, level): - return self.f_sin(1.0-position, 1.0-level) + def f_double_sin(self, position, level): + return self.f_sin(position, math.sin(level+0.5*math.pi)) # SequencePlugin methods def run_sequence(self, position): @@ -92,5 +99,6 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): if not self.active: return - for i in range(0,4): - self.pc.actions.call_method_name("modulate_param_%s_to_amount_continuous"%i, self.getLFO(position, i)) + for lfo in range(0,4): + if self.level[lfo]>0.0: + self.pc.actions.call_method_name("modulate_param_%s_to_amount_continuous"%lfo, self.getLFO(position, lfo)) From 2bbe5a57ec4dc50793535bfb65693bcf74060199 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Fri, 21 Feb 2020 19:56:02 +0000 Subject: [PATCH 5/6] remove some debug spam --- plugins/ManipulatePlugin.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/ManipulatePlugin.py b/plugins/ManipulatePlugin.py index 8aab1fb..bd36174 100644 --- a/plugins/ManipulatePlugin.py +++ b/plugins/ManipulatePlugin.py @@ -44,6 +44,8 @@ TODO: >> ?? invert|set_the_shader_param_0_layer_>>print_arguments>>set_variab class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): disabled = False + DEBUG = False + def __init__(self, plugin_collection): super().__init__(plugin_collection) @@ -74,7 +76,7 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): # Actions def run_multi(self, action1, action2, value): - print("ManipulatePlugin>> multi-running '%s' and '%s' with value %s" % (action1, action2, value)) + if self.DEBUG: print("ManipulatePlugin>> multi-running '%s' and '%s' with value %s" % (action1, action2, value)) self.pc.actions.call_method_name(action1, value) self.pc.actions.call_method_name(action2, value) @@ -90,20 +92,20 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): def formula(self, formula, action, value): self.variables['x'] = value - print("ManipulatePlugin>> evaluating formula `%s` with value `%s`" % (formula, value)) + if self.DEBUG: print("ManipulatePlugin>> evaluating formula `%s` with value `%s`" % (formula, value)) value = eval(formula, globals(), self.variables) - print("ManipulatePlugin>> got evaluated value `%s`" % value) + if self.DEBUG: print("ManipulatePlugin>> got evaluated value `%s`" % value) self.pc.actions.call_method_name( action, value ) def set_variable(self, var_name, value): - print("ManipulatePlugin>> set_variable (%s) to %s" % (var_name, value)) + if self.DEBUG: print("ManipulatePlugin>> set_variable (%s) to %s" % (var_name, value)) self.variables[var_name] = value def recall_variable(self, var_name, action, *args): - print ("ManipulatePlugin>> recall_variable (%s) as %s" % (var_name,args)) + if self.DEBUG: print ("ManipulatePlugin>> recall_variable (%s) as %s" % (var_name,args)) self.pc.actions.call_method_name( action, self.variables.get(var_name)# + list(args) ) @@ -113,6 +115,6 @@ class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): # methods for ModulationReceiverPlugin - receives changes to the in-built modulation levels (-1 to +1) def set_modulation_value(self, param, value): # take modulation value and throw it to local parameter - print("||||| ManipulatePlugin received set_modulation_value for param %s with value %s!" % (param, value)) + if self.DEBUG: print("||||| ManipulatePlugin received set_modulation_value for param %s with value %s!" % (param, value)) self.set_variable("MODVALUE%s" % ('ABCD'[param]), value) From a77367f9d3c8565f9c684c83a981871864dbcdff Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 22 Feb 2020 00:07:21 +0000 Subject: [PATCH 6/6] Overhaul of WJSendPlugin to define parameters programmatically and so enable modulation --- .../midi_action_mapping_APC Key 25.json | 14 +- plugins/WJSendPlugin.py | 205 ++++++++++-------- 2 files changed, 121 insertions(+), 98 deletions(-) diff --git a/json_objects/midi_action_mapping_APC Key 25.json b/json_objects/midi_action_mapping_APC Key 25.json index 2ccd933..5dbb7dd 100644 --- a/json_objects/midi_action_mapping_APC Key 25.json +++ b/json_objects/midi_action_mapping_APC Key 25.json @@ -42,27 +42,27 @@ "control_change 48": { "DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous","set_strobe_amount_continuous"], "NAV_DETOUR": ["set_detour_speed_position_continuous"], - "NAV_WJMX": ["wj_set_position_N_x"], + "NAV_WJMX": ["wj_set_position_N:x"], "NAV_MANI": ["set_variable_A"], "NAV_LFO": ["set_lfo_modulation_0_level"] }, "control_change 49": { "DEFAULT": ["set_the_shader_param_1_layer_offset_0_continuous","set_shader_speed_layer_0_amount"], "NAV_DETOUR": ["set_detour_start_continuous"], - "NAV_WJMX": ["wj_set_position_N_y"], + "NAV_WJMX": ["wj_set_position_N:y"], "NAV_MANI": ["f:sin(x*pi):|set_variable_SIN"], "NAV_LFO": ["set_lfo_modulation_1_level"] }, "control_change 50": { "DEFAULT": ["set_the_shader_param_2_layer_offset_0_continuous","set_shader_speed_layer_1_amount"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_colour_T_x"], + "NAV_WJMX": ["wj_set_colour_T:x"], "NAV_LFO": ["set_lfo_modulation_2_level"] }, "control_change 51": { "DEFAULT": ["set_the_shader_param_3_layer_offset_0_continuous","set_shader_speed_layer_2_amount"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_colour_T_y"], + "NAV_WJMX": ["wj_set_colour_T:y"], "NAV_LFO": ["set_lfo_modulation_3_level"] }, "control_change 52": { @@ -74,17 +74,17 @@ "control_change 53": { "DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous","set_param_1_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_start_continuous"], - "NAV_WJMX": ["wj_set_back_colour_x","wj_set_back_wash_colour_x"] + "NAV_WJMX": ["wj_set_back_colour:h","wj_set_dsk_level"] }, "control_change 54": { "DEFAULT": ["set_the_shader_param_2_layer_offset_1_continuous","set_param_2_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_back_colour_y","wj_set_back_wash_colour_y"] + "NAV_WJMX": ["wj_set_back_colour:s","wj_set_colour_gain_T:y"] }, "control_change 55": { "DEFAULT": ["set_the_shader_param_3_layer_offset_1_continuous","set_param_3_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_back_colour_z","wj_set_back_wash_colour_z"] + "NAV_WJMX": ["wj_set_back_colour:v","wj_set_back_wash_colour:z"] }, "control_change 56": { "DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"], diff --git a/plugins/WJSendPlugin.py b/plugins/WJSendPlugin.py index d164a24..740a9b3 100644 --- a/plugins/WJSendPlugin.py +++ b/plugins/WJSendPlugin.py @@ -6,7 +6,7 @@ import threading class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationReceiverPlugin, AutomationSourcePlugin): disabled = False#True - DEBUG = False#True + DEBUG = False #True ser = None # from http://depot.univ-nc.nc/sources/boxtream-0.9999/boxtream/switchers/panasonic.py """serial.Serial(device, baudrate=9600, @@ -17,18 +17,6 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei rtscts=True, # TODO : test without this one timeout=timeout)""" - """self.commands = { - 'VCG:': { - 'name': 'Colour Corrector Gain', - 'cmd': 'VCG:', - }, - 'VCC:': { - 'name': 'Colour Corrector XY', - 'cmd': 'VCC', - 'callback': self.set_colour - } - }""" - THROTTLE = 1 # milliseconds to wait between refreshing parameters def __init__(self, plugin_collection): @@ -38,6 +26,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei print ("WJSendPlugin is disabled, not opening serial") return + for cmd,struct in self.commands.items(): + self.command_by_queue[struct['queue']] = struct + self.pc.actions.tk.after(500, self.refresh) # methods/vars for AutomationSourcePlugin @@ -64,20 +55,32 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei # methods for ModulationReceiverPlugin - receives changes to the in-built modulation levels (-1 to +1) # experimental & hardcoded ! # TODO: make this not hardcoded and configurable mapping modulation to parameters, preferably on-the-fly.. + modulation_value = [0.0]*4 def set_modulation_value(self, param, value): + + self.modulation_value[param] = 1.0-value ## invert so that no signal always gives a value .. + # take modulation value and throw it to local parameter if self.DEBUG: print("||||| WJSendPlugin received set_modulation_value for param %s with value %s!" % (param, value)) - if param==0: - self.set_mix((0.5+value)/2) - elif param==1: - self.set_colour('T', 'x', (0.5+value)/2) - elif param==2: - self.set_colour('T', 'y', (0.5+value)/2) - elif param==3: - self.set_back_colour('x', (0.5+value)/2) - else: - if self.DEBUG: print("\tunknown param %s!" % param) - + #v = (0.5+value)/2 + """mapped = [ + 'mix', + 'colour_T', + #'colour_T', + 'back_colour:x' + ]""" + #self.catch_all(*mapped[param].split(":")+[v]) + #self.commands[ + # find which commands are mapped to this modulation, and trigger a send of them + # so that they update with the new modulation value + for queue,cmd in sorted(self.command_by_queue.items()): + if cmd.get('modulation') is not None: + if self.DEBUG: print("\tparam %s, checking modulation %s" % (param, cmd.get('modulation'))) + if len(cmd.get('modulation')[param])>0: + self.DEBUG: print("\tyes! sending update of values? %s" % [x for x in cmd['arguments'].values() ]) + self.send_buffered(cmd['queue'], cmd['form'], [x for x in cmd['arguments'].values() ]) + continue + #methods for DisplayPlugin def show_plugin(self, display, display_mode): from tkinter import Text, END @@ -135,6 +138,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei try: # sorting the commands that are sent seems to fix jerk and lag that is otherwise pretty horrendous for queue, command in sorted(self.queue.items()): + # TODO: modulate the parameters self.send_buffered(queue, command[0], command[1]) #self.queue.clear() except Exception: @@ -154,9 +158,11 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei last = {} def send_buffered(self, queue, form, args, record = True): # only send if new command is different to the last one we sent - if self.last.get(queue)!=(form,args): + mod_args = self.modulate_arguments(self.command_by_queue.get(queue), args) + if self.last.get(queue)!=(form,mod_args): #print("WJSendPlugin>> send_buffered attempting to parse queue\t%s with form\t'%s' and args\t%s" % (queue, form, args)) - output = form.format(*args) + # TODO: actually output modulated version of args + output = form.format(*mod_args) self.send_serial_string(output) self.last[queue] = (form,args) if record: @@ -176,74 +182,91 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei def parserlist(self): return [ ( r"^open_serial$", self.open_serial ), - ( r"^wj_send_serial_([0-9a-zA-Z:]*)$", self.send_serial_string ), - ( r"^wj_set_colour_([A|B|T])_([x|y])$", self.set_colour ), - ( r"^wj_set_back_colour_([x|y|z])$", self.set_back_colour ), - ( r"^wj_set_back_wash_colour_([x|y|z])$", self.set_back_wash_colour ), - ( r"^wj_set_position_([N|L])_([x|y])$", self.set_position ), - ( r"^wj_set_mix$", self.set_mix ), - ( r"^wj_send_append_pad_([0-9]*)_([[:0-9a-zA-Z]*)$", self.send_append_pad ), - ( r"^wj_send_append_([:0-9a-zA-Z]*)$", self.send_append ), + ( r"^wj_send_serial:([0-9a-zA-Z:]*)$", self.send_serial_string ), + #( r"^wj_set_colour:([A|B|T])_([x|y])$", self.set_colour ), + #( r"^wj_set_back_colour:([x|y|z])$", self.set_back_colour ), + #( r"^wj_set_position:([N|L])_([x|y])$", self.set_position ), + #( r"^wj_set_mix$", self.set_mix ), + ( r"^wj_send_append_pad:([0-9]*)_([[:0-9a-zA-Z]*)$", self.send_append_pad ), + ( r"^wj_send_append:([:0-9a-zA-Z]*)$", self.send_append ), + ( r"^wj_set_([a-zA-Z_]*)[:]?([a-zA-Z_]*)$", self.catch_all ) ] - # methods for handling some Panasonic control settings as parameters - # TODO: come up with a way to represent this programmatically - # TODO: add more! + def catch_all(self, param, argument_name, value): + #print ("got catch-all %s, %s, %s" % (param, argument_name, value)) + #arguments = packed_arguments.split("_") + [ value ] + #print("commands looks like %s" % self.commands) + msg = self.commands[param] + if len(msg['arg_names'])==1: argument_name = msg['arg_names'][0] + msg['arguments'][argument_name] = int(value*255) # = arguments + self.send(msg['queue'], msg['form'], [ msg['arguments'][p] for p in msg['arg_names'] ] ) - colour_x = 127 - colour_y = 127 - def set_colour(self, chan, dim, value): - # chan can be A, B or T (both) - if dim=='x': - self.colour_x = int(255*value) - elif dim=='y': - self.colour_y = int(255*value) + def modulate_arguments(self, command, args): + args = args.copy() + if self.DEBUG: print("modulate_arguments passed %s and %s" % (command,args)) + for slot in range(0,4): + modlevels = command.get('modulation',[{}]*4)[slot] + if self.DEBUG: print("\tfor modulate_arguments for slot %s got modlevels: %s" % (slot, modlevels)) + #if len(command.get('modulation',[{}]*4)[slot])>0: + for i,m in enumerate(modlevels.values()): + if m>0.0: + if self.DEBUG: print("\t\tupdating modulation slot %s with %s * %s" % (i, m, self.modulation_value[slot])) + newvalue = int(args[i] * m * self.modulation_value[slot]) + if self.DEBUG: print("\t\tnewvalue is %s" %newvalue) + args[i] = newvalue #int(args[i] * (int(255 * m * self.modulation_value[slot]))) - self.send('VCC', "VCC:{}{:02X}{:02X}", [chan, self.colour_x, self.colour_y]) + """ if command.get('modulation',{}).get(arg_name,None) is not None: + args[argindex] = args[argindex] * command.get('modulation',{}).get(arg_name,[0.0]*4)[argindex] + #for slot in range(0,4): # for each modulation slot""" + return args - # RGB control of matte colour! - back_colour_x = 127 - back_colour_y = 127 - back_colour_z = 127 - def set_back_colour(self, dim, value): - # chan can be A, B or T (both) - if dim=='x': - self.back_colour_x = int(255*value) - elif dim=='y': - self.back_colour_y = int(255*value) - elif dim=='z': - self.back_colour_z = int(255*value) - - self.send('VBM', "VBM:{:02X}{:02X}{:02X}", [ self.back_colour_x,self.back_colour_y,self.back_colour_z ]) - - # this doesnt seem to work on WJ-MX30 at least, or maybe i dont know how to get it into the right mode? - # TODO: replace with downstream key control - back_wash_colour_x = 127 - back_wash_colour_y = 127 - back_wash_colour_z = 127 - def set_back_wash_colour(self, dim, value): - # chan can be A, B or T (both) - if dim=='x': - self.back_wash_colour_x = int(255*value) - elif dim=='y': - self.back_wash_colour_y = int(255*value) - elif dim=='z': - self.back_wash_colour_z = int(255*value) - - self.send('VBW', "VBW:{:02X}{:02X}{:02X}", [ self.back_wash_colour_x,self.back_wash_colour_y,self.back_wash_colour_z ] ) - - # positioner joystick - position_x = 127 - position_y = 127 - def set_position(self, mode, dim, value): - if dim=='y': # yes, y is really x! - self.position_x = int(255*value) - elif dim=='x': # yes, x is really y! - self.position_y = int(255*value) - - self.send('VPS:{}'.format(mode), "VPS:{}{:02X}{:02X}", [ mode,self.position_x,self.position_y ]) - - # wipe / mix level - def set_mix(self, value): - self.send('VMM', "VMM:{:02X}", [ int(255*value) ]) + commands = { + 'colour_gain_T': { + 'name': 'Colour Corrector gain - both', + 'queue': 'VCG', + 'form': 'VCG:T{:02X}', + 'arg_names': [ 'v' ], + 'arguments': { 'v': 127 } + }, + 'colour_T': { + 'name': 'Colour Corrector - both', + 'queue': 'VCC', + 'form': 'VCC:T{:02X}{:02X}', + 'arg_names': [ 'x', 'y' ], + 'arguments': { 'x': 127, 'y': 127 }, + 'modulation': [ {}, {}, { 'x': 1.0 }, { 'y': 1.0 } ] + #'callback': self.set_colour + }, + 'mix': { + 'name': 'Mix/wipe', + 'queue': 'VMM', + 'form': 'VMM:{:02X}', + 'arg_names': [ 'v' ], + 'arguments': { 'v': 127 }, + 'modulation': [ { 'v': 1.0 }, {}, {}, {} ] + }, + 'back_colour': { + 'name': 'Back colour/matte HSV', + 'queue': 'VBM', + 'form': 'VBM:{:02X}{:02X}{:02X}', + 'arg_names': [ 'h', 's', 'v' ], + 'arguments': { 'h': 127, 's': 127, 'v': 127 }, + 'modulation': [ {}, { 'h': 1.0 }, {}, {} ] + }, + 'position_N': { + 'name': 'Positioner joystick', + 'queue': 'VPS', + 'form': 'VPS:N{:02X}{:02X}', + 'arg_names': [ 'y', 'x' ], + 'arguments': { 'y': 127, 'x': 127 } + }, + 'dsk_level': { + 'name': 'Downstream Key level', + 'queue': 'VDL', + 'form': 'VDL:{:02X}', + 'arg_names': [ 'v' ], + 'arguments': { 'v': 127 } + } + } + command_by_queue = {}