node-red-contrib-music
Version:
Synthesise music with node-red. The beat node creates regular beats at a rate you can control, and which can be sunchronised with other machines. The divider node adds information to beat events, dividing beats into bars, bars into phrases, phrases into s
202 lines (154 loc) • 9.38 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('beat',{
category: 'music',
color: '#a6bbcf',
defaults: {
name: {value:""},
output: {value:"beat"},
bpm: {value: ""},
latency: {value: 0},
autostart: {value: false},
sharing: {value: "standalone"},
conductorIP: {value: "127.0.0.1"},
subBeats: {value: []}
},
inputs:1,
inputLabels: "start,stop,reset,tap,bpm:",
outputs:1,
outputLabels: ["tick"],
icon: "heartbeat.png",
label: function() {
return this.name||"beat";
},
oneditprepare: function(){
node = this;
node.subBeats = node.subBeats || [];
$('#node-input-sub-beat-container').editableList({
addItem: function(container,i,subBeat) {
var row = $('<div/>').appendTo(container);
$("<span>sub-beat </span>").appendTo(row);
var nameField = $('<input/>',{class: "node-input-sub-beat-name", type:"text", size: 15, placeholder:"e.g. triplet", style:"width:10em"}).appendTo(row);
$("<span> # per beat </span>").appendTo(row);
var countField = $('<input/>',{class: "node-input-sub-beat-count", type:"number", size:6, placeholder: "e.g. 3", style:"width:6em"}).appendTo(row);
nameField.val(subBeat.name);
countField.val(subBeat.count);
},
removable: true
});
for (var i=0;i<node.subBeats.length;i++) {
var subBeat = node.subBeats[i];
$("#node-input-sub-beat-container").editableList('addItem',subBeat);
}
$('#node-input-sharing').on('change',() =>
{
var sharing = $('#node-input-sharing').val();
$('.sharing-control').hide();
switch(sharing){
case 'conductor':
$('#conductor-container').show();
break;
case 'follower':
$('#follower-container').show();
break;
default:
// do nothing
break;
}
});
},
oneditsave: function(){
var subBeats = $("#node-input-sub-beat-container").editableList('items');
var node = this;
node.subBeats = [];
subBeats.each(function(i){
var name = $(this).find(".node-input-sub-beat-name").val();
var count = $(this).find(".node-input-sub-beat-count").val();
node.subBeats.push({name: name, count: count});
});
}
});
</script>
<script type="text/x-red" data-template-name="beat">
<div class="form-row">
<label for="node-input-output"> Output counter</label>
<input type="text" id="node-input-output" placeholder="e.g. beat)">
</div>
<div class="form-row">
<label for="node-input-bpm"> Beats per minute (bpm)</label>
<input type="number" id="node-input-bpm" placeholder="bpm (e.g. 200)">
</div>
<div class="form-row">
<label for="node-input-latency"> Latency</label>
<input type="number" id="node-input-latency" placeholder="in milliseconds">
</div>
<div class="form-row">
<label for="node-input-autostart"> Auto-start on deployment</label>
<input type="checkbox" id="node-input-autostart">
</div>
<div class="form-row">
<label for="node-input-sharing"> Sharing</label>
<select id ="node-input-sharing">
<option>standalone</option>
<option>conductor</option>
<option>follower</option>
</select>
<div id='conductor-container' class='sharing-control'></div>
<div id='follower-container' class='sharing-control'>
<label for="node-input-conductorIP">Conductor IP address</label>
<input type='text' id='node-input-conductorIP' placeholder="e.g. 127.0.0.1"/>
</div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
</div>
<div class="form-row node-input-sub-beat-container-row">
<label><b>sub-beats</b> Enter the name and number per beat</label>
<ol id="node-input-sub-beat-container"></ol>
</div>
</script>
<script type="text/x-red" data-help-name="beat">
<p>The beat generator outputs tick messages at a regular interval once it has started, like a metronome.</p>
<h3>Inputs</h3>
<dl class='message-properties'>
<dt class="optional">topic <span class="property-type">(string)</span></dt>
<dd>If <code>msg.topic</code> is <code>bpm</code> then <code>msg.payload</code> is used to set the number of beats-per-minute for the ticks that are output. </dd>
<dt>payload <span class='property-type'>(string)</span></dt>
<dd>If the input <code>msg.topic</code> is not <code>bpm</code> then <code>msg.payload</code> is used to control the beats with <code>start</code>, <code>stop</code>, <code>tap</code> or <code>reset</code>. A single beat can be generated with a <code>tick</code> input.</dd>
</dl>
<h3>Outputs</h3>
<dl class='message-properties'>
<dt>payload <span class='property-type'>(string)</span></dt>
<dd>Usually the string <code>tick</code>, which is generated at the specified <code>bpm</code>, but all input messages are also output without change</dd>
<dt><span class='optional'>beat</span> <span class='property-type'>(number)</span></dt>
<dd>The number of ticks since the last deployment or <code>reset</code> is counted and added as a property to the message. By default this property is simply called <code>beat</code> but this can be configured with the <code>output counter</code> configuration.</dd>
<dt>start <span class='property-type'>(array)</span></dt>
<dd>This property usually contains the single string <code>beat</code>, indicating that the tick represents the start of a beat. However if the beat is divided into sub-beats then only some of the <code>tick</code> messages will contain <code>beat</code> in the <code>start</code> array.</dd>
<dt>end <span class='property-type'>(array)</span></dt>
<dd>Similar to <code>start</code> except for the end of a beat/sub-beat</dd>
<dt><span class='optional'>timeTag</span> <span class='property-type'>(number)</span></dt>
<dd>If <code>latency</code> is defined in the configuration then a <code>timeTag</code> property is given for <code>tick</code> messages to indicate the timestamp on which the beat should occur.</dd>
</dl>
<h3>Configuration</h3>
<dl class='message-properties'>
<dt><span class='optional'>name</span> <span class='property-type'>(string)</span></dt>
<dd>The label for the node. If not specified then the name of the <code>output counter</code> is used</dd>
<dt>output counter <span class='property-type'>(string)</span></dt>
<dd>The name of the counter property used for ticks: this is usually <code>beat</code>, but could be e.g. <code>crotchet</code></dd>
<dt><span class='optional'>initial beats per minute</span> <span class='property-type'>(number)</span></dt>
<dd>Which can be altered by input messages with a <code>msg.topic</code> of <code>bpm</code>, or with a payload of <code>tap</code></dd>
<dt><span class='optional'>latency</span> <span class='property-type'>(number)</span></dt>
<dd>If specified, this many milliseconds will be added to the current time when ticks are generated, and included in <code>msg.timeTag</code>. This can be useful for dealing with jitter between beats or sub-beats.</dd>
<dt><span class='optional'>sharing</span> <span class='property-type'>('standalone'|'conductor'|'follower')</span></dt>
<dd>By default this is <code>standalone</code>, which means no synchronisation. To synchronise beats across a network, one instance should be a <code>conductor</code> node. Then other instances should define a <code>follower</code> that gets the beat from the conductor specified by its IP address.</dd>
<dt><span class='optional'>sub-beats</span> <span class='property-type'>(string,number)</span></dt>
<dd>Named sub-beats (e.g. quaver, triplet) can be added, including how many should be included in the standard beat. Each sub-beat will add a property to tick messages to give the position of the sub-beat within the beat, and the name of the sub-beat will also be included in the <code>msg.start</code> array when relevant. </dd>
</dl>
<h3>Details</h3>
<p>Initially the number of beats per minute is set by the <code>bpm</code> property, or if none is specified then the global beat setting is used. This can be altered by sending a message to the beat generator with a <code>msg.topic</code> of <code>bpm</code> and the required beats-per-minute in <code>msg.payload</code></p>
<p>To start the beat generator, send it a message with a <code>msg.payload</code> of <code>start</code>.</p>
<p>Alternatively the beat can be started by a series of messages with a <code>msg.payload</code> of <code>tap</code>. If four <code>tap</code>s arrive together then the beat will be started at the place the fifth <code>tap</code> is expected, and the <code>bpm</code> will be set according to the speed of the arriving <code>tap</code>s. This can be used to set the tempo manually, or to synchronise with other inputs, such as a MIDI beat generated elsewhere.</p>
<p>Once the beat has started, an incoming stream of four <code>tap</code> messages will change the <code>bpm</code>.</p>
<p>A message with a <code>msg.payload</code> of <code>stop</code> will stop (pause) the generator. If the generator is started again then the beat count will continue from where it left off.</p>
<p>To start counting from 0 again, send a message with a payload of <code>reset</code>. Any reset messages will also be forwarded to the output of the node, as will any other input messages that do not have the topic <code>bpm</code>.</p>
</script>