This thread is a redux of this thread that demonstrates how creating User Actions in ClyphX Pro is much easier than it was in ClyphX.
As mentioned in the ClyphX Pro user manual, you can create your own actions (called User Actions) that will work just like any other ClyphX Pro actions. ClyphX Pro includes a file (called ExampleActions.py) that includes a bunch of instructions and examples. However, I think it would help some of you to see a practical example of the steps involved in creating your own actions.
For this example, we’ll be creating an action that allows Looper’s Speed parameter to be controlled more precisely than standard ClyphX Pro Device Actions allow for.
The following posts will provide a general overview of action development and then get specifically into developing our example action.
Step 1: Install an IDE (integrated development environment). This is similar to a text editor, but includes features that make writing code easier. One of the easiest to use (and somewhat limited in terms of features) of the free IDEs for Python is Wing IDE.
Step 2: Get a clear mental picture of what you want your action to do and then figure out the steps needed to make that happen (I call this a specification or spec). The clearer the image, the easier it will be to code. Unfortunately, it can be somewhat difficult to know what steps will be involved if you’re new to developing actions. The more you do it, the better you’ll get at it. Also, you can figure things out by searching for solutions online.
Python is used for many, many things and so there is lots of info about it online. In most cases though, you’ll need to figure out how to generalize the problem you’re having. For example, suppose you needed to sort a list of notes in a clip in some fashion. Searching for something like “sorting MIDI clip notes in Live MIDI Remote Scripts” is not going to yield very many (if any) results. Instead, search for something more general like “sorting lists in Python” and work from there.
Step 3: Code the action. You will probably make mistakes at this stage and that’s perfectly fine. Coding involves a lot of trial and error and testing. Sometimes you didn’t realize you needed a step in your spec. In that case, you should refactor your spec, which means that you’ll change the steps involved in the spec without changing what the spec attempts to accomplish. And then you should refactor your code, which means that you’ll change the code without changing what the code attempts to accomplish.
During this step, taking a look at what is going on in the code is extremely useful. For MIDI Remote Scripts, you’ll use Live’s Log.txt file. We’ll see examples of this as we’re developing our example action.
Step 1 So first we need the spec. Again, this action is going to allow for precise adjustment of Looper’s Speed parameter. What are the steps needed to make this happen?
A. We need a way of specifying which Looper device we want to operate on. B. We need a way of finding the parameter within Looper that we want to operate on. C. We need to know the range of the parameter we want to control. D. We need to figure out how we’ll specify the value we want to set the parameter to. The value range shown in Live is -36.0 to 36.0.
Step 2 Next we need a name for our action. SPD x (where x is the speed to set) should work. Note, however, that when we access this action from an X-Trigger, we'll use its full name, which is USER_DEV SPD x.
Step 3 Next we need to define the action in ExampleActions.py. Find that line that reads: self.add_clip_action('ex_clip', self.clip_action_example)
And then add this line directly beneath it: self.add_device_action('spd', self.set_looper_speed)
This defines that our action name will call the method named set_looper_speed. Now we need to define that method. We’ll just create a skeleton (method that doesn’t really do anything) to start with. def set_looper_speed(self, action_def, args): self.canonical_parent.log_message('set_looper_speed called for device=%s with args=%s' % (action_def['device'], args)) This will simply write to Live’s log file when the action is triggered from an X-Trigger. You’ll want to test this out to make sure that it works. To do that, load a new set in Live, create an X-Clip named  USER_DEV SPD 36.0, trigger it and then look in Live’s log file. Also, save the set. From here on out, every time we change our method, we’ll need to reload the set to test our changes.
Step 4 Now let’s tackle the A part of the spec. ClyphX Pro handles this for us by passing the device that the action should apply to. When accessing our action from an X-Trigger, we can use all the common options for specifying the device to operate on. For example, 4/USER_DEV("Looper") SPD 36.0 or “My Track”/USER_DEV(2) SPD 36.0. So all we need to do here is ensure that the device we're passed actually is Looper. Let’s modify our method to do that: def set_looper_speed(self, action_def, args): device = action_def['device'] self.canonical_parent.log_message('set_looper_speed called for device=%s with args=%s' % (device, args)) if device.class_name == 'Looper': self.canonical_parent.log_message('The device is Looper!')
Step 5 Now let’s tackle the B part of the spec. Assuming we don’t know where the Speed parameter is within Looper’s parameter list, we’d have to find it. We actually might as well tackle the C part of the spec at this point too. Let’s modify our method to do that: def set_looper_speed(self, action_def, args): device = action_def['device'] self.canonical_parent.log_message('set_looper_speed called for device=%s with args=%s' % (device, args)) if device.class_name == 'Looper': for i, p in enumerate(device.parameters): self.canonical_parent.log_message('%s: %s range=%s - %s' % (i, p.name, p.min, p.max)) We actually got lucky here because the range of the Speed parameter in the API is exactly the same as the range shown in Looper itself. This isn’t always the case. Anyhow, now we’ve got the range (-36.0 – 36.0) and the index of the parameter (5).
Step 6 The D part of the spec is simple since the range in the API is the same as the range shown in Looper, so it’s obvious how we’ll specify the value. So let’s get rid of all the logging and finish up the method: def set_looper_speed(self, action_def, args): device = action_def['device'] if device.class_name == 'Looper': device.parameters.value = float(args.strip()) Step 7 Test the action with a variety of correct (like USER_DEV SPD -14.5) and incorrect (like USER_DEV SPD 37.8 or USER_DEV SPD breakit) arguments to confirm that it works as expected.
To any experienced Python programmers reading this, the above may look a bit odd since we're not doing any sort of error checking. For example, args is a string and we're casting it to a float, which could fail if args was something like 'breakit' or if it was a float and was out of range. The reason for this is that all action methods in ClyphX Pro are executed through a try/except block. In fact, we don't really even need to check that the device is Looper. We only do that for ourselves when reading the code to point out that this action is intended to work with Looper.
As a side note, for technical reasons, builtin ClyphX Pro methods do not rely on the try/except block and do perform any necessary error checking themselves.
Sure, you can trigger ClyphX Pro builtin actions like so: self.canonical_parent.clyphx_pro_component.trigger_action_list('metro')
I'll add this to the info in ExampleActions.py.
OK, It works.
Another remark for Actions : Error handling when dispatching actions is silent. Consequently, if my user action sends exception, nothing is displayed. This requires to use my own error handlers in every action I write. I guess I would be great for users to have these traces when writing actions without the need to add their own error handler ?
Would it be possible to enable log tracing when dispatching actions from the Clyphx Pro framework, something like a call to log.info(traceback.format_exc()) before passing exception ?
Right, that's covered in the Notes About Actions section of ExampleActions.py. You can simply write a decorator to handle exception logging. No need to have each method handle it itself. For example, before your class definition, add this:
Okay thanks, so no way to get control over follow actions at all? Would there somehow be a way, to kind of automate different clips, so as to create a kind of song form in session view on the fly? Like Clip 1 = Verse, clip 2 = Chorus and so on and make them automatically play after each other. (like what follow actions do...)
If you have M4L, check out the device in this video. It can do follow action-like stuff and much more. If you have questions about that, please create a new thread as it's unrelated to the topic of this thread.
for i in device.parameters trigger( #how to write this )
(and i also think im writing the for loop wrong, do i need to wrap device.parameters in list() or something like that? i never wrote python before 🙉)
Regarding the second question, i can target and read the SimplerDevice sample property or any other device property but im lost. I think i have to access Application class to trigger hot swap. And also if i could do that, i have to trigger the file hot swap, no the simpler device hot swap! (attach and image to show what kind of hot swap im referring to)
I hope to be explaining myself correctly Thank you so much!
It's technically possible to bind controls to parameters via user actions, but that's quite complex and not something we could offer support on unfortunately.
As far as how to use the trigger_action_list method, you simply pass it a string that specifies the action list to trigger. For example, trigger_action_list('metro ; 1/mute')
The loop you've got there is syntactically correct.
Interacting with the browser object is a bit convoluted unfortunately and there is no real documentation on it. I haven't done what you're looking to do here although I'm pretty sure the Push scripts can do it, so it must be possible.
I hope that gives you some direction. Also, it wouldn't hurt to go through some online Python tutorials so that you have a better grasp on the language. Thankfully, it's relatively simple.