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/shader_bank_data.json
json_objects/settings.json
json_objects/plugins/*/*.json
json_objects/active_plugins.json
*.patch
*.orig
*.rej

View File

@@ -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,14 +469,12 @@ 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())
if not with_nav_mode:

View File

@@ -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)"""

View File

@@ -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)

View File

@@ -209,6 +209,10 @@ class PluginsMenu(Menu):
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)
if state:
self.data.plugins.stop_plugin_name(selected_item)
else:
self.data.plugins.start_plugin_name(selected_item)
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
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):

View File

@@ -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

View File

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

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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):