Up until now, our functions to modify a pattern were very much concerned with the timespan. Let's look at ways to modify the value of a Hap.
The function `withHap` lets us edit a hap as we like. `withValue` uses it to run a function on the Hap value specifically, returning a new Hap.
With that, we could put together different shades of blue:
Operating on values gets more exciting when we have numbers to work with. For that matter, let's update the draw logic that sets the rectangle color:
if (isNaN(Number(value))) {
ctx.fillStyle = value; // <- like before
} else {
value = Number(value);
ctx.fillStyle = `hsl(${value}turn 50% 50%)`;
}
Now, if we receive a valid number, we'll use it as the `hue` value in an `hsl` color! Here's a rainbow pattern:
Now that we have numbers, we can do some arithmetic!
Using `add` and `mod`, we could rotate the color palette:
..changing the value of `add`:
Right now, we are only able to pattern the hue. Wouldn't it be nice if we could pattern other properties separately, like hue, saturation, lightness, transparency, rect size, ...? We can do this by adjusting our draw code again:
if (typeof value === "object") {
let {
color,
h: hue,
s: saturation,
l: lightness,
a: alpha,
} = value;
if (color) {
ctx.fillStyle = color;
} else if (hue !== undefined) {
hue = hue * 360;
saturation = (saturation ?? 0.5) * 100;
lightness = (lightness ?? 0.5) * 100;
alpha = alpha ?? 1;
ctx.fillStyle = `hsla(${hue},${saturation}%,${lightness}%,${alpha})`;
}
}
With that in place, our patterns now need to contain objects:
This notation is rather clunky, we need control functions to the rescue!
A `control` is basically a shortcut to get a pattern of objects, which means each `Hap` has an object as value.
Controls are special in the sense that they can be used to create a pattern but also modify a pattern. This allows us to completely avoid curly braces `{}` and keep using functions with method chaining for object values.
In Chapter 4, we've implemented mini notation. We can modify the register function to treat any string as mini notation like this:
let register = (name, fn) => {
let q = (...args) => {
args = args.map((arg) => {
if (typeof arg === "string") {
return mini(arg);
}
return arg;
});
return fn(...args);
};
Pattern.prototype[name] = function (...args) {
return q(...args, this);
};
return q;
};
Now we can set our controls with mini notation:
This is starting to look very usable! In the next chapter we will finally make some noise with it
chapter 6: adding sound + joining patterns