Created
May 29, 2019 10:39
-
-
Save howiemnet/280d937d3507950a62b516dd93dcae38 to your computer and use it in GitHub Desktop.
Houdini control from a Behringer X-Touch One using pygame
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
# h's super dodgy, super-hacky MIDI controller for Houdini code | |
# | |
# Designed to get a Behringer X-Touch One controller to control Houdini | |
# while feeding back current frame and status to the lights and readouts | |
# on the controller. Super cool. | |
# | |
# But this really, really, shouldn't be public. My Python-fu is a little raw. | |
# But it may give someone a useful starting point... | |
# I place this file in $HSITE/houdini17.5/scripts/python, and then | |
# a couple of new toolbar buttons actually enable and disable things. | |
# The code for the "Enable MIDI Controller" button: | |
# | |
# import DAWmidi as dm | |
# reload(dm) # reload the module just in case I'm mid-debugging! | |
# dm.attachHandlers() | |
# | |
# The code for the "Disable MIDI Controller" button: | |
# | |
# import DAWmidi as dm | |
# dm.removeHandlers() | |
# I like my Midi Controller to show progress of renders/cooks/caching ops too. | |
# On your driver op, pop these code snippets into the script boxes (make sure | |
# the script flavour is set to Python) | |
# | |
# pre-render (this just preps your code with the start and end frames so it | |
# can calculate progress and set the lights up properly: | |
# | |
# import DAWmidi as dm | |
# dm.preRender(hou.evalParm('f1'),hou.evalParm('f2')) | |
# | |
# pre-frame (this gets called each frame): | |
# | |
# import DAWmidi as dm | |
# dm.preFrame($F) | |
# | |
# post-render (to clean up afterwards): | |
# | |
# import DAWmidi as dm | |
# dm.postRender() | |
# Lots of to-dos and bugs to fix, including: | |
# If you cancel a render/cook/cache, the clean-up code doesn't always get run | |
# so you might end up with your controller lights looking wrong | |
# Those device IDs really shouldn't be hardcoded hehehe | |
# | |
import hou | |
import pygame.midi as pm | |
# Values for the input and output device ID | |
# are hardcoded for now (yuck!) - to find out | |
# which IDs represent your X-Touch one (or equivalent) | |
# you can do something like this: | |
# | |
#for num in range(0,pm.get_count()): | |
#... pm.get_device_info(num) | |
#('CoreMIDI', 'X-Touch One', 0, 1, 0) | |
#('CoreMIDI', 'USB Midi ', 0, 1, 0) | |
#('CoreMIDI', 'Scarlett 2i4 USB', 0, 1, 0) | |
#>>> print(pm.get_device_info(5)[1]) | |
#X-Touch One | |
# | |
# | |
# Note that the device ID may change after reboots or | |
# if you change anything about your MIDI setup... | |
inputDevID = 3 | |
outputDevID = 2 | |
inM = None | |
outM = None | |
handlerAttached = 0 | |
tTimeDisplayFrames = False | |
tTimeDisplayRemember = False | |
renderMode = False | |
tCycle = False | |
tFastScrub = False | |
uiFrame = 0 | |
def attachHandlers(): | |
global handlerAttached | |
global inM | |
global outM | |
global inputDevID | |
global outputDevID | |
global tTimeDisplayFrames | |
global tCycle | |
global tFastScrub | |
if handlerAttached == 0: | |
pm.init() | |
tCycle = (hou.playbar.playMode()==hou.playMode.Loop) | |
outM = pm.Output(outputDevID,0) | |
inM = pm.Input(inputDevID,0) | |
hou.ui.addEventLoopCallback(checkForMIDIevents) | |
hou.playbar.addEventCallback(updateTransport) | |
updateCycleButton() | |
outM.note_on(32,65) | |
outM.note_on(29,65) | |
updateDisplayToggleButton() | |
updateTransport(hou.playbarEvent.Stopped,hou.frame()) | |
updateTransport(hou.playbarEvent.FrameChanged,hou.frame()) | |
handlerAttached = 1 | |
def removeHandlers(): | |
global handlerAttached | |
global inM | |
global outM | |
global inputDevID | |
global outputDevID | |
if handlerAttached == 1: | |
hou.ui.removeEventLoopCallback(checkForMIDIevents) | |
hou.playbar.removeEventCallback(updateTransport) | |
outM.close() | |
inM.close() | |
handlerAttached = 0 | |
renderStartFrame = 0 | |
renderFrameCount = 0 | |
def preRender(startFrame,endFrame): | |
global handlerAttached | |
global inM | |
global outM | |
global tTimeDisplayRemember | |
global tTimeDisplayFrames | |
global renderMode | |
global renderStartFrame | |
global renderFrameCount | |
global uiFrame | |
uiFrame = hou.frame() | |
renderStartFrame = startFrame | |
renderFrameCount = (endFrame - startFrame) + 1 | |
renderMode = True | |
tTimeDisplayRemember = tTimeDisplayFrames | |
tTimeDisplayFrames = True | |
renderModeLights() | |
def preFrame(theFrame): | |
global handlerAttached | |
global inM | |
global outM | |
if handlerAttached == True: | |
updateTCDisplay(hou.frame()) | |
propComplete = (theFrame - renderStartFrame) / renderFrameCount | |
updateKnob(propComplete) | |
def postRender(): | |
global handlerAttached | |
global inM | |
global outM | |
global renderMode | |
global tTimeDisplayRemember | |
global tTimeDisplayFrames | |
global uiFrame | |
tTimeDisplayFrames = tTimeDisplayRemember | |
for i in range(0,6): | |
outM.note_on(7+i,1) | |
renderMode = False | |
renderModeLightsOff() | |
updateTransport(hou.playbarEvent.Stopped,uiFrame) | |
updateTransport(hou.playbarEvent.FrameChanged,uiFrame) | |
#updateKnob(1) | |
def updateKnob(prop): | |
global outM | |
prop = int(6*(prop)) | |
for i in range(0,6): | |
if i<prop: | |
outM.note_on(7+i,66) | |
if i==prop: | |
outM.note_on(7+i,64) | |
if i>prop: | |
outM.note_on(7+i,0) | |
def checkForMIDIevents(): | |
if inM.poll(): | |
handleInput() | |
def handleInput(): | |
while inM.poll(): | |
inputData = inM.read(1) | |
theMessage = inputData[0][0] | |
#print(theMessage) | |
if (theMessage[0]==144) and (theMessage[2]==127): | |
try: | |
function_dict[theMessage[1]]() | |
#print('ok') | |
except: | |
print('not handled:'), | |
print(theMessage) | |
if (theMessage[0]==176) and (theMessage[1]==88): | |
if theMessage[2]==65: | |
doJogFwd() | |
if theMessage[2]==1: | |
doJogBack() | |
def updateTCDisplay(myFrame): | |
global outM | |
global tTimeDisplayFrames | |
global renderMode | |
sevenSeg = [0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00] | |
displayDigits = [2,5,10,0,0,0,0,0,0,5,6,10] | |
displayDigitsFrames = [2,5,10,10,10,10,10,10,0] | |
sysexstring = [0xF0,0x00,0x20,0x32,0x41,0x37,0x01,0x01,0x76,0x5C,0x1C,0x5E,0x10,0x54,0x10,0x01,0x01,0x01,0x52,0x02,0xF7] | |
sysexstringFrames = [0xF0,0x00,0x20,0x32,0x41,0x37,0x00,0x00,0x00,0x00,0x00,0x5E,0x10,0x54,0x10,0x00,0x71,0x50,0x00,0x02,0xF7] | |
if renderMode == True: | |
displayDigitsFrames[0] = 10 | |
displayDigitsFrames[1] = 10 | |
theFrame = int(myFrame) | |
if tTimeDisplayFrames==True: | |
displayDigitsFrames[8] = theFrame % 10 | |
theFrame = theFrame / 10 | |
if theFrame>0: | |
displayDigitsFrames[7] = theFrame % 10 | |
theFrame = theFrame / 10 | |
if theFrame>0: | |
displayDigitsFrames[6] = theFrame % 10 | |
theFrame = theFrame / 10 | |
if theFrame>0: | |
displayDigitsFrames[5] = theFrame % 10 | |
theFrame = theFrame / 10 | |
if theFrame>0: | |
displayDigitsFrames[4] = theFrame % 10 | |
# update string | |
for x in range(0,9): | |
sysexstringFrames[x+6] = sevenSeg[displayDigitsFrames[x]] | |
outM.write_sys_ex(0,sysexstringFrames) | |
else: | |
tframes = theFrame % 25 | |
displayDigits[10] = tframes % 10 | |
displayDigits[9] = int(tframes / 10) | |
theFrame = int(theFrame/25) | |
tsecs = theFrame % 60 | |
displayDigits[8] = tsecs % 10 | |
displayDigits[7] = int(tsecs / 10) | |
theFrame = int(theFrame/60) | |
tmins = theFrame % 60 | |
displayDigits[6] = tmins % 10 | |
displayDigits[5] = int(tmins / 10) | |
# update string | |
for x in range(0,12): | |
sysexstring[x+6] = sevenSeg[displayDigits[x]] | |
outM.write_sys_ex(0,sysexstring) | |
# -------------------------------- | |
def doPlay(): | |
hou.playbar.play() | |
def doPlayReverse(): | |
hou.playbar.reverse() | |
def doStop(): | |
hou.playbar.stop() | |
def doJogFwd(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
if hou.frame()==hou.playbar.playbackRange()[1]: | |
hou.setFrame(hou.playbar.playbackRange()[0]) | |
else: | |
hou.setFrame(hou.frame()+1) | |
def doJogBack(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
if hou.frame()==hou.playbar.playbackRange()[0]: | |
hou.setFrame(hou.playbar.playbackRange()[1]) | |
else: | |
hou.setFrame(hou.frame()-1) | |
def doHome(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
hou.setFrame(hou.playbar.playbackRange()[0]) | |
def doEnd(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
hou.setFrame(hou.playbar.playbackRange()[1]) | |
def doPrevKey(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
hou.playbar.jumpToPreviousKeyframe() | |
def doNextKey(): | |
if hou.playbar.isPlaying(): | |
hou.playbar.stop() | |
hou.playbar.jumpToNextKeyframe() | |
def toggleCycle(): | |
global tCycle | |
if tCycle==True: | |
tCycle = False | |
hou.playbar.setPlayMode(hou.playMode.Once) | |
else: | |
tCycle = True | |
hou.playbar.setPlayMode(hou.playMode.Loop) | |
updateCycleButton() | |
def updateCycleButton(): | |
global tCycle | |
global outM | |
if tCycle==True: | |
outM.note_on(15,66) | |
else: | |
outM.note_on(15,1) | |
def toggleDisplay(): | |
global outM | |
global tTimeDisplayFrames | |
if tTimeDisplayFrames==True: | |
tTimeDisplayFrames = False | |
else: | |
tTimeDisplayFrames = True | |
updateDisplayToggleButton() | |
def updateDisplayToggleButton(): | |
if tTimeDisplayFrames==True: | |
outM.note_on(1,1) | |
else: | |
outM.note_on(1,66) | |
updateTCDisplay(hou.frame()) | |
def renderModeLights(): | |
global outM | |
outM.note_on(22,0) | |
outM.note_on(32,0) | |
outM.note_on(33,0) | |
outM.note_on(31,0) | |
outM.note_on(23,0) | |
outM.note_on(29,0) | |
outM.note_on(20,0) | |
outM.note_on(21,0) | |
outM.note_on(1,1) | |
outM.note_on(15,1) | |
outM.note_on(24,64) | |
def renderModeLightsOff(): | |
global outM | |
outM.note_on(32,0) | |
outM.note_on(33,65) | |
outM.note_on(31,65) | |
outM.note_on(29,65) | |
outM.note_on(24,0) | |
updateCycleButton() | |
updateDisplayToggleButton() | |
updateTransport(hou.playbarEvent.FrameChanged,hou.frame()) | |
function_dict = {1:toggleDisplay,15:toggleCycle,27:doPrevKey,28:doNextKey,20:doHome,21:doEnd,22:doStop,23:doPlay, 31:doPlayReverse, 32:doStop, 33:doPlay} | |
# -------------------------------- | |
def updateTransport(event_type,frame): | |
global renderMode | |
if renderMode==True: | |
renderMode = False | |
renderModeLightsOff() | |
if event_type==hou.playbarEvent.Started: | |
outM.note_on(22,66) | |
outM.note_on(32,64) | |
outM.note_on(33,1) | |
outM.note_on(31,1) | |
outM.note_on(23,0) | |
if event_type==hou.playbarEvent.Stopped: | |
outM.note_on(22,0) | |
outM.note_on(32,0) | |
outM.note_on(33,65) | |
outM.note_on(31,65) | |
outM.note_on(23,66) | |
if event_type==hou.playbarEvent.FrameChanged: | |
if hou.frame()==hou.playbar.playbackRange()[0]: | |
outM.note_on(20,0) | |
else: | |
outM.note_on(20,66) | |
if hou.frame()==hou.playbar.playbackRange()[1]: | |
outM.note_on(21,0) | |
else: | |
outM.note_on(21,66) | |
updateTCDisplay(frame) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment