please do not repetetively use the same “getCueChannelByIndex” with the same index. Use a variable instead. That will perform much better.
It’s just most, if not all of the options laid out on one page so it is easier to find what you are looking for. The index is superfluous.
thanks for making that listing - I find it very useful for learning to have it represented this way, because it makes it easier to figure out which object parent tree to look for when adding a child
You are welcome. I missed a few, probably because I already knew them by heart at that point.
The way to think about the architecture:
MIDI goes DIRECTLY to Cubase. Think of your script as a configuration to tell Cubase what to do with those messages. I have no way of knowing for sure this is how it works, but it IS how it behaves.
You also get to insert a callback to receive events from Cubase.
These are one per element!!!
Element = (knob, button, fader).
You can put whatever code you want in the callback, but you only get one, no matter what page you are on.
The 3 functors (i.e. callbacks) you can provide respond to those events.
mOnDisplayValueChange: When the text that is displayed in Cubase changes this functor is called.
mOnTitleChange: When the title of whatever the element is bound to changes this functor is called.
mOnProcessValueChange: When the actual value (0 to 1) of the bound value changes this functor is called.
- HOWEVER → it is called whether you move the encoder on the device or the bound value in Cubase changes. What is more if the value in changed in Cubase, the “value” field of this functor is returned with the last SENT value from the encoder. If the encoder actually moved the values are different.
I have a suspicion that this behavior with mOnProcessValueChange was not intentional. It absolutely would simplify things if it were only called when Cubase is receiving data from the device.
It would also be beneficial if it were split into the following.
mOnDisplayValueChange: When the text that is displayed in Cubase changes this functor is called.
mOnTitleChange: When the title of whatever the element is bound to changes this functor is called.
[ mOnHostValueChange ]: When the actual value (0 to 1) of the bound value changes and it did not come from the device, this functor is called.
[ mOnEncoderValueChange ]: When the value (0 to 1) of the encoder value sent to Cubase changes and it did not come from Cubase, this functor is called.
-
The expectation is:
Device → Script → Host
Host → Script → Device -
But the reality is:
Device → Host → Script
Host → Script → Device
Of course (1) would always actually be like (2) but putting it this way represents the fact that you don’t get to touch the messages coming from the device before the host acts on those messages. It’s not like a MIDI Transformer that you would get in other systems. You only get notified.
However, that notification seems to be in-line, in the thread prior to the change of ground-truth. Meaning that if you move a fader in the Cubase UI, the script functor is called before the value in Cubase of the fader actually changes. So if you put, say, a prime number computation in the functor it will cause Cubase tohang waiting for the computation in your script to complete.
The model in (1) is a much better choice than the model in (2) for anything real-time. It could be far superior to that in other systems iff the functor was called on a separate thread! i.e. If Cubase never had to wait on the script computation at all.
Ok back to the mOnEncoderValueChange idea:
You can simulate this fictionality of [ mOnEncoderValueChange ] to some extent by saving the last value from mOnProcessValueChange and checking if it is the same, and doing nothing if it is. This speeds things up a lot!!! But it has an edge condition in that sometimes, when you send data from an encoder it happens to also be the same value as the last time, especially at the end of the movement. So you need to identify when this has happened more than once, and linking this to the mOnDisplayValueChange.
Another thing to be aware of, as mentioned, is that these callbacks are not per-page. They are per-element only!!! So if you set mOn * more than once you are overwriting the callback globally! If you want to do something different depending on which page you are on, then you need to only provide this functor once, and to check in that functor to see which page you are on before you do the thing.
I think it that could look something procedurally like this, but I never tried it.
element.mSurfaceValue.mOnProcessValueChange = function(activeDevice, value) {
if(currentPage == "page1") {
// do something for page1
} else if (currentPage == "page2") {
// do something for page2
}...
}.bind(currentPage)
page1.mOnActivate = function (activeDevice) {
currentPage = "page1" ;
}.bind(currentPage);
I found this really all VERY difficult to keep track of, so I used TypeScript and a wee bit of OOD to manage it. Ironically I was prototyping, and haven’t used JS in years, have had no experience with ES5, and had no idea what I was doing, so I used "Type"Script with no “types”. I am not sure whether or not to be embarrassed about this, but the code is here.
This is the biggest issue I’ve had with the API.
From what I can see, There exists no method to disable those callbacks, and initiate new calls either, So i guess the ‘fix’ to have a callbacks that are page aware is to bind global variables that define the current page state?
I’ve not done anything with the API for a few days, as decided to go back to making music!
Look at the Encoder class in the TS. It solves this problem by only binding once, and then looking up the correct functor to call. And it’s not global. The Page class resets it’s Controllers, which inform the bound Encoder what the current page is.
I don’t understand why the MR_ API has to have the “page” everywhere, it takes a different slice from the design possibilities than I would have imagined.
I noticed @pettor has taken a different ES5 style approach, and isn’t bound to a device, but has the same issue with the callbacks.
I prefer TypeScript, as it is a much more strait forward Class definition. I have spent way too much time trying to figure out the import issue though.
I wanted to get a couple of devices done before starting to abstract the Controllers to be generically available, so I tackled the device I actually use. I like the way Pettor is thinking though, and we have some similarities.
@pettor, do you think you could help figure out this import issue, so I don’t have to write everything in one file? And so we could be more descriptive in TypeScript rather than struggling through ES5?
Also, would you like to collaborate on Controllers?
Ah yes, callbacks and imports were overall difficult to work with in many ways. Personally not a fan of ES5 and I work daily with Typescript so I would have much preferred that. I was actually looking into ways of transpiling Typescript into ES5 to go around the issue.
One big issue that I have still is working with imports over multiple files will give an error like “undefined on line 37” not referring to the actual script but in some other external file of my lib. So anytime Cubase didn’t like the code I wrote it was painful to actually locate the issue.
Now @oqion what was the import issue you had? I scrolled quickly but didn’t find which one you referred to. I had many import issues so maybe I can help
One lesson learned or maybe just a tip is to use a symlink from Cubase Device Scripts to the src
folder of the Github repo. Basically that’s how I develop my lib.
Since I use Windows for Cubase I used New-Item -ItemType Junction -Path "\Driver Scripts\.lib\some-name" -Target "\github-repo\"
from Powershell. This works quite fine actually and also the reason why I made the root the way I did. I wanted to make it into a module like the API is (by defining a package with main etc) but Cubase couldn’t load this. So that would be cooler in the future.
Also in the jsconfig.json
in the Driver Scripts root the config is defined with:
{
"paths": {
".": [".src"],
"midiremote_api_v1": [".api/v1"]
},
I really wanted to use my own lib in a similar way but this config is rewritten everytime scripts are reloaded.
Man thanks!
look at this part of the code
// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
// 62: Object.defineProperty(exports, "__esModule", { value: true }); // <-- Delete this line above /\/\/\/\
// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
// ============================================================================================
//-----------------------------------------------------------------------------
// 0. DRIVER SETUP - create driver object and detection information
//-----------------------------------------------------------------------------
// get the api's entry point - This like will show as an error, but still works
import midiremote_api from 'midiremote_api_v1';
const expectedName = "Launch Control XL";
// create the device driver main object
// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
// The preceding Line needs to be edited:
// 81 : var deviceDriver = midiremote_api_v1_1.default.makeDeviceDriver('Novation', expectedName, 'Oqion'); // <- change this
// 81 : var deviceDriver = midiremote_api_v1_1.makeDeviceDriver('Novation', expectedName, 'Oqion'); // <- to this
// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
const deviceDriver = midiremote_api.makeDeviceDriver(
'Novation',
expectedName,
'Oqion'
);
//-----------------------------------------------------------------------------
// Macros
// Does it log? Can't seem to attache the debuger. Since we still have the issue
// with the imports the console here reduces the error to one place in the code.
var doesLog = true;
var LOG = function(logString)
{
if(doesLog){
// this line will show as an error. But still works.
console.log(logString)
}
}
Do you see what I am doing? Every time I run the transpiler I have to go delete line 62 and edit line 81.
This also cascades though any other files I create. So all of the classes are in the same file. This slowed me down a lot. I really don’t understand JS import system.
I have spent so much time trying to figure this out, this was as close as I could get, but I don’t want to have to do it in multiple files, so I just have one.
It doesn’t look like the export from midiremote_api_v1 is done in the right way, but there is also like, so many ways to do it, I can’t keep it strait.
Hrmmmm that’s a rough one. Honestly never worked with ES5 before Midi Remote but I would start trying out different settings. Using ES5 lib and target in tsconfig does sound like it should be enough but maybe there’s even older targets or other libs to try. I would also try to use Babel to transpile the code into ES5 just to see if that works better (@babel/preset-typescript · Babel).
Some other random thoughts about the issue. Have you tried using var api = require(....)
in the ts instead of import? I find it strange that the output is midiremote_api_v1_1.default.makeDeviceDriver
because that’s just pure wrong. Default doesn’t have the makeDeviceDriver
and why is this even appended. Must be something silly
In regards to the even weirder Object.defineProperty(...)
I at least found this:
Not any solution as such but maybe possible to get something interesting out of it. Personally I think tinkering with transpiler settings should fix this. But all that would be easier if only we knew the details of Cubase JS engine. Or why it doesn’t support anything newer than ES5
Just to clearify import midiremote_api from 'midiremote_api_v1';
is a newer import variation indicating that you use the default export (otherwise it’s import {something} ...
and I believe this is why you get the Object.defineProperty(...)
and the midiremote_api_v1_1.default.makeDeviceDriver
lines. At least they are an effect of it. So I hope that using require
in the typescript file could avoid the transpiler to write these lines.
In my lib I always avoided using the import. I don’t use Typescript ofc so there’s a difference but I remember that never worked. Visual Studio Code always highlight that I can convert to ES module but that fails when trying to make Cubase run the script.
I just wrote and deleted a book that was mostly grousing about ES5 and how difficult it makes things, and how it doesn’t make sense when it could have at least been ES6, if not just TypeScript. I mean, why make it more difficult than it needs to be? I know I’m a rather proficient software engineer, so how is ES5 for non coders?
ES5 is like trying to put a fire out with buckets of water when there is a fire hydrant in the front garden. But maybe it has something to do with a fear of concurrence. But you can forget about that, because one could make that happen anyway, It’s a mystery.
Anyway, that one line is a forward reference to allow imports and exports, and if you flag it out, then it also takes away the inheritance boilerplate.
I found a lot of people writing scripts to be installed in other platforms that had the same issue, and they all seemed to resort to what I am doing. Writing the whole thing in one file, and deleting that one line. I thought that would be a temporary workaround.
The added “_1” is intentional. That doesn’t look right, but it is.
This fixes the second “default” rewrite. And also brings the types in nicely, but it complains a LOT about the midiremote_api_v1.d.ts file. Which it says is not complient.
/// <reference path="../../../.api/v1/midiremote_api_v1.d.ts" />
import midiremote_api = require('midiremote_api_v1');
if you put the following in that file it will stop complaining:
// @ts-nocheck
It will still stick the offending export forward definition in, but not add the default.
But this will produce perfectly functioning JS.
/// <reference path="../../../.api/v1/midiremote_api_v1.d.ts" />
var midiremote_api = require('midiremote_api_v1');
If you leave it as “import” and then just before transpiling, change it to a “var”, alles ist gut.
I really wish I knew a more strait forward work around. It would be nice if Jochen et. al. would add the nocheck to the .d.ts file or clean it up so it works in TS.
Personally, I would rather do something I really didn’t want to do, and just wrap the whole API so the issue only shows in one location, than to write anything else in ES5.
For now, I simply copied the ,api directory and added the flag at the top of the file.
Baby steps, but it’s closer.
Thank you for your help!!!
That was an error! Not one the effected anything, but that config was incorrect.
See above for the solution and the repo for the fixed tsconfig.json.
Also, again, I just copied the .api to another directory because it gets re-written every time.
Really wish that file was TS compliant, or the whole thing was just in TS. I will push more fixes when I get the files separated, and tested.
As I am digging into this, I must agree with this assessment and @oqion suggestion on mOnDisplayValueChange (that it be split in to two functors, mOnHostValueChange, and mOnEncoderValueChange)
@Jochen_Trappe. Please take this into consideration.
- Ron
Hi @oqion and @Ron.Garrison, the functor mOnDisplayValueChange is meant to be used to drive hardware displays only. That’s where the “Display” part of the name comes from.
Regarding the request having a mOnHostValueChange and a mOnEncoderValueChange separately, there is at least a way to get the mOnHostValueChange done. By using “makeValueBinding(…).mOnValueChange = function …”
@Jochen_Trappe - Can you please point me to “mOnValueChange”. I’m not finding it in the MIDI Remote API.