From fd738b060d0734d990c069667f84966221733598 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Mon, 24 Feb 2020 15:23:13 +0000 Subject: [PATCH 01/12] updated ACTIONS --- ACTIONS.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ACTIONS.md b/ACTIONS.md index 3e58276..7bdcbb0 100644 --- a/ACTIONS.md +++ b/ACTIONS.md @@ -1,8 +1,8 @@ # Auto-generated Actions list -Fri 21 Feb 00:56:39 UTC 2020 +Mon 24 Feb 15:22:24 UTC 2020 -for branch=feature_plugins_shader_gadgets +for branch=feature_plugins # Methods * change_composite_setting(setting_value) @@ -175,6 +175,7 @@ for branch=feature_plugins_shader_gadgets ### Plugin routes * set_lfo_modulation_([0-3])_level (from LFOModulation) * toggle_lfo_active (from LFOModulation) + * set_lfo_speed (from LFOModulation) * (.*)&&(.*) (from ManipulatePlugin) * invert|(.*) (from ManipulatePlugin) * f:(.*):|(.*) (from ManipulatePlugin) @@ -211,14 +212,20 @@ for branch=feature_plugins_shader_gadgets * toggle_loop_automation (from TestPlugin) * print_arguments (from TestPlugin) * set_the_shader_param_([0-3])_layer_offset_([0-2])_continuous_inverted_example (from TestPlugin) - * wj_send_serial_([0-9a-zA-Z:]*) (from WJSendPlugin) - * wj_set_colour_([A|B|T])_([x|y]) (from WJSendPlugin) - * wj_set_back_colour_([x|y|z]) (from WJSendPlugin) - * wj_set_back_wash_colour_([x|y|z]) (from WJSendPlugin) - * wj_set_position_([N|L])_([x|y]) (from WJSendPlugin) + * wj_send_serial:([0-9a-zA-Z:]*) (from WJSendPlugin) + * wj_set_colour:([A|B|T])_([x|y]) (from WJSendPlugin) + * wj_set_back_colour:([x|y|z]) (from WJSendPlugin) + * wj_set_position:([N|L])_([x|y]) (from WJSendPlugin) * wj_set_mix (from WJSendPlugin) - * wj_send_append_pad_([0-9]*)_([[:0-9a-zA-Z]*) (from WJSendPlugin) - * wj_send_append_([:0-9a-zA-Z]*) (from WJSendPlugin) + * wj_set_modulation_([a-zA-Z_]*)[:]?([a-zA-Z_]*)_slot_([0-3])_level (from WJSendPlugin) + * wj_set_current_modulation_slot_([0-3])_level (from WJSendPlugin) + * wj_send_append_pad:([0-9]*)_([[:0-9a-zA-Z]*) (from WJSendPlugin) + * wj_send_append:([:0-9a-zA-Z]*) (from WJSendPlugin) + * wj_set_([a-zA-Z_]*)[:]?([a-zA-Z_]*) (from WJSendPlugin) + * wj_select_next_command (from WJSendPlugin) + * wj_select_previous_command (from WJSendPlugin) + * wj_select_next_argument (from WJSendPlugin) + * wj_select_previous_argument (from WJSendPlugin) ---- From 6c1937520f5100335adb3bf88ca811c9d22cc58c Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 01:21:15 +0000 Subject: [PATCH 02/12] SoundReactPlugin that has assignable modulation from different processing of input signal, without using external helper script. --- json_objects/keypad_action_mapping.json | 3 +- .../midi_action_mapping_APC Key 25.json | 18 +- plugins/SoundReactPlugin.py | 171 ++++++++++++++++++ 3 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 plugins/SoundReactPlugin.py diff --git a/json_objects/keypad_action_mapping.json b/json_objects/keypad_action_mapping.json index ad8f0d1..4a9a74f 100644 --- a/json_objects/keypad_action_mapping.json +++ b/json_objects/keypad_action_mapping.json @@ -31,7 +31,8 @@ "CONFIRM": ["perform_confirm_action"], "NAV_DETOUR": ["toggle_detour_play"], "PLAY_SHADER": ["toggle_shaders", "toggle_shader_speed"], - "NAV_LFO": ["toggle_lfo_active"] + "NAV_LFO": ["toggle_lfo_active"], + "NAV_SND": ["toggle_sound_react_active"] }, "d": { "DEFAULT": ["switch_to_next_player", "toggle_player_mode"], diff --git a/json_objects/midi_action_mapping_APC Key 25.json b/json_objects/midi_action_mapping_APC Key 25.json index 1f72301..c2c65cb 100644 --- a/json_objects/midi_action_mapping_APC Key 25.json +++ b/json_objects/midi_action_mapping_APC Key 25.json @@ -45,14 +45,16 @@ "NAV_WJMX": ["wj_set_position_N:x"], "NAV_MANI": ["set_variable_A"], "NAV_LPRC": ["set_automation_speed"], - "NAV_LFO": ["set_lfo_modulation_0_level"] + "NAV_LFO": ["set_lfo_modulation_0_level"], + "NAV_SND": ["sound_set_config_energy_gain"] }, "control_change 49": { "DEFAULT": ["set_the_shader_param_1_layer_offset_0_continuous","set_shader_speed_layer_0_amount"], "NAV_DETOUR": ["set_detour_start_continuous"], "NAV_WJMX": ["wj_set_position_N:y"], "NAV_MANI": ["f:sin(x*pi):|set_variable_SIN"], - "NAV_LFO": ["set_lfo_modulation_1_level"] + "NAV_LFO": ["set_lfo_modulation_1_level"], + "NAV_SND": ["sound_set_config_energy_threshold"] }, "control_change 50": { "DEFAULT": ["set_the_shader_param_2_layer_offset_0_continuous","set_shader_speed_layer_1_amount"], @@ -70,22 +72,26 @@ "DEFAULT": ["set_the_shader_param_0_layer_offset_1_continuous","set_param_0_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_speed_position_continuous"], "NAV_WJMX": ["wj_set_mix","wj_set_current_modulation_slot_0_level"], - "NAV_LFO": ["set_lfo_speed"] + "NAV_LFO": ["set_lfo_speed"], + "NAV_SND": ["sound_set_modulation_energy_slot_0_level"] }, "control_change 53": { "DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous","set_param_1_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_start_continuous"], - "NAV_WJMX": ["wj_set_back_colour:h","wj_set_current_modulation_slot_1_level"] + "NAV_WJMX": ["wj_set_back_colour:h","wj_set_current_modulation_slot_1_level"], + "NAV_SND": ["sound_set_modulation_energy_slot_1_level"] }, "control_change 54": { "DEFAULT": ["set_the_shader_param_2_layer_offset_1_continuous","set_param_2_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_back_colour:s","wj_set_current_modulation_slot_2_level"] + "NAV_WJMX": ["wj_set_back_colour:s","wj_set_current_modulation_slot_2_level"], + "NAV_SND": ["sound_set_modulation_energy_slot_2_level"] }, "control_change 55": { "DEFAULT": ["set_the_shader_param_3_layer_offset_1_continuous","set_param_3_layer_offset_0_modulation_level_continuous"], "NAV_DETOUR": ["set_detour_end_continuous"], - "NAV_WJMX": ["wj_set_back_colour:v","wj_set_current_modulation_slot_3_level"] + "NAV_WJMX": ["wj_set_back_colour:v","wj_set_current_modulation_slot_3_level"], + "NAV_SND": ["sound_set_modulation_energy_slot_3_level"] }, "control_change 56": { "DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"], diff --git a/plugins/SoundReactPlugin.py b/plugins/SoundReactPlugin.py new file mode 100644 index 0000000..9b2f6d1 --- /dev/null +++ b/plugins/SoundReactPlugin.py @@ -0,0 +1,171 @@ +import math +import data_centre.plugin_collection +from data_centre.plugin_collection import ActionsPlugin, SequencePlugin, DisplayPlugin, AutomationSourcePlugin + +import pyaudio +import numpy as np +#import matplotlib.pyplot as plt + +np.set_printoptions(suppress=True) # don't use scientific notationn + +class SoundReactPlugin(ActionsPlugin,SequencePlugin,DisplayPlugin): + disabled = False + + DEBUG = False + + active = True + stop_flag = False + pause_flag = False + + CHUNK = 4096 # number of data points to read at a time + RATE = 48000 #44100 # time resolution of the recording device (Hz) + + frequency = 10 # how often messages are sampled+calculated+sent, not anything to do with audio frequency + + config = {} + + def __init__(self, plugin_collection): + super().__init__(plugin_collection) + + #self.PRESET_FILE_NAME = "ShaderLoopRecordPlugin/frames.json" + if self.active and not self.disabled: + try: + p=pyaudio.PyAudio() + self.stream=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.disabled = True + return + + print ("now setting to run automation..") + + self.pc.shaders.root.after(500, self.run_automation) + + @property + def sources(self): + # TODO: write more interpreters + return { + "energy": self.energy, + #"low": self.low, + #"mid": self.mid, + #"high": self.high, + "peakfreq": self.peakfreq + } + + values = {} + levels = { + "energy": [ 0.0, 0.0, 1.0, 0.0 ], + "peakfreq": [ 0.0, 0.0, 0.0, 1.0 ] + } + last_values = {} + display_values = {} + # triggers? + # sudden drop - sudden leap? + + # DisplayPlugin methods + def get_display_modes(self): + return ['SOUNDMOD','NAV_SND'] + + def show_plugin(self, display, display_mode): + from tkinter import Text, END + #super(DisplayPlugin).show_plugin(display, display_mode) + display.display_text.insert(END, '{} \n'.format(display.body_title)) + display.display_text.insert(END, "SoundReactPlugin - ") + + display.display_text.insert(END, "ACTIVE\n" if self.active else "not active\n") + + #display.display_text.insert(END, "\tSpeed: {:03.2f}\n\n".format(self.speed)) + + for sourcename in sorted(self.sources): + value = self.display_values.get(sourcename) or "{:03.2f}%".format(self.values.get(sourcename,0)*100) or "None" + value += "\t" + for i,l in enumerate(self.levels[sourcename]): + bar = u"_\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588" + g = "ABCD"[i]+'%s '%bar[int(l*(len(bar)-1))] + value += g + display.display_text.insert(END, "{}:\t{}\n".format(sourcename,value)) + """display.display_text.insert(END, "%s\n" %self.last_lfo_status[lfo]) + display.display_text.insert(END, "\t%s\n" % self.formula[lfo])""" + + #display.display_text.insert(END, "\nLevels:%s\n\n" % self.levels) + display.display_text.insert(END, "\n\n\n") + + + def run_sequence(self, position): + # position is irrelvant for this plugin, we just want to run continuously + if not self.active: + return + + data = np.fromstring(self.stream.read(self.CHUNK, exception_on_overflow = False),dtype=np.int16) + + for sourcename in self.sources: + value = self.sources[sourcename](data) + self.values[sourcename] = value + if value is None: + continue + for slot,level in enumerate(self.levels.get(sourcename,[])): + if level>0.0 and self.values.get(sourcename)!=self.last_values.get(sourcename): + self.pc.actions.call_method_name("modulate_param_%s_to_amount_continuous"%slot, self.values[sourcename]) + self.last_values[sourcename] = self.values[sourcename] + + config.setdefault('energy',{})['gain'] = 0.5 # how much to multiply signal by + config.setdefault('energy',{})['threshold'] = 0.5 # subtract from post-gain signal (hence ignore all values below) + GAIN_MULT = 1.0 + def energy(self,data): + peak=np.average(np.abs(data))*2 + value = (peak/2**16)/16 * 100 + + value *= (self.GAIN_MULT * self.config['energy']['gain']) + + value = value - self.config['energy']['threshold'] + if value<0.0: + value = 0.0 + if value>1.0: + value = 1.0 + + bars="#"*int(50*value) + if self.DEBUG: print("energy:\t\t%05d %s\t(converted to %s)"%(peak,bars,value)) + bar = u"_\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588" + g = '%s'%bar[int(value*(len(bar)-1))] + self.display_values['energy'] = "{} g{:03.2f} t{:03.2f}".format(g, self.config['energy']['gain'], self.config['energy']['threshold']) + + return value + + # dont think this works properly, or maybe it do just be like that + def peakfreq(self,data): + data = data.copy() * np.hanning(len(data)) # smooth the FFT by windowing data + fft = abs(np.fft.fft(data).real) + fft = fft[:int(len(fft)/2)] # keep only first half + freq = np.fft.fftfreq(self.CHUNK,1.0/self.RATE) + freq = freq[:int(len(freq)/2)] # keep only first half + freqPeak = freq[np.where(fft==np.max(fft))[0][0]]+1 + if freqPeak<400: + return False + value = freqPeak/16000 + value = value**16/16 + if self.DEBUG: print("peak frequency:\t%d\tHz\t(converted to %s)"%(freqPeak,value)) + self.display_values['peakfreq'] = ("%d Hz\t"%freqPeak) + "{:03.2f}".format(value) + + return value + + + # ActionsPlugin methods + @property + def parserlist(self): + return [ + ( r"^toggle_sound_react_active$", self.toggle_active ), + ( r"^sound_set_config_([a-z]*)_([a-z]*)$", self.set_config ), + ( r"^sound_set_modulation_([a-z]*)_slot_([0-3])_level$", self.set_modulation_source_slot_level ), + ] + + def set_modulation_source_slot_level(self, sourcename, slot, level): + self.levels.setdefault(sourcename,[0.0,0.0,0.0,0.0])[slot] = level + + def set_config(self, sourcename, setting, value): + if type(self.config.get(sourcename,{}).get(setting)) is str: + print ("SoundReactPlugin: type of existing setting is string, probably doesnt make sense to set this to a value of this type!") + self.config[sourcename][setting] = value + + def toggle_active(self): + self.active = not self.active From 4e3e5476dc8c43c79a0db2d8d8aae162b63d37ee Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 12:40:37 +0000 Subject: [PATCH 03/12] Make numpad input use actions the same as midi input etc, so that plugins can extend numpad functionality --- user_input/numpad_input.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_input/numpad_input.py b/user_input/numpad_input.py index 0093a02..aae0d97 100644 --- a/user_input/numpad_input.py +++ b/user_input/numpad_input.py @@ -61,9 +61,9 @@ class NumpadInput(object): print('the action being called is {}'.format(this_mapping[mode][is_function])) if value != -1: - getattr(self.actions, this_mapping[mode][is_function])(value) + self.actions.call_method_name(this_mapping[mode][is_function])(value) else: - getattr(self.actions, this_mapping[mode][is_function])() + self.actions.call_method_name(this_mapping[mode][is_function])() if is_function and self.data.settings['sampler']['FUNC_GATED']['value'] == 'off': self.data.function_on = False From 00aacd53605e49eb008144b8a20232be5d124f25 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 12:46:58 +0000 Subject: [PATCH 04/12] Updated ACTIONS and generate-list-actions to hide internal commands --- ACTIONS.md | 14 ++++++++++++-- dotfiles/generate-list-actions.sh | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ACTIONS.md b/ACTIONS.md index 260c9e8..5e1bc84 100644 --- a/ACTIONS.md +++ b/ACTIONS.md @@ -1,8 +1,8 @@ # Auto-generated Actions list -Fri 3 Jan 22:08:36 UTC 2020 +Sat 29 Feb 12:43:41 UTC 2020 -for branch=feature_shader_midi +for branch=dev # Methods * change_composite_setting(setting_value) @@ -30,6 +30,7 @@ for branch=feature_shader_midi * decrease_speed * decrease_this_param * disable_feedback + * eject_all_usb_drives * enable_feedback * enter_on_browser_selection * enter_on_settings_selection @@ -59,10 +60,16 @@ for branch=feature_shader_midi * _load_this_slot_into_next_player(slot) * map_on_shaders_selection * move_browser_selection_down + * move_browser_selection_page_down + * move_browser_selection_page_up * move_browser_selection_up * move_settings_selection_down + * move_settings_selection_page_down + * move_settings_selection_page_up * move_settings_selection_up * move_shaders_selection_down + * move_shaders_selection_page_down + * move_shaders_selection_page_up * move_shaders_selection_up * next_bank * next_shader_layer @@ -124,6 +131,7 @@ for branch=feature_shader_midi * set_the_shader_param_3_layer_offset_2_continuous(amount) * set_the_shader_param_3_layer_offset_3_continuous(amount) * shutdown_pi + * stop_remote_process * switch_conjur_player_type(value) * switch_dev_mode(state) * switch_display_to_hdmi @@ -135,6 +143,8 @@ for branch=feature_shader_midi * switch_to_next_player * switch_to_this_detour(number) * switch_video_backend(state) + * toggle_access_point_delay(setting_value, osc_setting_state ) + * toggle_access_point(setting_value) * toggle_action_on_player * toggle_capture_preview * toggle_capture_recording diff --git a/dotfiles/generate-list-actions.sh b/dotfiles/generate-list-actions.sh index dc555cb..3a5bd84 100755 --- a/dotfiles/generate-list-actions.sh +++ b/dotfiles/generate-list-actions.sh @@ -8,7 +8,7 @@ echo echo "# Methods" grep " def " actions.py | grep -v "^#" | sed -e 's/ def //' | sed -e 's/self//' | sed -e 's/(, /(/' | sed -e 's/()//' | sed -e 's/\(.*\)/ *\1/' | sed -e 's/://' | sort -n \ - | 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\|show_ip\|toggle_remote_server\|enable_osc\|shutdown_osc_server" echo echo "# Dynamic routes" From 391e8aef01ac9b5044b8e7ecff88d456b1caf4b1 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 13:28:24 +0000 Subject: [PATCH 05/12] oops, actually fixed, and fix if REMOTE_SERVER is missing from configuration --- data_centre/data.py | 5 +++-- user_input/numpad_input.py | 4 ++-- user_input/osc_input.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data_centre/data.py b/data_centre/data.py index 6155572..236e4db 100644 --- a/data_centre/data.py +++ b/data_centre/data.py @@ -69,11 +69,12 @@ class Data(object): self.shader_bank_data = [self.create_empty_shader_bank() for i in range(3)] if os.path.isfile(self.PATH_TO_DATA_OBJECTS + self.SHADER_BANK_DATA_JSON): self.shader_bank_data = self._read_json(self.SHADER_BANK_DATA_JSON) - self.settings = self._read_json(self.DEFAULT_SETTINGS_JSON) + self.settings = self.default_settings = self._read_json(self.DEFAULT_SETTINGS_JSON) if os.path.isfile(self.PATH_TO_DATA_OBJECTS + self.SETTINGS_JSON): self.settings = self._read_json(self.SETTINGS_JSON) - self.settings['user_input']['REMOTE_SERVER']['value'] = 'disabled' # remote server off at boot + self.settings['user_input'].setdefault('REMOTE_SERVER', + self.default_settings['user_input']['REMOTE_SERVER'])['value'] = 'disabled' # remote server off at boot self.key_mappings = self._read_json(self.KEYPAD_MAPPING_JSON) self.osc_mappings = self._read_json(self.OSC_MAPPING_JSON) diff --git a/user_input/numpad_input.py b/user_input/numpad_input.py index aae0d97..dd3c765 100644 --- a/user_input/numpad_input.py +++ b/user_input/numpad_input.py @@ -61,9 +61,9 @@ class NumpadInput(object): print('the action being called is {}'.format(this_mapping[mode][is_function])) if value != -1: - self.actions.call_method_name(this_mapping[mode][is_function])(value) + self.actions.call_method_name(this_mapping[mode][is_function],value) else: - self.actions.call_method_name(this_mapping[mode][is_function])() + self.actions.call_method_name(this_mapping[mode][is_function]) if is_function and self.data.settings['sampler']['FUNC_GATED']['value'] == 'off': self.data.function_on = False diff --git a/user_input/osc_input.py b/user_input/osc_input.py index 01f57ad..80819af 100644 --- a/user_input/osc_input.py +++ b/user_input/osc_input.py @@ -23,6 +23,8 @@ class OscInput(object): self.poll_settings_for_osc_info() def poll_settings_for_osc_info(self): + self.data.settings['user_input'].setdefault('OSC_INPUT', + self.data.default_settings['user_input'].get('OSC_INPUT')) osc_setting_enabled = self.data.settings['user_input']['OSC_INPUT']['value'] == 'enabled' if osc_setting_enabled and not self.osc_enabled: self.setup_osc_server() From 10e03cf8998ca0b9ed9885862855798683a10d31 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 13:40:53 +0000 Subject: [PATCH 06/12] updated ACTIONS --- ACTIONS.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ACTIONS.md b/ACTIONS.md index 1654f40..5e77262 100644 --- a/ACTIONS.md +++ b/ACTIONS.md @@ -1,8 +1,8 @@ # Auto-generated Actions list -Sat 29 Feb 13:17:13 UTC 2020 +Sat 29 Feb 13:40:28 UTC 2020 -for branch=feature_plugins +for branch=feature_plugins_sound # Methods * change_composite_setting(setting_value) @@ -212,6 +212,9 @@ for branch=feature_plugins * switch_to_preset_([0-%i]) (from ShaderQuickPresetPlugin) * select_preset_([0-%i]) (from ShaderQuickPresetPlugin) * clear_current_preset (from ShaderQuickPresetPlugin) + * toggle_sound_react_active (from SoundReactPlugin) + * sound_set_config_([a-z]*)_([a-z]*) (from SoundReactPlugin) + * sound_set_modulation_([a-z]*)_slot_([0-3])_level (from SoundReactPlugin) * test_plugin (from TestPlugin) * cycle_shaders (from TestPlugin) * run_automation (from TestPlugin) From 638a78ab6841cd082515aa06c49d160d820d11a4 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 14:28:44 +0000 Subject: [PATCH 07/12] Save settings json on program quit -- shader modulation levels moved into settings data so they persist across restarts --- actions.py | 1 + video_centre/shaders.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/actions.py b/actions.py index f353a75..d6892f9 100644 --- a/actions.py +++ b/actions.py @@ -725,6 +725,7 @@ class Actions(object): def quit_the_program(self): + self.data._update_json(self.data.SETTINGS_JSON, self.data.settings) self.video_driver.exit_all_players() self.exit_openframeworks() self.exit_osc_server('','') diff --git a/video_centre/shaders.py b/video_centre/shaders.py index f427e3c..0511afc 100644 --- a/video_centre/shaders.py +++ b/video_centre/shaders.py @@ -20,11 +20,16 @@ class Shaders(object): self.selected_speed_list = [1.0, 1.0, 1.0] self.selected_modulation_slot = 0 - self.selected_modulation_level = [[[0.0,0.0,0.0,0.0] for i in range(4)] for i in range(3)] + #self.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.load_selected_shader() + @property + def modulation_level(self): + return self.data.settings['shader'].setdefault('modulation_level', + [[[0.0,0.0,0.0,0.0] for i in range(4)] for i in range(3)]) + def generate_shaders_list(self): shaders_menu_list = [] raw_list = self.shaders_menu.generate_raw_shaders_list() @@ -163,7 +168,7 @@ class Shaders(object): self.selected_modulation_slot = slot def reset_modulation(self, slot): - for layer in self.selected_modulation_level: + for layer in self.modulation_level: for layer,levels in enumerate(layer): levels[slot] = 0.0 @@ -224,7 +229,7 @@ class Shaders(object): self.modulation_value[param] = (value-0.5)*2 for layer,params in enumerate(self.selected_param_list): for ip,p in enumerate(params): - for p2,v in enumerate(self.selected_modulation_level[layer][ip]): + for p2,v in enumerate(self.modulation_level[layer][ip]): if v!=0: self.update_param_layer(ip,layer) break @@ -234,7 +239,7 @@ class Shaders(object): self.set_param_layer_modulation_level(param, layer, level) def set_param_layer_modulation_level(self, param, layer, level): - self.selected_modulation_level[layer][param][self.selected_modulation_slot] = level + self.modulation_level[layer][param][self.selected_modulation_slot] = level self.update_param_layer(param, layer) def update_param_layer(self, param, layer): @@ -244,7 +249,7 @@ class Shaders(object): self.get_modulation_value_list( self.selected_param_list[layer][param], self.modulation_value,#[0], #param], - self.selected_modulation_level[layer][param] + self.modulation_level[layer][param] ) ) From a0adb742e2ddef6d44a2a570aeeae81c01bf4460 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 14:32:03 +0000 Subject: [PATCH 08/12] changed selected_modulation_level -> modulation_level --- display_centre/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/display_centre/display.py b/display_centre/display.py index ae0d815..6550042 100644 --- a/display_centre/display.py +++ b/display_centre/display.py @@ -256,7 +256,7 @@ class Display(object): self.display_text.insert(END, "%s "%a) self.display_text.insert(END, "\n") bar = u"_\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588" - for layer, modulations in enumerate(self.shaders.selected_modulation_level): + for layer, modulations in enumerate(self.shaders.modulation_level): if (layer==self.data.shader_layer): self.display_text.insert(END, '*') else: From 5e3f148957c370094f67003bee50e143c52a0b8e Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 14:58:19 +0000 Subject: [PATCH 09/12] plugins are notified of exiting so they can save config etc --- actions.py | 1 + data_centre/data.py | 1 - data_centre/plugin_collection.py | 17 +++++++---------- plugins/ShaderLoopRecordPlugin.py | 5 +++++ plugins/ShaderQuickPresetPlugin.py | 4 ++++ 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/actions.py b/actions.py index 0e17948..50d4d31 100644 --- a/actions.py +++ b/actions.py @@ -729,6 +729,7 @@ class Actions(object): def quit_the_program(self): self.data._update_json(self.data.SETTINGS_JSON, self.data.settings) + self.data.plugins.quit_plugins() self.video_driver.exit_all_players() self.exit_openframeworks() self.exit_osc_server('','') diff --git a/data_centre/data.py b/data_centre/data.py index 38d221d..0dce385 100644 --- a/data_centre/data.py +++ b/data_centre/data.py @@ -111,7 +111,6 @@ class Data(object): def initialise_plugins(self): #initialise plugin manager self.plugins = plugin_collection.PluginCollection("plugins", self.message_handler, self) - self.plugins.apply_all_plugins_on_value(5) def load_midi_mapping_for_device(self, device_name): # check if custom config file exists on disk for this device name diff --git a/data_centre/plugin_collection.py b/data_centre/plugin_collection.py index 9f8485f..a0e304f 100644 --- a/data_centre/plugin_collection.py +++ b/data_centre/plugin_collection.py @@ -15,6 +15,9 @@ class Plugin(object): self.description = 'UNKNOWN' self.pc = plugin_collection + def quit_plugin(self): + print("quitting " + type(self).__name__) + class MidiFeedbackPlugin(Plugin): """Base class for MIDI feedback plugins """ @@ -476,6 +479,10 @@ class PluginCollection(object): print("Looking for plugins under package %s" % self.plugin_package) self.walk_package(self.plugin_package) + def quit_plugins(self): + # tell each plugin to quit + for plugin in self.get_plugins(): + plugin.quit_plugin() def get_plugins(self, clazz = None): if clazz: @@ -483,16 +490,6 @@ class PluginCollection(object): 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 """ diff --git a/plugins/ShaderLoopRecordPlugin.py b/plugins/ShaderLoopRecordPlugin.py index 45c6c43..fc3cf29 100644 --- a/plugins/ShaderLoopRecordPlugin.py +++ b/plugins/ShaderLoopRecordPlugin.py @@ -45,6 +45,11 @@ 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() + self.save_presets() + + # DisplayPlugin methods def get_display_modes(self): return ['LOOPREC','NAV_LPRC'] diff --git a/plugins/ShaderQuickPresetPlugin.py b/plugins/ShaderQuickPresetPlugin.py index 3da91fc..ce31a6e 100644 --- a/plugins/ShaderQuickPresetPlugin.py +++ b/plugins/ShaderQuickPresetPlugin.py @@ -25,6 +25,10 @@ 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() + self.save_presets() + @property def parserlist(self): return [ From 86f547204d3bc55bae4131f7e0deb6ce8b277bb8 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 15:40:13 +0000 Subject: [PATCH 10/12] WJSendPlugin saves its modulation levels, placeholders to make sure plugin json directories exist --- .../ShaderLoopRecordPlugin/.placeholder | 0 .../ShaderQuickPresetPlugin/.placeholder | 0 .../plugins/WJSendPlugin/.placeholder | 0 plugins/WJSendPlugin.py | 38 ++++++++++++++++--- 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 json_objects/plugins/ShaderLoopRecordPlugin/.placeholder create mode 100644 json_objects/plugins/ShaderQuickPresetPlugin/.placeholder create mode 100644 json_objects/plugins/WJSendPlugin/.placeholder diff --git a/json_objects/plugins/ShaderLoopRecordPlugin/.placeholder b/json_objects/plugins/ShaderLoopRecordPlugin/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/json_objects/plugins/ShaderQuickPresetPlugin/.placeholder b/json_objects/plugins/ShaderQuickPresetPlugin/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/json_objects/plugins/WJSendPlugin/.placeholder b/json_objects/plugins/WJSendPlugin/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/plugins/WJSendPlugin.py b/plugins/WJSendPlugin.py index b01c0b0..746f7c8 100644 --- a/plugins/WJSendPlugin.py +++ b/plugins/WJSendPlugin.py @@ -8,6 +8,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei disabled = False#True DEBUG = False #True ser = None + + PRESET_FILE_NAME = "WJSendPlugin/presets.json" + presets = {} # from http://depot.univ-nc.nc/sources/boxtream-0.9999/boxtream/switchers/panasonic.py """serial.Serial(device, baudrate=9600, bytesize=serial.SEVENBITS, @@ -29,12 +32,34 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei print ("WJSendPlugin is disabled, not opening serial") 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)) + self.commands[cmd]['modulation'] = levels + + # build a reverse map 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.selected_command_name = list(self.commands.keys())[0] + self.selected_command_name = list(sorted(self.commands.keys()))[0] + + def load_presets(self): + print("trying load presets? %s " % self.PRESET_FILE_NAME) + return self.pc.read_json(self.PRESET_FILE_NAME) or { 'modulation_levels': {} } + + def save_presets(self): + for cmd,struct in self.commands.items(): + 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() + self.save_presets() + # methods/vars for AutomationSourcePlugin # a lot of the nitty-gritty handled in parent class, these are for interfacing to the plugin @@ -159,6 +184,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei output = b'\2' + string.encode('ascii') + b'\3' #with self.serial_lock: self.ser.write(output) #.encode()) + # TODO: sleeping here seems to help serial response lag problem? + #import time + #time.sleep(0.02) #yield from self.ser.drain() if self.DEBUG: print("send_serial_string: sent string '%s'" % output) #.encode('ascii')) @@ -251,11 +279,9 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei def set_modulation_command_argument_level(self, command_name, argument_name, slot, level): if self.DEBUG: print("set_modulation_command_argument_level(%s, %s, %s, %s)" % (command_name, argument_name, slot, level)) - if not argument_name: argument_name = 'v' + if not argument_name: self.commands[command_name]['arg_names'][0] #argument_name = 'v' - if self.commands[command_name].get('modulation') is None: - self.commands[command_name]['modulation'] = [{},{},{},{}] - self.commands[command_name]['modulation'][slot][argument_name] = level + self.commands[command_name].setdefault('modulation',[{},{},{},{}])[slot][argument_name] = level def set_current_modulation_level(self, slot, level): self.set_modulation_command_argument_level(self.selected_command_name, self.commands[self.selected_command_name]['arg_names'][self.selected_argument_index], slot, level) @@ -331,7 +357,7 @@ class WJSendPlugin(ActionsPlugin, SequencePlugin, DisplayPlugin, ModulationRecei 'form': 'VCC:T{:02X}{:02X}', 'arg_names': [ 'x', 'y' ], 'arguments': { 'x': 127, 'y': 127 }, - 'modulation': [ {}, {}, { 'x': 1.0 }, { 'y': 1.0 } ] + #'modulation': [ {}, {}, { 'x': 1.0 }, { 'y': 1.0 } ] #'callback': self.set_colour }, 'mix': { From 311ca2705c3a9d665fe6675f3218178c740aef82 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 15:52:58 +0000 Subject: [PATCH 11/12] Fix for output plugins now MIDI port detection is fixed (variable name changed) --- user_input/midi_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_input/midi_input.py b/user_input/midi_input.py index 128e822..6bcb338 100644 --- a/user_input/midi_input.py +++ b/user_input/midi_input.py @@ -48,7 +48,7 @@ class MidiInput(object): self.data.midi_device_name = self.midi_device.name self.message_handler.set_message('INFO', 'connected to midi device {}'.format(self.midi_device.name)) self.midi_mappings = self.data.load_midi_mapping_for_device(self.midi_device.name.split(":")[0]) - self.midi_output = self.find_output_plugin(midi_device_on_port[subport_index]) + self.midi_output = self.find_output_plugin(midi_devices[subport_index]) if self.midi_output: #self.midi_feedback_device = mido.open_output(midi_device_on_port[subport_index]) self.root.after(self.midi_delay, self.refresh_midi_feedback) From e8bbffad94e6c3f47aeb0c98e85ff63fd0cb6ba1 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sat, 29 Feb 2020 16:00:39 +0000 Subject: [PATCH 12/12] updated ACTIONS --- ACTIONS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ACTIONS.md b/ACTIONS.md index 5e77262..989c717 100644 --- a/ACTIONS.md +++ b/ACTIONS.md @@ -1,8 +1,8 @@ # Auto-generated Actions list -Sat 29 Feb 13:40:28 UTC 2020 +Sat 29 Feb 16:00:23 UTC 2020 -for branch=feature_plugins_sound +for branch=feature_plugins # Methods * change_composite_setting(setting_value)