Backported basic code for MIDI controller autodetection (custom config file per controller), plugins with example plugin, and Akai APC Key 25 feedback plugin

This commit is contained in:
Tristan Rowley
2020-01-10 17:29:47 +00:00
parent 794a794847
commit 11a26ce6ed
11 changed files with 1098 additions and 21 deletions

View File

@@ -1,8 +1,8 @@
# Auto-generated Actions list # Auto-generated Actions list
Fri 3 Jan 22:08:36 UTC 2020 Fri 10 Jan 17:25:14 UTC 2020
for branch=feature_shader_midi for branch=feature_plugins
# Methods # Methods
* change_composite_setting(setting_value) * change_composite_setting(setting_value)
@@ -58,6 +58,7 @@ for branch=feature_shader_midi
* load_this_detour_shader * load_this_detour_shader
* _load_this_slot_into_next_player(slot) * _load_this_slot_into_next_player(slot)
* map_on_shaders_selection * map_on_shaders_selection
* modulate_param_layer_offset_to_amount(param, layer, amount)
* move_browser_selection_down * move_browser_selection_down
* move_browser_selection_up * move_browser_selection_up
* move_settings_selection_down * move_settings_selection_down
@@ -156,7 +157,7 @@ for branch=feature_shader_midi
* toggle_x_autorepeat * toggle_x_autorepeat
* try_pull_code_and_reset * try_pull_code_and_reset
# Dynamic routes ## Dynamic routes
* play_shader_([0-9])_([0-9]) * play_shader_([0-9])_([0-9])
* toggle_shader_layer_([0-2]) * toggle_shader_layer_([0-2])
* start_shader_layer_([0-2]) * start_shader_layer_([0-2])
@@ -171,6 +172,15 @@ for branch=feature_shader_midi
* set_shader_speed_layer_offset_([0-2])_amount * set_shader_speed_layer_offset_([0-2])_amount
* set_shader_speed_layer_([0-2])_amount * set_shader_speed_layer_([0-2])_amount
### Plugin routes
* test_plugin (from MidiActionsTestPlugin)
* cycle_shaders (from MidiActionsTestPlugin)
* run_automation (from MidiActionsTestPlugin)
* stop_automation (from MidiActionsTestPlugin)
* toggle_pause_automation (from MidiActionsTestPlugin)
* pause_automation (from MidiActionsTestPlugin)
* toggle_loop_automation (from MidiActionsTestPlugin)
---- ----
Autogenerated by dotfiles/generate-list-actions.sh Autogenerated by dotfiles/generate-list-actions.sh

View File

