There are a variety of ways to connect your Flocking instruments up to sources of user input such as user interface components or touch events. The simplest but perhaps least expressive way to trigger events based on user input is with the flock.ugen.mouse
unit generators. Alternatively, you can map browser events from user interface components (e.g. buttons, sliders, pianos, whatever) to value changes on your synth's inputs.
The flocking-ugens-browser.js file contains unit generators that are specifically dependent on browser-based technology, such as DOM events. In particular, the flock.ugen.mouse collection of unit generators provide a means to treat user input as a signal directly within your synth. The advantage of these unit generators is that they can provide a constant stream of values and can be easily smoothed. The downside is that the input modality is hard-coded in your synth design, whereas an event-based approach outside the unit generator graph will make it easier to change the source of events later, or to have multiple simultaneous sources of input.
For more information about the mouse unit generators, see the Mouse Unit Generators documentation.
HTML markup:
<button id="myButton">Change it!</button>
SynthDef:
{
ugen: "flock.ugen.sin",
freq: {
ugen: "flock.ugen.latch",
rate: "audio",
source: {
ugen: "flock.ugen.lfNoise",
freq: 10,
mul: 540,
add: 660
},
trig: {
ugen: "flock.ugen.mouse.click",
target: "#myButton"
}
}
}
HTML:
<button id="myButton">Play it!</button>
SynthDef:
{
ugen: "flock.ugen.playBuffer",
buffer: {
url: "cat.wav"
},
trigger: {
ugen: "flock.ugen.mouse.click",
options: {
target: "#myButton"
}
}
}
HTML:
<div id="chaos"></div>
CSS:
#chaos {
height: 200px;
width: 200px
}
SynthDef:
{
ugen: "flock.ugen.square",
freq: {
ugen: "flock.ugen.mouse.cursor",
// Note that we have to scale this unit generator's output
// to a value range that is sensible for frequency.
// By default,cursor outputs values between 0.0 and 1.0.
mul: 1120
add: 60
options: {
axis: "x",
target: "#chaos"
}
},
mul: {
ugen: "flock.ugen.mouse.cursor",
options: {
axis: "y",
target: "#chaos"
}
}
}
Flocking synths provide two essental methods that can be used to query and update the state of the synthesis graph: get()
and set()
. You can call these methods in response to user input (e.g. a button click) to control a Flocking synth.
For more information about the methods provided by flock.synth
see the Synth documentation coming soon.
First, create a button that the user can click on. We'll wire this button up using jQuery so that it randomly changes the frequency of our synthesizer every time it is clicked. Here's the HTML markup for it:
<button id="myButton">Change it!</button>
For our example, let's create a noisy synth that uses frequency modulation. The carrier is a sawtooth oscillator, and the modulator is a sine wave. Since we want to be able to dynamically change the modulator whenever a user clicks the button, we'll give it an id so that we can refer to it later when we're calling Synth.set
.
var noisy = flock.synth({
synthDef: {
ugen: "flock.ugen.lfSaw",
freq: {
id: "modulator", // An id lets us refer to this ugen in subsequent calls to set().
ugen: "flock.ugen.sin",
freq: 400,
mul: 540,
add: 600
},
mul: 0.25
}
});
We want our example to work on all devices. iOS has a restrict that sounds can only be played as the result of a user action. If you try to start Flocking without the user first having tapped on something, it will be silent. So we'll need to make sure that we only start Flocking after the user has clicked the button for the first time.
To do so, we will bind a one-time click handler on our button. It will start the Flocking Environment by calling its play
method. We'll use jQuery's one()
method to register a handler that automatically gets removed after it has fired once. Here's the code for it:
$(container).one("click", function () {
var environment = flock.init();
environment.start();
});
Next, we'll register a click handler on the button that will randomly change the frequency of the modulator every time the user clicks the button. We'll use jQuery again for this, and call Synth.set()
to change the modulator.freq input:
// Every time the button is clicked, change the synth's modulator frequency.
$(container).click(function () {
// Randomly update the frequency of the modulator.
var newFreq = Math.random() * 1000 + 1060;
noisy.set("modulator.freq", newFreq);
});
We don't have to set just one input at at a time using Synth.set()
. Instead, we can specify an object of input path/value pairs whenever a user clicks the button. Try this click handler code instead:
// Every time the button is clicked, change the synth's modulator frequency and amplitude.
$(container).click(function () {
// Randomly update several of the modulator's inputs at once.
noisy.set({
"modulator.freq": Math.random() * 1000 + 1060,
"modulator.add": Math.random() * 1200
});
});
Note that if you're not a fan of jQuery, you can use plain DOM methods like addEventListener
or, if you like, use whatever toolkit you prefer. Flocking is designed to be agnostic of your presentation technology.
Infusion provides a robust system for defining "views" and events. It provides a highly declarative way of binding Flocking instruments up to any source of user input, from individual buttons and sliders to larger-scale interfaces like pianos and even physical input devices. Here's an example of how an Infusion View can be created, which will listen for click events on an element and dynamically update a synth's inputs.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Very Wow</title>
<script src="flocking/flocking-all.js"></script>
<script src="user-input-demo.js"></script>
</head>
<body>
<img id="clickTarget" src="https://wanna-joke.com/wp-content/uploads/2014/01/funny-picture-new-year-doge.jpg" />
<script>
userInputDemo.clickSource("#clickTarget");
</script>
</body>
</html>
JavaScript:
(function () {
"use strict";
fluid.registerNamespace("userInputDemo");
// Our instrument.
fluid.defaults("userInputDemo.doge", {
gradeNames: ["flock.synth"],
synthDef: {
id: "carrier",
ugen: "flock.ugen.squareOsc",
freq: 220,
mul: {
id: "modulator",
ugen: "flock.ugen.triOsc",
freq: {
id: "freqRamp",
ugen: "flock.ugen.xLine",
start: 60,
end: 113,
duration: 3
}
}
}
});
// Our view component, which takes a "container" argument.
// In this case, our container represents the thing that is clickable.
fluid.defaults("userInputDemo.clickSource", {
// Make an Infusion view component to bind with our markup.
gradeNames: ["fluid.viewComponent"],
components: {
// Make our synth a subcomponent of this view.
// It will automatically get instantiated for us by Infusion.
synth: {
type: "userInputDemo.doge"
}
},
// "Invokers" are methods on an Infusion component that are
// configured using IoC expressions.
invokers: {
// Define a method that will randomly set values on a line unit generator.
handleClick: {
// Bind this method to the "fireRandomLine" function,
// which we define below.
funcName: "userInputDemo.clickSource.fireRandomLine",
// Pass the synth as well as the click event's
// x and y DOM coordinates to the function.
args: ["{synth}", "{arguments}.0.clientX", "{arguments}.0.clientY"]
}
},
listeners: {
// Listen for the event that fires when this component has been fully
// instantiated.
"onCreate.playSynth": {
// The first time the user clicks, start the Flocking environment.
"this": "{that}.container",
method: "one",
args: ["click", "{synth}.play"]
},
"onCreate.bindClickHandler": {
// Bind a click handler to our container
// that will invoke the "fireRandomLine" method.
"this": "{that}.container",
method: "click",
args: "{that}.handleClick"
}
}
});
// There will eventually be a declarative syntax to manage these kinds of changes
// using "value synths" and a more robust expression system. In the meantime,
// it has to be done by hand.
userInputDemo.clickSource.fireRandomLine = function (synth, x, y) {
synth.set({
"freqRamp.start": synth.get("freqRamp").model.level,
"freqRamp.end": x + 60,
"freqRamp.duration": y / 50
})
};
}());
This example might look overly complicated for such a simple task, but the key here is reusability. This clickSource view component represents a reusable "click event" view, into which users can insert their own custom synths and their own custom handlers for user input. As Flocking's declarative approach is expanded, this will become even more data-oriented.
Here's a quick example of how a user could customize this component with their own new logic:
// Define a new component called a "segmentedClicker",
// which will track clicks in three different regions of the image.
fluid.defaults("userInputDemo.segmentedClicker", {
// Mix in the "clickSource" grade
gradeNames: ["userInputDemo.clickSource"],
segmentFreqs: [1760, 880, 220],
components: {
// Override the default synth with a noisier one.
synth: {
type: "flock.synth",
options: {
synthDef: {
id: "carrier",
ugen: "flock.ugen.lfNoise",
freq: 440,
mul: 0.25
}
}
}
},
invokers: {
// And swap out the handle click invoker with a new implementation.
handleClick: {
funcName: "userInputDemo.segmentedClicker.setFreqForSegment",
// We can even dynamically override the arguments passed to our invoker
// by drawing from any values currently in IoC's scope.
// Uber-polymorphism!
args: ["{synth}", "{that}.options.segmentFreqs", "{that}.container.0.width", "{arguments}.0.clientX"]
}
}
});
// Divides the target element up into three segments and sets the synth's frequency accordingly.
userInputDemo.segmentedClicker.setFreqForSegment = function (synth, segmentFreqs, targetWidth, x) {
// Split the image's x axis up into three different regions.
// This could be done more efficiently using a "members" expander.
var regionWidth = targetWidth / segmentFreqs.length,
regionNum = Math.round(regionWidth / x);
// Set the synth's frequency.
synth.set("carrier.freq", segmentFreqs[regionNum]);
};