tidy up + change way plugins are found+listed, solves some problems, plugins use the active status to indicate disabled and now have start/stop methods (seems to work to stop+restart Sound+WJ ok, needs restart to get MidiFeedback working if its disabled? needs more tidying up and testing and fixing

This commit is contained in:
Tristan Rowley
2020-03-01 23:37:58 +00:00
parent 9738fc74a5
commit 99006a2cd7
14 changed files with 120 additions and 67 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@
json_objects/display_data.json json_objects/display_data.json
json_objects/shader_bank_data.json json_objects/shader_bank_data.json
json_objects/settings.json json_objects/settings.json
json_objects/plugins/*/*.json
json_objects/active_plugins.json
*.patch *.patch
*.orig *.orig
*.rej *.rej

View File

@@ -119,8 +119,11 @@ class Data(object):
self.plugins = plugin_collection.PluginCollection("plugins", self.message_handler, self) self.plugins = plugin_collection.PluginCollection("plugins", self.message_handler, self)
self.compare_plugins_list() 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): 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_add = set(current_plugins) - set(self.active_plugins.keys())
plugins_to_remove = set(self.active_plugins) - set(current_plugins) plugins_to_remove = set(self.active_plugins) - set(current_plugins)
for k in plugins_to_remove: for k in plugins_to_remove:
@@ -466,15 +469,13 @@ class Data(object):
display_modes.append(["SHDR_BNK",'PLAY_SHADER']) display_modes.append(["SHDR_BNK",'PLAY_SHADER'])
if self.settings['detour']['TRY_DEMO']['value'] == 'enabled': if self.settings['detour']['TRY_DEMO']['value'] == 'enabled':
display_modes.append(["FRAMES",'NAV_DETOUR']) 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']) display_modes.append(["PLUGINS",'NAV_PLUGINS'])
if hasattr(self, 'plugins') and self.plugins is not None: if hasattr(self, 'plugins') and self.plugins is not None:
from data_centre.plugin_collection import DisplayPlugin from data_centre.plugin_collection import DisplayPlugin
for plugin in self.plugins.get_plugins(DisplayPlugin): for plugin in self.plugins.get_plugins(DisplayPlugin):
is_active = self.active_plugins[type(plugin).__name__] display_modes.append(plugin.get_display_modes())
if is_active:
display_modes.append(plugin.get_display_modes())
if not with_nav_mode: if not with_nav_mode:
return [mode[0] for mode in display_modes] return [mode[0] for mode in display_modes]

View File

@@ -9,14 +9,20 @@ class Plugin(object):
"""Base class that each plugin must inherit from. within this class """Base class that each plugin must inherit from. within this class
you must define the methods that all of your plugins must implement 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): def __init__(self, plugin_collection):
self.description = 'UNKNOWN' self.description = 'UNKNOWN'
self.pc = plugin_collection self.pc = plugin_collection
def quit_plugin(self): def stop_plugin(self):
print("quitting " + type(self).__name__) print(">>Stopping plugin " + type(self).__name__)
def start_plugin(self):
print(">>Starting plugin " + type(self).__name__)
class MidiFeedbackPlugin(Plugin): class MidiFeedbackPlugin(Plugin):
"""Base class for MIDI feedback plugins """Base class for MIDI feedback plugins
@@ -100,16 +106,18 @@ class SequencePlugin(Plugin):
speed = 0.25 #1.0 speed = 0.25 #1.0
def move_delta(self, delta, speed): def move_delta(self, delta, speed):
self.position += delta * speed self.position += delta * speed
if self.looping and self.position>1.0: if self.position>1.0:
self.position = 0.0 self.position = self.position-1.0
elif self.looping and self.position<0: self.iterations_count += 1
self.position = 1.0 elif self.position<0.0:
self.position = self.position+1.0
self.iterations_count += 1
store_passed = None store_passed = None
pause_flag = True pause_flag = True
stop_flag = False stop_flag = False
looping = True looping = True
automation_start = None #automation_start = None
iterations_count = 0 iterations_count = 0
duration = 2000 duration = 2000
frequency = 100 frequency = 100
@@ -146,12 +154,12 @@ class SequencePlugin(Plugin):
#print ("%s: reset automation_start to %s" % (time.time()-self.automation_start,self.automation_start)) #print ("%s: reset automation_start to %s" % (time.time()-self.automation_start,self.automation_start))
#return""" #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) self.pc.midi_input.root.after(self.frequency, self.run_automation)
else: 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.stop_flag = False
self.automation_start = None #self.automation_start = None
self.iterations_count = 0 self.iterations_count = 0
def is_paused(self): def is_paused(self):
@@ -482,13 +490,29 @@ class PluginCollection(object):
def quit_plugins(self): def quit_plugins(self):
# tell each plugin to quit # tell each plugin to quit
for plugin in self.get_plugins(): 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: 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: 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): def walk_package(self, package):
"""Recursively walk the supplied package to retrieve all plugins """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 # Now that we have looked at all the modules in the current package, start looking
# recursively for additional modules in sub packages # recursively for additional modules in sub packages
all_current_paths = [] """all_current_paths = []
if isinstance(imported_package.__path__, str): if isinstance(imported_package.__path__, str):
all_current_paths.append(imported_package.__path__) all_current_paths.append(imported_package.__path__)
else: else:
@@ -525,4 +549,4 @@ class PluginCollection(object):
# For each sub directory, apply the walk_package method recursively # For each sub directory, apply the walk_package method recursively
for child_pkg in child_pkgs: for child_pkg in child_pkgs:
self.walk_package(package + '.' + child_pkg) self.walk_package(package + '.' + child_pkg)"""

View File

@@ -180,7 +180,10 @@ class Display(object):
self.display_text.insert(END, '{} \n'.format(self.body_title)) self.display_text.insert(END, '{} \n'.format(self.body_title))
self.display_text.insert(END, '{:<40} {:<5} \n'.format('plugin', 'is_active')) self.display_text.insert(END, '{:<40} {:<5} \n'.format('plugin', 'is_active'))
## showing list of plugins: ## 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 self.plugins_menu.menu_list = plugins_list
number_of_plugins = len(plugins_list) number_of_plugins = len(plugins_list)

View File

@@ -209,6 +209,10 @@ class PluginsMenu(Menu):
selected_item = sorted(self.data.active_plugins)[self.selected_list_index] selected_item = sorted(self.data.active_plugins)[self.selected_list_index]
state = self.data.active_plugins[selected_item] 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): class ShadersMenu(Menu):

View File

@@ -1,9 +0,0 @@
{
"LFOModulationPlugin": false,
"ManipulatePlugin": true,
"MidiFeedbackAPCKey25Plugin": true,
"MidiFeedbackLaunchpadPlugin": false,
"ShaderLoopRecordPlugin": false,
"ShaderQuickPresetPlugin": true,
"WJSendPlugin": false
}

View File

@@ -3,7 +3,6 @@ import data_centre.plugin_collection
from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, DisplayPlugin from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, DisplayPlugin
class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
disabled = False
MAX_LFOS = 4 MAX_LFOS = 4
@@ -20,8 +19,10 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
#self.PRESET_FILE_NAME = "ShaderLoopRecordPlugin/frames.json" #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 # DisplayPlugin methods
def get_display_modes(self): def get_display_modes(self):
@@ -100,10 +101,10 @@ class LFOModulationPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
import time import time
now = time.time() now = time.time()
if self.pc.data.plugins is None: if self.pc.data.plugins is None: # not initialised yet
return return
if not self.active: if not self.active: # output is disabled
return return
for lfo in range(0,self.MAX_LFOS): for lfo in range(0,self.MAX_LFOS):

View File

@@ -42,7 +42,6 @@ TODO: >> ?? invert|set_the_shader_param_0_layer_>>print_arguments>>set_variab
""" """
class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin): class ManipulatePlugin(ActionsPlugin,DisplayPlugin,ModulationReceiverPlugin):
disabled = False
DEBUG = False DEBUG = False

View File

@@ -3,7 +3,7 @@ from data_centre.plugin_collection import MidiFeedbackPlugin
import mido import mido
class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin): class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin):
disabled = False #disabled = False
status = {} status = {}

View File

@@ -1,10 +1,10 @@
from data_centre import plugin_collection from data_centre import plugin_collection
from data_centre.plugin_collection import MidiFeedbackPlugin from data_centre.plugin_collection import MidiFeedbackPlugin
import mido import mido
from plugins.MidiFeedbackAPCKey25Plugin import MidiFeedbackAPCKey25Plugin import plugins
#from plugins.MidiFeedbackAPCKey25Plugin import MidiFeedbackAPCKey25Plugin
class MidiFeedbackLaunchpadPlugin(MidiFeedbackAPCKey25Plugin): class MidiFeedbackLaunchpadPlugin(plugins.MidiFeedbackAPCKey25Plugin.MidiFeedbackAPCKey25Plugin):
disabled = False
status = {} status = {}

View File

@@ -3,7 +3,7 @@ from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, Display
from plugins.frame_manager import Frame from plugins.frame_manager import Frame
class ShaderLoopRecordPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): class ShaderLoopRecordPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
disabled = False
MAX_CLIPS = 8 MAX_CLIPS = 8
frames = [] frames = []
@@ -45,8 +45,8 @@ class ShaderLoopRecordPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
def save_presets(self): def save_presets(self):
self.pc.update_json(self.PRESET_FILE_NAME, self.frames) self.pc.update_json(self.PRESET_FILE_NAME, self.frames)
def quit_plugin(self): def stop_plugin(self):
super().quit_plugin() super().stop_plugin()
self.save_presets() self.save_presets()
# DisplayPlugin methods # DisplayPlugin methods

View File

@@ -4,7 +4,6 @@ import copy
from plugins.frame_manager import Frame from plugins.frame_manager import Frame
class ShaderQuickPresetPlugin(ActionsPlugin): #,SequencePlugin): class ShaderQuickPresetPlugin(ActionsPlugin): #,SequencePlugin):
disabled = False
MAX_PRESETS = 8 MAX_PRESETS = 8
@@ -25,8 +24,8 @@ class ShaderQuickPresetPlugin(ActionsPlugin): #,SequencePlugin):
def save_presets(self): def save_presets(self):
self.pc.update_json(self.PRESET_FILE_NAME, self.presets) self.pc.update_json(self.PRESET_FILE_NAME, self.presets)
def quit_plugin(self): def stop_plugin(self):
super().quit_plugin() super().stop_plugin()
self.save_presets() self.save_presets()
@property @property

View File

@@ -12,7 +12,6 @@ from statistics import mean
np.set_printoptions(suppress=True) # don't use scientific notationn np.set_printoptions(suppress=True) # don't use scientific notationn
class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
disabled = False
DEBUG = False DEBUG = False
@@ -20,6 +19,8 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
stop_flag = False stop_flag = False
pause_flag = False pause_flag = False
stream = None
CHUNK = 4096 # number of data points to read at a time CHUNK = 4096 # number of data points to read at a time
RATE = 48000 #44100 # time resolution of the recording device (Hz) RATE = 48000 #44100 # time resolution of the recording device (Hz)
@@ -30,7 +31,7 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
def __init__(self, plugin_collection): def __init__(self, plugin_collection):
super().__init__(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: if self.active and not self.disabled:
try: try:
p=pyaudio.PyAudio() p=pyaudio.PyAudio()
@@ -43,7 +44,37 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
print ("now setting to run automation..") 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 @property
def sources(self): def sources(self):
@@ -97,7 +128,7 @@ class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin):
energy_history = [] energy_history = []
def run_sequence(self, position): def run_sequence(self, position):
# position is irrelvant for this plugin, we just want to run continuously # 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 return
data = np.fromstring(self.stream.read(self.CHUNK, exception_on_overflow = False),dtype=np.int16) data = np.fromstring(self.stream.read(self.CHUNK, exception_on_overflow = False),dtype=np.int16)

View File

@@ -5,7 +5,6 @@ from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, Display
import threading import threading
class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationReceiverPlugin, AutomationSourcePlugin): class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationReceiverPlugin, AutomationSourcePlugin):
disabled = False#True
DEBUG = False #True DEBUG = False #True
ser = None ser = None
@@ -28,24 +27,23 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei
def __init__(self, plugin_collection): def __init__(self, plugin_collection):
super().__init__(plugin_collection) super().__init__(plugin_collection)
if self.disabled: """if self.disabled:
print ("WJSendPlugin is disabled, not opening serial") print ("WJSendPlugin is disabled, not opening serial")
return return"""
self.presets = self.load_presets() self.presets = self.load_presets()
print("read presets:\n%s\n" % self.presets) print("read presets:\n%s\n" % self.presets)
# load the stored modulation levels into the current config # load the stored modulation levels into the current config
for cmd,levels in self.presets['modulation_levels'].items(): #.setdefault(cmd,[{},{},{},{}]): for cmd,levels in self.presets['modulation_levels'].items():
print("setting commands[%s]['modulation'] to %s" % (cmd, levels))
self.commands[cmd]['modulation'] = levels 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(): for cmd,struct in self.commands.items():
self.command_by_queue[struct['queue']] = struct 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): def load_presets(self):
print("trying load presets? %s " % self.PRESET_FILE_NAME) 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.presets.setdefault('modulation_levels',{})[cmd] = struct.get('modulation',[{},{},{},{}])
self.pc.update_json(self.PRESET_FILE_NAME, self.presets) self.pc.update_json(self.PRESET_FILE_NAME, self.presets)
def quit_plugin(self): def start_plugin(self):
super().quit_plugin() self.pc.actions.tk.after(0, self.refresh)
def stop_plugin(self):
super().stop_plugin()
self.save_presets() self.save_presets()
@@ -70,9 +71,6 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei
#print(">>> reporting frame data for rec\n\t%s" % diff) #print(">>> reporting frame data for rec\n\t%s" % diff)
return diff return diff
"""def clear_recorded_frame(self):
self.last_record = {}"""
def recall_frame_data(self, data): def recall_frame_data(self, data):
if data is None: if data is None:
return return
@@ -219,7 +217,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei
with self.queue_lock: with self.queue_lock:
self.queue.clear() 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) self.pc.shaders.root.after(self.THROTTLE, self.refresh)
def send(self, queue, form, args): def send(self, queue, form, args):