The Tower of Hanoi is a mathematical puzzle that can easily be solved by your computer using a recursive program. In the Hz context we can both hear and see the algorithm as it runs. Perhaps this or other puzzles can form the basis for a rhythm track for your next song?
This example was inspired by a ChucK example created by Ge Wang and the ChucK team.
Right-click to copy examples to your workspace
Here's the boilerplate code to setup the audio environment.
// Ensure audio engine is ready.
let scene = await Ascene.BeginFiber(this);
// setup our audio pipeline
let sp = scene.NewAnode("Hz.Samplo");
let gain = scene.NewAnode("Hz.Mix", {cfg:"stereo", preset:{Gain:3}});
let rev = scene.NewAnode("Hz.Reverb", {cfg:"stereo"});
let dac = scene.GetDAC();
scene.Chain(sp, gain, rev, dac);
await scene.Sync();
// preload sample files into sp
let files = [
"snare-chili.wav",
"kick.wav",
"snare-hop.wav"
];
let cwd = path.dirname(this.GetFilePath());
let wspath = await ResolveWSFile(cwd); // validate file and convert to fullpath
await sp.LoadPreset(SampleMgr.AsPresetFromLocalVoice(wspath, "drumkit", files));
const wait = scene.Seconds(.2);
const asap = 0;
let STEPS = 0, noteid;
let pans = [.2, .5, .8];
async function hanoi(num, src, dst, other)
{
// move all except the biggest
if(num > 1)
await hanoi(num-1, src, other, dst);
STEPS++;
// Sonify the move: playing a sound for the target peg.
// We could get fancier and play different sounds according
// to disk radius using the current disk states maintained
// by visualizer.
let velocity = .2 + .7 * Math.random();
let pan = pans[dst];
noteid = sp.Note(dst, velocity, wait, asap)[0];
sp.NoteExpression("pan", pan, noteid);
await scene.Wait(wait); // wait for now to complete
updateVisualization(src, dst);
// move onto the biggest
if(num > 1)
await hanoi(num-1, other, dst, src);
}
// start it
await hanoi(numdisks, 0, 2, 1);
NB: this example doesn't yield control back to the system. This means that Hz's Fiber-cancel feature doesn't work well. We've left the topic of yielding and fibers for another example.
The JavaScript sandbox environment is integrated into the WebView/WebKit environment on your computer. This means that in addition to triggering Hz's audio engine, you can program graphics as well. This example shows a crude visualization of the Hanoi algorithm's current state. Here is a snapshot of the state somewhere in the middle of the solution.
Here's all the code required to achieve it. Note that Hz includes
the javascript package, @svgjs
, described here.
// Hz's JavaScript sandbox exposes WWW DOM via the
// standard document object.
let content = globalThis.document.querySelector(".Content");
content.innerHTML = "";
// We use svgjs (https://svgjs.dev) to create colored rectangles.
let [xsize, ysize] = [400, 150];
let svg = SVG().addTo('.Content').size(xsize, ysize);
svg.text().plain("Ge Wang's Tower of Annoy").fill("orange").move(10, 10);
// make y point up
let draw = svg.group().transform({scale:[1, -1], translate:[0, ysize]});
let height = 10;
let spacing = height+2;
let maxrad = 110;
let centerX = [maxrad/2, maxrad*1.5, maxrad*2.5];
let disklist = []; // contains our svg rects for each disk
for(let i=0;i<numdisks;i++)
{
let yinit = (numdisks - i) * height;
disklist[i] = draw.rect(10 + i*10, height)
.fill(randomColor());
}
// initial conditions
const pegstacks = [[], [], []]; // holds the current disk stack for each peg
for(let i=0;i<numdisks;i++)
pegstacks[0].unshift(i); // largest disk on bottom of first stack
function updateVisualization(src, dst)
{
let d = pegstacks[src].pop(); // take the disk off src peg
pegstacks[dst].push(d); // push it onth the dst peg.
// rebuild state
for(let i=0;i<pegstacks.length;i++) // for each peg
{
let ps = pegstacks[i];
let pegx = centerX[i];
for(let j=0;j<ps.length;j++)
{
let d = disklist[ps[j]]; // ps[j] is id/radius, d is svg rect
d.cx(pegx).y(j*spacing); // move it into position.
}
}
}