@@ -530,7 +530,7 @@ class Actions(object):
self.data.settings['shader']['STROBE_AMOUNT']['value'] = scaled_amount self.data.settings['shader']['STROBE_AMOUNT']['value'] = scaled_amount
def get_midi_status(self): def get_midi_status(self):
self.message_handler.set_message('INFO', 'midi status is {}'.format(self.data.midi_status)) self.message_handler.set_message('INFO', ("midi status is {} to %s"%(self.data.midi_device_name)).format(self.data.midi_status))
def cycle_midi_port_index(self): def cycle_midi_port_index(self):
self.data.midi_port_index = self.data.midi_port_index + 1 self.data.midi_port_index = self.data.midi_port_index + 1
@@ -573,13 +573,13 @@ class Actions(object):
self.data.update_setting_value('video', 'OUTPUT', 'composite') self.data.update_setting_value('video', 'OUTPUT', 'composite')
else: else:
self.data.update_setting_value('video', 'OUTPUT', 'hdmi') self.data.update_setting_value('video', 'OUTPUT', 'hdmi')
if self.data.settings['video']['HDMI_MODE']['value'] == "CEA 4 HDMI": if self.data.settings['video']['HDMI_MODE']['value'] == "CEA 4 HDMI":
self.data.update_setting_value('video', 'HDMI_MODE', 'CEA 4 HDMI') self.data.update_setting_value('video', 'HDMI_MODE', 'CEA 4 HDMI')
self.change_hdmi_settings('CEA 4 HDMI') self.change_hdmi_settings('CEA 4 HDMI')
def check_dev_mode(self): def check_dev_mode(self):
#### check if in dev mode:(ie not using the lcd screen) #### check if in dev mode:(ie not using the lcd screen)
@@ -591,8 +591,9 @@ class Actions(object):
def check_if_should_start_openframeworks(self): def check_if_should_start_openframeworks(self):
if self.data.settings['video']['VIDEOPLAYER_BACKEND']['value'] != 'omxplayer': if self.data.settings['video']['VIDEOPLAYER_BACKEND']['value'] != 'omxplayer':
self.openframeworks_process = subprocess.Popen([self.data.PATH_TO_OPENFRAMEWORKS +'apps/myApps/c_o_n_j_u_r/bin/c_o_n_j_u_r']) with open("conjur.log","w+") as out:
print('conjur pid is {}'.format(self.openframeworks_process.pid)) self.openframeworks_process = subprocess.Popen([self.data.PATH_TO_OPENFRAMEWORKS +'apps/myApps/c_o_n_j_u_r/bin/c_o_n_j_u_r'], stdout=out)
print('conjur pid is {}'.format(self.openframeworks_process.pid))
def exit_openframeworks(self): def exit_openframeworks(self):
self.video_driver.osc_client.send_message("/exit", True) self.video_driver.osc_client.send_message("/exit", True)
@@ -736,13 +737,13 @@ class Actions(object):
options = self.data.settings['shader']['SHADER_PARAM']['options'] options = self.data.settings['shader']['SHADER_PARAM']['options']
current_index = [index for index, item in enumerate(options) if item == self.data.settings['shader']['SHADER_PARAM']['value'] ][0] current_index = [index for index, item in enumerate(options) if item == self.data.settings['shader']['SHADER_PARAM']['value'] ][0]
self.data.settings['shader']['SHADER_PARAM']['value'] = options[(current_index + 1) % len(options) ] self.data.settings['shader']['SHADER_PARAM']['value'] = options[(current_index + 1) % len(options) ]
self.message_handler.set_message('INFO', 'The Param amountis now ' + str(self.data.settings['shader']['SHADER_PARAM']['value'])) self.message_handler.set_message('INFO', 'The Param amount is now ' + str(self.data.settings['shader']['SHADER_PARAM']['value']))
def decrease_shader_param(self): def decrease_shader_param(self):
options = self.data.settings['shader']['SHADER_PARAM']['options'] options = self.data.settings['shader']['SHADER_PARAM']['options']
current_index = [index for index, item in enumerate(options) if item == self.data.settings['shader']['SHADER_PARAM']['value'] ][0] current_index = [index for index, item in enumerate(options) if item == self.data.settings['shader']['SHADER_PARAM']['value'] ][0]
self.data.settings['shader']['SHADER_PARAM']['value'] = options[(current_index - 1) % len(options) ] self.data.settings['shader']['SHADER_PARAM']['value'] = options[(current_index - 1) % len(options) ]
self.message_handler.set_message('INFO', 'The Param amountis now ' + str(self.data.settings['shader']['SHADER_PARAM']['value'])) self.message_handler.set_message('INFO', 'The Param amount is now ' + str(self.data.settings['shader']['SHADER_PARAM']['value']))
def set_fixed_length(self, value): def set_fixed_length(self, value):
@@ -893,6 +894,9 @@ class Actions(object):
def clear_message(self): def clear_message(self):
self.message_handler.clear_all_messages() self.message_handler.clear_all_messages()
def modulate_param_layer_offset_to_amount(self, param, layer, amount):
self.shaders.modulate_param_layer_offset_to_amount(param, amount, layer_offset=layer)
@staticmethod @staticmethod
def try_remove_file(path): def try_remove_file(path):
if os.path.exists(path): if os.path.exists(path):
@@ -902,12 +906,12 @@ class Actions(object):
# this would include eg a custom script module.. # this would include eg a custom script module..
@property @property
def parserlist(self): def parserlist(self):
return { return {
( r"play_shader_([0-9])_([0-9])", self.shaders.play_that_shader ), ( r"play_shader_([0-9])_([0-9])", self.shaders.play_that_shader ),
( r"toggle_shader_layer_([0-2])", self.toggle_shader_layer ), ( r"toggle_shader_layer_([0-2])", self.toggle_shader_layer ),
( r"start_shader_layer_([0-2])", self.shaders.start_shader ), ( r"start_shader_layer_([0-2])", self.shaders.start_shader ),
( r"stop_shader_layer_([0-2])", self.shaders.stop_shader ), ( r"stop_shader_layer_([0-2])", self.shaders.stop_shader ),
( r"set_the_shader_param_([0-3])_layer_([0-2])_continuous", self.shaders.set_param_layer_to_amount ), ( r"set_the_shader_param_([0-3])_layer_([0-2])_continuous", self.shaders.set_param_layer_to_amount ),
( r"modulate_param_([0-3])_to_amount_continuous", self.shaders.modulate_param_to_amount ), ( r"modulate_param_([0-3])_to_amount_continuous", self.shaders.modulate_param_to_amount ),
( r"set_param_([0-3])_layer_([0-2])_modulation_level_continuous", self.shaders.set_param_layer_offset_modulation_level ), ( r"set_param_([0-3])_layer_([0-2])_modulation_level_continuous", self.shaders.set_param_layer_offset_modulation_level ),
( r"set_param_([0-3])_layer_offset_([0-2])_modulation_level_continuous", self.shaders.set_param_layer_offset_modulation_level ), ( r"set_param_([0-3])_layer_offset_([0-2])_modulation_level_continuous", self.shaders.set_param_layer_offset_modulation_level ),
@@ -915,7 +919,7 @@ class Actions(object):
( r"reset_modulation_([0-3])", self.shaders.reset_modulation ), ( r"reset_modulation_([0-3])", self.shaders.reset_modulation ),
( r"select_shader_modulation_slot_([0-3])", self.shaders.select_shader_modulation_slot ), ( r"select_shader_modulation_slot_([0-3])", self.shaders.select_shader_modulation_slot ),
( r"set_shader_speed_layer_offset_([0-2])_amount", self.shaders.set_speed_offset_to_amount ), ( r"set_shader_speed_layer_offset_([0-2])_amount", self.shaders.set_speed_offset_to_amount ),
( r"set_shader_speed_layer_([0-2])_amount", self.shaders.set_speed_layer_to_amount ), ( r"set_shader_speed_layer_([0-2])_amount", self.shaders.set_speed_layer_to_amount ),
} }
def detect_types(self, args): def detect_types(self, args):
@@ -951,6 +955,16 @@ class Actions(object):
def call_parse_method_name(self, method_name, argument): def call_parse_method_name(self, method_name, argument):
# first test if a registered plugin handles this for us
from data_centre.plugin_collection import ActionsPlugin
for plugin in self.data.plugins.get_plugins(ActionsPlugin):
if plugin.is_handled(method_name):
print ("Plugin %s is handling %s" % (plugin, method_name))
method, arguments = plugin.get_callback_for_method(method_name, argument)
method(*arguments)
return
# if not then fall back to using internal method
try: try:
method, arguments = self.get_callback_for_method(method_name, argument) method, arguments = self.get_callback_for_method(method_name, argument)
method(*arguments) method(*arguments)

View File

@@ -7,6 +7,24 @@ import inspect
from itertools import cycle from itertools import cycle
from omxplayer.player import OMXPlayer from omxplayer.player import OMXPlayer
from shutil import copyfile from shutil import copyfile
import threading
from data_centre import plugin_collection
class AsyncWrite(threading.Thread):
def __init__(self, filename, data, mode='json'):
threading.Thread.__init__(self)
self.filename = filename
self.data = data
self.mode = mode
def run(self):
with open(self.filename, "w+") as data_file:
if self.mode=='json':
json.dump(self.data, data_file, indent=4, sort_keys=True)
else:
data_file.write(self.data)
data_file.close()
@@ -33,6 +51,11 @@ class Data(object):
#self.EMPTY_BANK = [self.EMPTY_SLOT for i in range(10)] #self.EMPTY_BANK = [self.EMPTY_SLOT for i in range(10)]
self.PATHS_TO_BROWSER = [self.PATH_TO_EXTERNAL_DEVICES, '/home/pi/Videos' ] self.PATHS_TO_BROWSER = [self.PATH_TO_EXTERNAL_DEVICES, '/home/pi/Videos' ]
self.PATHS_TO_SHADERS = [self.PATH_TO_EXTERNAL_DEVICES, '/home/pi/r_e_c_u_r/Shaders', '/home/pi/Shaders' ] self.PATHS_TO_SHADERS = [self.PATH_TO_EXTERNAL_DEVICES, '/home/pi/r_e_c_u_r/Shaders', '/home/pi/Shaders' ]
self.PATHS_TO_PLUGIN_DATA = [ '/home/pi/r_e_c_u_r/json_objects/plugins', self.PATH_TO_EXTERNAL_DEVICES ]
#initialise plugin manager
self.plugins = plugin_collection.PluginCollection("plugins", message_handler, self)
self.plugins.apply_all_plugins_on_value(5)
### state data ### state data
self.auto_repeat_on = True self.auto_repeat_on = True
@@ -78,7 +101,17 @@ class Data(object):
self.midi_mappings = self._read_json(self.MIDI_MAPPING_JSON) self.midi_mappings = self._read_json(self.MIDI_MAPPING_JSON)
self.analog_mappings = self._read_json(self.ANALOG_MAPPING_JSON) self.analog_mappings = self._read_json(self.ANALOG_MAPPING_JSON)
def load_midi_mapping_for_device(self, device_name):
# check if custom config file exists on disk for this device name
custom_file = self.MIDI_MAPPING_JSON.replace(".json","_%s.json"%device_name)
if os.path.isfile(self.PATH_TO_DATA_OBJECTS + custom_file):
self.midi_mappings = self._read_json(custom_file)
self.message_handler.set_message('INFO', "Loaded %s for %s" % (custom_file, device_name)) #the slot you pressed is empty')
print ("loaded custom midi mapping for %s" % custom_file)
else:
print ("loading default midi mapping for %s" % (device_name))
self.midi_mappings = self._read_json(self.MIDI_MAPPINGS_JSON)
return self.midi_mappings
@staticmethod @staticmethod
@@ -100,6 +133,23 @@ class Data(object):
with open('{}{}'.format(self.PATH_TO_DATA_OBJECTS, file_name), 'w') as data_file: with open('{}{}'.format(self.PATH_TO_DATA_OBJECTS, file_name), 'w') as data_file:
json.dump(data, data_file, indent=4, sort_keys=True) json.dump(data, data_file, indent=4, sort_keys=True)
def _read_plugin_json(self, file_name):
for path in self.PATHS_TO_PLUGIN_DATA:
print("trying path %s"%path)
try:
with open("%s/%s" % (path,file_name)) as data_file:
data = json.load(data_file)
return data
except:
pass
print ("no plugin data loaded for %s" % file_name)
def _update_plugin_json(self, file_name, data):
#with open("%s/%s" % (self.PATHS_TO_PLUGIN_DATA[0], file_name), "w+") as data_file:
# json.dump(data, data_file, indent=4, sort_keys=True)
writer = AsyncWrite("%s/%s" % (self.PATHS_TO_PLUGIN_DATA[0], file_name), data, mode='json')
writer.start()
def update_conjur_dev_mode(self, value): def update_conjur_dev_mode(self, value):
print(value) print(value)
tree = ET.parse(self.PATH_TO_CONJUR_DATA) tree = ET.parse(self.PATH_TO_CONJUR_DATA)

