Examples / Algo / Radio

Right-click to copy examples to your workspace


Here we build a soundscape comprising multiple internet radio streams, a morse-code generator and an ambient drone.

The Setup

Here's some code to open a single internet stream. Hz.Streamin is a built-in Anode that can stream internet radio stations. It collaborates with StreamMgr as shown in this code snippet:

let anode = ascene.NewAnode("Hz.StreamIn");
let gain = ascene.NewAnode("Hz.Mix", {name, cfg:"stereo"});
let modgain = ascene.NewModulator("Hz.ModRand");
ascene.Chain(anode, gain, rev); // wire the stream into the shared reverb
await ascene.Sync();
gain.SetParam("Gain", -8.);
modgain.SetParam("Mode", 3); // smooth
modgain.SetParam("Frequency", .5 * Math.random());
ascene.ModulateParam(modgain, gain, "Gain", 
    modgain.NewRemapper({scale: 8, bipolar: true})); // additive mod
let iid = anode.GetId();
console.log(`open ${host} ${stream} ${iid}`);
StreamMgr.AudioStreamOpen(host, stream, iid) // <-----------
.then((ret) =>
{
    if(verbose)
        console.log(`open returned ${JSON.stringify(ret)}`);
    streamStates[stationIndex].tid = ret.tid;
});

This example also shows how to record your performance with Hz.AudioFileOut.

const doRecord = true;
if(doRecord)
{
    // this will fail if your workspace doesn't have a directory
    // named '_tmp'.  You can create one via the workspace panel.
    let ts = Util.GetFileTimestamp();
    let outputFile = await ResolveWSFile(`_ws_/_tmp/HzRadio.${ts}.wav`, false);
    let preset = {filename: outputFile};
    let record = ascene.NewAnode("Hz.AudioFileOut", {preset});
    console.log("Recording to " + outputFile + "   <----------------");
    ascene.Chain(dac, record);
}

Here's a portion the audio graph responsible for delivering audio streams into our scene:

Each stream has its own downstream Hz.Mix node which is modulated by a Hz.ModRand to produce a swirling interplay between multiple live streams.

Further downstream we see that all roads lead to reverb.

As an alternate approach to graph-based parameter modulation we periodically update our drone tone and reverb mix explicitly via:

// alternative to graph-based modulator
function changeParams()
{
    setTimeout(() =>
    {
        if(rev)
        {
            let mix = .8 * Math.random();
            // console.log("new mix " + mix);
            rev.SetParam("Wet", mix);
        }
        if(drone)
        {
            if(lastDroneNote != -1)
                drone.NoteOff(lastDroneNote, 1);
            lastDroneNote = 40 + Math.floor(Math.random()*24);
            // console.log("new drone note " + lastDroneNote);
            drone.NoteOn(lastDroneNote, .2 + .8 * Math.random());
        }
        if(!done)
            changeParams(); // reinstall ourselves here
    }, 7000  * (1 + Math.random()));
}
changeParams();

Next we perform our morse code using the async generator idiom seen below. The text-to-morsecode conversion and performance is implemented in a separate file, morse.js included via await Require("./morse.js");. Prior to each new quote, we "change the channel" using the channelSweeper instrument with Glide enabled.

let toMorse = new Morse(60, .1); // note, rate
for(let q of zenquotes)
{
    // change the channel
    for(let i=0;i<40;i++)
    {
        let n = Math.floor(40 + 50 * Math.random());
        channelSweeper.NoteOn(n, .8);
        await ascene.Wait(ascene.Seconds(.05 + .05 * Math.random()));
        channelSweeper.NoteOff(n);
        yield;

    }
    let txt = q[0];
    for await (const value of toMorse.PerformText(ascene, beeps, txt))
    {
        yield;
    }
}
console.log("Done morseQuotes.");

Finally we gracefully shutdown after all quotes are delivered.

console.log("Done quoting, fade to black.");
let g = -.2;
while(g > -60)
{
    master.ModParam("Gain", g);
    await ascene.Wait(ascene.Seconds(.1));
    g -= .2;
}
done = true; // signal setparam

// trigger (OFF) GUI checkboxes for 4 favorite stations
ToggleStation(0);
ToggleStation(1);
ToggleStation(2);
ToggleStation(3);
await Aengine.Close();

Live Performance

This example produces its own interface in the form of checkboxes that allow you to add and subtract live feeds while it runs.

In addition, the Hz plugins produce a webview interface that allows you to modify various gain, oscillator and reverb settings. You can use their Show() method make it visible.

See Also

StreamMgr . Hz.Streamin . Hz.AudioFileOut

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