# Lemur programming headaches



## quickbrownf0x (May 10, 2020)

Guys, help me out here please;

I figured I'd have another go at my custom Lemur UI. Totally forgot that it's a pain in the *ss to script and debug. 🤦‍♂️
Can anybody tell me how to access the INSTRUMENT_BANK (tree) object variables (like 'notes', or 'version') and functions/expressions? As in;

decl some_id = {0, 1);
decl oInstrumentBank = findobject('INSTRUMENT_BANK');
decl currentInstrument = getexpression(oInstrumentBank, 'getInstrument(' + some_id + ')';

// getInstrument(id) searches the bank based on 2 variables; 'selected_track_id' and 'selected_track_channel_id' and returns the
right object with all its data

// Everything is nicely rendered, labeled, put in focus. Drinks and high fives all around.


A while back I thought I had it, but I can't get the stupid thing to work now. 4 hours down the toilet, so far, and none the wiser. 😣







Thanks and cheers!


----------



## pmcrockett (May 10, 2020)

In this line of code

decl currentInstrument = getexpression(oInstrumentBank, 'getInstrument(' + some_id + ')';

you're trying to pass the function _getInstrument()_ as the second argument of _getexpression()_, but that argument needs to be an expression name (one of the green dot items in the object tree), not a function.

To access INSTRUMENT_BANK's variables (or expressions, as Lemur calls them) you can either use the syntax_ getexpression(INSTRUMENT_BANK, 'notes')_ and _setexpression(INSTRUMENT_BANK, 'notes', newValue)_, or you can directly access them as INSTRUMENT_BANK.notes -- e.g. _INSTRUMENT_BANK.notes = newValue;_

If your script is in the same object as the function you're trying to access, you can call the function with just its name. If the calling script isn't in the same object, you can call the function by prefixing it with the object that contains it -- e.g. _INSTRUMENT_BANK.getInstrument(id)_

If you have nested objects, you may need to include parent objects when you call expressions or functions depending on where the calling script is located -- e.g _INSTRUMENT_BANK.INSTRUMENTS.SCORING.notes_

So if I understand what you're trying to do, the correct code would be

_decl some_id = {0, 1);
decl currentInstrument = INSTRUMENT_BANK.getInstrument(some_id); _


----------



## quickbrownf0x (May 12, 2020)

pmcrockett said:


> In this line of code
> 
> decl currentInstrument = getexpression(oInstrumentBank, 'getInstrument(' + some_id + ')';
> 
> ...


Cool, tx!

I've tried to find a way to loop through the whole object tree, but so far no luck. I'm trying to scan a 'folder' object to see if it has instrument objects attached and if so - return those objects (plus their properties, variables, etc.) into an array or something. If a folder has no instruments or subfolders I want to move up one node in the tree, pick the next folder, and so on..... do the whole thing all over again, until the last folder.


Here's my search function, which is called on track selection;

```
// Find and get the properties of an instrument based on a specific ID
decl selected_track_id = id[0];
decl selected_track_channel_id = id[1];


decl contName = getattribute(obj,'name');
decl object_id = getfirst(findobject(contName));
DEBUG += 'PARENT CONTAINER: ' + contName + ' | ';
decl title = '';
decl instrument_list, x, instrument_property;

if(object_id)
{
    DEBUG += 'Current object is there |';

while(object_id)
{

            //for (i = 0; i<sizeof(object_id); i++)
            //{
                    title = getattribute(object_id, 'name');

                    if (getexpression(object_id, 'type') == 'folder')
                    {
                        // Subfolder - traverse
                        DEBUG += 'This is a folder called: ' + title + ' | ';

                        // Check if this folder contains instruments (via getInstruments(FolderId))
                        instrument_list = INSTRUMENT_BANK.getInstruments(object_id);
                        if (sizeof(instrument_list > 0))
                        {
                            DEBUG += 'instrument_list size: ' + sizeof(instrument_list) + ' |';
                            // Folder contains instruments, run through them
                            for (x=0; x<sizeof(instrument_list); x++)
                            {
                                DEBUG += 'instrument title: ' + getexpression(instrument_list[x], 'title') + ' |';

                                if (getexpression(instrument_list[x], 'selected_track_id') == selected_track_id && getexpression(instrument_list[x], 'selected_track_id') == selected_track_channel_id)
                                {
                                    // Match! Stop searching and return the matched instrument
                                    // return object_id;

                                    DEBUG += 'Weve got a match!: ' + getexpression(instrument_list[x], 'title');
                                }
                                else
                                {
                                        // Move onto the next object
                                        DEBUG += 'No match, move on from: ' + getexpression(instrument_list[x], 'title');
                                }
                            }
                        }
    
                        INSTRUMENT_BANK.searchObject(object_id, {selected_track_id, selected_track_channel_id});
                    }
         
                //}
                object_id = getnext(object_id);
                i++;
        }
}
```

Here's the function (called getInstruments(folder)) that tries to grab a list of instruments connected to a folder;


```
// Find and get all the properties of all instruments in a folder named X in the form of an array
decl obj = folder;

decl contName = getattribute(obj,'name');
//decl object_id = getfirst(findobject(contName));

decl i, instrument_list_path, instrument_list, instrument_list_object, instrument_list_size, current_instrument;

DEBUG += 'checking for instruments in ' + contName + ' | ';

//if (object_id)
//{
        instrument_list_path = 'INSTRUMENT_BANK.' + getattribute(obj, 'name') + '.' + 'Instruments';
        instrument_list_object = getfirst(findobject(instrument_list_path));

        //instrument_list_size = sizeof(instrument_list_object);
        //DEBUG += 'CURRENT PATH: ' + instrument_list_path + ' | ';
        //DEBUG += 'CURRENT LIST SIZE: ' + instrument_list_size + ' | ';
    
        while (instrument_list_object)
        {
                current_instrument = findobject(instrument_list_object);
                DEBUG += 'Adding new instrument: ' + getexpression(current_instrument, 'title') + ' | ';

            
                instrument_list[i] = current_instrument;
                instrument_list_object = getnext(instrument_list_object);
                i++;
        }

        return instrument_list;

//}
//else
//{
//        DEBUG += 'this is not an object | ';
```

I wish I could just do a For....in X, not to mention things like reading from a JSON file or whatever, but apparently not. Anyway, cheers!


----------



## quickbrownf0x (May 12, 2020)

Right, ehr.. strike all of that stuff. I think right now all I'd really like to know is;

- how to loop through a bunch of objects and get their properties; is while() {} the only way to go here?
- how to pass on an object to a function/expression, like so:


```
folder_instruments = getInstruments(object);
```

I don't want to use findobject('some_object_name') all the time, because the name might not be unique, etc. Just looks a little flimsy to me. 🤷‍♂️

Simple things, you'd think right? But maybe I'm missing something. What do you guys think?


----------



## pmcrockett (May 12, 2020)

The functions for iterating through object trees are:
getfirst(object)
getnext(object)
getobject()
getparent(object)

They're described in the manual on pg. 129, but unfortunately there are no examples of how to actually use them in code. Basically, getfirst(object) will return the first object in the tree structure that you give it, and getnext(object) will return the next object after the object you give it in its respective tree structure. getparent(object) will return the parent of the object you give it, i.e. it allows you to move up a level in a tree structure. And getobject() will return the object that contains the script that is currently running.

To get a script to accept arguments so you can pass objects to it, you need to define the argument name when you create the script. You do this in the _Create Script _box that comes up when you hit the script button, same as where you name the script. If you want the getInstrument() script to accept one argument, you'd name it, for example, getInstrument(arg). (arg can be named whatever you want.) Then, within the getInstrument(arg) script, a variable named arg will be available (you don't have to declare it) that will contain whatever you passed as an argument when you called the function. If you want, say, three arguments, that will be defined as getInstrument(arg1,arg2,arg3).


----------



## quickbrownf0x (May 13, 2020)

pmcrockett said:


> The functions for iterating through object trees are:
> getfirst(object)
> getnext(object)
> getobject()
> ...



Thanks, PM. 

I'm familiar with the functions you mention, just don't know how to best use them and like you said the documentation is a bit sh*t. All I want to do is go through the entire tree, or at least a single object (a folder) to see if A) it contains more objects (folder); if so - go through those and B) look for instrument objects. If it has any; awesome - iterate through those and see if at least one of them has a specific property value.

I can only find one example where it iterates through a single level, but it uses a while statement;


```
decl obj;
obj = getfirst(Container);

while(obj)
{
        // Do something
        obj = getnext(obj);
}
```

How do I do something like this;


```
INSTRUMENT_BANK.getFolders(INSTRUMENT_BANK);

function getFolders(node)
{
    if (node.is_folder)
        node.children.forEach(function (child)
       {
             getFolders(child);
        });
   else
        return out(node.instruments);
}
```


Thought I had it, but it makes Lemur crash harder than a spaced-out Dutch guy after a 5-day rave during the 90's. Infinite loop somewhere, probably. Here it is;


```
// Find and get the properties of all (sub)folders within this level - getFolders(folder)

decl folder_name = getattribute(getobject(),'name');
decl folder_object = getfirst(findobject(folder_name));

if (folder)
{
    folder_name = getattribute(folder,'name');
}

decl folder_instruments, match_folder_instrument, folder_search; 

// If it has (sub)folders, loop through them and check every one for instruments
while(folder_object)
{
    DEBUG += 'PARENT title: ' + getexpression(folder_object, 'DAW_name') + ', ';
    
    if (getexpression(folder_object, 'type') == 'folder')
    {
        // This is a (sub)folder - look for instruments
        DEBUG += 'FOLDER, scan for instruments ';
        folder_instruments = getInstruments(getexpression(folder_object, 'name'));
         
        while (folder_instruments)
        {
            // Loop through all the returned instruments
            match_folder_instrument = matchCurrentInstrument({getexpression(folder_instruments, 'selected_track_id'), getexpression(folder_instruments, 'selected_track_channel_id')}); 
            
            
            if (match_folder_instrument == 1)
            {
                // Instrument Match - Exit this loop
                break; 
            }

            folder_instruments = getnext(folder_instruments); 
        }

        // Search the next subfolder
        folder_search = getFolders(folder_object);
    }

    if (getexpression(folder_object, 'type') == 'instruments')
    {
        //DEBUG += 'This folder looks like it contains instruments ';
        folder_instruments = getInstruments(getexpression(folder_object, 'name'));
    }

    
    folder_object = getnext(folder_object);
}
```


