Basic Random Round Robin Script



  • Here's a very simple random round robin script. It works with up to 50 RR groups but if you manage to exceed that limit it's easy to modify to handle more. I'm uploading an explanation/tutorial video of me writing this for my Patreon supporters.

    Content.setWidth(600);
    Content.setHeight(50);
    
    const var Sampler1 = Synth.getChildSynth("Sampler1");
    Sampler1.asSampler().enableRoundRobin(false);
    
    const var knbGroups = Content.addKnob("knbGroups", 0, 0);
    knbGroups.set("text", "RR Groups");
    knbGroups.setRange(1, 50, 1);
    
    function onNoteOn()
    {
        local groupNum = Math.randInt(1, knbGroups.getValue()+1);   
        Sampler1.asSampler().setActiveGroup(groupNum);
    }
    function onNoteOff()
    {
    	
    }
    function onController()
    {
    	
    }
    function onTimer()
    {
    	
    }
    function onControl(number, value)
    {
    	
    }
    


  • Anyone know if it's possible to enable more than one group at a time? I'm aware that enabling xfade mode will enable all groups, but how to enable a subset?



  • Ah, actually it's not possible - there's only Sampler.setActiveGroup() which takes an int and deactivates all others.

    I think the reason why I didn't implement it is because I never needed to play more than one RR group - for all scenarios that might want to do it, I just duplicated the samplers.



  • By the way, a nice way of avoiding repeating RR groups is to use a while loop to get a "really" new value:

    
    reg lastIndex = -1;
    reg numGroups = 3;
    
    inline function nextWithoutRepeat()
    {
        // We have to catch this case, otherwise we'll
        // be stuck in an endless loop later
        if(numGroups == 0)
            return 0;
    
        local newIndex = Math.randInt(0, numGroups);
        
        while(newIndex == lastIndex)
            newIndex = Math.randInt(0, numGroups);
    
        lastIndex = newIndex;
        return newIndex;
    }
    
    inline function next()
    {
        return Math.randInt(0, numGroups);
    }
    
    Console.print("Watch out for repeated groups");
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    Console.print(next());
    
    Console.print("Now they're gone");
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    Console.print(nextWithoutRepeat());
    

    Of course this might result in a theoretically endless runtime (if the RNG always spits out the same number, you're lost), but it really helps making the sound more natural if you're using less than 4 RR groups.



  • Is it possible to get the number of RR in a map?
    because if it differs from one to another, it is a bit tricky to run a random smoothly...



  • Yeah sure, it's a sampler property that you can query using getAttribute(7) - sorry for the magic number, but I still haven't figured out a smart way to avoid this for samplers since the constant ID slots are used for the sample properties.

    Anyways, the only missing piece to make this an automatic group randomizer without the requirement of carrying a knob around that sets the amount of groups is to find a place where to call this method. The brute force method would be to have a timer that periodically checks the amount, but it's ugly and might lead to glitches between loading samplemaps.

    I recommend using the loading callback of a (hidden) script panel which will be fired after each preloading task, which happens everytime you swap a samplemap:

    // This will hold the current RR group amount
    reg numRRGroups = 0;
    
    // Create a default reference to a sampler somehow
    const var Sampler1 = Synth.getChildSynth("Sampler1");
     
    // in a perfect world this would not be a UI element but a headless object like the thing
    // you get by Engine.createTimerObject(), but since the primary use case of this is to display some
    // sort of loading indication it makes sense to couple it to a panel.
    const var RRUpdater = Content.addPanel("RRUpdater", 0, 0);
    
    // You are ugly and we don't want to see you.
    RRUpdater.set("visible", false);
    
    // We can give the panel a function that is executed whenever the preload state changes, 
    // so we'll use this event to update the RR groups.
    RRUpdater.setLoadingCallback(function(isPreloading)
    {
        // the flag indicates the start / stop event and since we want 
        // to wait until everything is done, we'll use the stop event.
        if(!isPreloading)
        {
            numRRGroups = Sampler1.getAttribute(7);
            Console.print(numRRGroups);
        }
    });


  • Ok thanks 😉
    Or I can do it directly in the function that calls for the new samplemap, no?
    Why going on the wild side of using a hidden panel instead?



  • Because the loading of sample maps is asynchronous: the call to Sampler.loadSampleMap() doesn't do anything heavyweight, it just posts a notification to the worker thread which kills all notes gracefully, suspends the audio rendering and then performs the loading & preloading until everything is done and then reenables the audio rendering for the ultimate smooth swapping experience.

    Since this can take a few seconds for big sample sets, having the script engine in a busy-wait state is not an option (in fact, the time-out protection would fire). Hence you need to jump through the hidden Panel hoop.


Log in to reply
 

9
Online

474
Users

1.6k
Topics

12.1k
Posts