View File

@@ -0,0 +1,270 @@
import inspect
import os
import pkgutil
import re
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
def __init__(self, plugin_collection):
self.description = 'UNKNOWN'
self.pc = plugin_collection
class MidiFeedbackPlugin(Plugin):
"""Base class for MIDI feedback plugins
"""
def __init__(self, plugin_collection):
super().__init__(plugin_collection)
self.description = 'Outputs feedback about status to device eg MIDI pads'
def supports_midi_feedback(self, device_name):
return False
def set_midi_device(self, midi_device):
self.midi_feedback_device = midi_device
def refresh_midi_feedback(self):
raise NotImplementedError
class SequencePlugin(Plugin):
def __init__(self, plugin_collection):
super().__init__(plugin_collection)
@property
def parserlist(self):
return [
( r"run_automation", self.run_automation ),
( r"stop_automation", self.stop_automation ),
( r"toggle_pause_automation", self.toggle_pause_automation ),
( r"pause_automation", self.pause_automation ),
( r"toggle_loop_automation", self.toggle_loop_automation ),
]
def position(self, now):
import time
passed = now - self.automation_start
if self.duration>0:
position = passed / self.duration*1000
return position
def toggle_automation(self):
if not self.is_playing():
self.run_automation()
else:
self.stop_automation()
def toggle_loop_automation(self):
self.looping = not self.looping
def pause_automation(self):
self.pause_flag = not self.is_paused() and self.is_playing()
def stop_automation(self):
self.stop_flag = True
def toggle_pause_automation(self):
self.pause_flag = not self.is_paused()
self.pause_flag = self.is_paused() and self.is_playing()
if not self.is_paused() and not self.is_playing():
self.run_automation()
store_passed = None
pause_flag = True
stop_flag = False
looping = True
automation_start = None
iterations_count = 0
duration = 2000
frequency = 100
def run_automation(self):
import time
now = time.time()
if self.looping and self.automation_start is not None and (now - self.automation_start >= self.duration/1000):
print("restarting as start reached %s" % self.automation_start)
self.iterations_count += 1
self.automation_start = None
if not self.automation_start:
self.automation_start = now
print ("%s: starting automation" % self.automation_start)
self.pause_flag = False
#print("running automation at %s!" % self.position)
if not self.is_paused():
self.store_passed = None
self.run_sequence(self.position(now))
#print ("%s: automation_start is %s" % (time.time()-self.automation_start,self.automation_start))
else:
#print ("%s: about to reset automation_start" % self.automation_start)
#print (" got passed %s" % (time.time() - self.automation_start))
if not self.store_passed:
self.store_passed = (now - self.automation_start)
self.automation_start = now - self.store_passed
#print ("%s: reset automation_start to %s" % (time.time()-self.automation_start,self.automation_start))
#return
if (now - self.automation_start < self.duration/1000) and not self.stop_flag:
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) )
self.stop_flag = False
self.automation_start = None
self.iterations_count = 0
def is_paused(self):
return self.pause_flag
def is_playing(self):
return self.automation_start is not None
def run_sequence(self, position):
raise NotImplementedError
class ActionsPlugin(Plugin):
def __init__(self, plugin_collection):
super().__init__(plugin_collection)
@property
def parserlist(self):
return [
#( r"test_plugin", self.test_plugin )
]
def is_handled(self, method_name):
for a in self.parserlist:
if (a[0]==method_name):
return True
regex = a[0]
me = a[1]
matches = re.match(regex, method_name)
if matches:
return True
def get_callback_for_method(self, method_name, argument):
for a in self.parserlist:
regex = a[0]
me = a[1]
matches = re.search(regex, method_name)
if matches:
found_method = me
parsed_args = self.pc.actions.detect_types(matches.groups())
if argument is not None:
args = parsed_args + [argument]
else:
args = parsed_args
return (found_method, args)
#def call_parse_method_name(self, method_name, argument):
# method, arguments = self.actions.get_callback_for_method(method_name, argument)
# method(*arguments)
# adapted from https://github.com/gdiepen/python_plugin_example
class PluginCollection(object):
"""Upon creation, this class will read the plugins package for modules
that contain a class definition that is inheriting from the Plugin class
"""
@property
def shaders(self):
return self.message_handler.shaders
@property
def actions(self):
return self.message_handler.actions
"""@property
def midi_input(self):
return self.data.midi_input"""
def __init__(self, plugin_package, message_handler, data):
"""Constructor that initiates the reading of all available plugins
when an instance of the PluginCollection object is created
"""
self.plugin_package = plugin_package
self.message_handler = message_handler
#self.shaders = lambda: data.shaders
self.data = data
#self.actions = message_handler.actions
self.reload_plugins()
def read_json(self, file_name):
return self.data._read_plugin_json(file_name)
def update_json(self, file_name, data):
return self.data._update_plugin_json(file_name, data)
def reload_plugins(self):
"""Reset the list of all plugins and initiate the walk over the main
provided plugin package to load all available plugins
"""
self.plugins = []
self.seen_paths = []
print()
print("Looking for plugins under package %s" % self.plugin_package)
self.walk_package(self.plugin_package)
def get_plugins(self, clazz = None):
if clazz:
return [c for c in self.plugins if (isinstance(c, clazz) and not c.disabled)]
else:
return [c for c in self.plugins if not c.disabled]
def apply_all_plugins_on_value(self, argument):
"""Apply all of the plugins on the argument supplied to this function
"""
print()
print('Applying all plugins on value %s:' %argument)
for plugin in self.plugins:
#print(" Applying %s on value %s yields value %s" % (plugin.description, argument, plugin.perform_operation(argument)))
pass
def walk_package(self, package):
"""Recursively walk the supplied package to retrieve all plugins
"""
imported_package = __import__(package, fromlist=['blah'])
for _, pluginname, ispkg in pkgutil.iter_modules(imported_package.__path__, imported_package.__name__ + '.'):
if not ispkg:
plugin_module = __import__(pluginname, fromlist=['blah'])
clsmembers = inspect.getmembers(plugin_module, inspect.isclass)
for (_, c) in clsmembers:
# Only add classes that are a sub class of Plugin, but NOT Plugin itself
# or one of the base classes
ignore_list = [ Plugin, ActionsPlugin, SequencePlugin, MidiFeedbackPlugin ]
if issubclass(c, Plugin) & (c not in ignore_list):
print(' Found plugin class: %s.%s' % (c.__module__,c.__name__))
self.plugins.append(c(self))
# 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 = []
if isinstance(imported_package.__path__, str):
all_current_paths.append(imported_package.__path__)
else:
all_current_paths.extend([x for x in imported_package.__path__])
for pkg_path in all_current_paths:
if pkg_path not in self.seen_paths:
self.seen_paths.append(pkg_path)
# Get all sub directory of the current package path directory
child_pkgs = [p for p in os.listdir(pkg_path) if os.path.isdir(os.path.join(pkg_path, p))]
# For each sub directory, apply the walk_package method recursively
for child_pkg in child_pkgs:
self.walk_package(package + '.' + child_pkg)

