/ Examples / MusicAPI / Async

Right-click to copy examples to your workspace


Controlling asynchronous behavior in your code is a subtle topic but may be needed for complex soundscapes.

Note that we present multiple examples in this project.

Overview

You can use the Code Editor's Context Menu to launch multiple scripts to run in different asynchronous fibers within the same or different sandboxes. If you need script-control asynchronous behavior, read on, oh fearless one.

Loosely speaking, in order to make multiple things happen at the same time we need asynchronous control. The MusicAPI leverages JavaScript promises and Lua coroutines to provide these capabilities. Since these are standard language features you can find a wealth of generic online resources to learn more.

await

First we note that the await keyword "just works".

await.js

let scene = await Ascene.BeginFiber(this);
let sec = 1 + Math.random();
let dur = scene.Seconds(sec);
console.log("begin " + new Date());
for(let i=0;i<10;i++)
{
    await scene.Wait(dur); // <----- wait quietly  (blocking but async)
    console.log(`tick ${i} ${sec.toFixed(3)}`);
    // yield makes this Fiber cancellable. 
    // It's optional, but usually a good idea.
    yield; 
}
console.log("end " + new Date());

Here's an example of two independent launches of await.js.

Async.Run

In this example we develop an async function, myfunc. Each time through the loop, we employ Async.Run to launch our function as a separate fiber. It's job is to modify the value of Vital's Oscillator 1 Pan parameter with timing that is independent of the notes we produce.

The key behavior to notice is that both fibers can block without impacting the other.

asyncrun.js

let scene = await Ascene.BeginFiber(this);
let inst = scene.NewAnode("Vital"); // request creation of Vital synth node
let out = scene.GetDAC();
scene.Chain(inst, out); 
await scene.Sync(); // after this completes our nodes are "live"
inst.Show(); // show the interface so we can see the parameter change
let loop = 0;
let notedur = scene.Seconds(.25);
let myfunc = async () => // we must be async in order to use 'await'
{
    let l = loop;
    let pans = [0, .5, 1];
    for(let j=0;j<3;j++)
    {
        inst.SetParam("Oscillator 1 Pan", pans[j%3]);
        await scene.Wait(notedur*2); // <--- block in another fiber
    }
    console.log("done pan " + l);
};
let asap = 0;
for(loop=0;loop<10;loop++)
{
    Async.Run(myfunc); // <--- Launch myfunc on each loop
    for(let key=40;key<60;key+=2)
    {
        inst.NoteOn(key, .9/*velocity*/, asap);
        await scene.Wait(notedur); // <--- block in main fiber
        inst.NoteOff(key, 1, asap);
        yield;
    }
}

Generators

generator.js

/* This is a async generator; it can be asynchronously iterated.
 */
let scene = await Ascene.BeginFiber(this);
let inst = scene.NewAnode("Hz.Syntho"); // request creation of Vital synth node
let out = scene.GetDAC();
scene.Chain(inst, out); 
await scene.Sync(); // after this completes our nodes are "live"

let loop = 0;
let notedur = scene.Seconds(.25);

async function *doloop(startNote=0)
{
    console.log(`doloop ${startNote} begin`);
    let noteDur = scene.Seconds(.1);
    let restDur = scene.Seconds(.10);
    let note = startNote || 40 + Math.floor(12*Math.random());
    for(let i=0;i<30;i++)
    {
        // console.log("note " + i);
        inst.NoteOn(note, .7);
        await scene.Wait(noteDur); // <-- we block here
        inst.NoteOff(note);
        await scene.Wait(restDur); // <-- we block here too
        yield; // <<--- -yield control back to caller

        note = 32 + (note+1) % 30 ;
    }
    console.log(`doloop ${startNote} end`);
}

// interate our async function
for await (const val of doloop(40))
{
    yield; // here we yield to system, this updates AudioStatus/Fibers
}

// We must pass an iterator to AddFiber. 
// Fibers provide more feedback than Async.Run.
// They can also be canceld.
console.log("Let's try FiberMgr");
FiberMgr.AddFiber(doloop(50), {id: 10, name: "subfiber0"});
FiberMgr.AddFiber(doloop(52), {id: 11, name: "subfiber1"});

// another iteration over our local async function.
for await (const val of doloop(60))
{
    yield; // here we yield to system, this updates AudioStatus/Fibers
}

console.log("generate.js done.");

Produces this output:

See Also

Async API

home .. topics .. interface .. reference .. examples .. tipjar