NexusUI is a collection of HTML5 interfaces and Javascript helper functions to assist with building web audio instruments in the browser.
In addition to interfaces, the toolkit contains some helpers for tuning and timing. It does not provide any sound-making capabilities –– for that, check out Tone.js, WebPD, Gibber.lib, or the Web Audio API.
This toolkit is designed to help you rapidly prototype your musical ideas. It is lacking some features which would be useful for production –– for example, these interfaces are not responsive (they do not understand percentage widths and heights). NexusUI is an ongoing open-source project; please get in touch on GitHub if you would like to contribute.
Download NexusUI.js.
(The link grabs the latest version of the file directly from our NPM.)
Or access on a CDN if you want a hosted file:
Link to NexusUI.js in your HTML file.
<html>
<head>
<script src="path/to/NexusUI.js"></script>
</head>
<body>
</body>
</html>
You can transform regular HTML elements into NexusUI interfaces (<div> is recommended). By default, the interface component will adopt the height and width of the target element as specified in your CSS or in the element's style. Alternatively, you can specify the interface component's size in JavaScript when you create it (see the API for each interface).
<html>
<head>
<script src="path/to/NexusUI.js"></script>
</head>
<body>
<div id="dial"></div>
<script>
var dial = new Nexus.Dial('#dial')
</script>
</body>
</html>
You can also transform many elements at once by creating a rack.
You can also dynamically add interfaces to your page at any point using Nexus.Add. This lets you add many interfaces to the same parent element. You can later remove the interface using the interface's .destroy() method.
<html>
<head>
<script src="path/to/NexusUI.js"></script>
</head>
<body>
<div id="instrument"></div>
<script>
var dial = Nexus.Add.Dial('#instrument',{
'size': [100,100]
});
var slider = Nexus.Add.Slider('#instrument',{
'size': [25,100]
});
// then, to remove them tlater
dial.destroy();
slider.destroy();
</script>
</body>
</html>
Every NexusUI interface fires an event function when the interface is interacted with or changed programatically through the interface's API. If you use Node.JS, this syntax should look familiar –– every NexusUI interface extends Node.js's built-in EventEmitter.
var dial = new Nexus.Dial("#dial");
dial.on('change',function(v) {
// v holds the new numeric value of the dial
});
Many of the core interfaces also fire events on mouse click and mouse release. The following interfaces accept click and release events: button, dial, number, pan, pan2d, position, slider, textbutton, toggle. We hope to develop this feature for all interfaces in the future.
var slider = new Nexus.Slider("#slider");
slider.on('click',function() {
console.log('clicked!');
});
slider.on('release',function() {
console.log('released');
});
Each interface has an API through which you can customize its values and interaction modes. Common options include:
Example:
See the Pen MT - Interface Options Demo by Ben Taylor (@taylorbf) on CodePen.
Check out the API for more details on interface options, as well as for tutorials on tuning and timing mechanisms.
You can change an interface's colors using .colorize()
See the Pen Colors by Ben Taylor (@taylorbf) on CodePen.
You can create many interface components at once using Nexus.Rack(). Here is an example:
See the Pen Creating a Rack by Ben Taylor (@taylorbf) on CodePen.
Nexus.Rack optionally lets you specify which attribute to look for ('nexus-ui' by default). This is useful for using NexusUI with React, which requires data- attribute prefixes.
HTML:
<div id="target">
<div data-nx="dial"></div>
</div>
JS:
var synth = new Nexus.Rack("#target",{
attribute: 'data-nx'
})
A number box can be linked to the value of a dial or slider using .link()
In this case, when the dial or slider is updated, the number box will show its numeric value. When the number box is updated or interacted with, the dial or slider will be updated to the new value as well.
See the Pen Number Box Sync by Ben Taylor (@taylorbf) on CodePen.
Nexus.note() and Nexus.tune offer ways to manage pitches and scales.
// Nexus.note(0) returns the frequency of the 1st note of the scale
synth.frequency.value = Nexus.note(0);
// Nexus.note(2) returns the frequency of the 3rd note of the scale
synth.frequency.value = Nexus.note(2);
By default the scale is an equal-tempered major scale. An API for changing the scale is below.
This approach emphasizes algorithmic composition by using numbers to traverse musical scales.
Nexus.note() provides a method for turning scale degrees into frequency values which your web audio synth will understand.
// Nexus.note(0) returns the frequency of 1st note of the scale
synth.frequency.value = Nexus.note(0);
// Nexus.note(1) returns the frequency of 2nd note of the scale
synth.frequency.value = Nexus.note(1);
// Negative numbers wrap to a lower octave
// Nexus.note(-1) returns the frequency of the top note of the scale, 1 octave lower
synth.frequency.value = Nexus.note(-1);
// You can also specify the octave using an optional second parameter
// Nexus.note(0,2) returns the frequency of the 1st note of the scale, 2 octaves up.
synth.frequency.value = Nexus.note(0,2);
// Nexus.note(0,-1) returns the frequency of the 1st note of the scale, 1 octave lower
synth.frequency.value = Nexus.note(0,-1);
By using counters or number generators in tandem with Nexus.note(), you can play scales, arpeggios, etc.
// Play 5 octaves of the major scale
var counter = new Nexus.Counter( 0, 36 );
var beat = new Nexus.Interval(200,function() {
synth.frequency.value = Nexus.note( counter.next() );
})
If working with an audio sample, you may wish to tune your sample by changing its playback speed. You can use Nexus.tune.ratio() to get the frequency ratio of a note to the root of your scale. This ratio can be used to set the playback speed of your sample.
// This gives you the pitch ratio between the 5th note in scale and the root.
Nexus.tune.ratio(4) // returns 1.49830693925
// You can use this to set the playback speed of a sampler
sampler.playbackRate = Nexus.tune.ratio(4)
By default, the scale is an equal-tempered major scale.
We do not provide any other built-in scales. However, we provide two ways for you to create your own scale.
// Create a minor scale
Nexus.tune.createScale(0,2,3,5,7,8,10);
// Now, Nexus.note() will refer to the scale you defined
Nexus.note(0) // returns the frequency number of the root
Nexus.note(1) // returns the frequency number of a Major 2nd
Nexus.note(2) // returns the frequency number of a Minor 3rd
// Create a major scale in just intonation
Nexus.tune.createJIScale( 1/1, 9/8, 5/4, 4/3, 3/2, 8/5, 5/3, 15/8 );
// Now, Nexus.note() will refer to the scale you defined
Nexus.note(0) // returns the frequency number for ratio 1/1
Nexus.note(1) // returns the frequency number for ratio 9/8
Nexus.note(2) // returns the frequency number for ratio 5/4
By default, the root of the scale is Middle C. Therefore, Nexus.note(0) returns the frequency for Middle C.
You can set the root using Nexus.tune.root, which is a frequency value.
// Set the root to 3 octaves below Middle C.
Nexus.tune.root = Nexus.note(0,-3);
In this way, you can change your scale root on the fly by setting it to other notes of the scale you are currently using.
The root can also be set using traditional musical values like "C3" or "A1". This is the only place in this toolkit where this type of music notation is used.
// Set the root to low A.
Nexus.tune.root = "A1";
You can also set Nexus.tune.root direction to a frequency value, for example if you are working in Just Intonation.
// Set the root of your scale to 120hz.
Nexus.tune.root = 120;
Nexus.Interval creates a repeating pulse using WAAClock.
// Create a pulse of 100ms. At each pulse, the callback function is executed.
var interval = new Nexus.Interval(100, function() {
console.log('beep');
})
This interval uses a web audio context to handle timing, so it is more accurate than a regular setInterval or setTimeout
Be default, the interval will automatically start when it is created. However a third parameter can set autostart to false.
// Create a pulse that does not autostart.
var interval = new Nexus.Interval(100, function() {
console.log('beep');
}, false)
// Change your interval period to 200ms.
interval.ms(200)
// Create a pulse of 1 second
var interval = new Nexus.Interval(1000, function() {
console.log('beep');
})
// Stop the pulse
interval.stop();
// Start the pulse
interval.start();
// Change the interval time
interval.ms(200);
// Change the function that is called at each pulse
interval.event = function() {
console.log("bloop");
}
The Matrix Model lets you read, write, and manipulate a two-dimensional array of data (such as the data that is visualized by a sequencer).
matrix = new Nexus.Matrix(5,10);
var matrix = new Nexus.Matrix(5,10);
console.log( matrix.pattern );
var matrix = new Nexus.Matrix(5,10);
matrix.toggle.cell(2,2)
matrix.toggle.row(2)
matrix.toggle.column(2)
matrix.toggle.all()
var matrix = new Nexus.Matrix(5,10);
matrix.set.cell(0,0,1)
matrix.set.row(0,[0,1,0,1,0,1,0,1,0,1])
matrix.set.column(0,[0,1,1,1,0])
matrix.set.all([
[0,1,0,1,0,1,0,1,0,1],
[0,1,0,1,0,1,0,1,0,1],
[0,1,0,1,0,1,0,1,0,1],
[0,1,0,1,0,1,0,1,0,1],
[0,1,0,1,0,1,0,1,0,1]
])
var matrix = new Nexus.Matrix(5,10);
matrix.toggle.cell(0,0)
matrix.rotate.row(0)
matrix.rotate.row(0,4)
matrix.rotate.column(5)
matrix.rotate.all(2)
var matrix = new Nexus.Matrix(5,10);
matrix.populate.row( 0, [0,1] )
var matrix = new Nexus.Matrix(5,10);
matrix.populate.column( 0, [0,1] )
var matrix = new Nexus.Matrix(5,10);
matrix.populate.all( [0,1] )
var matrix = new Nexus.Matrix(5,10);
matrix.populate.row( 0, [0.3, 0.6] )
var matrix = new Nexus.Matrix(5,10);
matrix.populate.column( 0, [0.5] )
var matrix = new Nexus.Matrix(5,10);
matrix.populate.all( [0.2, 0.5, 0.2, 0.8] )
var sequencer = new Nexus.Sequencer("#target",5,10);
sequencer.matrix.toggle.row(2);
sequencer.matrix.set.row(1, [0,1,0,1,0,1,0,1,0,1])
A Counter model is an object with a minimal API for counting up or down.
counter = new Nexus.Counter(0,10)
counter.next() // returns 0
counter.next() // returns 1
counter.next() // returns 2
// ...
counter.next() // returns 9
counter.next() // returns 10
counter.next() // returns 0
counter = new Nexus.Counter(0,10,"down")
counter.next() // returns 10
counter.next() // returns 9
counter.next() // returns 8
counter.min = 10
counter.max = 100
counter.mode = "down"
counter.value = 50 // jumps the counter to this value
A Sequence model steps through a series of numeric values defined by you.
var sequence = new Nexus.Sequence([1,3,5,7])
sequence.next() // returns 1
sequence.next() // returns 3
sequence.next() // returns 5
sequence.next() // returns 7
sequence.next() // returns 1
sequence.next() // returns 3
var sequence = new Nexus.Sequence([1,3,5,7],"down")
sequence.next() // returns 7
sequence.next() // returns 5
sequence.next() // returns 3
sequence.next() // returns 1
sequence.next() // returns 7
sequence.next() // returns 5
sequence.values = [2,4,5,8] // updates the sequence of values
sequence.mode = "drunk"
sequence.value = 2 // this jumps to where the '2' value is in the sequence, which is index 0.
A Drunk Model does a "drunk walk" through a given range, meaning it will randomly step up or down by a given increment.
// This drunk model has a range of 0 to 100
// Its starting value is 50
// Its stepping increment is 10
var drunk = new Nexus.Drunk(0,100,50,10)
drunk.next() // returns 40
drunk.next() // returns 50
drunk.next() // returns 40
drunk.next() // returns 30
drunk.next() // returns 40
drunk.next() // returns 30
drunk.min = 100
drunk.max = 1000
drunk.increment = 100
drunk.value = 500 // jumps to this value