View File

@@ -11,8 +11,12 @@ grep " def " actions.py | grep -v "^#" | sed -e 's/ def //' | sed -e 's/self//'
| grep -v "parserlist\|check_if_should_start_openframeworks\|create_serial_port_process\|__init__\|persist_composite_setting\|receive_detour_info\|_refresh_frame_buffer\|refresh_frame_buffer_and_restart_openframeworks\|run_script\|setup_osc_server\|start_confirm_action\|stop_serial_port_process\|stop_openframeworks_process\|update_capture_settings\|update_config_settings\|update_video_settings\|try_remove_file\|get_callback\|call_method_name\|call_parse_method\|detect_types" | grep -v "parserlist\|check_if_should_start_openframeworks\|create_serial_port_process\|__init__\|persist_composite_setting\|receive_detour_info\|_refresh_frame_buffer\|refresh_frame_buffer_and_restart_openframeworks\|run_script\|setup_osc_server\|start_confirm_action\|stop_serial_port_process\|stop_openframeworks_process\|update_capture_settings\|update_config_settings\|update_video_settings\|try_remove_file\|get_callback\|call_method_name\|call_parse_method\|detect_types"
echo echo
echo "# Dynamic routes" echo "## Dynamic routes"
grep '( r"' actions.py | sed -e 's/\(.*\)"\(.*\)"\(.*\)/ * \2/' grep '( r"' actions.py | sed -e 's/\(.*\)"\(.*\)"\(.*\)/ * \2/'
echo
echo "### Plugin routes"
grep "( r\"" plugins/*.py | sed -e 's/plugins\/\(.*\)\.py:\(.*\)\( r\"\)\(.*\)\"\(.*\)/ * \4\t(from \1)/' | grep -v "open_serial"
echo echo
echo "----" echo "----"

View File

@@ -1,6 +1,6 @@
{ {
"control_change 0": { "control_change 0": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous"], "DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous","set_strobe_amount_continuous"],
"NAV_DETOUR": ["set_detour_mix_continuous"] "NAV_DETOUR": ["set_detour_mix_continuous"]
}, },
"control_change 1": { "control_change 1": {

View File

@@ -0,0 +1,266 @@
{
"control_change 0": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous"],
"NAV_DETOUR": ["set_detour_mix_continuous"]
},
"control_change 1": {
"DEFAULT": ["set_the_shader_param_1_layer_offset_0_continuous"],
"NAV_DETOUR": ["set_detour_speed_position_continuous"]
},
"control_change 2": {
"DEFAULT": ["set_the_shader_param_2_layer_offset_0_continuous"],
"NAV_DETOUR": ["set_detour_start_continuous"]
},
"control_change 3": {
"DEFAULT": ["set_the_shader_param_3_layer_offset_0_continuous"],
"NAV_DETOUR": ["set_detour_end_continuous"]
},
"control_change 4": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_1_continuous"]
},
"control_change 5": {
"DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous"]
},
"control_change 6": {
"DEFAULT": ["set_the_shader_param_2_layer_offset_1_continuous"]
},
"control_change 7": {
"DEFAULT": ["set_the_shader_param_3_layer_offset_1_continuous"]
},
"control_change 8": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"]
},
"control_change 9": {
"DEFAULT": ["set_the_shader_param_1_layer_offset_2_continuous"]
},
"control_change 10": {
"DEFAULT": ["set_the_shader_param_2_layer_offset_2_continuous"]
},
"control_change 11": {
"DEFAULT": ["set_the_shader_param_3_layer_offset_2_continuous"]
},
"control_change 48": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous","set_strobe_amount_continuous"],
"NAV_DETOUR": ["set_detour_speed_position_continuous"]
},
"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"]
},
"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"]
},
"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"]
},
"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"]
},
"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"]
},
"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"]
},
"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"]
},
"control_change 56": {
"DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"],
"NAV_DETOUR": ["set_detour_speed_position_continuous"]
},
"control_change 57": {
"DEFAULT": ["set_the_shader_param_1_layer_offset_2_continuous"],
"NAV_DETOUR": ["set_detour_start_continuous"]
},
"control_change 58": {
"DEFAULT": ["set_the_shader_param_2_layer_offset_2_continuous"],
"NAV_DETOUR": ["set_detour_end_continuous"]
},
"control_change 59": {
"DEFAULT": ["set_the_shader_param_3_layer_offset_2_continuous"],
"NAV_DETOUR": ["set_detour_end_continuous"]
},
"note_on 64": {
"DEFAULT": ["clear_automation"]
},
"note_on 65": {
"DEFAULT": ["toggle_pause_automation"]
},
"note_on 66": {
"DEFAULT": ["toggle_record_automation"]
},
"note_on 67": {
"DEFAULT": ["toggle_overdub_automation"]
},
"note_on 68": {
"DEFAULT": ["store_next_preset"]
},
"note_on 69": {
"DEFAULT": ["store_current_preset","clear_current_preset"]
},
"note_on 0": {
"DEFAULT": ["switch_to_preset_0","select_preset_0"]
},
"note_on 1": {
"DEFAULT": ["switch_to_preset_1","select_preset_1"]
},
"note_on 2": {
"DEFAULT": ["switch_to_preset_2","select_preset_2"]
},
"note_on 3": {
"DEFAULT": ["switch_to_preset_3","select_preset_3"]
},
"note_on 4": {
"DEFAULT": ["switch_to_preset_4","select_preset_4"]
},
"note_on 5": {
"DEFAULT": ["switch_to_preset_5","select_preset_5"]
},
"note_on 6": {
"DEFAULT": ["switch_to_preset_6","select_preset_6"]
},
"note_on 7": {
"DEFAULT": ["switch_to_preset_7","select_preset_7"]
},
"note_on 81": {
"DEFAULT": ["","reset_selected_modulation"]
},
"note_on 82": {
"DEFAULT": ["toggle_shader_layer_0","select_shader_modulation_slot_0"]
},
"note_on 83": {
"DEFAULT": ["toggle_shader_layer_1","select_shader_modulation_slot_1"]
},
"note_on 84": {
"DEFAULT": ["toggle_shader_layer_2","select_shader_modulation_slot_2"]
},
"note_on 85": {
"DEFAULT": ["toggle_feedback","select_shader_modulation_slot_3"]
},
"note_on 86": {
"DEFAULT": ["toggle_capture_preview"]
},
"note_on 70": {
"DEFAULT": ["previous_shader_layer"]
},
"note_on 71": {
"DEFAULT": ["next_shader_layer"]
},
"note_on 8": {
"DEFAULT": ["toggle_automation_clip_0","select_automation_clip_0"]
},
"note_on 9": {
"DEFAULT": ["toggle_automation_clip_1","select_automation_clip_1"]
},
"note_on 10": {
"DEFAULT": ["toggle_automation_clip_2","select_automation_clip_2"]
},
"note_on 11": {
"DEFAULT": ["toggle_automation_clip_3","select_automation_clip_3"]
},
"note_on 12": {
"DEFAULT": ["toggle_automation_clip_4","select_automation_clip_4"]
},
"note_on 13": {
"DEFAULT": ["toggle_automation_clip_5","select_automation_clip_5"]
},
"note_on 14": {
"DEFAULT": ["toggle_automation_clip_6","select_automation_clip_6"]
},
"note_on 15": {
"DEFAULT": ["toggle_automation_clip_7","select_automation_clip_7"]
},
"note_on 32": {
"DEFAULT": ["play_shader_0_0"]
},
"note_on 33": {
"DEFAULT": ["play_shader_0_1"]
},
"note_on 34": {
"DEFAULT": ["play_shader_0_2"]
},
"note_on 35": {
"DEFAULT": ["play_shader_0_3"]
},
"note_on 36": {
"DEFAULT": ["play_shader_0_4"]
},
"note_on 37": {
"DEFAULT": ["play_shader_0_5"]
},
"note_on 38": {
"DEFAULT": ["play_shader_0_6"]
},
"note_on 39": {
"DEFAULT": ["play_shader_0_7"]
},
"note_on 24": {
"DEFAULT": ["play_shader_1_0"]
},
"note_on 25": {
"DEFAULT": ["play_shader_1_1"]
},
"note_on 26": {
"DEFAULT": ["play_shader_1_2"]
},
"note_on 27": {
"DEFAULT": ["play_shader_1_3"]
},
"note_on 28": {
"DEFAULT": ["play_shader_1_4"]
},
"note_on 29": {
"DEFAULT": ["play_shader_1_5"]
},
"note_on 30": {
"DEFAULT": ["play_shader_1_6"]
},
"note_on 31": {
"DEFAULT": ["play_shader_1_7"]
},
"note_on 16": {
"DEFAULT": ["play_shader_2_0"]
},
"note_on 17": {
"DEFAULT": ["play_shader_2_1"]
},
"note_on 18": {
"DEFAULT": ["play_shader_2_2"]
},
"note_on 19": {
"DEFAULT": ["play_shader_2_3"]
},
"note_on 20": {
"DEFAULT": ["play_shader_2_4"]
},
"note_on 21": {
"DEFAULT": ["play_shader_2_5"]
},
"note_on 22": {
"DEFAULT": ["play_shader_2_6"]
},
"note_on 23": {
"DEFAULT": ["play_shader_2_7"]
},
"note_on 98": {
"DEFAULT": ["function_on"]
},
"note_off 98": {
"DEFAULT": ["function_off"]
},
"note_on 91": {
"DEFAULT": ["send_serial_macro_0","send_serial_macro_1"]
},
"note_on 93": {
"DEFAULT": ["send_serial_string_hellO","send_random_settings"]
}
}

View File

@@ -0,0 +1,51 @@
import data_centre.plugin_collection
from data_centre.plugin_collection import ActionsPlugin, SequencePlugin
class MidiActionsTestPlugin(ActionsPlugin,SequencePlugin):
disabled = True
def __init__(self, plugin_collection):
super().__init__(plugin_collection)
@property
def parserlist(self):
return [
( r"test_plugin", self.test_plugin ),
( r"cycle_shaders", self.cycle_shaders ),
( r"run_automation", self.run_automation ),
( r"stop_automation", self.stop_automation ),
( r"toggle_pause_automation", self.toggle_pause_automation ),
( r"pause_automation", self.pause_automation ),
( r"toggle_loop_automation", self.toggle_loop_automation ),
]
def test_plugin(self):
print ("TEST PLUGIN test_plugin CALLED!!")
# can now access various parts of recur via self.pc
cycle_count = 0
def cycle_shaders(self):
print ("Cycle shaders!!!")
if self.cycle_count>9:
self.cycle_count = 0
for i,shader in enumerate(self.pc.message_handler.shaders.selected_shader_list):
self.pc.midi_input.call_method_name(
"play_shader_%s_%s" % (i, self.cycle_count), None
)
self.pc.midi_input.call_method_name(
"start_shader_layer_%s" % i, None
)
self.cycle_count += 1
duration = 5000
frequency = 50
def run_sequence(self, position):
self.pc.midi_input.call_method_name(
"set_the_shader_param_0_layer_0_continuous", position
)
self.pc.midi_input.call_method_name(
"set_the_shader_param_1_layer_0_continuous", position
)

View File

@@ -0,0 +1,202 @@
from data_centre import plugin_collection
from data_centre.plugin_collection import MidiFeedbackPlugin
import mido
class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin):
disabled = False
status = {}
def __init__(self, plugin_collection):
super().__init__(plugin_collection)
self.description = 'Outputs feedback to APC Key 25'
def set_midi_device(self, device):
super().set_midi_device(device)
self.last_state = None
def supports_midi_feedback(self, device_name):
supported_devices = ['APC Key 25']
for supported_device in supported_devices:
if device_name.startswith(supported_device):
return True
def set_status(self, command='note_on', note=None, velocity=None):
self.status[note] = {
'command': command,
'note': note,
'velocity': velocity
}
#print("set status to %s: %s" % (note, self.status[note]))
def send_command(self, command='note_on', note=None, velocity=None):
#print("send_command(%s, %s)" % (note, velocity))
self.midi_feedback_device.send(
mido.Message(command, note=note, velocity=velocity)
)
def feedback_shader_feedback(self, on):
self.set_status(note=85, velocity=int(on))
def feedback_capture_preview(self, on):
self.set_status(note=86, velocity=int(on))
def feedback_shader_on(self, layer, slot, colour=None):
if colour is None: colour = self.COLOUR_GREEN
self.set_status(note=(32-(layer)*8)+slot, velocity=int(colour))
def feedback_shader_off(self, layer, slot):
self.set_status(note=(32-(layer)*8)+slot, velocity=self.COLOUR_OFF)
NOTE_SCENE_LAUNCH_COLUMN = 82
def feedback_shader_layer_on(self, layer):
self.set_status(note=self.NOTE_SCENE_LAUNCH_COLUMN+layer, velocity=self.COLOUR_GREEN)
def feedback_shader_layer_off(self, layer):
self.set_status(note=self.NOTE_SCENE_LAUNCH_COLUMN+layer, velocity=self.COLOUR_OFF)
def feedback_show_layer(self, layer):
self.set_status(note=70, velocity=layer)
def feedback_show_modulation(self, slot):
for i in range(self.NOTE_SCENE_LAUNCH_COLUMN,self.NOTE_SCENE_LAUNCH_COLUMN+4):
if slot==i-self.NOTE_SCENE_LAUNCH_COLUMN:
self.set_status(note=i, velocity=self.COLOUR_GREEN)
else:
self.set_status(note=i, velocity=self.COLOUR_OFF)
def feedback_plugin_status(self):
from data_centre.plugin_collection import SequencePlugin
from plugins.MidiActionsTestPlugin import MidiActionsTestPlugin
from plugins.ShaderLoopRecordPlugin import ShaderLoopRecordPlugin
for plugin in self.pc.get_plugins(SequencePlugin):
if isinstance(plugin, ShaderLoopRecordPlugin): #MidiActionsTestPlugin):
NOTE_PLAY_STATUS = 65
NOTE_RECORD_STATUS = 66
NOTE_OVERDUB_STATUS = 67
NOTE_CLIP_STATUS_ROW = 8
colour = self.COLOUR_OFF
if plugin.is_playing():
colour = self.COLOUR_GREEN
if plugin.is_paused():
colour += self.BLINK
self.set_status(command='note_on', note=NOTE_PLAY_STATUS, velocity=colour)
colour = self.COLOUR_OFF
if plugin.recording:
colour = self.COLOUR_GREEN
if plugin.is_ignoring():
colour += self.BLINK
self.set_status(command='note_on', note=NOTE_RECORD_STATUS, velocity=colour)
colour = self.COLOUR_OFF
if plugin.overdub:
colour = self.COLOUR_RED
if plugin.is_paused() or plugin.is_ignoring():
colour += self.BLINK
self.set_status(command='note_on', note=NOTE_OVERDUB_STATUS, velocity=colour)
for i in range(plugin.MAX_CLIPS):
if i in plugin.running_clips:
if plugin.is_playing() and not plugin.is_paused():
colour = self.COLOUR_GREEN
else:
colour = self.COLOUR_AMBER
if plugin.selected_clip==i: #blink if selected
colour += self.BLINK
elif plugin.selected_clip==i:
colour = self.COLOUR_RED_BLINK
else:
colour = self.COLOUR_OFF
self.set_status(command='note_on', note=NOTE_CLIP_STATUS_ROW+i, velocity=colour)
from plugins.ShaderQuickPresetPlugin import ShaderQuickPresetPlugin
#print ("feedback_plugin_status")
for plugin in self.pc.get_plugins(ShaderQuickPresetPlugin):
#print ("for plugin %s" % plugin)
for pad in range(0,8):
#print ("checking selected_preset %s vs pad %s" % (plugin.selected_preset, pad))
colour = self.COLOUR_OFF
if plugin.presets[pad] is not None:
colour = self.COLOUR_AMBER
if plugin.last_recalled==pad:
colour = self.COLOUR_GREEN
if plugin.selected_preset==pad:
if plugin.presets[pad] is None:
colour = self.COLOUR_RED
colour += self.BLINK
self.set_status(command='note_on', note=pad, velocity=colour)
BLINK = 1
COLOUR_OFF = 0
COLOUR_GREEN = 1
COLOUR_GREEN_BLINK = 2
COLOUR_RED = 3
COLOUR_RED_BLINK = 4
COLOUR_AMBER = 5
COLOUR_AMBER_BLINK = 6
def refresh_midi_feedback(self):
# show which layer is selected (where parameter offset goes to)
self.feedback_show_layer(self.pc.data.shader_layer)
# show if internal feedback (the shader layer kind) is enabled
if self.pc.data.feedback_active and not self.pc.data.function_on:
self.feedback_shader_feedback(self.COLOUR_GREEN)
#elif self.pc.data.settings['shader']['X3_AS_SPEED']['value'] == 'enabled' and self.pc.data.function_on:
# self.feedback_shader_feedback(self.COLOUR_GREEN_BLINK)
else:
self.feedback_shader_feedback(self.COLOUR_OFF)
if self.pc.message_handler.actions.display.capture.is_previewing and not self.pc.data.function_on:
self.feedback_capture_preview(self.COLOUR_GREEN)
else:
self.feedback_capture_preview(self.COLOUR_OFF)
if self.pc.data.function_on:
self.feedback_show_modulation(self.pc.shaders.selected_modulation_slot)
self.feedback_plugin_status()
for n,shader in enumerate(self.pc.message_handler.shaders.selected_shader_list):
#print ("%s: in refresh_midi_feedback, got shader: %s" % (n,shader))
# show if layer is running or not
if not self.pc.data.function_on:
if self.pc.message_handler.shaders.selected_status_list[n] == '':
self.feedback_shader_layer_on(n)
else:
self.feedback_shader_layer_off(n)
for x in range(0,8):
if 'slot' in shader and shader.get('slot',None)==x:
if self.pc.message_handler.shaders.selected_status_list[n] == '':
# show that slot is selected and running
self.feedback_shader_on(n, x, self.COLOUR_GREEN)
else:
# show that slot is selected but not running
self.feedback_shader_on(n, x, self.COLOUR_AMBER_BLINK)
elif self.pc.data.shader_bank_data[n][x]['path']:
# show that slot is full but not selected
self.feedback_shader_on(n, x, self.COLOUR_AMBER)
else:
# hos that nothing in slot
self.feedback_shader_off(n, x)
self.update_device()
#print("refresh_midi_feedback")
last_state = None
def update_device(self):
from copy import deepcopy
#print("in update device status is %s" % self.status)
for i,c in self.status.items():
#'print("comparing\n%s to\n%s" % (c, self.last_state[i]))
if self.last_state is None or self.last_state[i]!=c:
#print("got command: %s: %s" % (i,c))
self.send_command(**c)
self.last_state = deepcopy(self.status)

View File

@@ -52,10 +52,13 @@ display = Display(tk, video_driver, shaders, message_handler, data)
# setup the actions # setup the actions
actions = Actions(tk, message_handler, data, video_driver, shaders, display, osc_client) actions = Actions(tk, message_handler, data, video_driver, shaders, display, osc_client)
message_handler.actions = actions
numpad_input = NumpadInput(tk, message_handler, display, actions, data) numpad_input = NumpadInput(tk, message_handler, display, actions, data)
osc_input = OscInput(tk, message_handler, display, actions, data) osc_input = OscInput(tk, message_handler, display, actions, data)
midi_input = MidiInput(tk, message_handler, display, actions, data) midi_input = MidiInput(tk, message_handler, display, actions, data)
data.plugins.midi_input = midi_input
analog_input = AnalogInput(tk, message_handler, display, actions, data) analog_input = AnalogInput(tk, message_handler, display, actions, data)
actions.check_and_set_output_mode_on_boot() actions.check_and_set_output_mode_on_boot()

View File

@@ -9,13 +9,18 @@ class Shaders(object):
self.root = root self.root = root
self.osc_client = osc_client self.osc_client = osc_client
self.message_handler = message_handler self.message_handler = message_handler
self.message_handler.shaders = self
self.data = data self.data = data
self.shaders_menu = menu.ShadersMenu(self.data, self.message_handler, self.MENU_HEIGHT ) self.shaders_menu = menu.ShadersMenu(self.data, self.message_handler, self.MENU_HEIGHT )
self.selected_shader_list = [self.EMPTY_SHADER for i in range(3)] self.selected_shader_list = [self.EMPTY_SHADER for i in range(3)]
self.focused_param = 0 self.focused_param = 0
self.shaders_menu_list = self.generate_shaders_list() self.shaders_menu_list = self.generate_shaders_list()
self.selected_modulation_slot = 0
self.selected_status_list = ['-','-','-'] ## going to try using symbols for this : '-' means empty, '▶' means running, '■' means not running, '!' means error self.selected_status_list = ['-','-','-'] ## going to try using symbols for this : '-' means empty, '▶' means running, '■' means not running, '!' means error
self.selected_modulation_level = [[[0.0,0.0,0.0,0.0] for i in range(4)] for i in range(3)]
self.modulation_value = [0.0,0.0,0.0,0.0]
self.selected_param_list = [[0.0,0.0,0.0,0.0] for i in range(3)] self.selected_param_list = [[0.0,0.0,0.0,0.0] for i in range(3)]
self.selected_speed_list = [1.0, 1.0, 1.0] self.selected_speed_list = [1.0, 1.0, 1.0]
@@ -87,11 +92,13 @@ class Shaders(object):
def start_shader(self, layer): def start_shader(self, layer):
self.osc_client.send_message("/shader/{}/is_active".format(str(layer)), True) self.osc_client.send_message("/shader/{}/is_active".format(str(layer)), True)
self.selected_status_list[layer] = '' if self.selected_status_list[layer] != '-':
self.selected_status_list[layer] = ''
def stop_shader(self, layer): def stop_shader(self, layer):
self.osc_client.send_message("/shader/{}/is_active".format(str(layer)), False) self.osc_client.send_message("/shader/{}/is_active".format(str(layer)), False)
self.selected_status_list[layer] = '' if self.selected_status_list[layer] != '-':
self.selected_status_list[layer] = ''
def start_selected_shader(self): def start_selected_shader(self):
self.start_shader(self.data.shader_layer) self.start_shader(self.data.shader_layer)
@@ -128,9 +135,10 @@ class Shaders(object):
def play_that_shader(self, layer, slot): def play_that_shader(self, layer, slot):
if self.data.shader_bank_data[layer][slot]['path']: if self.data.shader_bank_data[layer][slot]['path']:
self.selected_shader_list[layer] = self.data.shader_bank_data[layer][slot] if self.selected_shader_list[layer].get('slot') is None or self.selected_shader_list[layer]['slot'] != slot:
self.selected_shader_list[layer]['slot'] = slot self.selected_shader_list[layer] = self.data.shader_bank_data[layer][slot]
self.load_shader_layer(layer) self.selected_shader_list[layer]['slot'] = slot
self.load_shader_layer(layer)
else: else:
self.message_handler.set_message('INFO', "shader slot %s:%s is empty"%(layer,slot)) self.message_handler.set_message('INFO', "shader slot %s:%s is empty"%(layer,slot))
@@ -259,4 +267,203 @@ class Shaders(object):
self.osc_client.send_message("/shader/{}/speed".format(str(layer)), amount ) self.osc_client.send_message("/shader/{}/speed".format(str(layer)), amount )
self.selected_speed_list[layer] = amount self.selected_speed_list[layer] = amount
# methods for helping dealing with storing and recalling shader parameter frame states
def get_live_frame(self):
#print("get_live_frame: %s" % self.pc.message_handler.shaders.selected_param_list)
import copy #from copy import deepcopy
frame = {
'selected_shader_slots': [ shader.get('slot',None) for shader in self.selected_shader_list ],
'shader_params': copy.deepcopy(self.selected_param_list),
'layer_active_status': copy.deepcopy(self.selected_status_list),
'feedback_active': self.data.feedback_active,
'x3_as_speed': self.data.settings['shader']['X3_AS_SPEED']['value'],
'shader_speeds': copy.deepcopy(self.selected_speed_list),
'strobe_amount': self.data.settings['shader']['STROBE_AMOUNT']['value'] / 10.0
}
#print("built frame: %s" % frame['shader_params'])
return frame
def recall_frame_params(self, preset):
if preset is None:
return
#print("recall_frame_params got: %s" % preset.get('shader_params'))
for (layer, param_list) in enumerate(preset.get('shader_params',[])):
if param_list:
for param,value in enumerate(param_list):
#if (ignored is not None and ignored['shader_params'][layer][param] is not None):
# print ("ignoring %s,%s because value is %s" % (layer,param,ignored['shader_params'][layer][param]))
# continue
if (value is not None):
#print("recalling layer %s param %s: value %s" % (layer,param,value))
self.data.plugins.actions.call_method_name('set_the_shader_param_%s_layer_%s_continuous' % (param,layer), value)
if preset.get('feedback_active') is not None:
self.data.feedback_active = preset.get('feedback_active',self.data.feedback_active)
if self.data.feedback_active:
self.data.plugins.actions.call_method_name('enable_feedback')
else:
self.data.plugins.actions.call_method_name('disable_feedback')
if preset.get('x3_as_speed') is not None:
self.data.settings['shader']['X3_AS_SPEED']['value'] = preset.get('x3_as_speed',self.data.settings['shader']['X3_AS_SPEED']['value'])
"""if self.data.settings['shader']['X3_AS_SPEED']['value']:
self.data.plugins.actions.call_method_name('enable_x3_as_speed')
else:
self.data.plugins.actions.call_method_name('disable_x3_as_speed')"""
for (layer, speed) in enumerate(preset.get('shader_speeds',[])):
if speed is not None:
self.data.plugins.actions.call_method_name('set_shader_speed_layer_%s_amount' % layer, speed)
if preset.get('strobe_amount') is not None:
self.data.plugins.actions.set_strobe_amount_continuous(preset.get('strobe_amount'))
def recall_frame(self, preset):
self.data.settings['shader']['X3_AS_SPEED']['value'] = preset.get('x3_as_speed')
# x3_as_speed affects preset recall, so do that first
self.recall_frame_params(preset)
for (layer, slot) in enumerate(preset.get('selected_shader_slots',[])):
if slot is not None:
#print("setting layer %s to slot %s" % (layer, slot))
self.data.plugins.actions.call_method_name('play_shader_%s_%s' % (layer, slot))
for (layer, active) in enumerate(preset.get('layer_active_status',[])):
# print ("got %s layer with status %s " % (layer,active))
if active=='':
self.data.plugins.actions.call_method_name('start_shader_layer_%s' % layer)
else:
self.data.plugins.actions.call_method_name('stop_shader_layer_%s' % layer)
DEBUG_FRAMES = False
# overlay frame2 on frame1
def merge_frames(self, frame1, frame2):
from copy import deepcopy
f = deepcopy(frame1) #frame1.copy()
if self.DEBUG_FRAMES: print("merge_frames: got frame1\t%s" % frame1)
if self.DEBUG_FRAMES: print("merge_frames: got frame2\t%s" % frame2)
for i,f2 in enumerate(frame2['shader_params']):
for i2,p in enumerate(f2):
if p is not None:
f['shader_params'][i][i2] = p
if frame2['feedback_active'] is not None:
f['feedback_active'] = frame2['feedback_active']
if frame2['x3_as_speed'] is not None:
f['x3_as_speed'] = frame2['x3_as_speed']
if f.get('shader_speeds') is None:
f['shader_speeds'] = frame2.get('shader_speeds')
else:
for i,s in enumerate(frame2['shader_speeds']):
if s is not None:
f['shader_speeds'][i] = s
if frame2.get('strobe_amount'):
f['strobe_amount'] = frame2.get('strobe_amount')
if self.DEBUG_FRAMES: print("merge_frames: got return\t%s" % f)
return f
def get_frame_ignored(self, frame, ignored):
from copy import deepcopy
f = deepcopy(frame) #frame1.copy()
if self.DEBUG_FRAMES: print("get_frame_ignored: got frame\t%s" % frame)
for i,f2 in enumerate(frame['shader_params']):
for i2,p in enumerate(f2):
if ignored['shader_params'][i][i2] is not None:
f['shader_params'][i][i2] = None
if ignored.get('feedback_active') is not None:
f['feedback_active'] = None
if ignored.get('x3_as_speed') is not None:
f['x3_as_speed'] = None
if ignored.get('shader_speeds') is not None and frame.get('shader_speeds'):
for i,s in enumerate(frame.get('shader_speeds')):
if ignored['shader_speeds'][i] is not None:
f['shader_speeds'][i] = None
if ignored.get('strobe_amount') is not None:
f['strobe_amount'] = None
if self.DEBUG_FRAMES: print("get_frame_ignored: got return\t%s" % f)
return f
def is_frame_empty(self, frame):
#from copy import deepcopy
#f = deepcopy(frame) #frame1.copy()
if self.DEBUG_FRAMES: print("is_frame_empty: got frame\t%s" % frame)
if frame.get('feedback_active') is not None:
return False
if frame.get('x3_as_speed') is not None:
return False
if frame.get('strobe_amount') is not None:
return False
for i,f in enumerate(frame['shader_params']):
for i2,p in enumerate(f):
if p is not None: #ignored['shader_params'][i][i2] is not None:
return False
if frame.get('shader_speeds') is not None:
for i,f in enumerate(frame['shader_speeds']):
if f is not None:
return False
if self.DEBUG_FRAMES: print("is_frame_empty: got return true")
return True
def get_frame_diff(self, last_frame, current_frame):
if not last_frame: return current_frame
if self.DEBUG_FRAMES:
print(">>>>get_frame_diff>>>>")
print("last_frame: \t%s" % last_frame['shader_params'])
print("current_frame: \t%s" % current_frame['shader_params'])
param_values = [[None]*4,[None]*4,[None]*4]
for layer,params in enumerate(current_frame.get('shader_params',[[None]*4]*3)):
#if self.DEBUG_FRAMES: print("got layer %s params: %s" % (layer, params))
for param,p in enumerate(params):
if p is not None and p != last_frame.get('shader_params')[layer][param]:
if self.DEBUG_FRAMES: print("setting layer %s param %s to %s" % (layer,param,p))
param_values[layer][param] = p
if current_frame['feedback_active'] is not None and last_frame['feedback_active'] != current_frame['feedback_active']:
feedback_active = current_frame['feedback_active']
else:
feedback_active = None
if current_frame['x3_as_speed'] is not None and last_frame['x3_as_speed'] != current_frame['x3_as_speed']:
x3_as_speed = current_frame['x3_as_speed']
else:
x3_as_speed = None
speed_values = [None]*3
for layer,param in enumerate(current_frame.get('shader_speeds',[None]*3)):
if param is not None and param != last_frame['shader_speeds'][layer]:
speed_values[layer] = param
if current_frame['strobe_amount'] is not None and last_frame['strobe_amount'] != current_frame['strobe_amount']:
strobe_amount = current_frame['strobe_amount']
else:
strobe_amount = None
if self.DEBUG_FRAMES:
print("param_values is\t%s" % param_values)
print("speed_values is\t%s" % speed_values)
diff = {
'shader_params': param_values,
'feedback_active': feedback_active,
'x3_as_speed': x3_as_speed,
'shader_speeds': speed_values,
'strobe_amount': strobe_amount,
}
if self.DEBUG_FRAMES: print("returning\t%s\n^^^^" % diff['shader_params'])
return diff