----------



## pmcrockett (May 13, 2020)

quickbrownf0x said:


> Thanks, PM.
> 
> I'm familiar with the functions you mention, just don't know how to best use them and like you said the documentation is a bit sh*t. All I want to do is go through the entire tree, or at least a single object (a folder) to see if A) it contains more objects (folder); if so - go through those and B) look for instrument objects. If it has any; awesome - iterate through those and see if at least one of them has a specific property value.
> 
> ...


I'd have attempt to run the code myself to come to a definite conclusion, but I think the problem may be that you're calling getFolders() recursively. This is not documented, AFAIK, but my experience has been that Lemur behaves strangely when you make recursive function calls -- I've thrown together a tiny recursive test project just to confirm that I'm not misremembering, and in a recursive loop it only seems to be running 11 recursive calls before just giving up. So maybe try to isolate and test the recursion aspect and/or experiment with ways of structuring the code that aren't recursive.


----------



## quickbrownf0x (May 13, 2020)

pmcrockett said:


> I'd have attempt to run the code myself to come to a definite conclusion, but I think the problem may be that you're calling getFolders() recursively. This is not documented, AFAIK, but my experience has been that Lemur behaves strangely when you make recursive function calls -- I've thrown together a tiny recursive test project just to confirm that I'm not misremembering, and in a recursive loop it only seems to be running 11 recursive calls before just giving up. So maybe try to isolate and test the recursion aspect and/or experiment with ways of structuring the code that aren't recursive.



Thanks for that! 
This really is like programming a washing machine, where half of the buttons are missing, isn't it? Jesus. 🤦‍♂️

Here you go, btw. The script is triggered on track selection from Cubase, through listenTrackSelection(MIDI_ARGS). This grabs 2 values; polypressure and midi channel - then you're off to the races... 
Also added a bit of pseudo code to help me keep track of what I;m trying to do. See the .txt file 

Really appreciate your help.


----------



## pmcrockett (May 13, 2020)

quickbrownf0x said:


> Thanks for that!
> This really is like programming a washing machine, where half of the buttons are missing, isn't it? Jesus. 🤦‍♂️
> 
> Here you go, btw. The script is triggered on track selection from Cubase, through listenTrackSelection(MIDI_ARGS). This grabs 2 values; polypressure and midi channel - then you're off to the races...
> ...


Nice — I'll hopefully have time to take a look at it over the next couple days.

But yeah, coding in Lemur is a wild ride. My favorite oddity is that there are certain ASCII characters that will throw errors and _literally delete chunks of code _if you try to parse them as string elements.


----------



## quickbrownf0x (May 13, 2020)

pmcrockett said:


> Nice — I'll hopefully have time to take a look at it over the next couple days.
> 
> But yeah, coding in Lemur is a wild ride. My favorite oddity is that there are certain ASCII characters that will throw errors and _literally delete chunks of code _if you try to parse them as string elements.


wait, whut!?


----------



## pmcrockett (May 14, 2020)

Lemur flips out if you try to use double quote marks instead of/inside of single quote marks. I can't find any rhyme or reason to its reaction to them, but if you put them anywhere in your code, instead of gracefully informing you that you can't use double quotes, when you click out of the code window it either colors the code strangely, throws an XML parser error, or deletes all of the code after the double quote mark.



So I haven't attempted to integrate this with your code, but I've written a folder search script that doesn't use recursion (it stores folder paths in an array instead). Example project attached. I think it's robust enough that a more specific search function could be built from it pretty easily.

EDIT: Just realized that because objects at different folder levels can have the same name, the search will abort prematurely if it encounters an object whose name is identical to the object after the search folder if that first object also falls immediately after a folder. An edge case, to be sure, but something to be aware of if you're going to use the code.

EDIT 2: Nevermind, fixed it. The attached file has been updated.


----------



## quickbrownf0x (May 14, 2020)

pmcrockett said:


> Lemur flips out if you try to use double quote marks instead of/inside of single quote marks. I can't find any rhyme or reason to its reaction to them, but if you put them anywhere in your code, instead of gracefully informing you that you can't use double quotes, when you click out of the code window it either colors the code strangely, throws an XML parser error, or deletes all of the code after the double quote mark.
> 
> 
> 
> ...


Hi PM, thanks for the time and effort! 

I'll check it out as soon as I can and let you know. This is great.


----------

