Skip to content

Instantly share code, notes, and snippets.

@howiemnet
Created May 29, 2019 10:39
Show Gist options
  • Save howiemnet/280d937d3507950a62b516dd93dcae38 to your computer and use it in GitHub Desktop.
Save howiemnet/280d937d3507950a62b516dd93dcae38 to your computer and use it in GitHub Desktop.
Houdini control from a Behringer X-Touch One using pygame
# 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