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

215 lines (166 loc) 10.2 kB
<script type="text/javascript"> RED.nodes.registerType('sequencer',{ category: 'music', color: '#a6bbcf', defaults: { name: {value: "sequencer"}, input: {value: "beat", required: true}, notesrand: {value: false, required: false}, rhythm: {value: "[3,1]", required: true}, rhythmrand: {value: false, required: false}, loop: {value: true, required: true}, start: {value: "bar", required: true}, output: {value: "single", required: true}, controls: {value:[{name: "note", value: "[1,4,5,4]"}], required: true} }, inputs:1, inputLabels: "tick", outputs:2, outputLabels: ["tick", "control:"], icon: "queue-music.png", label: function() { return this.name||"sequencer"; }, oneditprepare: function(){ node = this; var defaultControls = [{name: "note", values:""}]; node.controls = node.controls || defaultControls; if(!Array.isArray(node.controls) ||node.controls.length == 0){ node.controls = defaultControls; } $('#node-input-control-container').editableList({ addItem: function(container, i, control) { var row = $('<div/>').appendTo(container); $("<span>name </span>").appendTo(row); var nameField = $('<input/>',{class: "node-input-control-name", type:"text", size: 15, placeholder:"e.g. note", style:"width:8em"}).appendTo(row); $("<span> values </span>").appendTo(row); var countField = $('<input/>',{class: "node-input-control-values", type:"text", size:20, placeholder: "e.g. [1,4,5,4]", style:"width:12em"}).appendTo(row); nameField.val(control.name); countField.val(control.values); }, removable: true }); for (var i=0;i<node.controls.length;i++) { var control = node.controls[i]; $("#node-input-control-container").editableList('addItem', control); } }, oneditsave: function(){ var controls = $("#node-input-control-container").editableList('items'); var node = this; node.controls = []; controls.each(function(i){ var name = $(this).find(".node-input-control-name").val(); var values = $(this).find(".node-input-control-values").val(); node.controls.push({name: name, values: values}); }); } }); </script> <script type="text/x-red" data-template-name="sequencer"> <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"> <label for="node-input-input"> Input event</label> <input type="text" id="node-input-input" placeholder="e.g. beat"> </div> <div class="form-row"> <label for="node-input-start"> Start event</label> <input type="text" id="node-input-start" placeholder="e.g. bar"> </div> <div class="form-row"> <label for="node-input-rhythm"> List of lengths</label> <input type="text" id="node-input-rhythm" placeholder="e.g. [3,1]"> </div> <div class="form-row"> <label for="node-input-output">Tick output</label> <select id="node-input-output"> <option value="single">single tick at sequence points</option> <option value="all">all ticks, with sequenced values added</option> </select> </div> <div class="form-row"> <label for="node-input-loop"> Loop</label> <input type="checkbox" id="node-input-loop"> </div> <div class="form-row"> <label for="node-input-rhythmrand"> Choose lengths randomly from list</label> <input type="checkbox" id="node-input-rhythmrand"> </div> <div class="form-row"> <label for="node-input-notesrand"> Choose values randomly from list</label> <input type="checkbox" id="node-input-notesrand"> </div> <div class="form-row node-input-control-container-row"> <label><b>Controls</b> Enter the name and list of values</label> <ol id="node-input-control-container"></ol> </div> </script> <script type="text/x-red" data-help-name="sequencer"> <p>Plays a sequence of notes with a given rhythm, which can be looped and randomised. Can be configured to control arbitrary properties over time.</p> <h3>Inputs</h3> <p>A sequencer usually gets its input from a <code>beat</code> node or a <code>divider</code> node, possibly via a <code>link</code> node.</p> <dl class='message-properties'> <dt>payload <span class='property-type'>string</span></dt> <dd>There are three relevant values for <code>msg.payload</code>: <ol> <li>a <code>tick</code> input is counted by the sequencer if <code>msg.start</code> includes the input event (usually <code>beat</code>)</li> <li>a <code>reset</code> input returns the sequencer to its starting state, and also forwards the message to the output</li> <li>a <code>next</code> input will move the sequencer on to the next value(s) in the list, irrespective of how many <code>tick</code> inputs have been received</li> </ol> Any other value for <code>msg.payload</code> is sent straight to the output. </dd> </dl> <h3>Outputs</h3> <p>A sequencer usually sends ticks to a <code>synth</code> node, which turns them into sound controls. As well as sending ticks, the sequencer also sends control messages which can be sent to any other node type.</p> <ol class="node-ports"> <li>ticks <dl class='message-properties'> <dt>payload <span class='property-type'>string</span></dt> <dd>By default a <code>tick</code> is sent when enough <code>tick</code> inputs have been received, according to the <code>list of lengths</code> configuration. If the <code>Tick Output</code> property is change to <code>all ticks</code> then all input ticks are sent to the output, but with the current value for any sequenced property added as a message property.</dd> <dt><span class='optional'>note</span> <span class='property-type'>number</span></dt> <dd>For each control specified he sequencer moves through its list of <code>values</code> with the rhythm specified and the values are added as a message property. Other sequenced properties can be added to the sequencer with the <code>Controls</code> configuration.</dd> <dt><span class='optional'>timeTag</span> <span class='property-type'>(number)</span></dt> <dd>If a <code>timeTag</code> property is given for the incoming <code>tick</code> message it is included in the outgoing <code>tick</code> message.</dd> </dl> </li> <li>control <p>As well as sending tick events to the ticks output, control signals are sent for each sequenced property as they are changed</p> <dl class="message-properties"> <dt>topic <span class="property-type">string</span></dt> <dd>the name of the sequenced property</dd> <dt>payload <span class="property-type">string</span></dt> <dd>the value of the sequenced property (usually a list)</dd> <dd>if <code>payload</code> is <code>pop</code> then the last item of the current list of values is removed</dd> <dd>if <code>payload</code> is <code>push <i>n</i></code> then <i>n</i> is added to the current list of values</dd> </dl> </li> </ol> <h3>Details</h3> <p>With the default settings of <code>[1,4,5,4]</code> for notes and <code>[3,1]</code> for lengths, the note 1 will be played on the first beat of the bar, then 4 on the fourth beat (i.e. 3 beats later), then 5 on the fifth beat (i.e. 1 beat later, going back to the start of the list of lengths) and finally 4 is played on the eighth beat (i.e. 3 beats later) before the whole sequence starts again.</p> <p>Changing the list of notes to <code>[1,4,5,null]</code> has a rest on the eighth beat instead of another note 4.</p> <p>Using the <code>start event</code> means that a new sequencer node can be deployed or reconfigured during a piece and will line up with a bar (by default) so that the timing of the deployment does not have to be exact, as long as it is some time in the bar before it needs to start.</p> <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.</dd> <dt>input event <span class='property-type'>string</span></dt> <dd>The <code>tick</code> event (included in <code>msg.start</code>) that is counted by the sequencer. By default this is <code>beat</code>, but could be e.g. <code>bar</code></dd> <dt>start event <span class='property-type'>string</span></dt> <dd>After a reset (or reaching the end of a non-looping sequence) the sequencer will wait until this event (included in <code>msg.start</code>) to start the sequence. By default this is <code>bar</code>, but could be e.g. <code>section</code></dd> <dt>loop <span class='property-type'>boolean</span></dt> <dd>If selected, the sequencer will loop straight back to the beginning again and continue. If not the sequencer will restart at the next <code>start event</code>.</dd> <dt>list of notes <span class='property-type'>list,number</span> <dd>A list of numbers representing the position in the scale of the note to be played. The list should be specified as a JSON array, with default <code>[1,4,5,4]</code> or a number can be used if a single note is to be repeated. If no note is to be played at some part of the sequence (i.e. a rest), the value <code>null</code> should be used.</dd> <dt>choose notes randomly from list <span class='property-type'>boolean</span> <dd>If selected, the list of notes is shuffled randomly each time the sequence starts.</dd> <dt>list of lengths <span class='property-type'>list,number</span> <dd>A list of numbers (or null values) representing the number of ticks to be counted for a note. The list should be specified as a JSON array, with default <code>[3,1]</code> or a number can be used if all notes have the same length. The size of this list does not have to be the same as the size of the list of notes: the list will be repeated as many times as necessary. If a <code>null</code> value is given then the sequencer will not progress until a message with payload <code>next</code> is received.</dd> <dt>choose lengths randomly from list <span class='property-type'>boolean</span> <dd>If selected, the list of lengths is shuffled randomly each time the sequence starts.</dd> </dt> </dl> </script>