UNPKG

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
<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>