Created
January 21, 2020 11:23
-
-
Save vanjac/1bcde7d86d363114bcc336632e9b9d9e to your computer and use it in GitHub Desktop.
Blender MIDI control add-on
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bl_info = { | |
"name": "MIDI Control", | |
"author": "Jacob van't Hoog", | |
"version": (1, 0), | |
"blender": (2, 80, 0), | |
"category": "User Interface", | |
} | |
import pygame.midi | |
import bpy | |
midi_in = None | |
# dictionary of midi controls to values | |
midi_positions = {} | |
# dictionary of midi controls to property paths. | |
midi_properties = {} | |
queued_property = None | |
def get_area_context(name): | |
for area in bpy.context.screen.areas: | |
if area.type == name: | |
ctx = bpy.context.copy() | |
ctx['area'] = area | |
ctx['region'] = area.regions[-1] | |
return ctx | |
def change_property(full_path, amount): | |
# set attribute if type is integer | |
current_value = eval(full_path) | |
prop_type = type(current_value) | |
if prop_type is int: | |
exec(full_path + '=' + str(current_value + amount)) | |
elif prop_type is float: | |
exec(full_path + '=' + str(current_value + float(amount) / 10.0)) | |
return {'FINISHED'} | |
def midi_update(): | |
global midi_in | |
if not pygame.midi.get_init(): | |
return | |
try: | |
midi_events = midi_in.read(1023) | |
for midi_event in midi_events: | |
midi_event_data = midi_event[0] | |
handle_midi_event(midi_event_data) | |
except Exception as e: | |
print(e) | |
finally: | |
return 1.0/15.0 # 15 times per second | |
def handle_midi_event(midi_event_data): | |
global midi_positions, midi_properties, queued_property | |
command = midi_event_data[0] | |
data1 = midi_event_data[1] | |
data2 = midi_event_data[2] | |
if command == 176: # control change | |
if data1 in midi_properties and data1 in midi_positions: | |
change_property(midi_properties[data1], data2 - midi_positions[data1]) | |
if queued_property: | |
midi_properties[data1] = queued_property | |
queued_property = None | |
midi_positions[data1] = data2 | |
def midi_stop(): | |
global midi_in, midi_positions, midi_properties, queued_property | |
print("Stop MIDI Control") | |
midi_positions = {} | |
midi_properties = {} | |
queued_property = {} | |
if midi_in is not None: | |
midi_in.close() | |
midi_in = None | |
if bpy.app.timers.is_registered(midi_update): | |
bpy.app.timers.unregister(midi_update) | |
if pygame.midi.get_init(): | |
pygame.midi.quit() | |
class MIDIStartOperator(bpy.types.Operator): | |
bl_idname = "object.midi_start" | |
bl_label = "Start MIDI Control" | |
bl_options = {'REGISTER'} | |
def selected_device_callback(self, context): | |
return MIDIStartOperator._list_midi_devices(self, context) | |
selected_device: bpy.props.EnumProperty(items=selected_device_callback, name="Device") | |
def invoke(self, context, event): | |
midi_stop() | |
pygame.midi.init() | |
wm = context.window_manager | |
return wm.invoke_props_dialog(self) | |
def execute(self, context): | |
global midi_in | |
midi_stop() | |
pygame.midi.init() | |
input_device = int(self.selected_device) | |
midi_in = pygame.midi.Input(input_device) | |
bpy.app.timers.register(midi_update) | |
self.report({'INFO'}, "MIDI control started!") | |
return {'FINISHED'} | |
def _list_midi_devices(self, context): | |
enum_items = [] | |
num_devices = pygame.midi.get_count() | |
for device_i in range(0, num_devices): | |
# (interface name, device name, is input?, is output?, in use?) | |
info = pygame.midi.get_device_info(device_i) | |
if info[2]: # device supports input | |
id = str(device_i) | |
name = info[1].decode("utf-8") # bytes to string | |
if info[4]: | |
name += " (in use!)" | |
description = info[0].decode("utf-8") | |
# (identifier, name, description number) | |
enum_items.append((id, name, description)) | |
return enum_items | |
class MIDIStopOperator(bpy.types.Operator): | |
bl_idname = "object.midi_stop" | |
bl_label = "Stop MIDI Control" | |
bl_options = {'REGISTER'} | |
def execute(self, context): | |
midi_stop() | |
self.report({'INFO'}, "MIDI control stopped!") | |
return {'FINISHED'} | |
class MIDIAdjustPropertyOperator(bpy.types.Operator): | |
bl_idname = "screen.active_property_midi" | |
bl_label = "Attach MIDI control to property" | |
bl_options = {'REGISTER'} | |
@classmethod | |
def poll(cls, context): | |
return bpy.ops.ui.copy_data_path_button.poll() | |
def execute(self, context): | |
global queued_property, midi_in | |
if not midi_in: | |
self.report({'ERROR'}, "Please start MIDI control first!") | |
else: | |
# get full data path | |
bpy.ops.ui.copy_data_path_button(full_path=True) | |
queued_property = context.window_manager.clipboard | |
return {'FINISHED'} | |
class MIDIClearPropertyOperator(bpy.types.Operator): | |
bl_idname = "screen.active_property_midi_clear" | |
bl_label = "Unattach MIDI control from property" | |
bl_options = {'REGISTER'} | |
@classmethod | |
def poll(cls, context): | |
return bpy.ops.ui.copy_data_path_button.poll() | |
def execute(self, context): | |
global midi_properties, midi_in | |
if not midi_in: | |
self.report({'ERROR'}, "Please start MIDI control first!") | |
else: | |
# get full data path | |
bpy.ops.ui.copy_data_path_button(full_path=True) | |
path = context.window_manager.clipboard | |
if path in midi_properties.values(): | |
index = list(midi_properties.values()).index(path) | |
key = list(midi_properties.keys())[index] | |
del midi_properties[key] | |
return {'FINISHED'} | |
def register(): | |
midi_stop() | |
bpy.utils.register_class(MIDIStartOperator) | |
bpy.utils.register_class(MIDIStopOperator) | |
bpy.utils.register_class(MIDIAdjustPropertyOperator) | |
bpy.utils.register_class(MIDIClearPropertyOperator) | |
def unregister(): | |
bpy.utils.unregister_class(MIDIStartOperator) | |
bpy.utils.unregister_class(MIDIStopOperator) | |
bpy.utils.unregister_class(MIDIAdjustPropertyOperator) | |
bpy.utils.unregister_class(MIDIClearPropertyOperator) | |
midi_stop() | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment