diff --git a/.gitignore b/.gitignore index dce1033..1352d98 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ json_objects/display_data.json json_objects/shader_bank_data.json json_objects/settings.json +json_objects/plugins/*/*.json +json_objects/active_plugins.json *.patch *.orig *.rej diff --git a/data_centre/data.py b/data_centre/data.py index 80f3a4d..07c03f3 100644 --- a/data_centre/data.py +++ b/data_centre/data.py @@ -119,8 +119,11 @@ class Data(object): self.plugins = plugin_collection.PluginCollection("plugins", self.message_handler, self) self.compare_plugins_list() + def get_active_plugin_class_names(self): + return [k for k,v in self.active_plugins.items() if v is True] + def compare_plugins_list(self): - current_plugins = [type(plugin).__name__ for plugin in self.plugins.get_plugins(plugin_collection.Plugin)] + current_plugins = [type(plugin).__name__ for plugin in self.plugins.get_plugins(include_disabled=True)] plugins_to_add = set(current_plugins) - set(self.active_plugins.keys()) plugins_to_remove = set(self.active_plugins) - set(current_plugins) for k in plugins_to_remove: @@ -466,15 +469,13 @@ class Data(object): display_modes.append(["SHDR_BNK",'PLAY_SHADER']) if self.settings['detour']['TRY_DEMO']['value'] == 'enabled': display_modes.append(["FRAMES",'NAV_DETOUR']) - if self.settings['system']['USE_PLUGINS']['value'] == 'enabled': + if self.settings['system'].setdefault('USE_PLUGINS',self.default_settings.setdefault('USE_PLUGINS',{'value': 'enabled'})).get('value') == 'enabled': display_modes.append(["PLUGINS",'NAV_PLUGINS']) if hasattr(self, 'plugins') and self.plugins is not None: from data_centre.plugin_collection import DisplayPlugin for plugin in self.plugins.get_plugins(DisplayPlugin): - is_active = self.active_plugins[type(plugin).__name__] - if is_active: - display_modes.append(plugin.get_display_modes()) + display_modes.append(plugin.get_display_modes()) if not with_nav_mode: return [mode[0] for mode in display_modes] diff --git a/data_centre/plugin_collection.py b/data_centre/plugin_collection.py index a0e304f..d4b7db3 100644 --- a/data_centre/plugin_collection.py +++ b/data_centre/plugin_collection.py @@ -9,14 +9,20 @@ class Plugin(object): """Base class that each plugin must inherit from. within this class you must define the methods that all of your plugins must implement """ - disabled = False + #disabled = False + @property + def disabled(self): + return type(self).__name__ not in self.pc.data.get_active_plugin_class_names() def __init__(self, plugin_collection): self.description = 'UNKNOWN' self.pc = plugin_collection - def quit_plugin(self): - print("quitting " + type(self).__name__) + def stop_plugin(self): + print(">>Stopping plugin " + type(self).__name__) + + def start_plugin(self): + print(">>Starting plugin " + type(self).__name__) class MidiFeedbackPlugin(Plugin): """Base class for MIDI feedback plugins @@ -100,16 +106,18 @@ class SequencePlugin(Plugin): speed = 0.25 #1.0 def move_delta(self, delta, speed): self.position += delta * speed - if self.looping and self.position>1.0: - self.position = 0.0 - elif self.looping and self.position<0: - self.position = 1.0 + if self.position>1.0: + self.position = self.position-1.0 + self.iterations_count += 1 + elif self.position<0.0: + self.position = self.position+1.0 + self.iterations_count += 1 store_passed = None pause_flag = True stop_flag = False looping = True - automation_start = None + #automation_start = None iterations_count = 0 duration = 2000 frequency = 100 @@ -146,12 +154,12 @@ class SequencePlugin(Plugin): #print ("%s: reset automation_start to %s" % (time.time()-self.automation_start,self.automation_start)) #return""" - if not self.stop_flag: # and (now - self.automation_start < self.duration/1000): + if not self.stop_flag and not self.disabled: # and (now - self.automation_start < self.duration/1000): self.pc.midi_input.root.after(self.frequency, self.run_automation) else: - print("%s: stopping ! (stop_flag %s)" % ((now - self.automation_start),self.stop_flag) ) + #print("%s: stopping ! (stop_flag %s)" % ((now - self.automation_start),self.stop_flag) ) self.stop_flag = False - self.automation_start = None + #self.automation_start = None self.iterations_count = 0 def is_paused(self): @@ -482,13 +490,29 @@ class PluginCollection(object): def quit_plugins(self): # tell each plugin to quit for plugin in self.get_plugins(): - plugin.quit_plugin() + if not plugin.disabled: plugin.stop_plugin() - def get_plugins(self, clazz = None): + def stop_plugin_name(self, name): + for plugin in self.get_plugins(include_disabled=True): + if type(plugin).__name__ == name: + plugin.stop_plugin() + + def start_plugin_name(self, name): + #print("start_plugin_name got %s"%name) + for plugin in self.get_plugins(include_disabled=True): + #print("looking for %s vs %s" % (type(plugin).__name__, name)) + if type(plugin).__name__ == name: + #print("starting %s" %name) + plugin.start_plugin() + + + def get_plugins(self, clazz = None, include_disabled = False): + # is_active = self.active_plugins[type(plugin).__name__] if clazz: - return [c for c in self.plugins if (isinstance(c, clazz) and not c.disabled)] + return [c for c in self.plugins if isinstance(c, clazz) and (include_disabled or not c.disabled)] + #and type(c).__name__ in self.data.get_active_plugin_class_names()) else: - return [c for c in self.plugins if not c.disabled] + return [c for c in self.plugins if include_disabled or not c.disabled] def walk_package(self, package): """Recursively walk the supplied package to retrieve all plugins @@ -510,7 +534,7 @@ class PluginCollection(object): # Now that we have looked at all the modules in the current package, start looking # recursively for additional modules in sub packages - all_current_paths = [] + """all_current_paths = [] if isinstance(imported_package.__path__, str): all_current_paths.append(imported_package.__path__) else: @@ -525,4 +549,4 @@ class PluginCollection(object): # For each sub directory, apply the walk_package method recursively for child_pkg in child_pkgs: - self.walk_package(package + '.' + child_pkg) + self.walk_package(package + '.' + child_pkg)""" diff --git a/display_centre/display.py b/display_centre/display.py index dbae275..ec3f36e 100644 --- a/display_centre/display.py +++ b/display_centre/display.py @@ -180,7 +180,10 @@ class Display(object): self.display_text.insert(END, '{} \n'.format(self.body_title)) self.display_text.insert(END, '{:<40} {:<5} \n'.format('plugin', 'is_active')) ## showing list of plugins: - plugins_list = sorted(self.data.active_plugins.items()) + plugins_list = sorted([ + (type(plugin).__name__, type(plugin).__name__ in self.data.get_active_plugin_class_names())\ + for plugin in self.data.plugins.get_plugins(include_disabled=True) + ]) self.plugins_menu.menu_list = plugins_list number_of_plugins = len(plugins_list) diff --git a/display_centre/menu.py b/display_centre/menu.py index f0a99d8..5dcfd57 100644 --- a/display_centre/menu.py +++ b/display_centre/menu.py @@ -208,7 +208,11 @@ class PluginsMenu(Menu): def enter_on_plugins_selection(self): selected_item = sorted(self.data.active_plugins)[self.selected_list_index] state = self.data.active_plugins[selected_item] - self.data.update_active_plugins(selected_item, not state) + self.data.update_active_plugins(selected_item, not state) + if state: + self.data.plugins.stop_plugin_name(selected_item) + else: + self.data.plugins.start_plugin_name(selected_item) class ShadersMenu(Menu): diff --git a/json_objects/active_plugins.json b/json_objects/active_plugins.json deleted file mode 100644 index bdf7d9c..0000000 --- a/json_objects/active_plugins.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "LFOModulationPlugin": false, - "ManipulatePlugin": true, - "MidiFeedbackAPCKey25Plugin": true, - "MidiFeedbackLaunchpadPlugin": false, - "ShaderLoopRecordPlugin": false, - "ShaderQuickPresetPlugin": true, - "WJSendPlugin": false -} \ No newline at end of file diff --git a/plugins/LFOModulation.py b/plugins/LFOModulation.py index edd6e37..dc779d4 100644 --- a/plugins/LFOModulation.py +++ b/plugins/LFOModulation.py @@ -3,7 +3,6 @@ import data_centre.plugin_collection from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, DisplayPlugin class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): - disabled = False MAX_LFOS = 4 @@ -20,8 +19,10 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): #self.PRESET_FILE_NAME = "ShaderLoopRecordPlugin/frames.json" - self.pc.shaders.root.after(1000, self.run_automation) - + self.pc.shaders.root.after(1000, self.start_plugin) + + def start_plugin(self): + self.pc.shaders.root.after(0, self.run_automation) # DisplayPlugin methods def get_display_modes(self): @@ -100,10 +101,10 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): import time now = time.time() - if self.pc.data.plugins is None: + if self.pc.data.plugins is None: # not initialised yet return - if not self.active: + if not self.active: # output is disabled return for lfo in range(0,self.MAX_LFOS): diff --git a/plugins/ManipulatePlugin.py b/plugins/ManipulatePlugin.py index bd36174..4111f33 100644 --- a/plugins/ManipulatePlugin.py +++ b/plugins/ManipulatePlugin.py @@ -42,7 +42,6 @@ TODO: >> ?? invert|set_the_shader_param_0_layer_>>print_arguments>>set_variab """ class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): - disabled = False DEBUG = False diff --git a/plugins/MidiFeedbackAPCKey25Plugin.py b/plugins/MidiFeedbackAPCKey25Plugin.py index 48c4616..4095070 100644 --- a/plugins/MidiFeedbackAPCKey25Plugin.py +++ b/plugins/MidiFeedbackAPCKey25Plugin.py @@ -3,7 +3,7 @@ from data_centre.plugin_collection import MidiFeedbackPlugin import mido class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin): - disabled = False + #disabled = False status = {} diff --git a/plugins/MidiFeedbackLaunchpadPlugin.py b/plugins/MidiFeedbackLaunchpadPlugin.py index b853f4a..81d3209 100644 --- a/plugins/MidiFeedbackLaunchpadPlugin.py +++ b/plugins/MidiFeedbackLaunchpadPlugin.py @@ -1,10 +1,10 @@ from data_centre import plugin_collection from data_centre.plugin_collection import MidiFeedbackPlugin import mido -from plugins.MidiFeedbackAPCKey25Plugin import MidiFeedbackAPCKey25Plugin +import plugins +#from plugins.MidiFeedbackAPCKey25Plugin import MidiFeedbackAPCKey25Plugin -class MidiFeedbackLaunchpadPlugin(MidiFeedbackAPCKey25Plugin): - disabled = False +class MidiFeedbackLaunchpadPlugin(plugins.MidiFeedbackAPCKey25Plugin.MidiFeedbackAPCKey25Plugin): status = {} diff --git a/plugins/ShaderLoopRecordPlugin.py b/plugins/ShaderLoopRecordPlugin.py index fc3cf29..f2a0bf4 100644 --- a/plugins/ShaderLoopRecordPlugin.py +++ b/plugins/ShaderLoopRecordPlugin.py @@ -3,7 +3,7 @@ from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, Display from plugins.frame_manager import Frame class ShaderLoopRecordPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): - disabled = False + MAX_CLIPS = 8 frames = [] @@ -45,8 +45,8 @@ class ShaderLoopRecordPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): def save_presets(self): self.pc.update_json(self.PRESET_FILE_NAME, self.frames) - def quit_plugin(self): - super().quit_plugin() + def stop_plugin(self): + super().stop_plugin() self.save_presets() # DisplayPlugin methods diff --git a/plugins/ShaderQuickPresetPlugin.py b/plugins/ShaderQuickPresetPlugin.py index ce31a6e..d06203b 100644 --- a/plugins/ShaderQuickPresetPlugin.py +++ b/plugins/ShaderQuickPresetPlugin.py @@ -4,7 +4,6 @@ import copy from plugins.frame_manager import Frame class ShaderQuickPresetPlugin(ActionsPlugin): #,SequencePlugin): - disabled = False MAX_PRESETS = 8 @@ -25,8 +24,8 @@ class ShaderQuickPresetPlugin(ActionsPlugin): #,SequencePlugin): def save_presets(self): self.pc.update_json(self.PRESET_FILE_NAME, self.presets) - def quit_plugin(self): - super().quit_plugin() + def stop_plugin(self): + super().stop_plugin() self.save_presets() @property diff --git a/plugins/SoundReactPlugin.py b/plugins/SoundReactPlugin.py index c668380..baf2ccc 100644 --- a/plugins/SoundReactPlugin.py +++ b/plugins/SoundReactPlugin.py @@ -12,7 +12,6 @@ from statistics import mean np.set_printoptions(suppress=True) # don't use scientific notationn class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): - disabled = False DEBUG = False @@ -20,6 +19,8 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): stop_flag = False pause_flag = False + stream = None + CHUNK = 4096 # number of data points to read at a time RATE = 48000 #44100 # time resolution of the recording device (Hz) @@ -30,7 +31,7 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): def __init__(self, plugin_collection): super().__init__(plugin_collection) - #self.PRESET_FILE_NAME = "ShaderLoopRecordPlugin/frames.json" + """#self.PRESET_FILE_NAME = "ShaderLoopRecordPlugin/frames.json" if self.active and not self.disabled: try: p=pyaudio.PyAudio() @@ -43,7 +44,37 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): print ("now setting to run automation..") - self.pc.shaders.root.after(500, self.run_automation) + self.pc.shaders.root.after(500, self.run_automation)""" + if not self.disabled: + self.start_plugin() + + def stop_plugin(self): + self.close_sound_device() + super().stop_plugin() + + def start_plugin(self): + super().start_plugin() + self.open_sound_device() + + def open_sound_device(self): + try: + self.p=pyaudio.PyAudio() + self.stream=self.p.open(format=pyaudio.paInt16,channels=1,rate=self.RATE,input=True, + frames_per_buffer=self.CHUNK) + except: + print("Failed to open sound device - disabling SoundReactPlugin!") + self.active = False + return + + self.pc.shaders.root.after(250, self.run_automation) + + def close_sound_device(self): + if self.stream: + self.stream.stop_stream() + self.stream.close() + self.stream = None + if self.p: + self.p.terminate() @property def sources(self): @@ -97,7 +128,7 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): energy_history = [] def run_sequence(self, position): # position is irrelvant for this plugin, we just want to run continuously - if not self.active: + if not self.active or self.stream is None: return data = np.fromstring(self.stream.read(self.CHUNK, exception_on_overflow = False),dtype=np.int16) diff --git a/plugins/WJSendPlugin.py b/plugins/WJSendPlugin.py index da4f2ec..ee8728a 100644 --- a/plugins/WJSendPlugin.py +++ b/plugins/WJSendPlugin.py @@ -5,7 +5,6 @@ from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, Display import threading class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationReceiverPlugin, AutomationSourcePlugin): - disabled = False#True DEBUG = False #True ser = None @@ -28,24 +27,23 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei def __init__(self, plugin_collection): super().__init__(plugin_collection) - if self.disabled: + """if self.disabled: print ("WJSendPlugin is disabled, not opening serial") - return + return""" self.presets = self.load_presets() print("read presets:\n%s\n" % self.presets) # load the stored modulation levels into the current config - for cmd,levels in self.presets['modulation_levels'].items(): #.setdefault(cmd,[{},{},{},{}]): - print("setting commands[%s]['modulation'] to %s" % (cmd, levels)) + for cmd,levels in self.presets['modulation_levels'].items(): self.commands[cmd]['modulation'] = levels - # build a reverse map for later use + # build a reverse map of friendly name -> command struct for later use for cmd,struct in self.commands.items(): self.command_by_queue[struct['queue']] = struct - self.pc.actions.tk.after(500, self.refresh) + self.pc.actions.tk.after(500, self.start_plugin) - self.selected_command_name = list(sorted(self.commands.keys()))[0] + self.selected_command_name = list(sorted(self.commands.keys()))[0] # select first command def load_presets(self): print("trying load presets? %s " % self.PRESET_FILE_NAME) @@ -56,8 +54,11 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei self.presets.setdefault('modulation_levels',{})[cmd] = struct.get('modulation',[{},{},{},{}]) self.pc.update_json(self.PRESET_FILE_NAME, self.presets) - def quit_plugin(self): - super().quit_plugin() + def start_plugin(self): + self.pc.actions.tk.after(0, self.refresh) + + def stop_plugin(self): + super().stop_plugin() self.save_presets() @@ -70,9 +71,6 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei #print(">>> reporting frame data for rec\n\t%s" % diff) return diff - """def clear_recorded_frame(self): - self.last_record = {}""" - def recall_frame_data(self, data): if data is None: return @@ -219,7 +217,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei with self.queue_lock: self.queue.clear() - if self.ser is not None: + if self.ser is not None and not self.disabled: self.pc.shaders.root.after(self.THROTTLE, self.refresh) def send(self, queue, form, args):