├── README.md ├── libraries ├── AsyncUpdater.js ├── GroupXFCurves.js ├── Helpers.js ├── ModalWindows.js ├── Notification.js ├── PersistentData.js ├── PreloadBar.js ├── Rect.js ├── TableCurves.js ├── Toaster.js ├── VuMeter.js └── l10n.js └── modules ├── ArticulationSwitcher.js ├── AttackOverlayHandler.js ├── BasicNonRepeatingRoundRobin.js ├── BorrowedSampleRoundRobin.js ├── BreathControl.js ├── Cc64Retrigger.js ├── ChordIdentifier.js ├── ColourPicker.js ├── ControllerToVelocity.js ├── DynamicsXfadeTables.js ├── GenericMicMixer.js ├── GlobalVariables.js ├── GuitarChordDictionary.js ├── GuitarPicker.js ├── GuitarStrummer.js ├── HarpGliss.js ├── Humaniser.js ├── KeyRangeColour.js ├── MappingOffset.js ├── MonoMode.js ├── MultiCCMapper.js ├── OctaveDoubler.js ├── PortGlide.js ├── RoundRobinBypassHandler.js ├── SamplerActivator.js ├── SimpleKeyswitcher.js ├── SyntheticRelease.js ├── TrillMaker.js ├── VelocityFilter.js ├── VelocityMappedRoundRobin.js ├── VelocityScaler.js ├── VelocityShaper.js └── VelocityToCC.js /README.md: -------------------------------------------------------------------------------- 1 | # HISEScripts 2 | 3 | A collection of libraries and modules for use with HISE (http://hise.audio). 4 | 5 | Released under various free software licenses, please see notes at the top of individual files for specific licensing information. 6 | 7 | If you would like to use one or more of these scripts in a proprietary project, and the script's license does not permit that, please contact me to discuss a commercial license. 8 | -------------------------------------------------------------------------------- /libraries/AsyncUpdater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: asyncUpdater.js 3 | * Original Author: Christoph Hart 4 | * Modifications by David Healey 5 | * License: Public Domain 6 | */ 7 | 8 | /* 9 | This library is only of benefit in a script that does not have deferred callbacks enabled. 10 | 11 | The purpose of this library is to trigger a function asynchronously from a MIDI callback. 12 | You'll usually want to use this to update the GUI from with onNoteOn, onNoteOff or onController.; 13 | Updating the GUI from one of these callbacks can cause audio dropouts because these callbacks run on the 14 | real-time thread, this library solves that by providing a method to defer a function using a timer object.; 15 | 16 | This library only supports the use of one deferred function at a time, to defer multiple functions you will need to 17 | either combine those functions into one or modify this library to allow for multiple functions. 18 | */ 19 | 20 | namespace AsyncUpdater 21 | { 22 | const var updater = Engine.createTimerObject(); 23 | reg func; 24 | reg param; 25 | 26 | updater.callback = function() 27 | { 28 | if (func) 29 | { 30 | param == null ? func() : func(param); 31 | } 32 | this.stopTimer(); 33 | }; 34 | 35 | //This is the only function you need to call. Pass in the name of the function you want to defer (f) 36 | // and if needed a parameter (p). If no parameter is required then set p to null. 37 | inline function deferFunction(f, p) 38 | { 39 | func = f; 40 | param = p; 41 | updater.startTimer(25); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /libraries/GroupXFCurves.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: David Healey 3 | * License: Public Domain 4 | */ 5 | 6 | namespace GroupXFCurves 7 | { 8 | inline function draw(samplerId, numGroups, shape) 9 | { 10 | local table = Synth.getTableProcessor(samplerId); 11 | 12 | switch (numGroups) { 13 | case 2: 14 | shape == "s" ? twoGroupsS(table) : twoGroups(table); 15 | break; 16 | case 3: 17 | shape == "s" ? threeGroupsS(table) : threeGroups(table); 18 | break; 19 | case 4: 20 | shape == "s" ? fourGroupsS(table) : fourGroups(table); 21 | break; 22 | } 23 | } 24 | 25 | inline function twoGroups(table) 26 | { 27 | table.reset(0); 28 | table.reset(1); 29 | 30 | //Group1 31 | table.setTablePoint(0, 0, 0.0, 1.0, 0.4); 32 | table.setTablePoint(0, 1, 1.0, 0.0, 0.4); 33 | 34 | //Group2 35 | table.setTablePoint(1, 0, 0.0, 0.0, 0.4); 36 | table.setTablePoint(1, 1, 1.0, 1.0, 0.6); 37 | } 38 | 39 | inline function twoGroupsS(table) 40 | { 41 | table.reset(0); 42 | table.reset(1); 43 | 44 | //Group1 45 | table.setTablePoint(0, 1, 0.0, 1.0, 1.0); 46 | table.addTablePoint(0, 0.5, 0.5); 47 | table.setTablePoint(0, 1, 0.5, 0.5, 0.4); 48 | table.setTablePoint(0, 2, 1.0, 0.0, 0.6); 49 | 50 | //Group2 51 | table.setTablePoint(1, 0, 0.0, 0.0, 0.6); 52 | table.addTablePoint(1, 0.5, 0.5); 53 | table.setTablePoint(1, 1, 0.5, 0.5, 0.4); 54 | table.setTablePoint(1, 2, 1.0, 1.0, 0.6); 55 | } 56 | 57 | 58 | inline function threeGroups(table) 59 | { 60 | table.reset(0); 61 | table.reset(1); 62 | table.reset(2); 63 | 64 | // Group1 65 | table.setTablePoint(0, 0, 0.0, 1.0, 0.4); 66 | table.addTablePoint(0, 0.5, 0.0); 67 | table.setTablePoint(0, 1, 0.5, 0.0, 0.4); 68 | table.setTablePoint(0, 2, 1.0, 0.0, 0.4); 69 | 70 | // Group2 71 | table.setTablePoint(1, 0, 0.0, 0.0, 0.6); 72 | table.addTablePoint(1, 0.5, 1.0); 73 | table.setTablePoint(1, 1, 0.5, 1.0, 0.6); 74 | table.setTablePoint(1, 2, 1.0, 0.0, 0.4); 75 | 76 | // Group3 77 | table.setTablePoint(2, 0, 0.0, 0.0, 0.4); 78 | table.addTablePoint(2, 0.5, 0.0); 79 | table.setTablePoint(2, 1, 0.5, 0.0, 0.4); 80 | table.setTablePoint(2, 2, 1.0, 1.0, 0.6); 81 | } 82 | 83 | inline function threeGroupsS(table) 84 | { 85 | table.reset(0); 86 | table.reset(1); 87 | table.reset(2); 88 | 89 | //Group1 90 | table.setTablePoint(0, 0, 0.0, 1.0, 1.0); 91 | table.addTablePoint(0, 0.25, 0.5); 92 | table.setTablePoint(0, 1, 0.25, 0.5, 0.4); 93 | table.addTablePoint(0, 0.5, 0.0); 94 | table.setTablePoint(0, 2, 0.5, 0.0, 0.6); 95 | table.setTablePoint(0, 3, 1.0, 0.0, 0.6); 96 | 97 | //Group2 98 | table.setTablePoint(1, 0, 0.0, 0.0, 1.0); 99 | table.addTablePoint(1, 0.25, 0.5); 100 | table.setTablePoint(1, 1, 0.25, 0.5, 0.4); 101 | table.addTablePoint(1, 0.5, 1.0); 102 | table.setTablePoint(1, 2, 0.5, 1.0, 0.6); 103 | table.addTablePoint(1, 0.75, 0.5); 104 | table.setTablePoint(1, 3, 0.75, 0.5, 0.4); 105 | table.setTablePoint(1, 4, 1.0, 0.0, 0.6); 106 | 107 | //Group3 108 | table.setTablePoint(2, 0, 0.0, 0.0, 1.0); 109 | table.addTablePoint(2, 0.5, 0.0); 110 | table.setTablePoint(2, 1, 0.5, 0, 0.6); 111 | table.addTablePoint(2, 0.75, 0.5); 112 | table.setTablePoint(2, 2, 0.75, 0.5, 0.4); 113 | table.setTablePoint(2, 3, 1.0, 1.0, 0.6); 114 | } 115 | 116 | inline function fourGroups(table) 117 | { 118 | table.reset(0); 119 | table.reset(1); 120 | table.reset(2); 121 | table.reset(3); 122 | 123 | // Group1 124 | table.setTablePoint(0, 0, 0.0, 1.0, 0.4); 125 | table.addTablePoint(0, 1, 1); 126 | table.setTablePoint(0, 1, 0.33, 0.0, 0.4); 127 | table.setTablePoint(0, 2, 1.0, 0.0, 0.4); 128 | 129 | //Group2 130 | table.setTablePoint(1, 0, 0.0, 0.0, 0.4); 131 | table.addTablePoint(1, 0.33, 1.0); 132 | table.setTablePoint(1, 1, 0.33, 1.0, 0.6); 133 | table.addTablePoint(1, 0.66, 0.0); 134 | table.setTablePoint(1, 2, 0.66, 0.0, 0.4); 135 | table.setTablePoint(1, 3, 1.0, 0.0, 0.4); 136 | 137 | //Group3 138 | table.setTablePoint(2, 0, 0.0, 0.0, 0.4); 139 | table.addTablePoint(2, 0.33, 0.0); 140 | table.setTablePoint(2, 1, 0.33, 0.0, 0.4); 141 | table.addTablePoint(2, 0.66, 1.0); 142 | table.setTablePoint(2, 2, 0.66, 1.0, 0.6); 143 | table.setTablePoint(2, 3, 1.0, 0.0, 0.4); 144 | 145 | //Group4 146 | table.setTablePoint(3, 0, 0.0, 0.0, 0.4); 147 | table.addTablePoint(3, 0.66, 0.0); 148 | table.setTablePoint(3, 1, 0.66, 0.0, 0.6); 149 | table.setTablePoint(3, 2, 1.0, 1.0, 0.6); 150 | } 151 | 152 | inline function fourGroupsS(table) 153 | { 154 | table.reset(0); 155 | table.reset(1); 156 | table.reset(2); 157 | table.reset(3); 158 | 159 | //Group1 160 | table.setTablePoint(0, 0, 0.0, 1.0, 1.0); 161 | table.addTablePoint(0, 0.16, 0.5); 162 | table.setTablePoint(0, 1, 0.16, 0.5, 0.4); 163 | table.addTablePoint(0, 0.33, 0.0); 164 | table.setTablePoint(0, 2, 0.33, 0.0, 0.6); 165 | table.setTablePoint(0, 3, 1.0, 0.0, 0.0); 166 | 167 | //Group2 168 | table.setTablePoint(1, 0, 1.0, 0.0, 1.0); 169 | table.addTablePoint(1, 0.16, 0.5); 170 | table.setTablePoint(1, 1, 0.16, 0.5, 0.4); 171 | table.addTablePoint(1, 0.33, 1.0); 172 | table.setTablePoint(1, 2, 0.33, 1.0, 0.6); 173 | table.addTablePoint(1, 0.5, 0.5); 174 | table.setTablePoint(1, 3, 0.5, 0.5, 0.4); 175 | table.addTablePoint(1, 0.65, 0.0); 176 | table.setTablePoint(1, 4, 0.65, 0.0, 0.6); 177 | table.setTablePoint(1, 5, 1.0, 0.0, 0.4); 178 | 179 | //Group3 180 | table.setTablePoint(2, 0, 0.0, 0.0, 1.0); 181 | table.addTablePoint(2, 0.33, 0.0); 182 | table.setTablePoint(2, 1, 0.33, 0.0, 0.4); 183 | table.addTablePoint(2, 0.5, 0.5); 184 | table.setTablePoint(2, 2, 0.5, 0.5, 0.4); 185 | table.addTablePoint(2, 0.66, 1.0); 186 | table.setTablePoint(2, 3, 0.66, 1.0, 0.6); 187 | table.addTablePoint(2, 0.83, 0.5); 188 | table.setTablePoint(2, 4, 0.83, 0.5, 0.4); 189 | table.setTablePoint(2, 5, 1.0, 0.0, 0.6); 190 | 191 | //Group4 192 | table.setTablePoint(3, 0, 0.0, 0.0, 1.0); 193 | table.addTablePoint(3, 0.66, 0.0); 194 | table.setTablePoint(3, 1, 0.66, 0.0, 0.6); 195 | table.addTablePoint(3, 0.83, 0.5); 196 | table.setTablePoint(3, 2, 0.83, 0.5, 0.4); 197 | table.setTablePoint(3, 3, 1.0, 1.0, 0.6); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /libraries/Helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: helpers.js 3 | * Main Author: David Healey 4 | * Additional Functions by Christoph Hart 5 | * Date: 29/07/2017 6 | * Modified: 03/12/2017 7 | * License: Public Domain 8 | */ 9 | 10 | namespace Helpers 11 | { 12 | //Returns note letter only, for letter + octave use built in Engine.getMidiNoteName() 13 | inline function noteNumberToLetter(n) 14 | { 15 | local noteLetters = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; 16 | return noteLetters[n % 12]; 17 | } 18 | 19 | inline function getAllNoteNames() 20 | { 21 | local i; 22 | local allNoteNames = []; 23 | 24 | for (i = 0; i < 128; i++) 25 | { 26 | allNoteNames[i] = Engine.getMidiNoteName(i); 27 | } 28 | 29 | return allNoteNames; 30 | } 31 | 32 | inline function tempoIndexToString(tempoIndex) 33 | { 34 | local tempos = ["1/1", "1/2", "1/2T", "1/4", "1/4T", "1/8", "1/8T", "1/16", "1/16T", "1/32", "1/32T"]; 35 | return tempos[tempoIndex]; 36 | } 37 | 38 | inline function inRange(value, min, max) 39 | { 40 | return value >= min && value <= max; 41 | } 42 | 43 | inline function isEven(n) 44 | { 45 | return !(n & 1); 46 | } 47 | 48 | inline function isBlackKey(n) 49 | { 50 | local blackKeys = [1, 3, 6, 8, 10]; //Black keys in one octave assuming C = 0 51 | 52 | if (blackKeys.indexOf(parseInt(n) % 12) != -1) 53 | { 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | inline function skew(value, max, skewFactor) 60 | { 61 | // values > 1 will yield more resolution at the lower end 62 | local normalised = value / max; 63 | 64 | // this will "bend" the line towards the lower end 65 | local skewed = Math.pow(normalised, skewFactor); 66 | return skewed * max; 67 | } 68 | 69 | inline function remapRange(value, oldMin, oldMax, newMin, newMax) 70 | { 71 | if (oldMax - oldMin == 0) 72 | return newMin; 73 | else 74 | return (((value - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin; 75 | } 76 | 77 | inline function getSamplers(exclude) 78 | { 79 | local samplerIds = Synth.getIdList("Sampler"); 80 | local samplers = []; 81 | 82 | for (id in samplerIds) 83 | { 84 | if (typeof exclude == "string") 85 | if (Engine.matchesRegex(id, exclude) == true) continue; // Skip IDs that match exclusion 86 | 87 | samplers.push(Synth.getSampler(id)); 88 | } 89 | 90 | return samplers; 91 | } 92 | 93 | inline function getIdListContainingString(type, containing) 94 | { 95 | local idList = Synth.getIdList(type); 96 | local filteredIds = []; 97 | local i; 98 | 99 | for (i = 0; i < idList.length; i++) 100 | { 101 | if (idList[i].indexOf(containing) == -1) continue; // Skip ids that don't contain the substring 102 | filteredIds.push(idList[i]); 103 | } 104 | 105 | return filteredIds; 106 | } 107 | 108 | inline function keyExists(obj, key) 109 | { 110 | return !(obj[key] == void); // Important: not undefined! 111 | } 112 | 113 | inline function getSize(obj) 114 | { 115 | local count = 0; 116 | 117 | for (k in obj) 118 | count++; 119 | 120 | return count; 121 | } 122 | 123 | inline function getPropertyByIndex(obj, index) 124 | { 125 | local count = 0; 126 | 127 | for (k in obj) 128 | { 129 | if (count == index) 130 | return obj[k]; 131 | 132 | count++; 133 | } 134 | 135 | return undefined; 136 | } 137 | 138 | inline function getIndexByProperty(obj, prop) 139 | { 140 | local count = 0; 141 | 142 | for (k in obj) 143 | { 144 | if (k == prop) return count; 145 | count++; 146 | } 147 | 148 | return -1; 149 | } 150 | 151 | inline function cartesianProduct(array) 152 | { 153 | local i; 154 | local j; 155 | local k; 156 | 157 | local result = [[]]; 158 | 159 | for (i = 0; i < array.length; i++) 160 | { 161 | local subArray = array[i]; 162 | local temp = []; 163 | 164 | for (j = 0; j < result.length; j++) 165 | { 166 | for (k = 0; k < subArray.length; k++) 167 | { 168 | local c = concatArrays(result[j], [subArray[k]]); 169 | temp.push(c); 170 | } 171 | } 172 | 173 | result = temp; 174 | } 175 | 176 | return result; 177 | } 178 | 179 | inline function concatArrays(a, b) 180 | { 181 | local c = []; 182 | local i; 183 | 184 | for (i = 0; i < a.length; i++) 185 | c[i] = a[i]; 186 | 187 | for (i = 0; i < b.length; i++) 188 | c.push(b[i]); 189 | 190 | return c; 191 | } 192 | 193 | inline function mergeObjects(arrayOfObjects) 194 | { 195 | local target = {}; 196 | 197 | for (object in arrayOfObjects) 198 | { 199 | if (!isDefined(object) || !object.length) continue; 200 | 201 | for (key in object) 202 | { 203 | target[key] = object[key]; 204 | } 205 | } 206 | 207 | return target; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /libraries/ModalWindows.js: -------------------------------------------------------------------------------- 1 | namespace ModalWindows 2 | { 3 | reg modals = {}; 4 | 5 | // pnlModalBackground 6 | const pnlModalBackground = Content.getComponent("pnlModalBackground"); 7 | 8 | pnlModalBackground.setMouseCallback(function(event) 9 | { 10 | if (event.clicked) 11 | hideAllModals(); 12 | }); 13 | 14 | // Functions 15 | inline function hideAllModals() 16 | { 17 | for (id in modals) 18 | modals[id].showControl(false); 19 | 20 | pnlModalBackground.showControl(false); 21 | } 22 | 23 | inline function add(component) 24 | { 25 | modals[component.get("id")] = component; 26 | } 27 | 28 | inline function show(id) 29 | { 30 | pnlModalBackground.showControl(true); 31 | modals[id].showControl(true); 32 | } 33 | 34 | // Calls 35 | hideAllModals(); 36 | } -------------------------------------------------------------------------------- /libraries/Notification.js: -------------------------------------------------------------------------------- 1 | namespace Notification 2 | { 3 | // pnlNotification 4 | const pnlNotification = Content.getComponent("pnlNotification"); 5 | pnlNotification.set("x", 901); 6 | 7 | pnlNotification.setPaintRoutine(function(g) 8 | { 9 | var a = this.getLocalBounds(0); 10 | 11 | g.drawDropShadow([a[0] + 5, a[1], a[2], a[3] - 10], Colours.withAlpha(Colours.black, 1.0 - (this.data.counter - 20) / 100), 10); 12 | 13 | g.setColour(Colours.withAlpha(this.get("bgColour"), 1.0 - (this.data.counter - 20) / 100)); 14 | g.fillRect([a[0] + 5, a[1], a[2], a[3] - 10]); 15 | 16 | g.setFont("medium", 18); 17 | g.setColour(Colours.withAlpha(this.get("textColour"), 1.0 - (this.data.counter - 20) / 100)); 18 | g.drawMultiLineText(this.get("text"), [25, 30], a[2] - 60, "left", 2.0); 19 | }); 20 | 21 | pnlNotification.setTimerCallback(function() 22 | { 23 | if (this.get("x") <= 601) 24 | this.data.counter++; 25 | else 26 | this.set("x", this.get("x") - 20); 27 | 28 | if (this.data.counter >= 120) 29 | { 30 | this.showControl(false); 31 | this.stopTimer(); 32 | } 33 | 34 | this.repaint(); 35 | }); 36 | 37 | pnlNotification.setMouseCallback(function(event) 38 | { 39 | if (event.clicked) 40 | pnlNotification.data.counter = 120; 41 | }); 42 | 43 | // Functions 44 | inline function show(text) 45 | { 46 | pnlNotification.set("text", text); 47 | pnlNotification.showControl(true); 48 | 49 | pnlNotification.data.counter = 0; 50 | pnlNotification.set("x", 901); 51 | pnlNotification.startTimer(50); 52 | } 53 | } -------------------------------------------------------------------------------- /libraries/PersistentData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Christoph Hart & David Healey 3 | * License: Public Domain 4 | */ 5 | 6 | namespace PersistentData 7 | { 8 | //The data object. This will be overwritten by the onControl callback of the restorer 9 | reg persistentData = {}; 10 | 11 | /*This panel will restore the storage object in its control callback. 12 | If the value is not an object, it won't do anything but use the given 13 | storage variable as value*/ 14 | const pnlDataRestorer = Content.addPanel("pnlDataRestorer", 0, 0); 15 | pnlDataRestorer.set("saveInPreset", true); 16 | pnlDataRestorer.set("visible", false); 17 | pnlDataRestorer.setControlCallback(onpnlDataRestorerControl); 18 | 19 | inline function onpnlDataRestorerControl(component, value) 20 | { 21 | // Check if the panel's value is an object 22 | // If not, set the value to the storage object 23 | 24 | if (typeof(value) == "object") 25 | persistentData = value; //Copy the object from the panel to the storage object 26 | else 27 | component.setValue(persistentData); //Initialize the restorer 28 | }; 29 | 30 | inline function set(id, value) 31 | { 32 | persistentData[id] = value; 33 | pnlDataRestorer.setValue(persistentData); 34 | } 35 | 36 | inline function get(id) 37 | { 38 | return persistentData[id]; 39 | } 40 | } -------------------------------------------------------------------------------- /libraries/PreloadBar.js: -------------------------------------------------------------------------------- 1 | //License: Public Domain 2 | //Author: Christoph Hart + David Healey 3 | 4 | namespace PreloadBar 5 | { 6 | reg component; 7 | 8 | inline function init(panelId, properties) 9 | { 10 | component = Content.getComponent(panelId); 11 | 12 | if (typeof properties == "object") 13 | { 14 | for (p in properties) 15 | component.set(p, properties[p]); 16 | } 17 | 18 | component.setPaintRoutine(function(g) 19 | { 20 | g.fillAll(this.get("bgColour")); 21 | g.setColour(this.get("itemColour")); 22 | g.fillRect([0, 0, this.getWidth() * this.data.progress, this.getHeight()]); 23 | }); 24 | 25 | component.setTimerCallback(function() 26 | { 27 | this.data.progress = Engine.getPreloadProgress(); 28 | this.repaint(); 29 | }); 30 | 31 | component.setLoadingCallback(function(isPreloading) 32 | { 33 | this.data.progress = 0.0; 34 | this.set("visible", isPreloading); 35 | isPreloading ? this.startTimer(30) : this.stopTimer(); 36 | }); 37 | } 38 | 39 | inline function setPosition(x, y, w, h) 40 | { 41 | component.setPosition(x, y, w, h); 42 | } 43 | } -------------------------------------------------------------------------------- /libraries/Rect.js: -------------------------------------------------------------------------------- 1 | /* 2 | License: Public Domain 3 | Author: Christoph Hart, David Healey 4 | */ 5 | 6 | namespace Rect 7 | { 8 | inline function verticalCentre(h1, h2) 9 | { 10 | return h1 / 2 - h2 / 2; 11 | } 12 | 13 | inline function reduced(area, amount) 14 | { 15 | return [area[0] + amount, area[1] + amount, area[2] - 2 * amount, area[3] - 2 * amount]; 16 | } 17 | 18 | inline function withSizeKeepingCentre(area, width, height) 19 | { 20 | return [area[0] + (area[2] - width) / 2, area[1] + (area[3] - height) / 2, width, height]; 21 | } 22 | 23 | inline function removeFromLeft(area, amount) 24 | { 25 | area[0] += amount; 26 | area[2] -= amount; 27 | return [area[0] - amount, area[1], amount, area[3]]; 28 | } 29 | 30 | inline function removeFromRight(area, amount) 31 | { 32 | area[2] -= amount; 33 | return [area[0] + area[2], area[1], amount, area[3]]; 34 | } 35 | 36 | inline function removeFromTop(area, amount) 37 | { 38 | area[1] += amount; 39 | area[3] -= amount; 40 | return [area[0], area[1] - amount, area[2], amount]; 41 | } 42 | 43 | inline function removeFromBottom(area, amount) 44 | { 45 | area[3] -= amount; 46 | return [area[0], area[1] + area[3], area[2], amount]; 47 | } 48 | 49 | inline function translated(area, xDelta, yDelta) 50 | { 51 | return [area[0] + xDelta, area[1] + yDelta, area[2], area[3]]; 52 | } 53 | } -------------------------------------------------------------------------------- /libraries/TableCurves.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: David Healey 3 | * License: Public Domain 4 | */ 5 | 6 | namespace TableCurves 7 | { 8 | inline function equalPower2(tableProcessor, stage) 9 | { 10 | tableProcessor.reset(0); 11 | 12 | switch (stage) 13 | { 14 | case 0: 15 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 16 | tableProcessor.setTablePoint(0, 1, 1, 0, 0.25); 17 | break; 18 | 19 | case 1: 20 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 21 | tableProcessor.setTablePoint(0, 1, 1, 1, 0.75); 22 | break; 23 | } 24 | }; 25 | 26 | inline function equalPower3(tableProcessor, stage) 27 | { 28 | tableProcessor.reset(0); 29 | 30 | switch (stage) 31 | { 32 | case 0: 33 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 34 | tableProcessor.addTablePoint(0, 1, 1); 35 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0, 0.25); 36 | tableProcessor.setTablePoint(0, 2, 1, 0, 0); 37 | break; 38 | 39 | case 1: 40 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 41 | tableProcessor.addTablePoint(0, 1, 1); 42 | tableProcessor.setTablePoint(0, 1, 1/2, 1, 0.75); 43 | tableProcessor.setTablePoint(0, 2, 1, 0, 0.25); 44 | break; 45 | 46 | case 2: 47 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 48 | tableProcessor.addTablePoint(0, 1, 1); 49 | tableProcessor.setTablePoint(0, 1, 1/2, 0, 1); 50 | tableProcessor.setTablePoint(0, 2, 1, 1, 0.75); 51 | break; 52 | } 53 | }; 54 | 55 | inline function equalPower4(tableProcessor, stage) 56 | { 57 | tableProcessor.reset(0); 58 | 59 | switch (stage) 60 | { 61 | case 0: 62 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 63 | tableProcessor.addTablePoint(0, 1, 1); 64 | tableProcessor.setTablePoint(0, 1, 1.0/3, 0, 0.25); 65 | tableProcessor.setTablePoint(0, 2, 1, 0, 0); 66 | break; 67 | 68 | case 1: 69 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 70 | tableProcessor.addTablePoint(0, 1, 1); 71 | tableProcessor.setTablePoint(0, 1, 1/3, 1, 0.75); 72 | tableProcessor.addTablePoint(0, 1, 1); 73 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 0, 0.25); 74 | tableProcessor.setTablePoint(0, 3, 1, 0, 0.25); 75 | break; 76 | 77 | case 2: 78 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 79 | tableProcessor.addTablePoint(0, 1, 1); 80 | tableProcessor.setTablePoint(0, 1, 1/3, 0, 0.25); 81 | tableProcessor.addTablePoint(0, 1, 1); 82 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 1, 0.75); 83 | tableProcessor.setTablePoint(0, 3, 1, 0, 0.25); 84 | break; 85 | 86 | case 3: 87 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 88 | tableProcessor.addTablePoint(0, 1, 1); 89 | tableProcessor.setTablePoint(0, 1, 1/3+1/3, 0, 0.75); 90 | tableProcessor.addTablePoint(0, 1, 1); 91 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 0, 0.25); 92 | tableProcessor.setTablePoint(0, 3, 1, 1, 0.75); 93 | break; 94 | } 95 | }; 96 | 97 | inline function equalPower5(tableProcessor, stage) 98 | { 99 | tableProcessor.reset(0); 100 | 101 | switch (stage) 102 | { 103 | case 0: 104 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 105 | tableProcessor.addTablePoint(0, 1, 1); 106 | tableProcessor.setTablePoint(0, 1, 1/4, 0, 0.25); 107 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.25); 108 | break; 109 | 110 | case 1: 111 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 112 | tableProcessor.addTablePoint(0, 1, 1); 113 | tableProcessor.setTablePoint(0, 1, 1/4, 1, 0.75); 114 | tableProcessor.addTablePoint(0, 1, 1); 115 | tableProcessor.setTablePoint(0, 2, 1/4+1/4, 0, 0.25); 116 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 117 | break; 118 | 119 | case 2: 120 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 121 | tableProcessor.addTablePoint(0, 1, 1); 122 | tableProcessor.setTablePoint(0, 1, 1/4, 0, 0.75); 123 | tableProcessor.addTablePoint(0, 1, 1); 124 | tableProcessor.setTablePoint(0, 2, 1/4+1/4, 1, 0.75); 125 | tableProcessor.addTablePoint(0, 1, 1); 126 | tableProcessor.setTablePoint(0, 3, (1/4)*3, 0, 0.25); 127 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.25); 128 | break; 129 | 130 | case 3: 131 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 132 | tableProcessor.addTablePoint(0, 1, 1); 133 | tableProcessor.setTablePoint(0, 1, 1/4*2, 0, 0.75); 134 | tableProcessor.addTablePoint(0, 1, 1); 135 | tableProcessor.setTablePoint(0, 2, 1/4+1/4*2, 1, 0.75); 136 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 137 | break; 138 | 139 | case 4: 140 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 141 | tableProcessor.addTablePoint(0, 1, 1); 142 | tableProcessor.setTablePoint(0, 1, 1/4*3, 0, 0.75); 143 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 144 | break; 145 | } 146 | }; 147 | 148 | inline function equalPower6(tableProcessor, stage) 149 | { 150 | tableProcessor.reset(0); 151 | 152 | switch (stage) 153 | { 154 | case 0: 155 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 156 | tableProcessor.addTablePoint(0, 1, 1); 157 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.25); 158 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.75); 159 | break; 160 | 161 | case 1: 162 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 163 | tableProcessor.addTablePoint(0, 1, 1); 164 | tableProcessor.setTablePoint(0, 1, 1/5, 1, 0.75); 165 | tableProcessor.addTablePoint(0, 1, 1); 166 | tableProcessor.setTablePoint(0, 2, 1/5*2, 0, 0.25); 167 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 168 | break; 169 | 170 | case 2: 171 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 172 | tableProcessor.addTablePoint(0, 1, 1); 173 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.75); 174 | tableProcessor.addTablePoint(0, 1, 1); 175 | tableProcessor.setTablePoint(0, 2, 1/5*2, 1, 0.75); 176 | tableProcessor.addTablePoint(0, 1, 1); 177 | tableProcessor.setTablePoint(0, 3, 1/5*3, 0, 0.25); 178 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 179 | break; 180 | 181 | case 3: 182 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 183 | tableProcessor.addTablePoint(0, 1, 1); 184 | tableProcessor.setTablePoint(0, 1, 1/5*2, 0, 0.75); 185 | tableProcessor.addTablePoint(0, 1, 1); 186 | tableProcessor.setTablePoint(0, 2, 1/5*3, 1, 0.75); 187 | tableProcessor.addTablePoint(0, 1, 1); 188 | tableProcessor.setTablePoint(0, 3, 1/5*4, 0, 0.25); 189 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 190 | break; 191 | 192 | case 4: 193 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 194 | tableProcessor.addTablePoint(0, 1, 1); 195 | tableProcessor.setTablePoint(0, 1, 1/5*3, 0, 0.75); 196 | tableProcessor.addTablePoint(0, 1, 1); 197 | tableProcessor.setTablePoint(0, 2, 1/5*4, 1, 0.75); 198 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 199 | break; 200 | 201 | case 5: 202 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 203 | tableProcessor.addTablePoint(0, 1, 1); 204 | tableProcessor.setTablePoint(0, 1, 1/5*4, 0, 0.75); 205 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 206 | break; 207 | } 208 | }; 209 | 210 | inline function equalPower7(tableProcessor, stage) 211 | { 212 | tableProcessor.reset(0); 213 | 214 | switch (stage) 215 | { 216 | case 0: 217 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 218 | tableProcessor.addTablePoint(0, 1, 1); 219 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0, 0.25); 220 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.25); 221 | break; 222 | 223 | case 1: 224 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 225 | tableProcessor.addTablePoint(0, 1, 1); 226 | tableProcessor.setTablePoint(0, 1, (1.0/4), 1, 0.75); 227 | tableProcessor.addTablePoint(0, 1, 1); 228 | tableProcessor.setTablePoint(0, 2, (1.0/4)*2, 0, 0.25); 229 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 230 | break; 231 | 232 | case 2: 233 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 234 | tableProcessor.addTablePoint(0, 1, 1); 235 | tableProcessor.setTablePoint(0, 1, (1.0/4/2), 0.0, 0.25); 236 | tableProcessor.addTablePoint(0, 1, 1); 237 | tableProcessor.setTablePoint(0, 2, (1.0/4+1/4/2), 1, 0.75); 238 | tableProcessor.addTablePoint(0, 1, 1); 239 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8, 0, 0.25); 240 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 241 | break; 242 | 243 | case 3: 244 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 245 | tableProcessor.addTablePoint(0, 1, 1); 246 | tableProcessor.setTablePoint(0, 1, (1.0/4), 0.0, 0.25); 247 | tableProcessor.addTablePoint(0, 1, 1); 248 | tableProcessor.setTablePoint(0, 2, (1.0/4*2), 1, 0.75); 249 | tableProcessor.addTablePoint(0, 1, 1); 250 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*2, 0, 0.25); 251 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 252 | break; 253 | 254 | case 4: 255 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 256 | tableProcessor.addTablePoint(0, 1, 1); 257 | tableProcessor.setTablePoint(0, 1, (1.0/4+1/4/2), 0.0, 0.25); 258 | tableProcessor.addTablePoint(0, 1, 1); 259 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8, 1, 0.75); 260 | tableProcessor.addTablePoint(0, 1, 1); 261 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*3, 0, 0.25); 262 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 263 | break; 264 | 265 | case 5: 266 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 267 | tableProcessor.addTablePoint(0, 1, 1); 268 | tableProcessor.setTablePoint(0, 1, (1.0/4*2), 0.0, 0.25); 269 | tableProcessor.addTablePoint(0, 1, 1); 270 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8*2, 1, 0.75); 271 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*4, 0, 0.25); 272 | break; 273 | 274 | case 6: 275 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 276 | tableProcessor.addTablePoint(0, 1, 1); 277 | tableProcessor.setTablePoint(0, 1, (1.0/4*2)+1/8*2, 0, 0.75); 278 | tableProcessor.setTablePoint(0, 2, ((1.0/4)*2)+1/8*4, 1, 0.75); 279 | break; 280 | } 281 | }; 282 | 283 | inline function sShape2(tableProcessor, stage) 284 | { 285 | tableProcessor.reset(0); 286 | 287 | switch (stage) 288 | { 289 | case 0: 290 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 291 | tableProcessor.addTablePoint(0, 1, 1); 292 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0.5, 0.25); 293 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.75); 294 | break; 295 | 296 | case 1: 297 | tableProcessor.setTablePoint(0, 0, 0, 0, 0.75); 298 | tableProcessor.addTablePoint(0, 1, 1); 299 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0.5, 0.25); 300 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 301 | break; 302 | } 303 | }; 304 | 305 | inline function sShape3(tableProcessor, stage) 306 | { 307 | tableProcessor.reset(0); 308 | 309 | switch (stage) 310 | { 311 | case 0: 312 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 313 | tableProcessor.addTablePoint(0, 1, 1); 314 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0.5, 0.25); 315 | tableProcessor.addTablePoint(0, 1, 1); 316 | tableProcessor.setTablePoint(0, 2, 1.0/2, 0, 0.75); 317 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 318 | break; 319 | 320 | case 1: 321 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 322 | tableProcessor.addTablePoint(0, 1, 1); 323 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0.5, 0.25); 324 | tableProcessor.addTablePoint(0, 1, 1); 325 | tableProcessor.setTablePoint(0, 2, 1.0/2, 1, 0.75); 326 | tableProcessor.addTablePoint(0, 1, 1); 327 | tableProcessor.setTablePoint(0, 3, 1.0/4*3, 0.5, 0.25); 328 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 329 | break; 330 | 331 | case 2: 332 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 333 | tableProcessor.addTablePoint(0, 1, 1); 334 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0, 0.75); 335 | tableProcessor.addTablePoint(0, 1, 1); 336 | tableProcessor.setTablePoint(0, 2, 1.0/4*3, 0.5, 0.25); 337 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 338 | break; 339 | } 340 | }; 341 | 342 | inline function sShape4(tableProcessor, stage) 343 | { 344 | tableProcessor.reset(0); 345 | 346 | switch (stage) 347 | { 348 | case 0: 349 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 350 | tableProcessor.addTablePoint(0, 1, 1); 351 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 352 | tableProcessor.addTablePoint(0, 1, 1); 353 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0, 0.75); 354 | tableProcessor.setTablePoint(0, 3, 1, 0, 0); 355 | break; 356 | 357 | case 1: 358 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 359 | tableProcessor.addTablePoint(0, 1, 1); 360 | tableProcessor.setTablePoint(0, 1, 1/3/2, 0.5, 0.25); 361 | tableProcessor.addTablePoint(0, 1, 1); 362 | tableProcessor.setTablePoint(0, 2, 1/3, 1, 0.75); 363 | tableProcessor.addTablePoint(0, 1, 1); 364 | tableProcessor.setTablePoint(0, 3, 1/3+1/3/2, 0.5, 0.25); 365 | tableProcessor.addTablePoint(0, 1, 1); 366 | tableProcessor.setTablePoint(0, 4, 1/3+1/3, 0, 0.75); 367 | tableProcessor.setTablePoint(0, 5, 1, 0, 0.25); 368 | break; 369 | 370 | case 2: 371 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 372 | tableProcessor.addTablePoint(0, 1, 1); 373 | tableProcessor.setTablePoint(0, 1, 1/3, 0, 0.25); 374 | tableProcessor.addTablePoint(0, 1, 1); 375 | tableProcessor.setTablePoint(0, 2, 1/3+1/3/2, 0.5, 0.25); 376 | tableProcessor.addTablePoint(0, 1, 1); 377 | tableProcessor.setTablePoint(0, 3, 1/3+1/3, 1, 0.75); 378 | tableProcessor.addTablePoint(0, 1, 1); 379 | tableProcessor.setTablePoint(0, 4, 1/3+1/3+1/6, 0.5, 0.25); 380 | tableProcessor.setTablePoint(0, 5, 1, 0, 0.75); 381 | break; 382 | 383 | case 3: 384 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 385 | tableProcessor.addTablePoint(0, 1, 1); 386 | tableProcessor.setTablePoint(0, 1, 1/3+1/3, 0, 0.75); 387 | tableProcessor.addTablePoint(0, 1, 1); 388 | tableProcessor.setTablePoint(0, 2, 1/3+1/3+1/6, 0.5, 0.25); 389 | tableProcessor.setTablePoint(0, 3, 1, 1, 0.75); 390 | break; 391 | } 392 | }; 393 | 394 | inline function sShape5(tableProcessor, stage) 395 | { 396 | tableProcessor.reset(0); 397 | 398 | switch (stage) 399 | { 400 | case 0: 401 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 402 | tableProcessor.addTablePoint(0, 1, 1); 403 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 404 | tableProcessor.addTablePoint(0, 1, 1); 405 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0, 0.75); 406 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 407 | break; 408 | 409 | case 1: 410 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 411 | tableProcessor.addTablePoint(0, 1, 1); 412 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 413 | tableProcessor.addTablePoint(0, 1, 1); 414 | tableProcessor.setTablePoint(0, 2, 1.0/3, 1, 0.75); 415 | tableProcessor.addTablePoint(0, 1, 1); 416 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3/2, 0.5, 0.25); 417 | tableProcessor.addTablePoint(0, 1, 1); 418 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3, 0, 0.75); 419 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 420 | break; 421 | 422 | case 2: 423 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 424 | tableProcessor.addTablePoint(0, 1, 1); 425 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0, 0.75); 426 | tableProcessor.addTablePoint(0, 1, 1); 427 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0.5, 0.25); 428 | tableProcessor.addTablePoint(0, 1, 1); 429 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3/2, 1, 0.75); 430 | tableProcessor.addTablePoint(0, 1, 1); 431 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3, 0.5, 0.25); 432 | tableProcessor.addTablePoint(0, 1, 1); 433 | tableProcessor.setTablePoint(0, 5, 1.0/3+1/3+1/6, 0, 0.75); 434 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 435 | break; 436 | 437 | case 3: 438 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 439 | tableProcessor.addTablePoint(0, 1, 1); 440 | tableProcessor.setTablePoint(0, 1, 1.0/3, 0, 0.75); 441 | tableProcessor.addTablePoint(0, 1, 1); 442 | tableProcessor.setTablePoint(0, 2, 1.0/3+1/3/2, 0.5, 0.25); 443 | tableProcessor.addTablePoint(0, 1, 1); 444 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3, 1, 0.75); 445 | tableProcessor.addTablePoint(0, 1, 1); 446 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3+1/6, 0.5, 0.25); 447 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 448 | break; 449 | 450 | case 4: 451 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 452 | tableProcessor.addTablePoint(0, 1, 1); 453 | tableProcessor.setTablePoint(0, 1, 1.0/3+1/3, 0, 0.75); 454 | tableProcessor.addTablePoint(0, 1, 1); 455 | tableProcessor.setTablePoint(0, 2, 1.0/3+1/3+1/6, 0.5, 0.25); 456 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 457 | break; 458 | } 459 | }; 460 | 461 | inline function sShape6(tableProcessor, stage) 462 | { 463 | tableProcessor.reset(0); 464 | 465 | switch (stage) 466 | { 467 | case 0: 468 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 469 | tableProcessor.addTablePoint(0, 1, 1); 470 | tableProcessor.setTablePoint(0, 1, 1/5/2, 0.5, 0.25); 471 | tableProcessor.addTablePoint(0, 1, 1); 472 | tableProcessor.setTablePoint(0, 2, 1/5, 0, 0.75); 473 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 474 | break; 475 | 476 | case 1: 477 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 478 | tableProcessor.addTablePoint(0, 1, 1); 479 | tableProcessor.setTablePoint(0, 1, 1/5/2, 0.5, 0.25); 480 | tableProcessor.addTablePoint(0, 1, 1); 481 | tableProcessor.setTablePoint(0, 2, 1/5, 1, 0.75); 482 | tableProcessor.addTablePoint(0, 1, 1); 483 | tableProcessor.setTablePoint(0, 3, 1/5+1/5/2, 0.5, 0.25); 484 | tableProcessor.addTablePoint(0, 1, 1); 485 | tableProcessor.setTablePoint(0, 4, 1/5*2, 0, 0.75); 486 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 487 | break; 488 | 489 | case 2: 490 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 491 | tableProcessor.addTablePoint(0, 1, 1); 492 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.75); 493 | tableProcessor.addTablePoint(0, 1, 1); 494 | tableProcessor.setTablePoint(0, 2, 1/5+1/5/2, 0.5, 0.25); 495 | tableProcessor.addTablePoint(0, 1, 1); 496 | tableProcessor.setTablePoint(0, 3, 1/5*2, 1, 0.75); 497 | tableProcessor.addTablePoint(0, 1, 1); 498 | tableProcessor.setTablePoint(0, 4, 1/5+1/5+1/10, 0.5, 0.25); 499 | tableProcessor.addTablePoint(0, 1, 1); 500 | tableProcessor.setTablePoint(0, 5, 1/5*3, 0, 0.75); 501 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 502 | break; 503 | 504 | case 3: 505 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 506 | tableProcessor.addTablePoint(0, 1, 1); 507 | tableProcessor.setTablePoint(0, 1, 1/5+1/5, 0, 0.75); 508 | tableProcessor.addTablePoint(0, 1, 1); 509 | tableProcessor.setTablePoint(0, 2, 1/5+1/5+1/5/2, 0.5, 0.25); 510 | tableProcessor.addTablePoint(0, 1, 1); 511 | tableProcessor.setTablePoint(0, 3, 1/5*3, 1, 0.75); 512 | tableProcessor.addTablePoint(0, 1, 1); 513 | tableProcessor.setTablePoint(0, 4, 1/5*3+1/5/2, 0.5, 0.25); 514 | tableProcessor.addTablePoint(0, 1, 1); 515 | tableProcessor.setTablePoint(0, 5, 1/5*4, 0, 0.75); 516 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 517 | break; 518 | 519 | case 4: 520 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 521 | tableProcessor.addTablePoint(0, 1, 1); 522 | tableProcessor.setTablePoint(0, 1, 1/5+1/5+1/5, 0, 0.75); 523 | tableProcessor.addTablePoint(0, 1, 1); 524 | tableProcessor.setTablePoint(0, 2, 1/5+1/5+1/5+1/5/2, 0.5, 0.25); 525 | tableProcessor.addTablePoint(0, 1, 1); 526 | tableProcessor.setTablePoint(0, 3, 1/5*4, 1, 0.75); 527 | tableProcessor.addTablePoint(0, 1, 1); 528 | tableProcessor.setTablePoint(0, 4, 1/5*4+1/5/2, 0.5, 0.25); 529 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 530 | break; 531 | 532 | case 5: 533 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 534 | tableProcessor.addTablePoint(0, 1, 1); 535 | tableProcessor.setTablePoint(0, 1, 1/5*4, 0, 0.75); 536 | tableProcessor.addTablePoint(0, 1, 1); 537 | tableProcessor.setTablePoint(0, 2, 1/5*4+1/5/2, 0.5, 0.25); 538 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 539 | break; 540 | } 541 | }; 542 | 543 | inline function sShape7(tableProcessor, stage) 544 | { 545 | tableProcessor.reset(0); 546 | 547 | switch (stage) 548 | { 549 | case 0: 550 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 551 | tableProcessor.addTablePoint(0, 1, 1); 552 | tableProcessor.setTablePoint(0, 1, 1.0/4/2, 0.5, 0.25); 553 | tableProcessor.addTablePoint(0, 1, 1); 554 | tableProcessor.setTablePoint(0, 2, 1.0/4, 0, 0.75); 555 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 556 | break; 557 | 558 | case 1: 559 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 560 | tableProcessor.addTablePoint(0, 1, 1); 561 | tableProcessor.setTablePoint(0, 1, 1.0/4/2, 0.5, 0.25); 562 | tableProcessor.addTablePoint(0, 1, 1); 563 | tableProcessor.setTablePoint(0, 2, (1.0/4), 1, 0.75); 564 | tableProcessor.addTablePoint(0, 1, 1); 565 | tableProcessor.setTablePoint(0, 3, (1/4+1/4/2), 0.5, 0.25); 566 | tableProcessor.addTablePoint(0, 1, 1); 567 | tableProcessor.setTablePoint(0, 4, (1.0/4)*2, 0, 0.75); 568 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 569 | break; 570 | 571 | case 2: 572 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 573 | tableProcessor.addTablePoint(0, 1, 1); 574 | tableProcessor.setTablePoint(0, 1, (1.0/4/2), 0.0, 0.25); 575 | tableProcessor.addTablePoint(0, 1, 1); 576 | tableProcessor.setTablePoint(0, 2, (1.0/4), 0.5, 0.25); 577 | tableProcessor.addTablePoint(0, 1, 1); 578 | tableProcessor.setTablePoint(0, 3, (1.0/4+1/4/2), 1, 0.75); 579 | tableProcessor.addTablePoint(0, 1, 1); 580 | tableProcessor.setTablePoint(0, 4, ((1.0/4)*2), 0.5, 0.25); 581 | tableProcessor.addTablePoint(0, 1, 1); 582 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8, 0, 0.75); 583 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 584 | break; 585 | 586 | case 3: 587 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 588 | tableProcessor.addTablePoint(0, 1, 1); 589 | tableProcessor.setTablePoint(0, 1, (1.0/4), 0.0, 0.25); 590 | tableProcessor.addTablePoint(0, 1, 1); 591 | tableProcessor.setTablePoint(0, 2, (1.0/4+1/4/2), 0.5, 0.25); 592 | tableProcessor.addTablePoint(0, 1, 1); 593 | tableProcessor.setTablePoint(0, 3, (1.0/4*2), 1, 0.75); 594 | tableProcessor.addTablePoint(0, 1, 1); 595 | tableProcessor.setTablePoint(0, 4, ((1.0/4)*2)+1/8, 0.5, 0.25); 596 | tableProcessor.addTablePoint(0, 1, 1); 597 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*2, 0, 0.75); 598 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 599 | break; 600 | 601 | case 4: 602 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 603 | tableProcessor.addTablePoint(0, 1, 1); 604 | tableProcessor.setTablePoint(0, 1, (1.0/4+1/4/2), 0.0, 0.25); 605 | tableProcessor.addTablePoint(0, 1, 1); 606 | tableProcessor.setTablePoint(0, 2, (1.0/4*2), 0.5, 0.25); 607 | tableProcessor.addTablePoint(0, 1, 1); 608 | tableProcessor.setTablePoint(0, 3, (1.0/4*2)+1/8, 1, 0.75); 609 | tableProcessor.addTablePoint(0, 1, 1); 610 | tableProcessor.setTablePoint(0, 4, (1.0/4*2)+1/8*2, 0.5, 0.25); 611 | tableProcessor.addTablePoint(0, 1, 1); 612 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*3, 0, 0.75); 613 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 614 | break; 615 | 616 | case 5: 617 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 618 | tableProcessor.addTablePoint(0, 1, 1); 619 | tableProcessor.setTablePoint(0, 1, (1.0/4*2), 0.0, 0.25); 620 | tableProcessor.addTablePoint(0, 1, 1); 621 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8, 0.5, 0.25); 622 | tableProcessor.addTablePoint(0, 1, 1); 623 | tableProcessor.setTablePoint(0, 3, (1.0/4*2)+1/8*2, 1, 0.75); 624 | tableProcessor.addTablePoint(0, 1, 1); 625 | tableProcessor.setTablePoint(0, 4, (1.0/4*2)+1/8*3, 0.5, 0.25); 626 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*4, 0, 0.75); 627 | break; 628 | 629 | case 6: 630 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 631 | tableProcessor.addTablePoint(0, 1, 1); 632 | tableProcessor.setTablePoint(0, 1, (1.0/4*2)+1/8*2, 0, 0.75); 633 | tableProcessor.addTablePoint(0, 1, 1); 634 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8*3, 0.5, 0.25); 635 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*4, 1, 0.75); 636 | break; 637 | } 638 | }; 639 | } 640 | -------------------------------------------------------------------------------- /libraries/Toaster.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 David Healey 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this file (Toaster.js), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to permit 10 | persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | namespace Toaster 25 | { 26 | const MODES = {Top: 0, Left: 1, Centre: 2}; 27 | 28 | reg count = 0; 29 | 30 | // pnlToaster 31 | const pnlToaster = Content.getComponent("pnlToaster"); 32 | pnlToaster.showControl(false); 33 | 34 | pnlToaster.setPaintRoutine(function(g) 35 | { 36 | var a = this.getLocalBounds(0); 37 | 38 | g.setColour(Colours.withAlpha(this.get("bgColour"), 0.6)); 39 | 40 | switch (this.data.mode) 41 | { 42 | case MODES.Top: 43 | a = [0, 0, a[2], 50 * this.getValue()]; 44 | g.fillRect(a); 45 | break; 46 | 47 | case MODES.Bottom: 48 | a = [0, a[3] - 50 * this.getValue(), a[2], 55]; 49 | g.fillRect(a); 50 | break; 51 | 52 | case MODES.Centre: 53 | a = this.getLocalBounds(a[2] / 3.0); 54 | g.setColour(Colours.withAlpha(this.get("bgColour"), 0.8 * this.getValue())); 55 | g.fillRoundedRectangle(a, 8); 56 | g.setColour(Colours.withAlpha(this.get("itemColour"), 0.7 * this.getValue())); 57 | g.drawRoundedRectangle(a, 8, 2); 58 | break; 59 | 60 | default: 61 | } 62 | 63 | g.setFont("bold", 20); 64 | g.setColour(Colours.withAlpha(this.get("textColour"), this.getValue())); 65 | g.drawFittedText(this.data.message, a, "centred", 2, 1); 66 | }); 67 | 68 | pnlToaster.setMouseCallback(function(event) 69 | { 70 | if (event.clicked) 71 | count = 100; 72 | }); 73 | 74 | pnlToaster.setTimerCallback(function() 75 | { 76 | if (count < 40 && this.getValue() < 1.0) 77 | this.setValue(this.getValue() + 0.1); 78 | else if (count > 40) 79 | this.setValue(this.getValue() - 0.1); 80 | 81 | count++; 82 | 83 | if (this.getValue() < 0) 84 | this.stopTimer(); 85 | 86 | this.showControl(this.getValue() > 0); 87 | 88 | this.repaint(); 89 | }); 90 | 91 | // Functions 92 | inline function show(mode, msg) 93 | { 94 | pnlToaster.data.mode = mode; 95 | pnlToaster.data.message = msg; 96 | count = 0; 97 | pnlToaster.startTimer(60); 98 | } 99 | 100 | inline function top(msg) 101 | { 102 | show(MODES.Top, msg); 103 | } 104 | 105 | inline function bottom(msg) 106 | { 107 | show(MODES.Bottom, msg); 108 | } 109 | 110 | inline function centre(msg) 111 | { 112 | show(MODES.Centre, msg); 113 | } 114 | 115 | inline function hide() 116 | { 117 | pnlToaster.showControl(false); 118 | } 119 | } -------------------------------------------------------------------------------- /libraries/VuMeter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Original Author: Christoph Hart 3 | Modifications: David Healey 4 | License: Public Domain 5 | */ 6 | namespace VuMeter 7 | { 8 | /** Creates a peak meter. 9 | * 10 | * Usage: Give it a reference to a module (either synth or effect). 11 | * 12 | * It looks best using a width and height with multiple of 4. 13 | * Customize the colours using the scriptPanel colour Ids 14 | */ 15 | inline function createVuMeter(name) 16 | { 17 | local widget = Content.getComponent(name); 18 | 19 | Content.setPropertiesFromJSON(name, {"saveInPreset": false, "opaque": 1}); 20 | 21 | widget.setPaintRoutine(function(g) 22 | { 23 | g.fillAll(this.get("bgColour")); 24 | 25 | g.setColour(this.get("itemColour")); 26 | 27 | var lsize = parseInt(this.data.lvalue * (this.getHeight()-4)); 28 | var rsize = parseInt(this.data.rvalue * (this.getHeight()-4)); 29 | 30 | g.fillRect([2, this.getHeight() - lsize - 2, (this.getWidth()-4)/2-1, lsize]); 31 | g.fillRect([2 + this.getWidth() / 2 - 1, this.getHeight() - rsize - 2, (this.getWidth()-4)/2-1, rsize]); 32 | 33 | g.setColour(this.get("itemColour2")); 34 | 35 | for(i = 1; i < this.getHeight()-1; i = i + 3) 36 | g.fillRect([1, i, this.getWidth()-2, 1]); 37 | }); 38 | 39 | widget.setTimerCallback(function() 40 | { 41 | var lvalue; 42 | var rvalue; 43 | 44 | if(this.data.fx) 45 | { 46 | lvalue = getNormalizedPeakValue(this.data.fx.getCurrentLevel(0)); 47 | rvalue = getNormalizedPeakValue(this.data.fx.getCurrentLevel(1)); 48 | } 49 | else 50 | { 51 | lvalue = getNormalizedPeakValue(Engine.getMasterPeakLevel(0)); 52 | rvalue = getNormalizedPeakValue(Engine.getMasterPeakLevel(1)); 53 | } 54 | 55 | this.data.lvalue = Math.max(lvalue, this.data.lvalue - 0.04); 56 | this.data.rvalue = Math.max(rvalue, this.data.rvalue - 0.04); 57 | 58 | this.repaintImmediately(); 59 | }); 60 | 61 | widget.startTimer(30); 62 | 63 | return widget; 64 | }; 65 | 66 | inline function setModule(vuMeter, module) 67 | { 68 | vuMeter.data.fx = module; 69 | } 70 | 71 | inline function getNormalizedPeakValue(gain) 72 | { 73 | return 0.01 * (100.0 + Engine.getDecibelsForGainFactor(gain)); 74 | } 75 | } -------------------------------------------------------------------------------- /libraries/l10n.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | 18 | namespace l10n 19 | { 20 | const systemInfo = Engine.getSystemStats(); 21 | 22 | //Console.print(trace(systemInfo)); 23 | 24 | // #TODO 25 | inline function get(input) 26 | { 27 | return input; 28 | } 29 | } -------------------------------------------------------------------------------- /modules/ArticulationSwitcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: Articulation Switcher.js 3 | * Author: David Healey 4 | * Date: 30/06/2017 5 | * Modified: 20/07/2017 6 | * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html 7 | */ 8 | 9 | Content.setWidth(650); 10 | Content.setHeight(50); 11 | 12 | const var UACC_NUM = 32; 13 | 14 | // Create a storage panel, hide it, and save it in the preset 15 | const var storagePanel = Content.addPanel("storagePanel", 0, 0); 16 | storagePanel.set("visible", false); 17 | storagePanel.set("saveInPreset", true); 18 | 19 | // Create object that will hold the preset values. 20 | var data = {channels:[], ks:[], uacc:[]}; 21 | 22 | // Set the storage as widget value for the hidden panel. 23 | // Important: this will not clone the object but share a reference! 24 | storagePanel.setValue(data); 25 | 26 | const var muterList = Synth.getIdList("MidiMuter"); //Get MIDI Muter IDs 27 | const var filterList = Synth.getIdList("MIDI Channel Filter"); //Get MIDI Channel filter IDs 28 | const var containerList = Synth.getIdList("Container"); //Get containers ids 29 | const var articulations = []; //IDs of containers that have a midi muter whose ID includes the container name 30 | 31 | reg muters = []; //MIDI Muter modules - every articulation must have a MIDI muter with an ID that includes its container's ID 32 | reg filters = []; //MIDI Channel Filters - either every articulation must have one or none of them, other combinations may lead to strange results. 33 | reg current; //Current articulation/container 34 | 35 | //GUI 36 | const var cmbContainers = Content.addComboBox("Containers", 0, 0); 37 | const var cmbChan = Content.addComboBox("Channel", 150, 0); 38 | const var cmbKs = Content.addComboBox("Key Switch", 300, 0); 39 | const var cmbUacc = Content.addComboBox("UACC", 450, 0); 40 | 41 | //Populate cmbContainers with the names of container that have midiMuters 42 | for (i = 0; i < containerList.length; i++) //Each container 43 | { 44 | for (m in muterList) //Each MIDI Muter 45 | { 46 | if (m.indexOf(containerList[i]) == -1) continue; //Skip MIDI muter if it does not have the container name as part of its ID 47 | muters.push(Synth.getMidiProcessor(m)); //Add MIDI Muter to muters object 48 | articulations.push(containerList[i]); //Add container ID to array of articulation names 49 | cmbContainers.addItem(containerList[i]); //Populate container drop down 50 | } 51 | } 52 | 53 | //If the channel filters list was populated find the correct filter for each articulation 54 | if (filterList.length > 0) 55 | { 56 | for (i = 0; i < articulations.length; i++) //Each articulation (containers with a MIDI muter that have the include the container ID in their ID) 57 | { 58 | for (f in filterList) //Each channel filter 59 | { 60 | if (f.indexOf(articulations[i]) == -1) continue; //Skip filter if it does not have the articulation name as part of its ID 61 | filters.push(Synth.getMidiProcessor(f)); //Add channel filter for this articulation/container to the filters object 62 | } 63 | } 64 | } 65 | 66 | //Populate channel dropdwon 67 | for (i = 0; i < 17; i++) 68 | { 69 | i == 0 ? cmbChan.addItem("Omni") : cmbChan.addItem(i); 70 | } 71 | 72 | //Populate KS and UACC dropdownsContent.setHeight(50); 73 | for (i = 0; i < 128; i++) 74 | { 75 | cmbKs.addItem(Engine.getMidiNoteName(i)); //Populate KS dropdown 76 | i == 0 ? cmbUacc.addItem("Disabled") : cmbUacc.addItem(i); //Populate UACC dropdown 77 | } 78 | 79 | inline function displayCurrentSettings() 80 | { 81 | cmbContainers.setValue(current+1); 82 | if (data.channels[current] != undefined) cmbChan.setValue(data.channels[current]+1); 83 | if (data.ks[current] != undefined) cmbKs.setValue(data.ks[current]+1); 84 | if (data.uacc[current] != undefined) cmbUacc.setValue(data.uacc[current]+1); 85 | } 86 | 87 | function onNoteOn() 88 | { 89 | if (data.ks.contains(Message.getNoteNumber())) //A KS triggered callback 90 | { 91 | //Mute all articulations by default 92 | for (m in muters) 93 | { 94 | m.setAttribute(0, 1); 95 | } 96 | current = data.ks.indexOf(Message.getNoteNumber()); 97 | muters[current].setAttribute(0, 0); //Unmute 98 | displayCurrentSettings(); 99 | } 100 | } 101 | 102 | function onNoteOff() 103 | { 104 | 105 | } 106 | function onController() 107 | { 108 | if (Message.getControllerNumber() == UACC_NUM && Message.getControllerValue() > 0) //UACC 109 | { 110 | if (data.uacc.contains(Message.getControllerValue())) //Triggered UACC value is assigned to an articulation 111 | { 112 | //Mute all articulations by default 113 | for (m in muters) 114 | { 115 | m.setAttribute(0, 1); 116 | } 117 | current = data.ks.indexOf(Message.getControllerValue()); 118 | muters[current].setAttribute(0, 0); //Unmute 119 | displayCurrentSettings(); 120 | } 121 | } 122 | } 123 | 124 | function onTimer() 125 | { 126 | 127 | } 128 | function onControl(number, value) 129 | { 130 | switch(number) 131 | { 132 | case storagePanel: 133 | if (typeof storagePanel.getValue() == "object") //If the data object has been stored 134 | { 135 | data = storagePanel.getValue(); //Restore data from panel 136 | } 137 | break; 138 | 139 | case cmbContainers: 140 | current = value-1; //Currently selected articulation 141 | displayCurrentSettings(); 142 | break; 143 | 144 | case cmbChan: 145 | if (filters.length > 0) 146 | { 147 | if (value == 1) //Omni 148 | { 149 | filters[current].setBypassed(true); 150 | } 151 | else 152 | { 153 | filters[current].setBypassed(false); 154 | filters[current].setAttribute(0, value-1); 155 | } 156 | data.channels[current] = value-1; 157 | } 158 | break; 159 | 160 | case cmbKs: 161 | data.ks[current] = value-1; 162 | break; 163 | 164 | case cmbUacc: 165 | data.uacc[current] = value-1; 166 | break; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /modules/AttackOverlayHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | reg id; 18 | 19 | const var btnMode = Content.addButton("btnMode", 10, 10); 20 | btnMode.set("text", "Fade Out"); 21 | btnMode.set("tooltip", "When enabled notes will fade out rather than fade in."); 22 | 23 | const var knbLength = Content.addKnob("knbLength", 150, 0); 24 | knbLength.set("text", "Length"); 25 | knbLength.set("mode", "Time"); 26 | knbLength.set("middlePosition", 500); 27 | knbLength.setRange(5, 1000, 1); 28 | 29 | const var knbVelo = Content.addKnob("knbVelo", 300, 0); 30 | knbVelo.set("text", "Velocity Filter"); 31 | knbVelo.setRange(0, 127, 1); 32 | 33 | const var btnLegato = Content.addButton("btnLegato", 450, 10); 34 | btnLegato.set("text", "Legato");function onNoteOn() 35 | { 36 | if ((!btnLegato.getValue() || g_keys <= 1) && g_realVelocity >= knbVelo.getValue()) 37 | { 38 | if (btnMode.getValue()) //Fade out 39 | { 40 | id = Message.makeArtificial(); 41 | Synth.addVolumeFade(id, knbLength.getValue(), -100); 42 | } 43 | else //Fade in 44 | { 45 | Synth.addVolumeFade(Message.getEventId(), 0, -99); 46 | Synth.addVolumeFade(Message.getEventId(), knbLength.getValue(), 0); 47 | } 48 | } 49 | } 50 | function onNoteOff() 51 | { 52 | 53 | } 54 | function onController() 55 | { 56 | 57 | } 58 | function onTimer() 59 | { 60 | 61 | } 62 | function onControl(number, value) 63 | { 64 | 65 | } 66 | -------------------------------------------------------------------------------- /modules/BasicNonRepeatingRoundRobin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: basicRoundRobin.js 3 | * Author: David Healey 4 | * Date: 02/01/2018 5 | * License: GPL V3.0 6 | */ 7 | 8 | Content.setHeight(50); 9 | Content.setWidth(650); 10 | 11 | const var samplerIds = Synth.getIdList("Sampler"); 12 | const var childSamplers = []; 13 | reg rr = -1; 14 | reg lastRR = -1; //RR step number used for the last onNote 15 | reg lastTime = 0; //Track how long between notes 16 | 17 | const var btnBypass = Content.addButton("Bypass", 0, 10); //Script bypass 18 | const var btnRandom = Content.addButton("Random", 150, 10); //Random mode button 19 | const var knbGroups = Content.addKnob("Groups", 300, 0); //Number of groups 20 | knbGroups.setRange(0, 100, 1); 21 | 22 | for (id in samplerIds) 23 | { 24 | childSamplers.push(Synth.getSampler(id)); //Get child sampler 25 | childSamplers[childSamplers.length-1].enableRoundRobin(false); //Disable default RR behaviour 26 | }function onNoteOn() 27 | { 28 | if (!btnBypass.getValue()) //Not bypassed and note in range 29 | { 30 | if (Engine.getUptime() - lastTime > 2) //More than 2 seconds between notes 31 | { 32 | rr = -1; //Reset RR counter 33 | } 34 | else 35 | { 36 | //Random RR 37 | if (btnRandom.getValue()) 38 | { 39 | rr = Math.randInt(0, parseInt(knbGroups.getValue())); 40 | } 41 | 42 | //Random is disabled, or the randomly generated RR is the same as the last RR 43 | if (!btnRandom.getValue() || lastRR == rr) 44 | { 45 | rr = (rr + 1) % parseInt(knbGroups.getValue()); 46 | } 47 | } 48 | 49 | lastRR = rr; //Make a note of the RR number for next time 50 | lastTime = Engine.getUptime(); 51 | 52 | for (s in childSamplers) 53 | { 54 | s.setActiveGroup(rr+1); 55 | } 56 | } 57 | }function onNoteOff() 58 | { 59 | 60 | } 61 | function onController() 62 | { 63 | 64 | } 65 | function onTimer() 66 | { 67 | 68 | } 69 | function onControl(number, value) 70 | { 71 | 72 | } 73 | -------------------------------------------------------------------------------- /modules/BorrowedSampleRoundRobin.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018, 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | Content.setWidth(730); 18 | Content.setHeight(50); 19 | 20 | reg lastTime = Engine.createMidiList(); //Track how long between notes 21 | lastTime.fill(0); 22 | reg lastRR = Engine.createMidiList(); //RR step number used for the last onNote 23 | 24 | //Knobs used to set instrument's playable range 25 | const var knbLowNote = Content.addKnob("knbLowNote", 0, 0); //Lowest playable note 26 | knbLowNote.set("text", "Low Note"); 27 | knbLowNote.setRange(0, 127, 1); 28 | 29 | const var knbHighNote = Content.addKnob("knbHighNote", 150, 0); //Highest playable note 30 | knbHighNote.setRange(0, 127, 1); 31 | knbHighNote.set("text", "High Note"); 32 | knbHighNote.set("defaultValue", 127); 33 | 34 | const var knbReset = Content.addKnob("knbResetTime", 300, 0); //Time in seconds until RR reset 35 | knbReset.setRange(1, 60, 1); 36 | knbReset.set("text", "Reset Time"); 37 | knbReset.set("defaultValue", 3); 38 | 39 | const var knbFine = Content.addKnob("knbFineTune", 450, 0); 40 | knbFine.setRange(0, 50, 1); 41 | knbFine.set("text", "Fine Tune"); 42 | knbFine.set("defaultValue", 0); 43 | 44 | const var knbGain = Content.addKnob("knbGain", 600, 0); 45 | knbGain.setRange(0, 3, 1); 46 | knbGain.set("mode", "Decibel"); 47 | knbGain.set("text", "knbGain"); 48 | knbGain.set("defaultValue", 0);function onNoteOn() 49 | { 50 | local n = Message.getNoteNumber(); 51 | 52 | //Not legato and in playable range 53 | if (n >= knbLowNote.getValue() && n <= knbHighNote.getValue()) 54 | { 55 | local v = lastRR.getValue(n); //Transpose value 56 | 57 | if (Engine.getUptime() - lastTime.getValue(n) > knbReset.getValue()) //Reset time between notes 58 | { 59 | v = 1; //Reset RR counter 60 | } 61 | else 62 | { 63 | local range = [0, 3]; 64 | 65 | if (n == knbHighNote.getValue()) 66 | { 67 | range[0] = 1; 68 | range[1] = 3; 69 | } 70 | else if (n == knbLowNote.getValue()) 71 | { 72 | range[0] = 0; 73 | range[1] = 2; 74 | } 75 | 76 | v = Math.randInt(range[0], range[1]); 77 | 78 | //Generated RR is the same as the last RR 79 | if (lastRR.getValue(n) == v) 80 | { 81 | v = v + 1; 82 | if (v > range[1]-1) v = range[0]; 83 | if (v < range[0]) v = range[1]; 84 | } 85 | } 86 | 87 | lastRR.setValue(n, v); //Record the RR tranpose value for n 88 | lastTime.setValue(n, Engine.getUptime()); 89 | 90 | Message.setTransposeAmount(1-v); 91 | Message.setCoarseDetune(Message.getCoarseDetune()+-(1-v)); 92 | } 93 | 94 | if (knbFine.getValue() > 0) 95 | Message.setFineDetune(Message.getFineDetune() + Math.randInt(-knbFine.getValue(), knbFine.getValue()+1)); 96 | 97 | if (knbGain.getValue() > 0) 98 | Message.setGain(Math.randInt(-knbGain.getValue(), knbGain.getValue()+1)); 99 | }function onNoteOff() 100 | { 101 | if (knbFine.getValue() > 0) 102 | Message.setFineDetune(Message.getFineDetune() + Math.randInt(-knbFine.getValue(), knbFine.getValue()+1)); 103 | 104 | if (knbGain.getValue() > 0) 105 | Message.setGain(Math.randInt(-knbGain.getValue(), knbGain.getValue()+1)); 106 | } 107 | function onController() 108 | { 109 | 110 | } 111 | function onTimer() 112 | { 113 | 114 | } 115 | function onControl(number, value) 116 | { 117 | 118 | } 119 | -------------------------------------------------------------------------------- /modules/BreathControl.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | 18 | Content.setWidth(600); 19 | Content.setHeight(50); 20 | 21 | const var velo = Engine.createMidiList(); //Note on velocities 22 | const var ids = Engine.createMidiList(); //Note Ids 23 | ids.fill(false); 24 | 25 | reg lastLevel = 0; //Last breath level 26 | reg lastTime = 0; 27 | 28 | //GUI 29 | const var btnBypass = Content.addButton("btnBypass", 10, 10); 30 | btnBypass.set("text", "Bypass"); 31 | 32 | const var knbThreshold = Content.addKnob("knbThreshold", 160, 0); 33 | knbThreshold.set("text", "Threshold"); 34 | knbThreshold.set("defaultValue", 10); 35 | knbThreshold.setRange(0, 127, 1); 36 | 37 | const var knbLevel = Content.addKnob("knbLevel", 310, 0); 38 | knbLevel.set("text", "Level"); 39 | knbLevel.setRange(0, 127, 1); 40 | knbLevel.setControlCallback(onknbLevelControl); 41 | 42 | const var btnVelMode = Content.addButton("btnVelMode", 460, 10) 43 | btnVelMode.set("text", "Speed = Velocity"); 44 | 45 | inline function onknbLevelControl(component, value) 46 | { 47 | if (!btnBypass.getValue()) 48 | { 49 | local threshold = knbThreshold.getValue(); 50 | local velocity = 1; 51 | 52 | //If btnVelMode enabled calculate the velocity 53 | if (btnVelMode.getValue()) 54 | velocity = getVelocity(); 55 | 56 | //Going up but haven't reached threshold, update lastTime 57 | if (lastTime == 0 && value < threshold && value > lastLevel) 58 | lastTime = Engine.getUptime(); 59 | 60 | for (i = 0; i < 127; i++) 61 | { 62 | if (Synth.isKeyDown(i)) 63 | { 64 | if (value >= threshold && lastLevel < threshold) 65 | { 66 | //Turn off existing note (if any) 67 | if (ids.getValue(i) != false) 68 | Synth.noteOffByEventId(ids.getValue(i)); 69 | 70 | //Get velocity for note on if btnVelMode disabled 71 | if (!btnVelMode.getValue()) 72 | velocity = velo.getValue(i); 73 | 74 | //Play new note 75 | ids.setValue(i, Synth.playNote(i, velocity)); 76 | } 77 | else if (value < threshold && ids.getValue(i) != false) 78 | { 79 | //Turn off note 80 | Synth.noteOffByEventId(ids.getValue(i)); 81 | ids.setValue(i, false); 82 | lastTime = 0; 83 | } 84 | } 85 | } 86 | 87 | //Update lastLevel value 88 | lastLevel = value; 89 | } 90 | }; 91 | 92 | //Calculate the velocity based on the breath speed - up to 1 second window 93 | inline function getVelocity() 94 | { 95 | local timeDiff = Math.min(1, Engine.getUptime()-lastTime) / 1; //Limit timeDiff to 0-1 seconds 96 | local v = Math.pow(timeDiff, 0.3); //Skew value 97 | return parseInt(127-(v*117)); 98 | }function onNoteOn() 99 | { 100 | if (!btnBypass.getValue()) 101 | { 102 | if (knbLevel.getValue() < knbThreshold.getValue()) 103 | Message.ignoreEvent(true); 104 | else 105 | { 106 | if (ids.getValue(Message.getNoteNumber()) != false) 107 | Synth.noteOffByEventId(ids.getValue(Message.getNoteNumber())); 108 | 109 | ids.setValue(Message.getNoteNumber(), Message.makeArtificial()); 110 | } 111 | 112 | velo.setValue(Message.getNoteNumber(), Message.getVelocity()); 113 | } 114 | } 115 | function onNoteOff() 116 | { 117 | if (ids.getValue(Message.getNoteNumber()) != false) 118 | { 119 | Synth.noteOffByEventId(ids.getValue(Message.getNoteNumber())); 120 | ids.setValue(Message.getNoteNumber(), false); 121 | lastTime = 0; 122 | } 123 | } 124 | function onController() 125 | { 126 | 127 | } 128 | function onTimer() 129 | { 130 | 131 | } 132 | function onControl(number, value) 133 | { 134 | 135 | } -------------------------------------------------------------------------------- /modules/Cc64Retrigger.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | //INIT 19 | Content.setWidth(650); 20 | 21 | const var eventId = Engine.createMidiList(); 22 | eventId.fill(-99); 23 | 24 | const var heldKeys = []; 25 | 26 | const var btnMute = Content.addButton("Mute", 10, 10); 27 | const var velocities = Engine.createMidiList(); 28 | 29 | const var async = Engine.createTimerObject(); 30 | async.setTimerCallback(function(){ 31 | 32 | for (i = 0; i < 127; i++) 33 | { 34 | if (eventId.getValue(i) != -99 && heldKeys.indexOf(i) == -1) 35 | { 36 | Synth.noteOffByEventId(eventId.getValue(i)); 37 | eventId.setValue(i, -99); 38 | } 39 | } 40 | 41 | async.stopTimer(); 42 | });function onNoteOn() 43 | { 44 | if (!btnMute.getValue() && Synth.isSustainPedalDown()) 45 | { 46 | local n = Message.getNoteNumber(); 47 | 48 | //Turn off retriggered note if there is one 49 | if (eventId.getValue(n) != -99) 50 | { 51 | Synth.noteOffByEventId(eventId.getValue(n)); 52 | eventId.setValue(n, -99); 53 | } 54 | 55 | velocities.setValue(n, Message.getVelocity()); 56 | heldKeys.push(n); 57 | async.startTimer(20); //Turn off old notes 58 | } 59 | }function onNoteOff() 60 | { 61 | if (!btnMute.getValue()) 62 | { 63 | local n = Message.getNoteNumber(); 64 | 65 | if (Synth.isSustainPedalDown() && velocities.getValue(n) > 0) 66 | { 67 | if (eventId.getValue(n) != -99) 68 | { 69 | Synth.noteOffByEventId(eventId.getValue(n)); 70 | eventId.setValue(n, -99); 71 | } 72 | 73 | eventId.setValue(n, Synth.playNote(n, velocities.getValue(n))); 74 | heldKeys.remove(n); 75 | } 76 | } 77 | }function onController() 78 | { 79 | if (Message.getControllerNumber() == 64) 80 | { 81 | Message.ignoreEvent(true); 82 | 83 | if (!Synth.isSustainPedalDown()) 84 | { 85 | Engine.allNotesOff(); 86 | eventId.fill(-99); 87 | } 88 | } 89 | }function onTimer() 90 | { 91 | 92 | } 93 | function onControl(number, value) 94 | { 95 | 96 | } 97 | -------------------------------------------------------------------------------- /modules/ChordIdentifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | 18 | namespace ChordDictionary 19 | { 20 | const var patterns = [ 21 | [0, 4, 7], //Major 22 | [0, 4, 7, 9], //Major 6th 23 | [0, 2, 4, 7, 9], //Major 6 add9 24 | [0, 4, 7, 11], //Major 7th 25 | [0, 4, 6, 7, 11], //Major 7#11 26 | [0, 2, 4, 7, 11], //Major 9th 27 | [0, 2, 4, 5, 7, 11], //Major 11th 28 | [0, 2, 4, 6, 7, 9, 11], //Major 13th 29 | [0, 2, 4, 6, 9, 11], //Major 13th #11 30 | [0, 3, 7], //Minor 31 | [0, 3, 7, 9], //"Minor 6th" 32 | [0, 2, 3, 7, 9], //Minor 6 add9 33 | [0, 3, 7, 10], //Minor 7th 34 | [0, 4, 6, 8, 11], //Minor 7#11 35 | [0, 2, 3, 7, 10], //Minor 9th 36 | [0, 2, 3, 5, 7, 10], //Minor 11th 37 | [0, 2, 3, 5, 7, 9, 10], //Minor 13th 38 | [0, 3, 7, 11], //mM7 39 | [0, 3, 6], //Diminished 40 | [0, 3, 6, 9], //Diminished 7th 41 | [0, 3, 6, 11], //Diminished Major 7th 42 | [0, 3, 6, 10], //Half diminished 7th 43 | [0, 4, 7, 10], //Dominant 7th 44 | [0, 2, 7], //Sus2 45 | [0, 5, 7], //Sus4 46 | [0, 4, 7, 10], //Harmonic 7th 47 | [0, 4, 8], //Augmented 48 | [0, 6, 10], //Augmented 6th v1 49 | [0, 4, 6, 10], //Augmented 6th v2 50 | [0, 4, 7, 10], //Augmented 6th v3 51 | [0, 4, 8, 10], //Augmented 7th 52 | [0, 2, 4, 6, 7, 10], //Augmented 11th 53 | [0, 4, 8, 11], //Augmented major seventh chord 54 | [0, 2, 4, 7, 10], //Dominant 9th 55 | [0, 1, 3, 5, 6, 10], //Magic chord 56 | [0, 2, 4, 7], //Mu Chord 57 | [0, 2, 4, 6, 9, 10], //Mystic chord 58 | [1, 5, 8], //Neapolitan chord 59 | [0, 2, 4, 8, 10], //9th Augmented 5th 60 | [0, 2, 4, 6, 10], //9th flat 5th 61 | [0, 1, 4, 6, 7, 10], //Petrushka chord 62 | [0, 7], //Power chord 63 | [0, 4, 7, 9, 10], //7 6 Chord 64 | [0, 5, 7, 10], //7th sus4 65 | [0, 3, 5, 7, 10], //So What 66 | [0, 3, 6, 10], //Tristan 67 | [0, 1, 6], //Viennese trichord 1 68 | [0, 6, 7], //Viennese trichord 2 69 | [0, 5, 6, 7], //Dream chord 70 | [0, 1, 4, 7, 9], //Elektra 71 | [0, 4, 8, 9, 11], //Farben 72 | ]; 73 | 74 | const var names = [ 75 | "Major", 76 | "Major 6th", 77 | "Major 6 add9", 78 | "Major 7th", 79 | "Major 7#11", 80 | "Major 9th", 81 | "Major 11th", 82 | "Major 13th", 83 | "Major 13th #11", 84 | "Minor", 85 | "Minor 6th", 86 | "Minor 6 add9", 87 | "Minor 7th", 88 | "Minor 7#11", 89 | "Minor 9th", 90 | "Minor 11th", 91 | "Minor 13th", 92 | "mM7", 93 | "Dimished", 94 | "Dimished 7th", 95 | "Diminished Major 7th", 96 | "Half Diminished 7th", 97 | "Dominant 7th", 98 | "Sus2", 99 | "Sus4", 100 | "Harmonic 7th", 101 | "Augmented", 102 | "Augmented 6th", //v1 103 | "Augmented 6th", //v2 104 | "Augmented 6th", //v3 105 | "Augmented 7th", 106 | "Augmented 11th", 107 | "Augmented major seventh chord", 108 | "Dominant 9th", 109 | "Magic Chord", 110 | "Mu", 111 | "Mystic", 112 | "Neapolitan", 113 | "9th Augmented 5th", 114 | "9th flat 5", 115 | "Petrushka", 116 | "Power Chord", 117 | "7/6", 118 | "7th Sus4", 119 | "So What", 120 | "Tristan", 121 | "Viennese trichord", //v1 122 | "Viennese trichord", //v2 123 | "Dream", 124 | "Elektra", 125 | "Farben" 126 | ]; 127 | } 128 | 129 | const var noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; 130 | 131 | reg heldKeys = []; 132 | 133 | inline function determineChord(data) 134 | { 135 | local notes = []; //Holds sorted data 136 | local search = []; //Holds pattern to search for in the dictionary 137 | local chordRoot; 138 | local chordName = ""; 139 | 140 | //Sort the data and remove duplicates 141 | for ( i = 0; i < data.length; i++) 142 | { 143 | if (data[i] != undefined && !notes.contains(data[i] % 12)) 144 | notes.push(data[i] % 12); 145 | } 146 | 147 | notes.sort(); 148 | 149 | //Seach dictionary for match to notes array 150 | //Shuffle array if no match is found until all choices have been tried. 151 | for (i = 0; i < notes.length; i++) 152 | { 153 | for (j = 0; j < notes.length; j++) 154 | { 155 | chordRoot = notes[i]; //Try next note as root 156 | search[j] = (notes[j] + ((j < i) * 12)) - chordRoot; 157 | } 158 | 159 | search.sort(); 160 | 161 | //Check if the search pattern is present in the dictionary 162 | local result = ChordDictionary.patterns.indexOf(search); 163 | 164 | //Exit loop if search match entry in chord dictionary 165 | if (result != -1) 166 | { 167 | local chordType = ChordDictionary.patterns[result]; 168 | chordName = noteNames[chordRoot] + " " + ChordDictionary.names[result]; 169 | break; 170 | } 171 | else //No matching chord in dictionary 172 | chordName = "---"; 173 | } 174 | Console.print(chordName); 175 | }function onNoteOn() 176 | { 177 | heldKeys.push(Message.getNoteNumber()); //Add this key to end of the heldKeys array 178 | 179 | if (heldKeys.length > 2) 180 | determineChord(heldKeys); 181 | } 182 | function onNoteOff() 183 | { 184 | if (Synth.getNumPressedKeys() == 0) //All keys up 185 | heldKeys.clear(); //Reset array 186 | else 187 | { 188 | local index = heldKeys.indexOf(Message.getNoteNumber()); //Get index of released key 189 | heldKeys.remove(heldKeys[index]); //Remove matching element from the heldKeys array 190 | 191 | //Check for chord based on updated heldKeys array 192 | if (heldKeys.length > 2) 193 | determineChord(heldKeys); 194 | } 195 | } 196 | function onController() 197 | { 198 | 199 | } 200 | function onTimer() 201 | { 202 | 203 | } 204 | function onControl(number, value) 205 | { 206 | 207 | } 208 | -------------------------------------------------------------------------------- /modules/ColourPicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: colourPicker.js 3 | * Author: David Healey 4 | * Date: 07/09/2017 5 | * Modified: 07/09/2017 6 | * License: Public Domain 7 | */ 8 | 9 | Content.setWidht(650); 10 | Content.setHeight(175); 11 | 12 | reg colour = []; 13 | reg colourString; 14 | 15 | const var alpha = Content.addKnob("Alpha", 0, 0); 16 | alpha.setRange(0, 255, 1); 17 | alpha.set("style", "Vertical"); 18 | alpha.set("itemColour", 0xFFAAAAAA); 19 | alpha.set("bgColour", 0xFF000000); 20 | const var red = Content.addKnob("Red", 150, 0); 21 | red.setRange(0, 255, 1); 22 | red.set("style", "Vertical"); 23 | red.set("itemColour", 0xFFFF0000); 24 | red.set("bgColour", 0xFF000000); 25 | const var green = Content.addKnob("Green", 300, 0); 26 | green.setRange(0, 255, 1); 27 | green.set("itemColour", 0xFF64FF4E); 28 | green.set("bgColour", 0xFF000000); 29 | green.set("style", "Vertical"); 30 | const var blue = Content.addKnob("Blue", 450, 0); 31 | blue.setRange(0, 255, 1); 32 | blue.set("style", "Vertical"); 33 | blue.set("itemColour", 0xFF1C00FF); 34 | blue.set("bgColour", 0xFF000000); 35 | 36 | const var code = Content.addLabel("code", 600, 10); 37 | code.set("bgColour", 0xFF000000); 38 | 39 | const var pnlColour = Content.addPanel("pnlColour", 0, 50); 40 | pnlColour.set("width", 700); 41 | pnlColour.set("height", 100); 42 | pnlColour.setPaintRoutine(function(g){g.fillAll(parseInt(colourString));}); 43 | 44 | inline function convertToHex(v) 45 | { 46 | reg hexTable = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; 47 | reg d1 = hexTable[Math.floor(v/16)]; 48 | reg d2 = hexTable[Math.floor(v % 16)]; 49 | 50 | return d1 + d2; 51 | } 52 | 53 | inline function updateCode() 54 | { 55 | code.set("text", "0x" + colour[0] + colour[1] + colour[2] + colour[3]); 56 | colourString = "0x" + colour[0] + colour[1] + colour[2] + colour[3]; 57 | }function onNoteOn() 58 | { 59 | 60 | } 61 | function onNoteOff() 62 | { 63 | 64 | } 65 | function onController() 66 | { 67 | 68 | } 69 | function onTimer() 70 | { 71 | 72 | } 73 | function onControl(number, value) 74 | { 75 | switch (number) 76 | { 77 | case alpha: 78 | colour[0] = convertToHex(parseInt(value)); 79 | updateCode(); 80 | pnlColour.repaintImmediately(); 81 | break; 82 | 83 | case red: 84 | colour[1] = convertToHex(parseInt(value)); 85 | updateCode(); 86 | pnlColour.repaintImmediately(); 87 | break; 88 | 89 | case green: 90 | colour[2] = convertToHex(parseInt(value)); 91 | updateCode(); 92 | pnlColour.repaintImmediately(); 93 | break; 94 | 95 | case blue: 96 | colour[3] = convertToHex(parseInt(value)); 97 | updateCode(); 98 | pnlColour.repaintImmediately(); 99 | break; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /modules/ControllerToVelocity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: controllerToVelocity.js 3 | * Author: David Healey 4 | * Date: 30/06/2017 5 | * Modified: 30/06/2017 6 | * License: Public Domain 7 | */ 8 | 9 | Content.setWidth(650); 10 | Content.setHeight(50); 11 | 12 | const var knbCC = Content.addKnob("CC Number", 0, 0); 13 | knbCC.setRange(1, 127, 1); 14 | 15 | reg lastCCValue = 1; 16 | 17 | 18 | function onNoteOn() 19 | { 20 | Message.setVelocity(lastCCValue); 21 | } 22 | 23 | function onNoteOff() 24 | { 25 | } 26 | 27 | function onController() 28 | { 29 | if (Message.getControllerNumber() == knbCC.getValue()) 30 | { 31 | lastCCValue = Math.max(1, Message.getControllerValue()); 32 | } 33 | } 34 | 35 | function onTimer() 36 | { 37 | } 38 | 39 | function onControl(number, value) 40 | { 41 | } 42 | -------------------------------------------------------------------------------- /modules/DynamicsXfadeTables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: dynamicXFadeTables.js 3 | * Author: David Healey 4 | * Date: 07/09/2017 5 | * Modified: 07/09/2017 6 | * License: Public Domain 7 | */ 8 | 9 | Content.setWidth(650); 10 | Content.setHeight(50); 11 | 12 | const var tableEnvelopes = Synth.getIdList("Midi Controller"); 13 | const var modulators = []; 14 | 15 | for (env in tableEnvelopes) 16 | { 17 | modulators.push(Synth.getTableProcessor(env)); 18 | } 19 | 20 | //---GUI 21 | const var cmbType = Content.addComboBox("Type", 0, 0); 22 | cmbType.set("items", ["Equal Power", "S-Shaped"].join("\n")); 23 | 24 | const var cmbCount = Content.addComboBox("Count", 150, 0); 25 | cmbCount.set("items", ["2", "3", "4", "5", "6", "7"].join("\n")); 26 | 27 | const var btnApply = Content.addButton("Apply", 300, 0); 28 | 29 | //FUNCTIONS 30 | inline function equalPower2(tableProcessor, stage) 31 | { 32 | tableProcessor.reset(0); 33 | 34 | switch (stage) 35 | { 36 | case 0: 37 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 38 | tableProcessor.setTablePoint(0, 1, 1, 0, 0.25); 39 | break; 40 | 41 | case 1: 42 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 43 | tableProcessor.setTablePoint(0, 1, 1, 1, 0.75); 44 | break; 45 | } 46 | }; 47 | 48 | inline function equalPower3(tableProcessor, stage) 49 | { 50 | tableProcessor.reset(0); 51 | 52 | switch (stage) 53 | { 54 | case 0: 55 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 56 | tableProcessor.addTablePoint(0, 1, 1); 57 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0, 0.25); 58 | tableProcessor.setTablePoint(0, 2, 1, 0, 0); 59 | break; 60 | 61 | case 1: 62 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 63 | tableProcessor.addTablePoint(0, 1, 1); 64 | tableProcessor.setTablePoint(0, 1, 1/2, 1, 0.75); 65 | tableProcessor.setTablePoint(0, 2, 1, 0, 0.25); 66 | break; 67 | 68 | case 2: 69 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 70 | tableProcessor.addTablePoint(0, 1, 1); 71 | tableProcessor.setTablePoint(0, 1, 1/2, 0, 1); 72 | tableProcessor.setTablePoint(0, 2, 1, 1, 0.75); 73 | break; 74 | } 75 | }; 76 | 77 | inline function equalPower4(tableProcessor, stage) 78 | { 79 | tableProcessor.reset(0); 80 | 81 | switch (stage) 82 | { 83 | case 0: 84 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 85 | tableProcessor.addTablePoint(0, 1, 1); 86 | tableProcessor.setTablePoint(0, 1, 1.0/3, 0, 0.25); 87 | tableProcessor.setTablePoint(0, 2, 1, 0, 0); 88 | break; 89 | 90 | case 1: 91 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 92 | tableProcessor.addTablePoint(0, 1, 1); 93 | tableProcessor.setTablePoint(0, 1, 1/3, 1, 0.75); 94 | tableProcessor.addTablePoint(0, 1, 1); 95 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 0, 0.25); 96 | tableProcessor.setTablePoint(0, 3, 1, 0, 0.25); 97 | break; 98 | 99 | case 2: 100 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 101 | tableProcessor.addTablePoint(0, 1, 1); 102 | tableProcessor.setTablePoint(0, 1, 1/3, 0, 0.25); 103 | tableProcessor.addTablePoint(0, 1, 1); 104 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 1, 0.75); 105 | tableProcessor.setTablePoint(0, 3, 1, 0, 0.25); 106 | break; 107 | 108 | case 3: 109 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 110 | tableProcessor.addTablePoint(0, 1, 1); 111 | tableProcessor.setTablePoint(0, 1, 1/3+1/3, 0, 0.75); 112 | tableProcessor.addTablePoint(0, 1, 1); 113 | tableProcessor.setTablePoint(0, 2, 1/3+1/3, 0, 0.25); 114 | tableProcessor.setTablePoint(0, 3, 1, 1, 0.75); 115 | break; 116 | } 117 | }; 118 | 119 | inline function equalPower5(tableProcessor, stage) 120 | { 121 | tableProcessor.reset(0); 122 | 123 | switch (stage) 124 | { 125 | case 0: 126 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 127 | tableProcessor.addTablePoint(0, 1, 1); 128 | tableProcessor.setTablePoint(0, 1, 1/4, 0, 0.25); 129 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.25); 130 | break; 131 | 132 | case 1: 133 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 134 | tableProcessor.addTablePoint(0, 1, 1); 135 | tableProcessor.setTablePoint(0, 1, 1/4, 1, 0.75); 136 | tableProcessor.addTablePoint(0, 1, 1); 137 | tableProcessor.setTablePoint(0, 2, 1/4+1/4, 0, 0.25); 138 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 139 | break; 140 | 141 | case 2: 142 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 143 | tableProcessor.addTablePoint(0, 1, 1); 144 | tableProcessor.setTablePoint(0, 1, 1/4, 0, 0.75); 145 | tableProcessor.addTablePoint(0, 1, 1); 146 | tableProcessor.setTablePoint(0, 2, 1/4+1/4, 1, 0.75); 147 | tableProcessor.addTablePoint(0, 1, 1); 148 | tableProcessor.setTablePoint(0, 3, (1/4)*3, 0, 0.25); 149 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.25); 150 | break; 151 | 152 | case 3: 153 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 154 | tableProcessor.addTablePoint(0, 1, 1); 155 | tableProcessor.setTablePoint(0, 1, 1/4*2, 0, 0.75); 156 | tableProcessor.addTablePoint(0, 1, 1); 157 | tableProcessor.setTablePoint(0, 2, 1/4+1/4*2, 1, 0.75); 158 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 159 | break; 160 | 161 | case 4: 162 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 163 | tableProcessor.addTablePoint(0, 1, 1); 164 | tableProcessor.setTablePoint(0, 1, 1/4*3, 0, 0.75); 165 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 166 | break; 167 | } 168 | }; 169 | 170 | inline function equalPower6(tableProcessor, stage) 171 | { 172 | tableProcessor.reset(0); 173 | 174 | switch (stage) 175 | { 176 | case 0: 177 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 178 | tableProcessor.addTablePoint(0, 1, 1); 179 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.25); 180 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.75); 181 | break; 182 | 183 | case 1: 184 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 185 | tableProcessor.addTablePoint(0, 1, 1); 186 | tableProcessor.setTablePoint(0, 1, 1/5, 1, 0.75); 187 | tableProcessor.addTablePoint(0, 1, 1); 188 | tableProcessor.setTablePoint(0, 2, 1/5*2, 0, 0.25); 189 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 190 | break; 191 | 192 | case 2: 193 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 194 | tableProcessor.addTablePoint(0, 1, 1); 195 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.75); 196 | tableProcessor.addTablePoint(0, 1, 1); 197 | tableProcessor.setTablePoint(0, 2, 1/5*2, 1, 0.75); 198 | tableProcessor.addTablePoint(0, 1, 1); 199 | tableProcessor.setTablePoint(0, 3, 1/5*3, 0, 0.25); 200 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 201 | break; 202 | 203 | case 3: 204 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 205 | tableProcessor.addTablePoint(0, 1, 1); 206 | tableProcessor.setTablePoint(0, 1, 1/5*2, 0, 0.75); 207 | tableProcessor.addTablePoint(0, 1, 1); 208 | tableProcessor.setTablePoint(0, 2, 1/5*3, 1, 0.75); 209 | tableProcessor.addTablePoint(0, 1, 1); 210 | tableProcessor.setTablePoint(0, 3, 1/5*4, 0, 0.25); 211 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 212 | break; 213 | 214 | case 4: 215 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 216 | tableProcessor.addTablePoint(0, 1, 1); 217 | tableProcessor.setTablePoint(0, 1, 1/5*3, 0, 0.75); 218 | tableProcessor.addTablePoint(0, 1, 1); 219 | tableProcessor.setTablePoint(0, 2, 1/5*4, 1, 0.75); 220 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.25); 221 | break; 222 | 223 | case 5: 224 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 225 | tableProcessor.addTablePoint(0, 1, 1); 226 | tableProcessor.setTablePoint(0, 1, 1/5*4, 0, 0.75); 227 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 228 | break; 229 | } 230 | }; 231 | 232 | inline function equalPower7(tableProcessor, stage) 233 | { 234 | tableProcessor.reset(0); 235 | 236 | switch (stage) 237 | { 238 | case 0: 239 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 240 | tableProcessor.addTablePoint(0, 1, 1); 241 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0, 0.25); 242 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.25); 243 | break; 244 | 245 | case 1: 246 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 247 | tableProcessor.addTablePoint(0, 1, 1); 248 | tableProcessor.setTablePoint(0, 1, (1.0/4), 1, 0.75); 249 | tableProcessor.addTablePoint(0, 1, 1); 250 | tableProcessor.setTablePoint(0, 2, (1.0/4)*2, 0, 0.25); 251 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 252 | break; 253 | 254 | case 2: 255 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 256 | tableProcessor.addTablePoint(0, 1, 1); 257 | tableProcessor.setTablePoint(0, 1, (1.0/4/2), 0.0, 0.25); 258 | tableProcessor.addTablePoint(0, 1, 1); 259 | tableProcessor.setTablePoint(0, 2, (1.0/4+1/4/2), 1, 0.75); 260 | tableProcessor.addTablePoint(0, 1, 1); 261 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8, 0, 0.25); 262 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 263 | break; 264 | 265 | case 3: 266 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 267 | tableProcessor.addTablePoint(0, 1, 1); 268 | tableProcessor.setTablePoint(0, 1, (1.0/4), 0.0, 0.25); 269 | tableProcessor.addTablePoint(0, 1, 1); 270 | tableProcessor.setTablePoint(0, 2, (1.0/4*2), 1, 0.75); 271 | tableProcessor.addTablePoint(0, 1, 1); 272 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*2, 0, 0.25); 273 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 274 | break; 275 | 276 | case 4: 277 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 278 | tableProcessor.addTablePoint(0, 1, 1); 279 | tableProcessor.setTablePoint(0, 1, (1.0/4+1/4/2), 0.0, 0.25); 280 | tableProcessor.addTablePoint(0, 1, 1); 281 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8, 1, 0.75); 282 | tableProcessor.addTablePoint(0, 1, 1); 283 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*3, 0, 0.25); 284 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 285 | break; 286 | 287 | case 5: 288 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 289 | tableProcessor.addTablePoint(0, 1, 1); 290 | tableProcessor.setTablePoint(0, 1, (1.0/4*2), 0.0, 0.25); 291 | tableProcessor.addTablePoint(0, 1, 1); 292 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8*2, 1, 0.75); 293 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*4, 0, 0.25); 294 | break; 295 | 296 | case 6: 297 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 298 | tableProcessor.addTablePoint(0, 1, 1); 299 | tableProcessor.setTablePoint(0, 1, (1.0/4*2)+1/8*2, 0, 0.75); 300 | tableProcessor.setTablePoint(0, 2, ((1.0/4)*2)+1/8*4, 1, 0.75); 301 | break; 302 | } 303 | }; 304 | 305 | inline function sShape2(tableProcessor, stage) 306 | { 307 | tableProcessor.reset(0); 308 | 309 | switch (stage) 310 | { 311 | case 0: 312 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 313 | tableProcessor.addTablePoint(0, 1, 1); 314 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0.5, 0.25); 315 | tableProcessor.setTablePoint(0, 2, 0, 0, 0.75); 316 | break; 317 | 318 | case 1: 319 | tableProcessor.setTablePoint(0, 0, 0, 0, 0.75); 320 | tableProcessor.addTablePoint(0, 1, 1); 321 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0.5, 0.25); 322 | tableProcessor.setTablePoint(0, 2, 0, 1, 0.75); 323 | break; 324 | } 325 | }; 326 | 327 | inline function sShape3(tableProcessor, stage) 328 | { 329 | tableProcessor.reset(0); 330 | 331 | switch (stage) 332 | { 333 | case 0: 334 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 335 | tableProcessor.addTablePoint(0, 1, 1); 336 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0.5, 0.25); 337 | tableProcessor.addTablePoint(0, 1, 1); 338 | tableProcessor.setTablePoint(0, 2, 1.0/2, 0, 0.75); 339 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 340 | break; 341 | 342 | case 1: 343 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 344 | tableProcessor.addTablePoint(0, 1, 1); 345 | tableProcessor.setTablePoint(0, 1, 1.0/4, 0.5, 0.25); 346 | tableProcessor.addTablePoint(0, 1, 1); 347 | tableProcessor.setTablePoint(0, 2, 1.0/2, 1, 0.75); 348 | tableProcessor.addTablePoint(0, 1, 1); 349 | tableProcessor.setTablePoint(0, 3, 1.0/4*3, 0.5, 0.25); 350 | tableProcessor.setTablePoint(0, 4, 0, 0, 0.75); 351 | break; 352 | 353 | case 2: 354 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 355 | tableProcessor.addTablePoint(0, 1, 1); 356 | tableProcessor.setTablePoint(0, 1, 1.0/2, 0, 0.75); 357 | tableProcessor.addTablePoint(0, 1, 1); 358 | tableProcessor.setTablePoint(0, 2, 1.0/4*3, 0.5, 0.25); 359 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 360 | break; 361 | } 362 | }; 363 | 364 | inline function sShape4(tableProcessor, stage) 365 | { 366 | tableProcessor.reset(0); 367 | 368 | switch (stage) 369 | { 370 | case 0: 371 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 372 | tableProcessor.addTablePoint(0, 1, 1); 373 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 374 | tableProcessor.addTablePoint(0, 1, 1); 375 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0, 0.75); 376 | tableProcessor.setTablePoint(0, 3, 1, 0, 0); 377 | break; 378 | 379 | case 1: 380 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 381 | tableProcessor.addTablePoint(0, 1, 1); 382 | tableProcessor.setTablePoint(0, 1, 1/3/2, 0.5, 0.25); 383 | tableProcessor.addTablePoint(0, 1, 1); 384 | tableProcessor.setTablePoint(0, 2, 1/3, 1, 0.75); 385 | tableProcessor.addTablePoint(0, 1, 1); 386 | tableProcessor.setTablePoint(0, 3, 1/3+1/3/2, 0.5, 0.25); 387 | tableProcessor.addTablePoint(0, 1, 1); 388 | tableProcessor.setTablePoint(0, 4, 1/3+1/3, 0, 0.75); 389 | tableProcessor.setTablePoint(0, 5, 1, 0, 0.25); 390 | break; 391 | 392 | case 2: 393 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 394 | tableProcessor.addTablePoint(0, 1, 1); 395 | tableProcessor.setTablePoint(0, 1, 1/3, 0, 0.25); 396 | tableProcessor.addTablePoint(0, 1, 1); 397 | tableProcessor.setTablePoint(0, 2, 1/3+1/3/2, 0.5, 0.25); 398 | tableProcessor.addTablePoint(0, 1, 1); 399 | tableProcessor.setTablePoint(0, 3, 1/3+1/3, 1, 0.75); 400 | tableProcessor.addTablePoint(0, 1, 1); 401 | tableProcessor.setTablePoint(0, 4, 1/3+1/3+1/6, 0.5, 0.25); 402 | tableProcessor.setTablePoint(0, 5, 1, 0, 0.75); 403 | break; 404 | 405 | case 3: 406 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 407 | tableProcessor.addTablePoint(0, 1, 1); 408 | tableProcessor.setTablePoint(0, 1, 1/3+1/3, 0, 0.75); 409 | tableProcessor.addTablePoint(0, 1, 1); 410 | tableProcessor.setTablePoint(0, 2, 1/3+1/3+1/6, 0.5, 0.25); 411 | tableProcessor.setTablePoint(0, 3, 1, 1, 0.75); 412 | break; 413 | } 414 | }; 415 | 416 | inline function sShape5(tableProcessor, stage) 417 | { 418 | tableProcessor.reset(0); 419 | 420 | switch (stage) 421 | { 422 | case 0: 423 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 424 | tableProcessor.addTablePoint(0, 1, 1); 425 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 426 | tableProcessor.addTablePoint(0, 1, 1); 427 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0, 0.75); 428 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 429 | break; 430 | 431 | case 1: 432 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 433 | tableProcessor.addTablePoint(0, 1, 1); 434 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0.5, 0.25); 435 | tableProcessor.addTablePoint(0, 1, 1); 436 | tableProcessor.setTablePoint(0, 2, 1.0/3, 1, 0.75); 437 | tableProcessor.addTablePoint(0, 1, 1); 438 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3/2, 0.5, 0.25); 439 | tableProcessor.addTablePoint(0, 1, 1); 440 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3, 0, 0.75); 441 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 442 | break; 443 | 444 | case 2: 445 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 446 | tableProcessor.addTablePoint(0, 1, 1); 447 | tableProcessor.setTablePoint(0, 1, 1.0/3/2, 0, 0.75); 448 | tableProcessor.addTablePoint(0, 1, 1); 449 | tableProcessor.setTablePoint(0, 2, 1.0/3, 0.5, 0.25); 450 | tableProcessor.addTablePoint(0, 1, 1); 451 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3/2, 1, 0.75); 452 | tableProcessor.addTablePoint(0, 1, 1); 453 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3, 0.5, 0.25); 454 | tableProcessor.addTablePoint(0, 1, 1); 455 | tableProcessor.setTablePoint(0, 5, 1.0/3+1/3+1/6, 0, 0.75); 456 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 457 | break; 458 | 459 | case 3: 460 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 461 | tableProcessor.addTablePoint(0, 1, 1); 462 | tableProcessor.setTablePoint(0, 1, 1.0/3, 0, 0.75); 463 | tableProcessor.addTablePoint(0, 1, 1); 464 | tableProcessor.setTablePoint(0, 2, 1.0/3+1/3/2, 0.5, 0.25); 465 | tableProcessor.addTablePoint(0, 1, 1); 466 | tableProcessor.setTablePoint(0, 3, 1.0/3+1/3, 1, 0.75); 467 | tableProcessor.addTablePoint(0, 1, 1); 468 | tableProcessor.setTablePoint(0, 4, 1.0/3+1/3+1/6, 0.5, 0.25); 469 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 470 | break; 471 | 472 | case 4: 473 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 474 | tableProcessor.addTablePoint(0, 1, 1); 475 | tableProcessor.setTablePoint(0, 1, 1.0/3+1/3, 0, 0.75); 476 | tableProcessor.addTablePoint(0, 1, 1); 477 | tableProcessor.setTablePoint(0, 2, 1.0/3+1/3+1/6, 0.5, 0.25); 478 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 479 | break; 480 | } 481 | }; 482 | 483 | inline function sShape6(tableProcessor, stage) 484 | { 485 | tableProcessor.reset(0); 486 | 487 | switch (stage) 488 | { 489 | case 0: 490 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 491 | tableProcessor.addTablePoint(0, 1, 1); 492 | tableProcessor.setTablePoint(0, 1, 1/5/2, 0.5, 0.25); 493 | tableProcessor.addTablePoint(0, 1, 1); 494 | tableProcessor.setTablePoint(0, 2, 1/5, 0, 0.75); 495 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 496 | break; 497 | 498 | case 1: 499 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 500 | tableProcessor.addTablePoint(0, 1, 1); 501 | tableProcessor.setTablePoint(0, 1, 1/5/2, 0.5, 0.25); 502 | tableProcessor.addTablePoint(0, 1, 1); 503 | tableProcessor.setTablePoint(0, 2, 1/5, 1, 0.75); 504 | tableProcessor.addTablePoint(0, 1, 1); 505 | tableProcessor.setTablePoint(0, 3, 1/5+1/5/2, 0.5, 0.25); 506 | tableProcessor.addTablePoint(0, 1, 1); 507 | tableProcessor.setTablePoint(0, 4, 1/5*2, 0, 0.75); 508 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 509 | break; 510 | 511 | case 2: 512 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 513 | tableProcessor.addTablePoint(0, 1, 1); 514 | tableProcessor.setTablePoint(0, 1, 1/5, 0, 0.75); 515 | tableProcessor.addTablePoint(0, 1, 1); 516 | tableProcessor.setTablePoint(0, 2, 1/5+1/5/2, 0.5, 0.25); 517 | tableProcessor.addTablePoint(0, 1, 1); 518 | tableProcessor.setTablePoint(0, 3, 1/5*2, 1, 0.75); 519 | tableProcessor.addTablePoint(0, 1, 1); 520 | tableProcessor.setTablePoint(0, 4, 1/5+1/5+1/10, 0.5, 0.25); 521 | tableProcessor.addTablePoint(0, 1, 1); 522 | tableProcessor.setTablePoint(0, 5, 1/5*3, 0, 0.75); 523 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 524 | break; 525 | 526 | case 3: 527 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 528 | tableProcessor.addTablePoint(0, 1, 1); 529 | tableProcessor.setTablePoint(0, 1, 1/5+1/5, 0, 0.75); 530 | tableProcessor.addTablePoint(0, 1, 1); 531 | tableProcessor.setTablePoint(0, 2, 1/5+1/5+1/5/2, 0.5, 0.25); 532 | tableProcessor.addTablePoint(0, 1, 1); 533 | tableProcessor.setTablePoint(0, 3, 1/5*3, 1, 0.75); 534 | tableProcessor.addTablePoint(0, 1, 1); 535 | tableProcessor.setTablePoint(0, 4, 1/5*3+1/5/2, 0.5, 0.25); 536 | tableProcessor.addTablePoint(0, 1, 1); 537 | tableProcessor.setTablePoint(0, 5, 1/5*4, 0, 0.75); 538 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 539 | break; 540 | 541 | case 4: 542 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 543 | tableProcessor.addTablePoint(0, 1, 1); 544 | tableProcessor.setTablePoint(0, 1, 1/5+1/5+1/5, 0, 0.75); 545 | tableProcessor.addTablePoint(0, 1, 1); 546 | tableProcessor.setTablePoint(0, 2, 1/5+1/5+1/5+1/5/2, 0.5, 0.25); 547 | tableProcessor.addTablePoint(0, 1, 1); 548 | tableProcessor.setTablePoint(0, 3, 1/5*4, 1, 0.75); 549 | tableProcessor.addTablePoint(0, 1, 1); 550 | tableProcessor.setTablePoint(0, 4, 1/5*4+1/5/2, 0.5, 0.25); 551 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 552 | break; 553 | 554 | case 5: 555 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 556 | tableProcessor.addTablePoint(0, 1, 1); 557 | tableProcessor.setTablePoint(0, 1, 1/5*4, 0, 0.75); 558 | tableProcessor.addTablePoint(0, 1, 1); 559 | tableProcessor.setTablePoint(0, 2, 1/5*4+1/5/2, 0.5, 0.25); 560 | tableProcessor.setTablePoint(0, 3, 0, 1, 0.75); 561 | break; 562 | } 563 | }; 564 | 565 | inline function sShape7(tableProcessor, stage) 566 | { 567 | tableProcessor.reset(0); 568 | 569 | switch (stage) 570 | { 571 | case 0: 572 | tableProcessor.setTablePoint(0, 0, 0, 1, 1); 573 | tableProcessor.addTablePoint(0, 1, 1); 574 | tableProcessor.setTablePoint(0, 1, 1.0/4/2, 0.5, 0.25); 575 | tableProcessor.addTablePoint(0, 1, 1); 576 | tableProcessor.setTablePoint(0, 2, 1.0/4, 0, 0.75); 577 | tableProcessor.setTablePoint(0, 3, 0, 0, 0.75); 578 | break; 579 | 580 | case 1: 581 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 582 | tableProcessor.addTablePoint(0, 1, 1); 583 | tableProcessor.setTablePoint(0, 1, 1.0/4/2, 0.5, 0.25); 584 | tableProcessor.addTablePoint(0, 1, 1); 585 | tableProcessor.setTablePoint(0, 2, (1.0/4), 1, 0.75); 586 | tableProcessor.addTablePoint(0, 1, 1); 587 | tableProcessor.setTablePoint(0, 3, (1/4+1/4/2), 0.5, 0.25); 588 | tableProcessor.addTablePoint(0, 1, 1); 589 | tableProcessor.setTablePoint(0, 4, (1.0/4)*2, 0, 0.75); 590 | tableProcessor.setTablePoint(0, 5, 0, 0, 0.75); 591 | break; 592 | 593 | case 2: 594 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 595 | tableProcessor.addTablePoint(0, 1, 1); 596 | tableProcessor.setTablePoint(0, 1, (1.0/4/2), 0.0, 0.25); 597 | tableProcessor.addTablePoint(0, 1, 1); 598 | tableProcessor.setTablePoint(0, 2, (1.0/4), 0.5, 0.25); 599 | tableProcessor.addTablePoint(0, 1, 1); 600 | tableProcessor.setTablePoint(0, 3, (1.0/4+1/4/2), 1, 0.75); 601 | tableProcessor.addTablePoint(0, 1, 1); 602 | tableProcessor.setTablePoint(0, 4, ((1.0/4)*2), 0.5, 0.25); 603 | tableProcessor.addTablePoint(0, 1, 1); 604 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8, 0, 0.75); 605 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 606 | break; 607 | 608 | case 3: 609 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 610 | tableProcessor.addTablePoint(0, 1, 1); 611 | tableProcessor.setTablePoint(0, 1, (1.0/4), 0.0, 0.25); 612 | tableProcessor.addTablePoint(0, 1, 1); 613 | tableProcessor.setTablePoint(0, 2, (1.0/4+1/4/2), 0.5, 0.25); 614 | tableProcessor.addTablePoint(0, 1, 1); 615 | tableProcessor.setTablePoint(0, 3, (1.0/4*2), 1, 0.75); 616 | tableProcessor.addTablePoint(0, 1, 1); 617 | tableProcessor.setTablePoint(0, 4, ((1.0/4)*2)+1/8, 0.5, 0.25); 618 | tableProcessor.addTablePoint(0, 1, 1); 619 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*2, 0, 0.75); 620 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 621 | break; 622 | 623 | case 4: 624 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 625 | tableProcessor.addTablePoint(0, 1, 1); 626 | tableProcessor.setTablePoint(0, 1, (1.0/4+1/4/2), 0.0, 0.25); 627 | tableProcessor.addTablePoint(0, 1, 1); 628 | tableProcessor.setTablePoint(0, 2, (1.0/4*2), 0.5, 0.25); 629 | tableProcessor.addTablePoint(0, 1, 1); 630 | tableProcessor.setTablePoint(0, 3, (1.0/4*2)+1/8, 1, 0.75); 631 | tableProcessor.addTablePoint(0, 1, 1); 632 | tableProcessor.setTablePoint(0, 4, (1.0/4*2)+1/8*2, 0.5, 0.25); 633 | tableProcessor.addTablePoint(0, 1, 1); 634 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*3, 0, 0.75); 635 | tableProcessor.setTablePoint(0, 6, 0, 0, 0.75); 636 | break; 637 | 638 | case 5: 639 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 640 | tableProcessor.addTablePoint(0, 1, 1); 641 | tableProcessor.setTablePoint(0, 1, (1.0/4*2), 0.0, 0.25); 642 | tableProcessor.addTablePoint(0, 1, 1); 643 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8, 0.5, 0.25); 644 | tableProcessor.addTablePoint(0, 1, 1); 645 | tableProcessor.setTablePoint(0, 3, (1.0/4*2)+1/8*2, 1, 0.75); 646 | tableProcessor.addTablePoint(0, 1, 1); 647 | tableProcessor.setTablePoint(0, 4, (1.0/4*2)+1/8*3, 0.5, 0.25); 648 | tableProcessor.setTablePoint(0, 5, ((1.0/4)*2)+1/8*4, 0, 0.75); 649 | break; 650 | 651 | case 6: 652 | tableProcessor.setTablePoint(0, 0, 0, 0, 1); 653 | tableProcessor.addTablePoint(0, 1, 1); 654 | tableProcessor.setTablePoint(0, 1, (1.0/4*2)+1/8*2, 0, 0.75); 655 | tableProcessor.addTablePoint(0, 1, 1); 656 | tableProcessor.setTablePoint(0, 2, (1.0/4*2)+1/8*3, 0.5, 0.25); 657 | tableProcessor.setTablePoint(0, 3, ((1.0/4)*2)+1/8*4, 1, 0.75); 658 | break; 659 | } 660 | }; 661 | 662 | function onNoteOn() 663 | { 664 | 665 | } 666 | function onNoteOff() 667 | { 668 | 669 | } 670 | function onController() 671 | { 672 | 673 | } 674 | function onTimer() 675 | { 676 | 677 | } 678 | function onControl(number, value) 679 | { 680 | switch (number) 681 | { 682 | case btnApply: 683 | 684 | btnApply.setValue(0); 685 | 686 | for (i = 0; i < modulators.length; i++) 687 | { 688 | if (cmbType.getValue() == 1) //Equal power 689 | { 690 | switch (cmbCount.getValue()) 691 | { 692 | case 1: equalPower2(modulators[i], i); break; 693 | case 2: equalPower3(modulators[i], i); break; 694 | case 3: equalPower4(modulators[i], i); break; 695 | case 4: equalPower5(modulators[i], i); break; 696 | case 5: equalPower6(modulators[i], i); break; 697 | case 6: equalPower7(modulators[i], i); break; 698 | } 699 | } 700 | else 701 | { 702 | switch (cmbCount.getValue()) 703 | { 704 | case 1: sShape2(modulators[i], i); break; 705 | case 2: sShape3(modulators[i], i); break; 706 | case 3: sShape4(modulators[i], i); break; 707 | case 4: sShape5(modulators[i], i); break; 708 | case 5: sShape6(modulators[i], i); break; 709 | case 6: sShape7(modulators[i], i); break; 710 | } 711 | } 712 | } 713 | 714 | break; 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /modules/GenericMicMixer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title genericMicMixer 3 | * Author: David Healey 4 | * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html 5 | */ 6 | 7 | //INIT 8 | Content.setWidth(650); 9 | Content.setHeight(350); 10 | 11 | //Retrieve simple gain FX with "mic" in their ID and store in gainFX array 12 | const var simpleGainIds = Synth.getIdList("Simple Gain"); //Get IDs of simple gain FX - 1 per mic 13 | const var gainFx = []; 14 | 15 | reg soloOn = false; //Flag to indicate if at least one channel is soloed 16 | 17 | for (g in simpleGainIds) 18 | { 19 | if (!Engine.matchesRegex(g, "mic")) continue; //Skip if it doesn't contain "mic" in its ID 20 | gainFx.push(Synth.getEffect(g)); 21 | } 22 | 23 | //Retrieve samplers and store in samplers array 24 | const var samplerIds = Synth.getIdList("Sampler"); //Get IDs of samplers 25 | const var samplers = []; 26 | 27 | for (s in samplerIds) 28 | { 29 | samplers.push(Synth.getSampler(s)); 30 | } 31 | 32 | //GUI 33 | const var vol = []; 34 | const var delay = []; 35 | const var width = []; 36 | const var pan = []; 37 | const var mute = []; 38 | const var solo = []; 39 | const var purge = []; 40 | 41 | for (i = 0; i < Math.min(8, gainFx.length); i++) //Maximum of 8 channels 42 | { 43 | vol[i] = knobFactory(100*i, 0, "vol"+i, {width:95, mode:"Decibel", max:3, text:"Vol " + (i+1)}); 44 | delay[i] = knobFactory(100*i, 50, "delay"+i, {width:95, max:500, text:"Delay " + (i+1)}); 45 | width[i] = knobFactory(100*i, 100, "width"+i, {width:95, max:200, text:"Width " + (i+1)}); 46 | pan[i] = knobFactory(100*i, 150, "pan"+i, {width:95, min:-100, max:100, text:"Pan " + (i+1)}); 47 | mute[i] = buttonFactory(100*i, 200, "mute"+i, {width:95, text:"Mute " + (i+1)}); 48 | solo[i] = buttonFactory(100*i, 250, "solo"+i, {width:95, text:"Solo " + (i+1)}); 49 | purge[i] = buttonFactory(100*i, 300, "purge"+i, {width:95, text:"Purge " + (i+1)}); 50 | } 51 | 52 | //FUNCTIONS 53 | inline function knobFactory(x, y, name, obj) 54 | { 55 | local control = Content.addKnob(name, x, y); 56 | 57 | //Set control properties 58 | for (k in obj) //Each key in object 59 | { 60 | control.set(k, obj[k]); 61 | } 62 | 63 | return control; 64 | }; 65 | 66 | inline function buttonFactory(x, y, name, obj) 67 | { 68 | local control = Content.addButton(name, x, y); 69 | 70 | //Set control properties 71 | for (k in obj) //Each key in object 72 | { 73 | control.set(k, obj[k]); 74 | } 75 | 76 | return control; 77 | }; 78 | 79 | inline function muteSolo() 80 | { 81 | soloOn = false; 82 | //Determine if any channels are soloed in order to set soloOn flag 83 | for (i = 0; i < solo.length; i++) //Each solo slider 84 | { 85 | if (solo[i].getValue() == 1) 86 | { 87 | soloOn = true; //Flag that at least one channel is soloed 88 | mute[i].setValue(0); //Toggle mute state 89 | } 90 | } 91 | 92 | for (i = 0; i < mute.length; i++) 93 | { 94 | //If a channel is soloed set volume of all non-soloed channels to 0, if no channel is soloed restore volume of non-muted channels 95 | if (mute[i].getValue() == 1) continue; //Ignore muted channels 96 | 97 | if (solo[i].getValue() == 1) //Channel is soloed 98 | { 99 | gainFx[i].setAttribute(0, vol[i].getValue()); 100 | mute[i].setValue(0); //Toggle mute state 101 | } 102 | else //Channel is not soloed 103 | { 104 | if (soloOn == true) //If at least one channel is soloed 105 | { 106 | gainFx[i].setAttribute(0, -100); //Mute the channel 107 | } 108 | else //No channels soloed 109 | { 110 | gainFx[i].setAttribute(0, vol[i].getValue()); //Restore channel's volume 111 | } 112 | } 113 | } 114 | } 115 | function onNoteOn() 116 | { 117 | 118 | } 119 | function onNoteOff() 120 | { 121 | 122 | } 123 | function onController() 124 | { 125 | 126 | } 127 | function onTimer() 128 | { 129 | 130 | } 131 | function onControl(number, value) 132 | { 133 | for (i = 0; i < vol.length; i++) //Each mic position 134 | { 135 | if (number == vol[i]) 136 | { 137 | if (mute[i].getValue() == 0) //Channel isn't muted 138 | { 139 | //No channels are soloed or this channel is soloed 140 | if (soloOn == false || (soloOn == true && solo[i].getValue())) 141 | { 142 | gainFx[i].setAttribute(gainFx[i].Gain, value); 143 | } 144 | } 145 | } 146 | else if (number == delay[i]) 147 | { 148 | gainFx[i].setAttribute(gainFx[i].Delay, value); 149 | } 150 | else if (number == width[i]) 151 | { 152 | gainFx[i].setAttribute(gainFx[i].Width, value); 153 | } 154 | else if (number == pan[i]) 155 | { 156 | gainFx[i].setAttribute(gainFx[i].Balance, value); 157 | } 158 | else if (number == mute[i]) 159 | { 160 | if (value == 1) //Mute is active for channel 161 | { 162 | gainFx[i].setAttribute(gainFx[i].Gain, -100); //Silence channel 163 | solo[i].setValue(0); //Toggle solo state 164 | } 165 | muteSolo(); 166 | } 167 | else if (number == solo[i]) 168 | { 169 | muteSolo(); 170 | } 171 | else if (number == purge[i]) 172 | { 173 | for (s in samplers) //Each sampler 174 | { 175 | if (s.getNumMicPositions() > 1 && s.isMicPositionPurged(i) != value) //Only purge or load if it's not already purged or loaded 176 | { 177 | s.purgeMicPosition(s.getMicPositionName(i), value); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | -------------------------------------------------------------------------------- /modules/GlobalVariables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: globalVariables.js 3 | * Author: David Healey 4 | * License: Public Domain 5 | */ 6 | 7 | /*Should be placed directly after interface script*/ 8 | 9 | global g_realVelocity; //Keeps track of velocity as it comes into the chain 10 | global g_keys = 0; //Keeps track of number of pressed keys - avoids issues with artificial notes 11 | function onNoteOn() 12 | { 13 | g_realVelocity = Message.getVelocity(); 14 | g_keys++; 15 | } 16 | function onNoteOff() 17 | { 18 | g_keys--; 19 | } 20 | function onController() 21 | { 22 | 23 | } 24 | function onTimer() 25 | { 26 | 27 | } 28 | function onControl(number, value) 29 | { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /modules/GuitarChordDictionary.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019, 2020 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | namespace ChordDictionary 19 | { 20 | const var chordTypes = [ 21 | [0, 4, 7], //Major 22 | [0, 3, 7], //Minor 23 | [0, 4, 7, 9], //Major 6th 24 | [0, 2, 3, 7, 9], //Minor 6 add9 25 | [0, 4, 7, 10], //Dominant 7th 26 | [0, 4, 7, 11], //Major 7th 27 | [0, 3, 7, 10], //Minor 7th 28 | [0, 3, 7, 11], //mM7 29 | [0, 4, 6, 7, 11], //Major 7#11 30 | [0, 5, 7, 10], //7th sus4 31 | [0, 2, 4, 6, 9, 11], //Major 13th #11 32 | [0, 2, 3, 7, 10], //Minor 9th 33 | [0, 2, 3, 5, 7, 10], //Minor 11th 34 | [0, 2, 3, 5, 7, 9, 10], //Minor 13th 35 | [0, 3, 6], //Diminished 36 | [0, 3, 6, 9], //Diminished 7th 37 | [0, 3, 6, 10], //Half diminished 7th 38 | [0, 3, 6, 11], //Diminished Major 7th 39 | [0, 4, 8], //Augmented 40 | [0, 2, 7] //Sus2 41 | ]; 42 | 43 | const var chordNames = [ 44 | "Major", 45 | "Minor", 46 | "Major 6th", 47 | "Minor 6 add9", 48 | "Dominant 7th", 49 | "Major 7th", 50 | "Minor 7th", 51 | "mM7", 52 | "Major 7#11", 53 | "7th sus4", 54 | "Major 13th #11", 55 | "Minor 9th", 56 | "Minor 11th", 57 | "Minor 13th", 58 | "Diminished", 59 | "Diminished 7th", 60 | "7b5", 61 | "Dim M7", 62 | "Augmented", 63 | "Sus2" 64 | ]; 65 | 66 | inline function getUserChord(tuning, fret, keys) 67 | { 68 | local data = g_userChords; //Get persistent data object by ref 69 | 70 | if (data != undefined && data[tuning.join()] != undefined && data[tuning.join()][fret] != undefined) 71 | { 72 | keys.sort(); 73 | 74 | //Search for existing entry 75 | local index = data[tuning.join()][fret].indexes.indexOf(keys.join()); 76 | 77 | if (index != -1) 78 | return data[tuning.join()][fret].strings[index]; 79 | else 80 | return []; 81 | } 82 | else 83 | return []; 84 | } 85 | 86 | inline function storeUserChord(tuning, fret, keys, strings) 87 | { 88 | local data = PersistentData.get("userChords"); //Get persistent data object 89 | 90 | if (data == undefined) data = {}; 91 | if (data[tuning.join()] == undefined) data[tuning.join()] = []; 92 | if (data[tuning.join()][fret] == undefined) data[tuning.join()][fret] = {"indexes":[], "strings":[]}; 93 | 94 | keys.sort(); 95 | 96 | //Search for existing entry 97 | local index = data[tuning.join()][fret].indexes.indexOf(keys.join()); 98 | 99 | if (index != -1) 100 | data[tuning.join()][fret].strings[index] = strings; 101 | else 102 | { 103 | data[tuning.join()][fret].indexes.push(keys.join()); 104 | data[tuning.join()][fret].strings.push(strings); 105 | } 106 | 107 | g_userChords = data; //Update global object 108 | PersistentData.set("userChords", data); //Update persistent data object 109 | } 110 | 111 | inline function removeUserChord(tuning, fret, keys) 112 | { 113 | local data = PersistentData.get("userChords"); //Get persistent data object 114 | 115 | if (data != undefined && data[tuning.join()] != undefined && data[tuning.join()][fret] != undefined) 116 | { 117 | keys.sort(); 118 | 119 | //Search for existing entry 120 | local index = data[tuning.join()][fret].indexes.indexOf(keys.join()); 121 | 122 | if (index != -1) 123 | { 124 | data[tuning.join()][fret].indexes.remove(data[tuning.join()][fret].indexes[index]); 125 | data[tuning.join()][fret].strings.remove(data[tuning.join()][fret].strings[index]); 126 | } 127 | } 128 | 129 | g_userChords = data; //Update global object 130 | PersistentData.set("userChords", data); //Update persistent data object 131 | } 132 | 133 | inline function getChord(notes, fret) 134 | { 135 | local sorted = []; //Holds sorted note data 136 | local search = []; //Holds pattern to search for in the dictionary 137 | local chordRoot; 138 | local index; 139 | 140 | //Sort the note data and remove duplicates 141 | for ( i = 0; i < notes.length; i++) 142 | { 143 | if (notes[i] != undefined && !sorted.contains(notes[i] % 12)) 144 | sorted.push(notes[i] % 12); 145 | } 146 | 147 | sorted.sort(); 148 | 149 | /*Seach dictionary for match to sorted array 150 | Shuffle array if no match is found until all choices have been tried.*/ 151 | for (i = 0; i < sorted.length; i++) 152 | { 153 | for (j = 0; j < sorted.length; j++) 154 | { 155 | chordRoot = sorted[i]; //Try next note as root 156 | search[j] = (sorted[j] + ((j < i) * 12)) - chordRoot; 157 | } 158 | 159 | search.sort(); 160 | 161 | //Check if the search pattern is present in the dictionary 162 | index = chordTypes.indexOf(search); 163 | 164 | //Exit loop if search match entry in chord dictionary 165 | if (index != -1) 166 | break; 167 | } 168 | 169 | if (index != -1) 170 | return g_chordMap[chordRoot][index][fretPos]; 171 | else 172 | return []; 173 | } 174 | 175 | inline function getChordVoicing(data, fretPos, ref) 176 | { 177 | local notes = []; //Holds sorted data 178 | local search = []; //Holds pattern to search for in the dictionary 179 | local chordRoot; 180 | local index; 181 | 182 | //Sort the note data and remove duplicates 183 | for ( i = 0; i < data.length; i++) 184 | { 185 | if (data[i] != undefined && !notes.contains(data[i] % 12)) 186 | notes.push(data[i] % 12); 187 | } 188 | 189 | notes.sort(); 190 | 191 | /*Seach dictionary for match to notes array 192 | Shuffle array if no match is found until all choices have been tried.*/ 193 | for (i = 0; i < notes.length; i++) 194 | { 195 | for (j = 0; j < notes.length; j++) 196 | { 197 | chordRoot = notes[i]; //Try next note as root 198 | search[j] = (notes[j] + ((j < i) * 12)) - chordRoot; 199 | } 200 | 201 | search.sort(); 202 | 203 | //Check if the search pattern is present in the dictionary 204 | index = chordTypes.indexOf(search); 205 | 206 | //Exit loop if search match entry in chord dictionary 207 | if (index != -1) 208 | break; 209 | } 210 | 211 | if (index != -1) 212 | { 213 | if (ref) 214 | return referenceMap[chordRoot][index][fretPos]; 215 | else 216 | return g_chordMap[chordRoot][index][fretPos]; 217 | } 218 | else 219 | return []; 220 | } 221 | 222 | inline function getType(index) 223 | { 224 | return chordTypes[index]; 225 | } 226 | 227 | //Create chord voicing lookup table 228 | inline function generateChordTable(tuning, numFrets) 229 | { 230 | local result = [[], [], [], [], [], [], [], [], [], [], [], []]; //One object per root note 231 | local i; 232 | local j; 233 | local f; 234 | 235 | for (i = 0; i < result.length; i++) //Each root note 236 | { 237 | for (j = 0; j < chordTypes.length; j++) //Each chord type 238 | { 239 | for (f = 0; f < numFrets; f++) //Each fret 240 | { 241 | local voicing = voiceChord(tuning, i, j, f); 242 | 243 | if (result[i][j] == undefined) 244 | result[i][j] = []; 245 | 246 | result[i][j][f] = voicing; 247 | } 248 | } 249 | } 250 | return result; 251 | } 252 | 253 | //Wrapper function 254 | inline function voiceChord(tuning, rootNote, chordType, fretPos) 255 | { 256 | local intervals = chordTypes[chordType]; 257 | local options = getOptions(tuning, rootNote, intervals, fretPos); 258 | return filterOptions(options, tuning, rootNote, intervals); 259 | } 260 | 261 | //Gather possible frets for each string 262 | inline function getOptions(tuning, rootNote, intervals, fretPos) 263 | { 264 | local result = []; 265 | local i; 266 | local j; 267 | 268 | for (i = 0; i < tuning.length; i++) 269 | { 270 | local s = (fretPos + tuning[i]) % 12; 271 | 272 | for (j = 0; j < intervals.length; j++) 273 | { 274 | local n = (rootNote + intervals[j]) % 12; 275 | 276 | if (n < s) n = n + 12; 277 | 278 | local f = Math.abs(n-s); 279 | 280 | if (f < 5) 281 | { 282 | if (result[i] == undefined) 283 | result[i] = []; 284 | 285 | result[i].push(f+fretPos); 286 | } 287 | } 288 | } 289 | return result; 290 | } 291 | 292 | //Filter fret options to generated chord voicing 293 | inline function filterOptions(options, tuning, rootNote, intervals) 294 | { 295 | local result = []; 296 | local fretted = 0; 297 | local count = []; 298 | local lowest = 99; 299 | local notes = []; 300 | local third = rootNote + intervals[1]; 301 | local i; 302 | local j; 303 | 304 | for (i = 0; i < options.length; i++) 305 | { 306 | result[i] = -1; 307 | local lastFret = 0; 308 | local s = (tuning[i]) % 12; 309 | 310 | if (options[i]) 311 | { 312 | for (j = 0; j < options[i].length; j++) 313 | { 314 | local f = options[i][j]; 315 | local n = s + f; //Get normalized note number 316 | 317 | if (count[f] == undefined) 318 | count[f] = 0; 319 | 320 | if (fretted < 4 || i < 5 || (f == lowest && count[f] > 1)) 321 | { 322 | //Don't double fretted thirds 323 | if (n == third && notes.contains(third) && f != 0) 324 | continue; 325 | 326 | local temp = result[i]; //Copy of fret for this string to compare 327 | 328 | //Go for the lowest fret possible 329 | if (f < result[i] || result[i] == -1) 330 | result[i] = f; 331 | 332 | //Make sure the fret is compatible with the options on the previous and next strings 333 | if (i > 0 && options[i-1] && options[i-1].contains(f) && i < options.length-1 && options[i+1] && !options[i+1].contains(result[i])) 334 | result[i] = f; 335 | 336 | if (typeof options[i+1] == "object" && i < options.length-1 && options[i+1].contains(f)) 337 | result[i] = f; 338 | 339 | if (i > 2 && result[i-1] == -1) 340 | result[i] = -1; 341 | 342 | lastFret = f; 343 | 344 | //Count number of frets being played 345 | if (result[i] != 0) 346 | { 347 | fretted++; 348 | count[f]++; 349 | } 350 | 351 | //Compare string with comparison copy and record the note number 352 | if (result[i] != temp && f != 0) 353 | notes[i] = n; 354 | } 355 | } 356 | 357 | //Keep track of lowest fret and fretted notes 358 | if (lastFret != 0 && lastFret < lowest) 359 | { 360 | lowest = lastFret; 361 | } 362 | } 363 | } 364 | return result; 365 | } 366 | } -------------------------------------------------------------------------------- /modules/GuitarPicker.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019, 2020 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | Content.setWidth(750); 19 | Content.setHeight(200); 20 | 21 | const var eventIds = Engine.createMidiList(); 22 | 23 | reg channel = 0; 24 | reg forceString = -1; 25 | reg lastTime = 0; 26 | reg forceString = -1; //Force the use of a particular string 27 | reg direction = 0; 28 | reg lastVelocity; 29 | 30 | //Round robin variables 31 | const var NUM_RR = 2; //Total RRs 32 | reg rr = 0; //Current RR 33 | const var lastRR = Engine.createMidiList(); 34 | lastRR.fill(0); 35 | 36 | const var noteString = Engine.createMidiList(); //Map string used for each note when played 37 | 38 | //Polyphonic variables (1 element per string) 39 | const var notes = []; //Current note for each string 40 | const var slideDecays = []; //Volume decay value 41 | const var timerCounters = []; //Timer loop counters 42 | const var timers = []; //Timer objects 43 | const var intervals = []; //Legato/slide interval 44 | const var timerOrigin = []; 45 | const var timerTarget = []; 46 | const var lastTimes = []; //Last time each string was triggered 47 | const var retrigger = []; //Legato retrigger notes 48 | 49 | //Timer object callback 50 | function timerAction() 51 | { 52 | var s = timers.indexOf(this); 53 | var v; 54 | var note; 55 | var bend; 56 | var rate = knbSlideSpeed.getValue(); 57 | var newId; 58 | var oldId = eventIds.getValue(timerOrigin[s]); 59 | 60 | //Select bend, either up or down 61 | intervals[s] > 0 ? bend = 100 : bend = -100; 62 | 63 | //Fade out old note 64 | if (oldId > 0) 65 | { 66 | Synth.addPitchFade(oldId, rate, 0, bend); //Pitch fade old note to bend amount 67 | Synth.addVolumeFade(oldId, rate, -100); //Fade out last note 68 | eventIds.setValue(timerOrigin[s], 0); 69 | } 70 | 71 | //Move to next note, either up or down 72 | intervals[s] > 0 ? timerOrigin[s]++ : timerOrigin[s]--; 73 | 74 | note = timerOrigin[s] + knbCapo.getValue(); 75 | 76 | v = 1 + (s * 4) + rr; //Get velocity to trigger corrent sample 77 | 78 | //Play new note 79 | newId = Synth.playNoteWithStartOffset(channel, note, v, 20000); 80 | 81 | //Fade in new note 82 | Synth.addVolumeFade(newId, 0, -99); //Set new note's initial volume 83 | Synth.addVolumeFade(newId, rate, Math.min(0, slideDecays[s] * timerCounters[s])); //Fade in new note 84 | Synth.addPitchFade(newId, 0, 0, -bend); //Set new note's initial detuning 85 | Synth.addPitchFade(newId, rate, 0, 0); //Pitch fade new note to 0 86 | 87 | eventIds.setValue(timerOrigin[s], newId); 88 | 89 | timerCounters[s]++; 90 | 91 | if (Synth.getNumPressedKeys() < 1 || timerOrigin[s] == timerTarget[s] || timerCounters[s] >= Math.abs(intervals[s]) || (intervals[s] < 0 && timerOrigin[s] == tuning[s])) 92 | this.stopTimer(); 93 | } 94 | 95 | //Make timer object (7 = max strings) 96 | for (i = 0; i < 7; i++) 97 | { 98 | timers[i] = Engine.createTimerObject(); 99 | timers[i].setTimerCallback(timerAction); 100 | } 101 | 102 | /*GUI*/ 103 | //btnMute 104 | const var btnMute = Content.addButton("btnMute", 10, 10); 105 | btnMute.set("text", "Mute"); 106 | 107 | //cmbDirection 108 | const var cmbDirection = Content.addComboBox("cmbDirection", 160, 10); 109 | cmbDirection.set("items", ["Down", "Up", "Alternate"].join("\n")); 110 | 111 | //btnRetrigger 112 | const var btnRetrigger = Content.addButton("btnRetrigger", 10, 60); 113 | btnRetrigger.set("text", "Retrigger"); 114 | 115 | //knbCapo 116 | const var knbCapo = Content.addKnob("knbCapo", 160, 50); 117 | knbCapo.set("text", "Capo"); 118 | knbCapo.setRange(0, 16, 1); 119 | 120 | //knbLegatoRange 121 | const var knbLegatoRange = Content.addKnob("knbLegatoRange", 310, 50); 122 | knbLegatoRange.set("text", "Legato Range"); 123 | knbLegatoRange.setRange(0, 15, 1); 124 | 125 | //btnSlide 126 | const var btnSlide = Content.addButton("btnSlide", 10, 110); 127 | btnSlide.set("text", "Slide"); 128 | 129 | //knbSlideDecay 130 | const var knbSlideDecay = Content.addKnob("knbSlideDecay", 160, 100); 131 | knbSlideDecay.set("text", "Slide Decay"); 132 | knbSlideDecay.setRange(-50, 0, 1); 133 | knbSlideDecay.set("suffix", " dB"); 134 | 135 | //knbSlideSpeed 136 | const var knbSlideSpeed = Content.addKnob("knbSlideSpeed", 310, 100); 137 | knbSlideSpeed.set("text", "Slide Speed"); 138 | knbSlideSpeed.set("mode", "Time"); 139 | knbSlideSpeed.set("defaultValue", 80); 140 | knbSlideSpeed.setRange(50, 200, 1); 141 | 142 | //Higher velocity = shorter slide time 143 | inline function setSlideRateFromVelocity(v) 144 | { 145 | local t = (knbSlideSpeed.get("max") - knbSlideSpeed.get("min")) / 127 * v; 146 | knbSlideSpeed.setValue(knbSlideSpeed.get("max") - t); 147 | }function onNoteOn() 148 | { 149 | if (!btnMute.getValue()) 150 | { 151 | channel = Message.getChannel(); 152 | local n = Message.getNoteNumber(); //Record incoming note number 153 | local d = 1 + g_velocityLayer * g_tuning.length * 4; //Dynamic velocity level 154 | local notesPerString = g_patch.notesPerString; 155 | 156 | local capo = knbCapo.getValue(); 157 | 158 | if (n >= g_ranges[1][0] + capo && n <= g_ranges[1][1]) //Force string keys 159 | { 160 | forceString = g_ranges[1][1] - n; 161 | } 162 | else if (n >= g_ranges[2][0] && n <= g_ranges[2][1]) //Strum keys 163 | { 164 | for (i = 0; i < notes.length; i++) 165 | { 166 | if (notes[i] && eventIds.getValue(notes[i]) > 0) 167 | { 168 | Synth.noteOffByEventId(eventIds.getValue(notes[i])); 169 | eventIds.setValue(notes[i], 0); 170 | notes[i] = undefined; 171 | } 172 | } 173 | } 174 | else 175 | { 176 | if (n >= g_ranges[0][0] + capo && n <= g_ranges[0][1]) 177 | { 178 | //Get default string for note 179 | local s = g_map.getValue(n - capo); 180 | 181 | //Set direction 182 | if (cmbDirection.getValue() == 3) 183 | direction == 0 ? direction = 1 : direction = 0; 184 | else 185 | direction = cmbDirection.getValue()-1; 186 | 187 | //Set round robin - accounting for direction 188 | if (direction || cmbDirection.getValue() != 3) 189 | { 190 | rr = (lastRR.getValue(n) - 1 + Math.randInt(2, NUM_RR)) % NUM_RR; 191 | lastRR.setValue(n, rr); 192 | } 193 | 194 | if (knbLegatoRange.getValue() == 0) //Polyphonic 195 | { 196 | local v = d + (s * 4) + (direction * 2) + rr; 197 | local id = Synth.playNote(n + capo, v); 198 | eventIds.setValue(n, id); 199 | } 200 | else 201 | { 202 | //Forced string 203 | if (forceString != -1 && n >= g_tuning[forceString] && n <= (g_tuning[forceString] + notesPerString-3)) 204 | { 205 | s = forceString; 206 | } 207 | else 208 | { 209 | //Check for two notes on same string 210 | local sameString; 211 | if (notes[s]) 212 | sameString = (Engine.getUptime() - lastTimes[s]) < 0.025; 213 | else 214 | sameString = false; 215 | 216 | //For two notes on same string at same time, find the next available string 217 | if (sameString && Synth.getNumPressedKeys() > 1) 218 | { 219 | while (s < g_tuning.length-1 && notes[s]) 220 | { 221 | s++; 222 | } 223 | } 224 | 225 | //Two notes played together, on any strings 226 | local isChord = (Engine.getUptime() - lastTime) < 0.025; 227 | 228 | //Figure out legato string config 229 | if (!isChord && Synth.getNumPressedKeys() > 1) 230 | { 231 | local lastS = s; 232 | 233 | while (lastS < g_tuning.length-1 && !notes[lastS]) 234 | { 235 | lastS++; 236 | } 237 | 238 | if (notes[lastS] && n - notes[lastS] < knbLegatoRange.getValue()) 239 | s = lastS; 240 | } 241 | } 242 | 243 | //Calculate velocity, accounting for note, string, rr, and direction 244 | local v = d + (s * 4) + rr + (direction * 2); 245 | 246 | //Reset force string if no force key held 247 | if (forceString != -1 && !Synth.isKeyDown(g_ranges[1][1]-forceString)) 248 | forceString = -1; 249 | 250 | if (notes[s] && !sameString && Math.abs(n - notes[s]) <= knbLegatoRange.getValue()) //Phrase note 251 | { 252 | if (btnSlide.getValue() && notes[s] != n) //Slide 253 | { 254 | //Stop string's timer 255 | timers[s].stopTimer(); 256 | 257 | //Set interval 258 | intervals[s] = n - notes[s]; 259 | 260 | //Calculate slide decay 261 | slideDecays[s] = knbSlideDecay.getValue() / Math.abs(intervals[s]); 262 | 263 | //Set start and end note for timer 264 | timerOrigin[s] = notes[s]; 265 | timerTarget[s] = n; 266 | 267 | //Reset fail safe counter 268 | timerCounters[s] = 0; 269 | 270 | //Set slide rate 271 | setSlideRateFromVelocity(g_velocity); 272 | g_velocity = lastVelocity / 2; 273 | 274 | //Start timer 275 | timers[s].startTimer(knbSlideSpeed.getValue()); 276 | } 277 | else //Legato 278 | { 279 | local detune; 280 | 281 | //Calculate played interval 282 | intervals[s] = n - notes[s]; 283 | intervals[s] > 0 ? detune = -10 : detune = 10; 284 | 285 | //Play and fade in new note 286 | local newId = Synth.playNoteWithStartOffset(channel, n + capo, v, 5000); 287 | Synth.addVolumeFade(newId, 0, -99); 288 | Synth.addVolumeFade(newId, 20, 0); 289 | Synth.addPitchFade(newId, 0, 0, detune); //Set initial deg_tuning 290 | Synth.addPitchFade(newId, 20, 0, 0); //Fade to final pitch 291 | 292 | //Fade out old note 293 | local oldId = eventIds.getValue(notes[s]); 294 | 295 | if (oldId > 0) 296 | { 297 | //Adjust fade time if playing a retriggered note 298 | local fadeTm; 299 | retrigger[s] == n ? fadeTm = 20 : fadeTm = 300; 300 | 301 | Synth.addVolumeFade(oldId, fadeTm, -100); 302 | Synth.addPitchFade(oldId, 250, 0, -detune); //Fade to final pitch 303 | eventIds.setValue(notes[s], 0); 304 | 305 | eventIds.setValue(n, newId); 306 | retrigger[s] = notes[s]; 307 | } 308 | } 309 | } 310 | else 311 | { 312 | //Turn off old note 313 | if (notes[s] && eventIds.getValue(notes[s])) 314 | { 315 | Synth.noteOffByEventId(eventIds.getValue(notes[s])); 316 | eventIds.setValue(notes[s], 0); 317 | } 318 | 319 | local id = Synth.playNote(n + capo, v); 320 | eventIds.setValue(n, id); 321 | retrigger[s] = notes[s]; 322 | } 323 | 324 | notes[s] = n; //Update current note for string 325 | noteString.setValue(n, s); //Update current string for note 326 | } 327 | } 328 | 329 | if (s >= 0 && s !== undefined) 330 | { 331 | if (n + capo < g_tuning[s] + capo) 332 | g_strings[s] = undefined; 333 | else 334 | g_strings[s] = Math.abs(n - (g_tuning[s] + capo)); 335 | 336 | lastTimes[s] = Engine.getUptime(); //Track time per string 337 | } 338 | 339 | lastVelocity = g_velocity; 340 | lastTime = Engine.getUptime(); //Track time all strings 341 | } 342 | } 343 | } function onNoteOff() 344 | { 345 | local n = Message.getNoteNumber(); 346 | local s = noteString.getValue(n); 347 | local d = 1 + g_velocityLayer * g_tuning.length * 4; //Dynamic velocity level 348 | local capo = knbCapo.getValue(); 349 | 350 | //Force string keys 351 | if (n >= g_ranges[1][0] + capo && n <= g_ranges[1][1]) 352 | { 353 | forceString = -1; 354 | } 355 | else 356 | { 357 | if (!eventIds.isEmpty() && n >= g_ranges[0][0] + capo && n <= g_ranges[0][1]) 358 | { 359 | if (knbLegatoRange.getValue() > 0) 360 | { 361 | //Turn off released note 362 | if (eventIds.getValue(n) > 0 && !Synth.isSustainPedalDown()) 363 | { 364 | Synth.noteOffByEventId(eventIds.getValue(n)); 365 | eventIds.setValue(n, 0); 366 | } 367 | 368 | //If retrigger note is released, reset retrigger note 369 | if (n == retrigger[s] && !Synth.isSustainPedalDown()) 370 | retrigger[s] = undefined; 371 | else if (Synth.isSustainPedalDown()) 372 | retrigger[s] = n; 373 | 374 | //Handle Retrigger 375 | if (retrigger[s] && btnRetrigger.getValue() && n == notes[s]) 376 | { 377 | direction = 1-direction; 378 | local v = d + (s * 4) + (direction * 2) + rr; 379 | 380 | //Play retrigger note 381 | local id = Synth.playNoteWithStartOffset(Message.getChannel(), retrigger[s] + capo, v, 5000); 382 | eventIds.setValue(retrigger[s], id); 383 | 384 | //Update variables 385 | notes[s] = retrigger[s]; 386 | g_strings[s] = Math.abs(retrigger[s] - g_tuning[s]); 387 | } 388 | else if (notes[s] && eventIds.getValue(notes[s]) <= 0) 389 | { 390 | notes[s] = undefined; 391 | g_strings[s] = undefined; 392 | } 393 | 394 | if (!Synth.getNumPressedKeys()) 395 | timers[s].stopTimer(); 396 | } 397 | else //Polyphonic 398 | { 399 | if (eventIds.getValue(n) > 0 && (!Synth.isSustainPedalDown() || btnRetrigger.getValue())) 400 | { 401 | local id = eventIds.getValue(n); 402 | Synth.noteOffByEventId(id); 403 | eventIds.setValue(n, 0); 404 | } 405 | 406 | if (btnRetrigger.getValue() && Synth.isSustainPedalDown()) 407 | { 408 | s = g_map.getValue(n); 409 | direction = 1-direction; 410 | local v = d + (s * 4) + (direction * 2) + rr; 411 | 412 | Synth.playNote(n, v); 413 | } 414 | } 415 | } 416 | 417 | //All keys up 418 | if (!Synth.getNumPressedKeys() && !Synth.isSustainPedalDown()) 419 | { 420 | //Stop all timers 421 | for (i = 0; i < timers.length; i++) 422 | { 423 | timers[i].stopTimer(); 424 | } 425 | 426 | //Kill all notes 427 | for (i = 0; i < 128; i++) 428 | { 429 | if (eventIds.getValue(i) > 0) 430 | Synth.noteOffByEventId(eventIds.getValue(i)); 431 | } 432 | 433 | eventIds.clear(); 434 | notes.clear(); 435 | retrigger.clear(); 436 | 437 | if (!btnMute.getValue() && g_strings) 438 | g_strings.clear(); 439 | } 440 | } 441 | }function onController() 442 | { 443 | if (Message.getControllerNumber() == 64) 444 | { 445 | if (!btnMute.getValue()) 446 | Message.ignoreEvent(true); 447 | 448 | if (!Synth.isSustainPedalDown() && !Synth.getNumPressedKeys()) 449 | { 450 | eventIds.clear(); 451 | notes.clear(); 452 | 453 | if (!btnMute.getValue() && g_strings) 454 | g_strings.clear(); 455 | 456 | Engine.allNotesOff(); 457 | Synth.stopTimer(); 458 | } 459 | } 460 | } function onTimer() 461 | { 462 | 463 | } 464 | function onControl(number, value) 465 | { 466 | 467 | } 468 | -------------------------------------------------------------------------------- /modules/GuitarStrummer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019, 2020 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | Content.setWidth(750); 19 | Content.setHeight(250); 20 | //include("chordDictionary.js"); //Chord dictionary is required 21 | 22 | Synth.deferCallbacks(true); 23 | 24 | reg channel; 25 | reg direction = 0; 26 | reg stringCounter; 27 | reg startString; 28 | reg chordTones; 29 | reg strings = []; //One element per string 30 | reg timerNote; 31 | reg timerString; 32 | reg capo = 0; 33 | reg dynamic; 34 | const var ids = []; //One element per string 35 | 36 | reg rr = 0; 37 | const var NUM_RR = 2; 38 | const var lastRR = Engine.createMidiList(); 39 | lastRR.fill(0); 40 | 41 | /*GUI*/ 42 | 43 | //btnMute 44 | const var btnMute = Content.addButton("btnMute", 10, 10); 45 | btnMute.set("text", "Mute"); 46 | 47 | //cmbDirection 48 | const var cmbDirection = Content.addComboBox("cmbDirection", 160, 10); 49 | cmbDirection.set("items", ["Silent", "Down", "Up", "Alternate"].join("\n")); 50 | 51 | //knbFret 52 | const var knbFret = Content.addKnob("knbFret", 310, 0); 53 | knbFret.set("text", "Fret Position"); 54 | knbFret.setRange(0, 30, 1); 55 | 56 | //knbCapo 57 | const var knbCapo = Content.addKnob("knbCapo", 460, 0); 58 | knbCapo.set("text", "Capo"); 59 | knbCapo.setRange(0, 16, 1); 60 | 61 | //btnAutoPos - not implemented 62 | const var btnAutoPos = Content.addButton("btnAutoPos", 10, 60); 63 | btnAutoPos.set("text", "Auto Fret Position"); 64 | 65 | //knbStart 66 | const var knbStart = Content.addKnob("knbStart", 160, 50); 67 | knbStart.set("text", "Start String"); 68 | knbStart.setRange(0, 3, 1); 69 | 70 | //knbLength 71 | const var knbLength = Content.addKnob("knbLength", 310, 50); 72 | knbLength.set("text", "Strum Length"); 73 | knbLength.setRange(2, 6, 1); 74 | 75 | //knbDecay 76 | const var knbDecay = Content.addKnob("knbDecay", 460, 50); 77 | knbDecay.set("text", "Decay"); 78 | knbDecay.set("mode", "normalizedPercentage"); 79 | knbDecay.set("middlePosition", 0.5); 80 | 81 | //knbSpeed 82 | const var knbSpeed = Content.addKnob("knbSpeed", 610, 50); 83 | knbSpeed.set("text", "Speed"); 84 | knbSpeed.setRange(100, 600, 1); 85 | knbSpeed.set("mode", "Time"); 86 | knbSpeed.set("middlePosition", 150); 87 | 88 | inline function strum() 89 | { 90 | Synth.stopTimer(); 91 | 92 | local start = knbStart.getValue(); 93 | local end = start + knbLength.getValue(); 94 | 95 | stringCounter = 0; 96 | direction == 0 ? startString = g_tuning.length-1 : startString = start; 97 | 98 | Synth.startTimer(knbSpeed.getValue()/10000); 99 | }function onNoteOn() 100 | { 101 | if (!btnMute.getValue() && !Message.isArtificial()) 102 | { 103 | local n = Message.getNoteNumber(); 104 | dynamic = 1 + g_velocityLayer * g_tuning.length * 4; //Dynamic velocity level 105 | channel = Message.getChannel(); 106 | capo = knbCapo.getValue(); //Set capo variable 107 | 108 | if (n >= g_ranges[2][0] && n <= g_ranges[2][1]) //Strum keys 109 | { 110 | if (strings) 111 | { 112 | //Determine strum direction based on strum key 113 | if (n - g_ranges[2][0] == 0 || n - g_ranges[2][0] == 1) 114 | direction = 0; 115 | else if (n - g_ranges[2][0] == 2 || n - g_ranges[2][0] == 3) 116 | direction = 1; 117 | 118 | //Set round robin 119 | rr = (lastRR.getValue(n) - 1 + Math.randInt(2, NUM_RR)) % NUM_RR; 120 | lastRR.setValue(n, rr); 121 | 122 | //Muted samples only have 1 dynamic 123 | if (n == g_ranges[2][0]+1 || n == g_ranges[2][1]) 124 | dynamic = 1; 125 | 126 | g_strings = strings.clone(); 127 | strum(); 128 | } 129 | } 130 | else //Playable range and picking keys 131 | { 132 | //Determine strum/pick direction 133 | if (cmbDirection.getValue() != 1) 134 | { 135 | if (cmbDirection.getValue() == 4) 136 | direction = 1-direction; 137 | else 138 | direction = cmbDirection.getValue() - 2; 139 | } 140 | 141 | //Set round robin - accounting for direction 142 | if (direction == 1 || cmbDirection.getValue() != 4) 143 | { 144 | rr = (lastRR.getValue(n) - 1 + Math.randInt(2, NUM_RR)) % NUM_RR; 145 | lastRR.setValue(n, rr); 146 | } 147 | 148 | if (n >= g_ranges[0][0] && n <= g_ranges[0][1]) //Playable range 149 | { 150 | if (Synth.getNumPressedKeys() > 2) //At least 3 keys are pressed 151 | { 152 | Synth.stopTimer(); 153 | 154 | //Check for userChord 155 | strings = ChordDictionary.getUserChord(g_tuning, knbFret.getValue(), g_heldKeys); 156 | 157 | //If no userChord found check for chord in chordMap 158 | if (strings.length == 0) 159 | strings = ChordDictionary.getChordVoicing(g_heldKeys, knbFret.getValue(), false); 160 | 161 | //Strum the chord if direction is not set to Silent 162 | if (cmbDirection.getValue() != 1) 163 | { 164 | g_strings = strings.clone(); 165 | strum(); 166 | } 167 | } 168 | } 169 | else if (n >= g_ranges[1][0] && n <= g_ranges[1][1]) //Picking keys 170 | { 171 | local s = g_ranges[1][1] - n; 172 | local stringNote = g_tuning[s] + parseInt(strings[s]); 173 | 174 | //If chord string is unused, use open string 175 | if (stringNote < g_ranges[0][0]) 176 | stringNote = g_tuning[s]; 177 | 178 | if (ids[s]) 179 | { 180 | Synth.noteOffByEventId(ids[s]); 181 | ids[s] = undefined; 182 | } 183 | 184 | if (stringNote - g_ranges[0][0] > -1) 185 | { 186 | local v = dynamic + (s * 4) + (2 * direction) + rr; 187 | ids[s] = Synth.playNote(stringNote + capo, v); 188 | } 189 | } 190 | } 191 | } 192 | }function onNoteOff() 193 | { 194 | local n = Message.getNoteNumber(); 195 | 196 | if (!Message.isArtificial() && (!Synth.isSustainPedalDown() || cmbDirection.getValue())) 197 | { 198 | if (n >= g_ranges[1][0] && n <= g_ranges[1][1]) 199 | { 200 | local s = g_ranges[1][1] - n; 201 | 202 | if (ids[s]) 203 | Synth.noteOffByEventId(ids[s]); 204 | 205 | ids[s] = undefined; 206 | } 207 | 208 | if (!Synth.getNumPressedKeys() && !Synth.isSustainPedalDown()) 209 | { 210 | if (!btnMute.getValue()) 211 | g_strings = []; 212 | 213 | Synth.stopTimer(); 214 | 215 | for (i = 0; i < ids.length; i++) 216 | { 217 | if (ids[i]) 218 | { 219 | Synth.noteOffByEventId(ids[i]); 220 | ids[i] = undefined; 221 | } 222 | } 223 | } 224 | } 225 | } function onController() 226 | { 227 | if (!Synth.isSustainPedalDown() && !Synth.getNumPressedKeys()) 228 | { 229 | if (!btnMute.getValue()) 230 | g_strings = []; 231 | 232 | Synth.stopTimer(); 233 | 234 | for (i = 0; i < ids.length; i++) 235 | { 236 | if (ids[i]) 237 | { 238 | Synth.noteOffByEventId(ids[i]); 239 | ids[i] = undefined; 240 | } 241 | } 242 | } 243 | } 244 | function onTimer() 245 | { 246 | local s; 247 | 248 | if (stringCounter < g_tuning.length && stringCounter <= knbLength.getValue()) 249 | { 250 | if (!direction) //Down 251 | { 252 | s = startString - stringCounter; 253 | 254 | //Skip unplayed strings 255 | if (strings[s] == -1) 256 | { 257 | startString--; 258 | s--; 259 | } 260 | } 261 | else //Up 262 | { 263 | s = startString + stringCounter; 264 | 265 | //Skip unplayed strings 266 | if (strings[s] == -1) 267 | { 268 | startString++; 269 | s++; 270 | } 271 | } 272 | 273 | //Turn off old note 274 | if (ids[stringCounter]) 275 | { 276 | Synth.noteOffByEventId(ids[stringCounter]); 277 | ids[stringCounter] = undefined; 278 | } 279 | 280 | if (s > -1 && s < strings.length && strings[s] != -1) 281 | { 282 | //Get note number 283 | local n = g_tuning[s] + strings[s] + capo; 284 | 285 | //Play new note 286 | local v = dynamic + (4 * s) + rr + (2 * direction); 287 | ids[stringCounter] = Synth.playNote(n, v); 288 | 289 | //String decay 290 | local decay = (stringCounter) * knbDecay.getValue() * -10 / (g_tuning.length-1); 291 | Synth.addVolumeFade(ids[stringCounter], 0, decay); 292 | } 293 | } 294 | 295 | stringCounter++; //Count played strings 296 | 297 | if (stringCounter > g_tuning.length) 298 | Synth.stopTimer(); 299 | } function onControl(number, value) 300 | { 301 | 302 | } 303 | -------------------------------------------------------------------------------- /modules/HarpGliss.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | 18 | Content.setWidth(750); 19 | Content.setHeight(100); 20 | 21 | const var whiteKeys = [0, 2, 4, 5, 7, 9, 11]; //White keys 22 | 23 | reg eventIds = Engine.createMidiList(); 24 | reg origin; 25 | reg target; 26 | reg velocity; 27 | reg rate = 50; 28 | 29 | function gliss() 30 | { 31 | var oldId = eventIds.getValue(origin); 32 | var newId; 33 | 34 | //Turn off last gliss note 35 | if (oldId > 0) 36 | { 37 | Synth.noteOffByEventId(oldId); 38 | eventIds.setValue(origin, 0); 39 | } 40 | 41 | //Inc/Dec origin based on interval direction 42 | target > origin ? origin++ : origin--; 43 | 44 | //Snap origin to nearest white note 45 | if (whiteKeys.indexOf(origin % 12) == -1) 46 | target > origin ? origin++ : origin--; 47 | 48 | //Adjust for pedalling 49 | var note = origin + slpPedals.getSliderValueAt(whiteKeys.indexOf(origin % 12)); 50 | 51 | //Play new note 52 | newId = Synth.playNote(note, velocity); 53 | eventIds.setValue(note, newId); 54 | 55 | //Stop timer when gliss completed 56 | if (Synth.getNumPressedKeys() < 1 || origin == target) 57 | this.stopTimer(); 58 | } 59 | 60 | //Two timers - only one used but potential to add polyphonic gliss 61 | const var timers = []; 62 | for (i = 0; i < 2; i++) 63 | { 64 | timers[i] = Engine.createTimerObject(); 65 | timers[i].setTimerCallback(gliss); 66 | } 67 | 68 | /*GUI*/ 69 | 70 | //btnMute 71 | const var btnMute = Content.addButton("btnMute", 10, 10); 72 | btnMute.set("text", "Mute"); 73 | 74 | //knbRate 75 | const var knbRate = Content.addKnob("knbRate", 160, 0); 76 | knbRate.set("mode", "TempoSync"); 77 | 78 | inline function onknbRateControl(component, value) 79 | { 80 | rate = Engine.getMilliSecondsForTempo(value); 81 | }; 82 | 83 | Content.getComponent("knbRate").setControlCallback(onknbRateControl); 84 | 85 | //slpPedals 86 | const var slpPedals = Content.addSliderPack("slpPedals", 310, 10); 87 | slpPedals.set("height", 50); 88 | slpPedals.set("sliderAmount", 7); 89 | slpPedals.set("min", -1); 90 | slpPedals.set("max", 1); 91 | slpPedals.set("stepSize", 1); 92 | slpPedals.referToData(g_harpPedals); 93 | 94 | function onNoteOn() 95 | { 96 | if (!btnMute.getValue()) 97 | { 98 | local n = Message.getNoteNumber(); 99 | local velocity = Message.getVelocity(); 100 | if (velocity == 0) velocity = 1; 101 | 102 | local idx = whiteKeys.indexOf(n % 12); 103 | 104 | if (idx != -1) //White key triggered callback 105 | { 106 | if (origin && Synth.isLegatoInterval() && Math.abs(origin - n) > 2) 107 | { 108 | Message.ignoreEvent(true); 109 | 110 | target = n; 111 | timers[0].startTimer(rate); 112 | } 113 | else //First note of gliss 114 | { 115 | local v = slpPedals.getSliderValueAt(idx); 116 | eventIds.setValue(n + v, Message.makeArtificial()); 117 | Message.setNoteNumber(n + v); 118 | origin = n + v; 119 | } 120 | } 121 | else 122 | Message.ignoreEvent(true); 123 | } 124 | } function onNoteOff() 125 | { 126 | if (!btnMute.getValue()) 127 | { 128 | local idx = whiteKeys.indexOf(Message.getNoteNumber() % 12); 129 | 130 | if (idx != -1) 131 | { 132 | local n = Message.getNoteNumber() + slpPedals.getSliderValueAt(idx); 133 | 134 | if (eventIds.getValue(n) > 0) 135 | { 136 | Synth.noteOffByEventId(eventIds.getValue(n)); 137 | eventIds.setValue(n, 0); 138 | } 139 | } 140 | 141 | if (!Synth.getNumPressedKeys()) 142 | origin = undefined; 143 | } 144 | } 145 | function onController() 146 | { 147 | 148 | } 149 | function onTimer() 150 | { 151 | 152 | } 153 | function onControl(number, value) 154 | { 155 | 156 | } 157 | -------------------------------------------------------------------------------- /modules/Humaniser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018, 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with This file. If not, see . 16 | */ 17 | 18 | Content.setWidth(600); 19 | Content.setHeight(100); 20 | 21 | reg gain; 22 | reg pitch; 23 | reg velocity; 24 | reg offset; 25 | 26 | const var noteOnDelays = Engine.createMidiList(); 27 | noteOnDelays.fill(0); 28 | 29 | const knbGain = Content.addKnob("knbGain", 0, 0); 30 | knbGain.set("text", "Gain"); 31 | knbGain.setRange(0, 6, 1); 32 | knbGain.set("suffix", "dB"); 33 | 34 | const knbPitch = Content.addKnob("knbPitch", 150, 0); 35 | knbPitch.set("text", "Pitch"); 36 | knbPitch.setRange(0, 50, 1); 37 | knbPitch.set("suffix", "ct"); 38 | 39 | const knbVel = Content.addKnob("knbVel", 300, 0); 40 | knbVel.set("text", "Velocity"); 41 | knbVel.setRange(0, 25, 1); 42 | 43 | const knbOffset = Content.addKnob("knbOffset", 450, 0); 44 | knbOffset.set("text", "Offset"); 45 | knbOffset.setRange(0, 500, 1); 46 | knbOffset.set("suffix", "ms"); 47 | 48 | const knbNoteOn = Content.addKnob("knbNoteOn", 0, 50); 49 | knbNoteOn.set("text", "Note On"); 50 | knbNoteOn.setRange(0, 100, 1); 51 | knbNoteOn.set("suffix", "ms"); 52 | 53 | const knbNoteOff = Content.addKnob("knbNoteOff", 150, 50); 54 | knbNoteOff.set("text", "Note Off"); 55 | knbNoteOff.setRange(0, 100, 1); 56 | knbNoteOff.set("suffix", "ms"); 57 | 58 | function onNoteOn() 59 | { 60 | gain = 0; 61 | pitch = 0; 62 | velocity = 0; 63 | offset = 0; 64 | 65 | //Generate random values 66 | if (knbGain.getValue() != 0) gain = Math.randInt(-knbGain.getValue(), knbGain.getValue()+1); //Gain 67 | if (knbPitch.getValue() != 0) pitch = Math.randInt(-knbPitch.getValue(), knbPitch.getValue()+1); //Pitch 68 | if (knbVel.getValue != 0) velocity = Math.randInt(-knbVel.getValue(), knbVel.getValue()+1); //Velocity 69 | if (knbOffset.getValue() != 0) offset = Engine.getSamplesForMilliSeconds(Math.random()*knbOffset.getValue()); //Start offset 70 | 71 | Message.setGain(Message.getGain() + gain); 72 | Message.setFineDetune(Message.getFineDetune() + pitch); 73 | Message.setVelocity(Math.range(Message.getVelocity() + velocity, 1, 127); 74 | Message.setStartOffset(offset); 75 | 76 | //Note on delay 77 | if (knbNoteOn.getValue() > 0) 78 | { 79 | noteOnDelays.setValue(Message.getNoteNumber(), Math.random() * knbNoteOn.getValue()); 80 | Message.delayEvent(Engine.getSamplesForMilliSeconds(noteOnDelays.getValue(Message.getNoteNumber()))); 81 | } 82 | } 83 | function onNoteOff() 84 | { 85 | Message.setGain(Message.getGain() + gain); 86 | Message.setFineDetune(Message.getFineDetune() + pitch); 87 | Message.setVelocity(Math.range(Message.getVelocity() + velocity, 1, 127); 88 | Message.setStartOffset(offset); 89 | 90 | //Note delay 91 | if (knbNoteOn.getValue() != 0 || knbNoteOff.getValue() != 0) 92 | { 93 | local delay = 0; //Reset 94 | 95 | //Compensate for note on delay 96 | if (knbNoteOn.getValue() != 0) 97 | { 98 | delay = noteOnDelays.getValue(Message.getNoteNumber()); 99 | } 100 | 101 | //Note Off 102 | if (knbNoteOff.getValue() > 0) 103 | { 104 | delay += Math.random() * knbNoteOff.getValue(); //Note off delay 105 | } 106 | 107 | Message.delayEvent(Engine.getSamplesForMilliSeconds(delay)); 108 | } 109 | } 110 | function onController() 111 | { 112 | 113 | } 114 | function onTimer() 115 | { 116 | 117 | } 118 | function onControl(number, value) 119 | { 120 | 121 | } 122 | 123 | -------------------------------------------------------------------------------- /modules/KeyRangeColour.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: keyRangeColour.js 3 | * Author: David Healey 4 | * Date: 30/06/2017 5 | * Modified: 30/06/2017 6 | * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html 7 | */ 8 | 9 | //Init 10 | Content.setWidth(650); 11 | Content.setHeight(50); 12 | 13 | const var colours = [Colours.blue, Colours.cyan, Colours.yellow, Colours.green, Colours.red, Colours.purple, Colours.black, Colours.white, Colours.orange, Colours.pink]; 14 | var noteRange = []; 15 | 16 | //GUI 17 | const var knbLow = Content.addKnob("knbLow", 0, 0); 18 | knbLow.set("text", "Low Note"); 19 | knbLow.setRange(0, 127, 1); 20 | knbLow.set("defaultValue", 0); 21 | 22 | const var knbHigh = Content.addKnob("knbHigh", 150, 0); 23 | knbHigh.set("text", "High Note"); 24 | knbHigh.setRange(0, 127, 1); 25 | knbHigh.set("defaultValue", 1); 26 | 27 | const var cmbColour = Content.addComboBox("cmbColour", 300, 10); 28 | cmbColour.set("width", 100); 29 | cmbColour.set("text", "Colour"); 30 | cmbColour.set("items", ["Blue", "Cyan", "Yellow", "Green", "Red", "Purple", "Black", "White", "Orange", "Pink"].join("\n")); 31 | 32 | const var btnSkipBlack = Content.addButton("btnSkipBlack", 410, 10); 33 | btnSkipBlack.set("width", 100); 34 | btnSkipBlack.set("text", "Skip Black"); 35 | 36 | const var btnSkipWhite = Content.addButton("btnSkipWhite", 520, 10); 37 | btnSkipWhite.set("width", 100); 38 | btnSkipWhite.set("text", "Skip White"); 39 | 40 | const var btnIgnore = Content.addButton("btnIgnore", 630, 10); 41 | btnIgnore.set("width", 100); 42 | btnIgnore.set("text", "Ignore Events"); 43 | 44 | //Functions 45 | inline function inRange(value, min, max) 46 | { 47 | return value >= min && value <= max; 48 | } 49 | 50 | inline function isBlackKey(n) 51 | { 52 | var blackKeys = [1, 3, 6, 8, 10]; //Black keys in one octave assuming C = 0 53 | 54 | if (blackKeys.indexOf(parseInt(n) % 12) != -1) 55 | { 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | inline function updateKeyboardColours() 62 | { 63 | local i; 64 | 65 | //Reset old range key colours 66 | for (i = noteRange[0]; i <= noteRange[1]; i++) 67 | { 68 | Engine.setKeyColour(i, Colours.withAlpha(Colours.white, 0.0)); 69 | } 70 | 71 | //Set new range key colours 72 | for (i = knbLow.getValue(); i <= knbHigh.getValue(); i++) 73 | { 74 | if (btnSkipBlack.getValue() == 1 && isBlackKey(i)) continue; 75 | if (btnSkipWhite.getValue() == 1 && !isBlackKey(i)) continue; 76 | 77 | Engine.setKeyColour(i, Colours.withAlpha(colours[cmbColour.getValue()-1], 0.38)); 78 | } 79 | } 80 | 81 | //Callbacks 82 | function onNoteOn() 83 | { 84 | if (btnIgnore.getValue()) 85 | { 86 | if (!inRange(Message.getNoteNumber(), noteRange[0], noteRange[1]) || (btnSkipBlack.getValue() == 1 && isBlackKey(Message.getNoteNumber())) || (btnSkipWhite.getValue() == 1 && !isBlackKey(Message.getNoteNumber()))) 87 | { 88 | Message.ignoreEvent(true); 89 | } 90 | } 91 | } 92 | 93 | function onNoteOff() 94 | { 95 | if (btnIgnore.getValue()) 96 | { 97 | if (!inRange(Message.getNoteNumber(), noteRange[0], noteRange[1]) || (btnSkipBlack.getValue() == 1 && isBlackKey(Message.getNoteNumber())) || (btnSkipWhite.getValue() == 1 && !isBlackKey(Message.getNoteNumber()))) 98 | { 99 | Message.ignoreEvent(true); 100 | } 101 | } 102 | } 103 | 104 | function onController() 105 | { 106 | } 107 | 108 | function onTimer() 109 | { 110 | } 111 | 112 | function onControl(number, value) 113 | { 114 | switch (number) 115 | { 116 | case knbLow: 117 | updateKeyboardColours(); 118 | noteRange[0] = value; 119 | if (value > knbHigh.getValue()) knbHigh.setValue(value); 120 | break; 121 | 122 | case knbHigh: 123 | updateKeyboardColours(); 124 | noteRange[1] = value; 125 | if (value < knbLow.getValue()) knbLow.setValue(value); 126 | break; 127 | 128 | case cmbColour: 129 | updateKeyboardColours(); 130 | break; 131 | 132 | case btnSkipBlack: 133 | btnSkipWhite.setValue(0); 134 | updateKeyboardColours(); 135 | break; 136 | 137 | case btnSkipWhite: 138 | btnSkipBlack.setValue(0); 139 | updateKeyboardColours(); 140 | break; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /modules/MappingOffset.js: -------------------------------------------------------------------------------- 1 | // knbVelocity 2 | const knbVelocity = Content.addKnob("Velocity", 0, 0); 3 | knbVelocity.set("text", "Velocity"); 4 | knbVelocity.setRange(0, 127, 1); 5 | 6 | // knbNoteOffset 7 | const knbNoteOffset = Content.addKnob("NoteOffset", 150, 0); 8 | knbNoteOffset.set("text", "Note Offset"); 9 | knbNoteOffset.setRange(-127, 127, 1);function onNoteOn() 10 | { 11 | local n = Message.getNoteNumber(); 12 | 13 | if (knbVelocity.getValue() > 0) 14 | Message.setVelocity(knbVelocity.getValue()); 15 | 16 | if (knbNoteOffset.getValue() != 0) 17 | Message.setNoteNumber(n + knbNoteOffset.getValue()); 18 | } 19 | function onNoteOff() 20 | { 21 | 22 | } 23 | function onController() 24 | { 25 | 26 | } 27 | function onTimer() 28 | { 29 | 30 | } 31 | function onControl(number, value) 32 | { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /modules/MonoMode.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright © 2017, 2018, 2019, 2020, 2021, 2022 David Healey 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this file (the “Software”), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished 11 | to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | Content.setWidth(650); 25 | Content.setHeight(50); 26 | 27 | reg lastNote = -1; 28 | reg retrigger = -1; 29 | reg eventId; 30 | reg lastTuning = 0; 31 | 32 | //GUI 33 | const var bypass = Content.addButton("Bypass", 10, 10); 34 | 35 | const var time = Content.addKnob("Time", 160, 0); 36 | time.setRange(0, 2000, 0.01); 37 | 38 | function onNoteOn() 39 | { 40 | if (!bypass.getValue()) 41 | { 42 | if (lastNote == -1) 43 | { 44 | lastNote = Message.getNoteNumber(); 45 | eventId = Message.makeArtificial(); 46 | } 47 | else 48 | { 49 | if (time.getValue() > 0 && eventId != -1) 50 | { 51 | Message.ignoreEvent(true); 52 | Synth.addPitchFade(eventId, time.getValue(), lastTuning + Message.getNoteNumber()-lastNote, 0); 53 | lastTuning = lastTuning + Message.getNoteNumber()-lastNote; 54 | } 55 | else 56 | { 57 | if (eventId != -1) Synth.noteOffByEventId(eventId); 58 | 59 | eventId = Message.makeArtificial(); 60 | } 61 | retrigger = lastNote; 62 | lastNote = Message.getNoteNumber(); 63 | } 64 | } 65 | } 66 | 67 | function onNoteOff() 68 | { 69 | if (!bypass.getValue()) 70 | { 71 | Message.ignoreEvent(true); 72 | 73 | if (eventId != -1 && Message.getNoteNumber() == lastNote) 74 | { 75 | if (Synth.isKeyDown(retrigger)) 76 | { 77 | Synth.addPitchFade(eventId, time.getValue(), 0, 0); 78 | lastTuning = 0; 79 | lastNote = retrigger; 80 | retrigger = -1; 81 | } 82 | else 83 | { 84 | Synth.noteOffByEventId(eventId); 85 | eventId = -1; 86 | } 87 | } 88 | 89 | if (!Synth.getNumPressedKeys()) 90 | { 91 | lastNote = -1; 92 | lastTuning = 0; 93 | } 94 | } 95 | else if (eventId != -1 && eventId != undefined) 96 | { 97 | Synth.noteOffByEventId(eventId); 98 | eventId = -1; 99 | lastNote = -1; 100 | lastTuning = 0; 101 | } 102 | } 103 | 104 | function onController() 105 | { 106 | 107 | } 108 | function onTimer() 109 | { 110 | 111 | } 112 | function onControl(number, value) 113 | { 114 | 115 | } -------------------------------------------------------------------------------- /modules/MultiCCMapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: multiCCMapper.js 3 | * Author: David Healey 4 | * Date: 30/06/2017 5 | * Modified: 30/06/2017 6 | * License: Public Domain 7 | */ 8 | Content.setWidth(650); 9 | Content.setHeight(275); 10 | 11 | const var COUNT = 15; //The number of CCs you want to remap 12 | 13 | const var origin = Content.addSliderPack("sliOrigin", 0, 0); 14 | const var target = Content.addSliderPack("sliTarget", 260, 0); 15 | const var ccValue = Content.addSliderPack("sliValue", 520, 0); 16 | 17 | origin.set("sliderAmount", COUNT); 18 | target.set("sliderAmount", COUNT); 19 | ccValue.set("sliderAmount", COUNT); 20 | origin.set("min", 0); 21 | target.set("min", 0); 22 | ccValue.set("min", 0); 23 | origin.set("max", 127); 24 | target.set("max", 127); 25 | ccValue.set("max", 127); 26 | origin.set("stepSize", 1); 27 | target.set("stepSize", 1); 28 | ccValue.set("stepSize", 1); 29 | origin.set("width", 250); 30 | target.set("width", 250); 31 | ccValue.set("width", 250); 32 | origin.set("height", 200); 33 | target.set("height", 200); 34 | ccValue.set("height", 200); 35 | origin.set("flashActive", false); 36 | target.set("flashActive", false); 37 | ccValue.set("flashActive", false); 38 | 39 | const var lblOrigin = Content.addLabel("Origin", 105, 225); 40 | const var lblTarget = Content.addLabel("Target", 365, 225); 41 | const var lblValue = Content.addLabel("Value", 625, 225); 42 | 43 | function onNoteOn() 44 | { 45 | } 46 | 47 | function onNoteOff() 48 | { 49 | } 50 | 51 | function onController() 52 | { 53 | for (i = 0; i < origin.getNumSliders(); i++) 54 | { 55 | if (origin.getSliderValueAt(i) == 0) continue; //0 = not used 56 | 57 | if (Message.getControllerNumber() == origin.getSliderValueAt(i)) 58 | { 59 | Message.ignoreEvent(true); 60 | if (target.getSliderValueAt(i) > 0) 61 | { 62 | Synth.sendController(target.getSliderValueAt(i), Message.getControllerValue()); 63 | ccValue.setSliderAtIndex(i, Message.getControllerValue()); 64 | } 65 | } 66 | } 67 | } 68 | 69 | function onTimer() 70 | { 71 | } 72 | 73 | function onControl(number, value) 74 | { 75 | if (number == ccValue) 76 | { 77 | if (target.getSliderValueAt(value) > 0) 78 | { 79 | Synth.sendController(target.getSliderValueAt(value), ccValue.getSliderValueAt(value)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /modules/OctaveDoubler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 David Healey 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | const var ids = Engine.createMidiList(); 24 | 25 | ids.fill(false);function onNoteOn() 26 | { 27 | if (Message.getNoteNumber()+12 < 128) 28 | { 29 | if (ids.getValue(Message.getNoteNumber()) != false) 30 | { 31 | Synth.noteOffByEventId(ids.getValue(Message.getNoteNumber())); 32 | ids.setValue(Message.getNoteNumber(), false); 33 | } 34 | 35 | if (!Synth.isKeyDown(Message.getNoteNumber()+12)) 36 | ids.setValue(Message.getNoteNumber()+12, Synth.playNote(Message.getNoteNumber()+12, Message.getVelocity())); 37 | } 38 | } 39 | function onNoteOff() 40 | { 41 | if (Message.getNoteNumber()+12 < 128) 42 | { 43 | if (ids.getValue(Message.getNoteNumber()+12) != false) 44 | { 45 | Synth.noteOffByEventId(ids.getValue(Message.getNoteNumber()+12)); 46 | ids.setValue(Message.getNoteNumber()+12, false); 47 | } 48 | } 49 | } 50 | function onController() 51 | { 52 | 53 | } 54 | function onTimer() 55 | { 56 | 57 | } 58 | function onControl(number, value) 59 | { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /modules/PortGlide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: portGlide.js 3 | * Author: David Healey 4 | * Date: 30/06/2017 5 | * Modified: 03/12/2017 6 | * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html 7 | */ 8 | 9 | reg note = -1; 10 | reg noteId = -1; 11 | reg origin = -1; 12 | reg target = -1; //Last note of the run 13 | reg velocity; //Velocity of the target note 14 | reg rate; //Tempo - taken from the host - to play the glide at. 15 | reg interval; 16 | 17 | //Get all child sample start constant modulators 18 | const var modulatorNames = Synth.getIdList("Constant"); //Get child constant modulator names 19 | const var startModulators = []; //For offsetting sample start position 20 | 21 | for (modName in modulatorNames) 22 | { 23 | if (Engine.matchesRegex(modName, "(?=.*ample)(?=.*tart)")) 24 | { 25 | startModulators.push(Synth.getModulator(modName)); 26 | } 27 | } 28 | 29 | //GUI 30 | Content.setWidth(650); 31 | Content.setHeight(100); 32 | 33 | const var btnMute = Content.addButton("btnMute", 0, 10); 34 | btnMute.set("text", "Mute"); 35 | 36 | const var knbOffset = Content.addKnob("knbOffset", 150, 0); 37 | knbOffset.setRange(0, 1, 0.01); 38 | knbOffset.set("text", "Offset"); 39 | 40 | const var knbRate = Content.addKnob("knbRate", 300, 0); 41 | knbRate.set("mode", "TempoSync"); 42 | knbRate.set("text", "Rate"); 43 | knbRate.set("max", 11); 44 | 45 | const var btnWholeStep = Content.addButton("btnWholeStep", 450, 10); 46 | btnWholeStep.set("text", "Whole Step"); 47 | 48 | const var knbMinInt = Content.addKnob("knbMinInt", 0, 50); 49 | knbMinInt.setRange(1, 24, 1); 50 | knbMinInt.set("text", "Min Interval"); 51 | knbMinInt.set("suffix", "st"); 52 | 53 | const var knbMaxInt = Content.addKnob("knbMaxInt", 150, 50); 54 | knbMaxInt.setRange(1, 36, 1); 55 | knbMaxInt.set("text", "Max Interval"); 56 | knbMaxInt.set("suffix", "st"); 57 | 58 | const var knbBendAmount = Content.addKnob("knbBendAmount", 300, 50); 59 | knbBendAmount.setRange(0, 100, 1); 60 | knbBendAmount.set("text", "Bend Amount"); 61 | knbBendAmount.set("suffix", "Ct"); 62 | 63 | inline function getRate(range, velocity) 64 | { 65 | reg rate = knbRate.getValue(); //Get rate knob value 66 | 67 | //If rate knob is set to the maximum then the actual rate will be determined by velocity 68 | if (rate == knbRate.get("max")) 69 | { 70 | rate = Math.floor((velocity / (knbRate.get("max") - 1))); 71 | 72 | if (rate > knbRate.get("max")) rate = max-1; //Cap rate at max rate 73 | } 74 | 75 | rate = (Engine.getMilliSecondsForTempo(rate) / 1000) / range; //Get rate based on host tempo and selected knob value 76 | 77 | if (rate < 0.04) rate = 0.04; //Cap lowest rate at timer's minimum 78 | 79 | return rate; 80 | } 81 | 82 | inline function setOffsetIntensity(value) 83 | { 84 | if (startModulators.length > 0) 85 | { 86 | for (mod in startModulators) 87 | { 88 | mod.setIntensity(value); 89 | } 90 | } 91 | } 92 | 93 | function onNoteOn() 94 | { 95 | if (!btnMute.getValue()) 96 | { 97 | Synth.stopTimer(); 98 | 99 | Message.ignoreEvent(true); 100 | 101 | if (origin != -1) interval = Math.abs(origin - Message.getNoteNumber()); //Calculate interval 102 | 103 | //In interval range or no note played yet 104 | if ((interval >= knbMinInt.getValue() && interval <= knbMaxInt.getValue()) || noteId == -1) 105 | { 106 | if (noteId != -1 && origin != -1) 107 | { 108 | setOffsetIntensity(knbOffset.getValue()); 109 | 110 | note = origin; 111 | target = Message.getNoteNumber(); 112 | velocity = Message.getVelocity(); 113 | 114 | rate = getRate(Math.abs(origin - target), velocity); 115 | 116 | Synth.startTimer(rate); 117 | } 118 | else if (origin == -1) //Origin has not yet been played 119 | { 120 | setOffsetIntensity(1); 121 | origin = Message.getNoteNumber(); 122 | noteId = Synth.playNote(origin, Message.getVelocity()); //Play first note 123 | } 124 | } 125 | } 126 | } 127 | 128 | function onNoteOff() 129 | { 130 | if (!btnMute.getValue()) 131 | { 132 | if (Message.getNoteNumber() == origin || Message.getNoteNumber() == target) 133 | { 134 | Message.ignoreEvent(true); 135 | 136 | if (Message.getNoteNumber() == target) //Target released 137 | { 138 | Synth.stopTimer(); 139 | if (noteId != -1) Synth.noteOffByEventId(noteId); 140 | origin = -1; 141 | noteId = -1; 142 | target = -1; 143 | } 144 | 145 | if (!Synth.isKeyDown(origin) && !Synth.isKeyDown(target) && noteId != -1) //Both keys have been lifted 146 | { 147 | Synth.stopTimer(); 148 | Synth.noteOffByEventId(noteId); 149 | noteId = -1; 150 | origin = -1; 151 | target = -1; 152 | } 153 | } 154 | } 155 | else 156 | { 157 | //Turn off hanging note, if any 158 | if (noteId != -1) 159 | { 160 | Synth.noteOffByEventId(noteId); 161 | noteId = -1; 162 | origin = -1; 163 | target = -1; 164 | setOffsetIntensity(1); //Reset sample start offset 165 | } 166 | } 167 | } 168 | 169 | function onController() 170 | { 171 | } 172 | 173 | function onTimer() 174 | { 175 | if (!btnMute.getValue()) 176 | { 177 | target > origin ? note++ : note--; //Increment/decrement the note number by 1 (a half step) 178 | 179 | //If the whole step button is enabled then increment/decrement the note by another half step 180 | if (btnWholeStep.getValue()) 181 | { 182 | target > origin ? note++ : note--; 183 | } 184 | 185 | if (noteId != -1 && origin != -1 && ((target > origin && note <= target) || (target < origin && note >= target))) 186 | { 187 | bendAmount = knbBendAmount.getValue(); 188 | 189 | if (origin > target) bendAmount = -bendAmount; 190 | 191 | Synth.addVolumeFade(noteId, rate*1000, -100); //Fade out old note 192 | Synth.addPitchFade(noteId, rate*1000, 0, bendAmount); //Pitch fade old note to bend amount 193 | 194 | noteId = Synth.playNote(note, velocity); //Play new note and store its ID 195 | 196 | Synth.addVolumeFade(noteId, 0, -99); //Set new note's initial volume 197 | Synth.addVolumeFade(noteId, rate*1000, 0); //Fade in new note 198 | Synth.addPitchFade(noteId, 0, 0, -bendAmount); //Set new note's initial detuning 199 | Synth.addPitchFade(noteId, rate*1000, 0, 0); //Pitch fade new note to 0 200 | 201 | } 202 | else 203 | { 204 | origin = target; 205 | note = target; 206 | Synth.stopTimer(); 207 | } 208 | } 209 | } 210 | 211 | function onControl(number, value) 212 | { 213 | switch (number) 214 | { 215 | case btnMute: 216 | 217 | origin = -1; 218 | target = -1; 219 | 220 | Synth.stopTimer(); 221 | setOffsetIntensity(1); 222 | break; 223 | 224 | case knbRate: 225 | //If timer's already started then update its Rate 226 | if (Synth.isTimerRunning() == 1) 227 | { 228 | rate = getRate(Math.abs(origin - target), velocity); 229 | Synth.startTimer(rate); 230 | } 231 | 232 | knbRate.set("text", "Rate"); //Default 233 | if (knbRate.getValue() == knbRate.get("max")) 234 | { 235 | knbRate.set("text", "Velocity"); 236 | } 237 | break; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /modules/RoundRobinBypassHandler.js: -------------------------------------------------------------------------------- 1 | const var scriptIds = Synth.getIdList("Script Processor"); 2 | const var rrScripts = []; 3 | 4 | for (id in scriptIds) 5 | { 6 | if (id.indexOf("roundRobin") != -1) 7 | rrScripts.push(Synth.getMidiProcessor(id)); 8 | } 9 | 10 | const var btnBypass = Content.addButton("btnBypass", 10, 10); 11 | btnBypass.set("text", "Bypass"); 12 | btnBypass.setControlCallback(onbtnBypassControl); 13 | 14 | inline function onbtnBypassControl(component, value) 15 | { 16 | for (i = 0; i < rrScripts.length; i++) 17 | { 18 | rrScripts[i].setAttribute(rrScripts[i].btnBypass, 1-value); 19 | } 20 | }function onNoteOn() 21 | { 22 | 23 | } 24 | function onNoteOff() 25 | { 26 | 27 | } 28 | function onController() 29 | { 30 | 31 | } 32 | function onTimer() 33 | { 34 | 35 | } 36 | function onControl(number, value) 37 | { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /modules/SamplerActivator.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 David Healey 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 11 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 12 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 13 | IN THE SOFTWARE. 14 | */ 15 | Content.setHeight(100); 16 | 17 | const var UACC = 32; 18 | 19 | const var btnActive = Content.addButton("btnActive", 10, 10); 20 | btnActive.set("text", "Active"); 21 | btnActive.set("defaultValue", 1); 22 | 23 | const var knbKs = Content.addKnob("knbKs", 160, 0); 24 | knbKs.setRange(-1, 127, 1); 25 | knbKs.set("text", "KS"); 26 | 27 | const var knbProgram = Content.addKnob("knbProgram", 310, 0); 28 | knbProgram.setRange(-1, 127, 1); 29 | knbProgram.set("text", "Program"); 30 | 31 | const var btnToggle = Content.addButton("btnToggle", 460, 10); 32 | btnToggle.set("text", "Toggle"); 33 | 34 | const var knbLoKs = Content.addKnob("knbLoKs", 0, 45); 35 | knbLoKs.setRange(0, 127, 1); 36 | knbLoKs.set("text", "Lowest Ks"); 37 | 38 | const var knbHiKs = Content.addKnob("knbHiKs", 160, 45); 39 | knbHiKs.setRange(0, 127, 1); 40 | knbHiKs.set("text", "Highest Ks");function onNoteOn() 41 | { 42 | if (!btnActive.getValue()) 43 | Message.ignoreEvent(true); 44 | 45 | if (knbKs.getValue() != -1 && Message.getNoteNumber() == knbKs.getValue()) 46 | { 47 | if (!btnToggle.getValue() || Message.getVelocity() > 64) 48 | btnActive.setValue(1); 49 | else 50 | btnActive.setValue(0); 51 | } 52 | else if (Message.getNoteNumber() >= knbLoKs.getValue() && Message.getNoteNumber() <= knbHiKs.getValue()) 53 | btnActive.setValue(0); 54 | } 55 | function onNoteOff() 56 | { 57 | 58 | } 59 | function onController() 60 | { 61 | if (knbProgram.getValue() != -1 && (Message.getControllerNumber() == UACC || Message.isProgramChange())) 62 | { 63 | local v; 64 | 65 | Message.getControllerNumber() == UACC ? v = Message.getControllerValue() : v = Message.getProgramChangeNumber(); 66 | 67 | if (v == knbProgram.getValue()) 68 | btnActive.setValue(1); 69 | else 70 | btnActive.setValue(0); 71 | } 72 | } 73 | function onTimer() 74 | { 75 | 76 | } 77 | function onControl(number, value) 78 | { 79 | 80 | } 81 | -------------------------------------------------------------------------------- /modules/SimpleKeyswitcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: Simple Keyswitcher.js 3 | * Author: David Healey 4 | * Date: 11/10/2020 5 | * License: Public Domain (CC0) 6 | */ 7 | 8 | //Get first sampler 9 | const var synthIds = Synth.getIdList("Sampler"); 10 | const var sampler = Synth.getSampler(synthIds[0]) 11 | 12 | //Disable default round robin 13 | sampler.enableRoundRobin(false); 14 | 15 | reg ks = []; 16 | reg currentGroup = 1; 17 | 18 | //GUI 19 | const var Groups = Content.addKnob("Groups", 0, 0); 20 | Groups.setRange(0, 50, 1); 21 | Groups.set("text", "Num Groups"); 22 | Groups.setControlCallback(update); 23 | 24 | const var FirstKs = Content.addKnob("FirstKs", 150, 0); 25 | FirstKs.setRange(0, 127, 1); 26 | FirstKs.set("defaultValue", 24); 27 | FirstKs.set("text", "First KS"); 28 | FirstKs.setControlCallback(update); 29 | 30 | inline function update(component, value) 31 | { 32 | //Clear old keyswitches 33 | for (i = 0; i < ks.length; i++) 34 | Engine.setKeyColour(ks[i], Colours.transparentWhite); 35 | 36 | //Get new keyswitches 37 | ks = []; 38 | for (i = 0; i < Groups.getValue(); i++) 39 | { 40 | ks[i] = FirstKs.getValue() + i; 41 | Engine.setKeyColour(ks[i], Colours.withAlpha(Colours.red, 0.3)); 42 | } 43 | }; 44 | function onNoteOn() 45 | { 46 | local n = Message.getNoteNumber(); 47 | 48 | if (ks.contains(n)) 49 | currentGroup = ks.indexOf(n) + 1; 50 | 51 | sampler.setActiveGroup(currentGroup); 52 | } 53 | function onNoteOff() 54 | { 55 | 56 | } 57 | function onController() 58 | { 59 | 60 | } 61 | function onTimer() 62 | { 63 | 64 | } 65 | function onControl(number, value) 66 | { 67 | 68 | } 69 | -------------------------------------------------------------------------------- /modules/SyntheticRelease.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: syntheticRelease.js 3 | * Author: David Healey 4 | * Date: 07/09/2017 5 | * Modified: 07/09/2017 6 | * License: Public Domain 7 | */ 8 | 9 | //INIT 10 | Content.setWidth(650); 11 | Content.setHeight(50); 12 | 13 | const var envelopeNames = Synth.getIdList("Table Envelope"); //Get all child table envelopes 14 | reg pitchEnvelope; 15 | 16 | //Get pitch envelope 17 | for (envelope in envelopeNames) 18 | { 19 | if (Engine.matchesRegex(envelope, "(?=.*itch)(?=.*elease)") == true) //Pitch Release envelope 20 | { 21 | pitchEnvelope = Synth.getModulator(envelope); 22 | } 23 | } 24 | 25 | const var knbPitch = Content.addKnob("Pitch", 0, 0); 26 | knbPitch.setRange(-1, 1, 0.01); 27 | 28 | const var knbReleaseTime = Content.addKnob("Release Time", 150, 0); 29 | knbReleaseTime.setRange(1, 50, 0.01); 30 | 31 | const var btnLegato = Content.addButton("Legato", 300, 10); 32 | 33 | //FUNCTIONS 34 | 35 | //CALLBACKS 36 | function onNoteOn() 37 | { 38 | } 39 | 40 | function onNoteOff() 41 | { 42 | if (btnLegato.getValue() == 1) 43 | { 44 | if (Globals.keyCount == 0) //All keys up 45 | { 46 | pitchEnvelope.setIntensity(knbPitch.getValue()); //Set pitch adjustment 47 | } 48 | else 49 | { 50 | pitchEnvelope.setIntensity(0); //No pitch adjustment 51 | } 52 | } 53 | } 54 | 55 | function onController() 56 | { 57 | } 58 | 59 | function onTimer() 60 | { 61 | } 62 | 63 | function onControl(number, value) 64 | { 65 | switch (number) 66 | { 67 | case knbPitch: 68 | pitchEnvelope.setIntensity(value); 69 | break; 70 | 71 | case knbReleaseTime: 72 | pitchEnvelope.setAttribute(1, value); 73 | break; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /modules/TrillMaker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: trillMaker.js 3 | * Author: David Healey 4 | * Date: 07/09/2017 5 | * Modified: 07/09/2017 6 | * License: Public Domain 7 | */ 8 | 9 | reg velocity; //Velocity of the notes[1] note 10 | reg count; //Note number to be played - in the range of the and notes[1] 11 | reg rate; //Tempo - taken from the host - to play the glide at. 12 | reg interval; 13 | reg notes = [-1, -1]; 14 | reg noteId = -1; 15 | reg stopTimerFlag = 0; 16 | reg microTuning; 17 | 18 | //Get all child sample start constant modulators 19 | const var modulatorNames = Synth.getIdList("Constant"); //Get child constant modulator names 20 | const var startModulators = []; //For offsetting sample start position 21 | 22 | for (modName in modulatorNames) 23 | { 24 | if (Engine.matchesRegex(modName, "(?=.*ample)(?=.*tart)")) 25 | { 26 | startModulators.push(Synth.getModulator(modName)); 27 | } 28 | } 29 | 30 | //GUI 31 | Content.setWidth(650); 32 | Content.setHeight(100); 33 | 34 | const var btnMute = Content.addButton("btnMute", 0, 10); 35 | btnMute.set("text", "Mute"); 36 | 37 | const var knbOffset = Content.addKnob("knbOffset", 150, 0); 38 | knbOffset.setRange(0, 1, 0.01); 39 | knbOffset.set("text", "Offset"); 40 | 41 | const var knbRate = Content.addKnob("knbRate", 300, 0); 42 | knbRate.set("mode", "TempoSync"); 43 | knbRate.set("text", "Rate"); 44 | 45 | const var knbMinInt = Content.addKnob("knbMinInt", 0, 50); 46 | knbMinInt.setRange(1, 11, 1); 47 | knbMinInt.set("text", "Min Interval"); 48 | knbMinInt.set("suffix", "st"); 49 | 50 | const var knbMaxInt = Content.addKnob("knbMaxInt", 150, 50); 51 | knbMaxInt.setRange(1, 12, 1); 52 | knbMaxInt.set("text", "Max Interval"); 53 | knbMaxInt.set("suffix", "st"); 54 | 55 | const var knbMinBendAmount = Content.addKnob("knbMinBendAmount", 300, 50); 56 | knbMinBendAmount.setRange(0, 100, 1); 57 | knbMinBendAmount.set("text", "Min Bend"); 58 | knbMinBendAmount.set("suffix", "Ct"); 59 | 60 | const var knbMaxBendAmount = Content.addKnob("knbMaxBendAmount", 450, 50); 61 | knbMaxBendAmount.setRange(1, 100, 1); 62 | knbMaxBendAmount.set("text", "Max Bend"); 63 | knbMaxBendAmount.set("suffix", "Ct"); 64 | 65 | //Microtuning knob 66 | const var knbMicroTuning = Content.addKnob("Microtuning", 600, 50); 67 | knbMicroTuning.setRange(0, 100, 0.1); 68 | 69 | inline function getRate() 70 | { 71 | rate = Engine.getMilliSecondsForTempo(knbRate.getValue()) / 1000; 72 | if (rate < 0.04) rate = 0.04; 73 | return rate; 74 | } 75 | 76 | inline function calculateBendAmount(lastNote, noteNumber) 77 | { 78 | local bendAmount; 79 | reg interval = Math.abs(lastNote - noteNumber); 80 | 81 | bendAmount = (((interval - 1) * (knbMaxBendAmount.getValue() - knbMinBendAmount.getValue())) / (12 - 1)) + knbMinBendAmount.getValue(); 82 | 83 | if (lastNote > noteNumber) bendAmount = -bendAmount; //Down interval - invert the bend amount 84 | 85 | return bendAmount; 86 | } 87 | 88 | inline function setOffsetIntensity(value) 89 | { 90 | if (startModulators.length > 0) 91 | { 92 | for (mod in startModulators) 93 | { 94 | mod.setIntensity(value); 95 | } 96 | } 97 | } 98 | 99 | function onNoteOn() 100 | { 101 | if (!btnMute.getValue()) 102 | { 103 | Synth.stopTimer(); 104 | 105 | Message.ignoreEvent(true); 106 | 107 | if (notes[0] != -1) interval = Math.abs(notes[0] - Message.getNoteNumber()); //Calculate interval 108 | 109 | //In interval range or no note played yet 110 | if ((interval >= knbMinInt.getValue() && interval <= knbMaxInt.getValue()) || noteId == -1) 111 | { 112 | if (noteId != -1 && notes[0] != -1) 113 | { 114 | setOffsetIntensity(knbOffset.getValue()); 115 | 116 | count = 0; 117 | notes[1] = Message.getNoteNumber(); 118 | velocity = Message.getVelocity(); 119 | 120 | bendAmount = calculateBendAmount(notes[0], notes[1]); 121 | rate = getRate(); 122 | 123 | Synth.startTimer(rate); 124 | } 125 | else if (notes[0] == -1) //notes[0] has not yet been played - first note 126 | { 127 | stopTimerFlag = 0; 128 | setOffsetIntensity(1); 129 | notes[0] = Message.getNoteNumber(); 130 | noteId = Synth.playNote(notes[0], Message.getVelocity()); //Play first note 131 | } 132 | } 133 | } 134 | } 135 | 136 | function onNoteOff() 137 | { 138 | if (!btnMute.getValue()) 139 | { 140 | if (Message.getNoteNumber() == notes[0] || Message.getNoteNumber() == notes[1]) 141 | { 142 | Message.ignoreEvent(true); 143 | 144 | if (Message.getNoteNumber() == notes[0]) //notes[0] released 145 | { 146 | notes[0] = -1; 147 | 148 | if (Synth.isKeyDown(notes[1])) //notes[1] is still held 149 | { 150 | if (count == 0) 151 | { 152 | stopTimerFlag = 1; //Timer will be stopped after next cycle so that notes[1] is sounding note 153 | } 154 | else 155 | { 156 | Synth.stopTimer(); 157 | } 158 | 159 | notes[0] = notes[1]; //notes[1] becomes the new notes[0] 160 | } 161 | } 162 | 163 | if (Message.getNoteNumber() == notes[1]) //notes[1] released 164 | { 165 | notes[1] = -1; 166 | 167 | if (Synth.isKeyDown(notes[0])) //notes[0] is still held 168 | { 169 | if (count == 1) 170 | { 171 | stopTimerFlag = 1; //Timer will be stopped after next cycle so that notes[0] is sounding note 172 | } 173 | else 174 | { 175 | Synth.stopTimer(); 176 | } 177 | } 178 | } 179 | 180 | if (!Synth.isKeyDown(notes[0]) && !Synth.isKeyDown(notes[1]) && noteId != -1) //Both keys have been lifted 181 | { 182 | Synth.stopTimer(); 183 | Synth.noteOffByEventId(noteId); 184 | notes = [-1, -1]; 185 | noteId = -1; 186 | setOffsetIntensity(1); //Reset sample start offset 187 | } 188 | } 189 | } 190 | else 191 | { 192 | //Turn off hanging note, if any 193 | if (noteId != -1) 194 | { 195 | Synth.noteOffByEventId(noteId); 196 | notes = [-1, -1]; 197 | noteId = -1; 198 | setOffsetIntensity(1); //Reset sample start offset 199 | } 200 | } 201 | } 202 | 203 | function onController() 204 | { 205 | } 206 | 207 | function onTimer() 208 | { 209 | if (!btnMute.getValue()) 210 | { 211 | count = 1-count; //Toggle count - to switch between origin and target notes 212 | 213 | //Get random microtuning, if any 214 | microTuning = 0; 215 | if (knbMicroTuning.getValue() > 0) 216 | { 217 | microTuning = (Math.random() * (knbMicroTuning.getValue() - -knbMicroTuning.getValue() + 1) + -knbMicroTuning.getValue()); 218 | } 219 | 220 | Synth.addPitchFade(noteId, rate*1000, 0, bendAmount + microTuning); //Pitch fade old note to bend amount 221 | Synth.addVolumeFade(noteId, rate*1000, -100); //Fade out last note 222 | 223 | noteId = Synth.playNote(notes[count], velocity); //Play new note 224 | 225 | Synth.addVolumeFade(noteId, 0, -99); //Set new note's initial volume 226 | Synth.addVolumeFade(noteId, rate*1000, 0); //Fade in new note 227 | Synth.addPitchFade(noteId, 0, 0, -bendAmount + microTuning); //Set new note's initial detuning 228 | Synth.addPitchFade(noteId, rate*1000, 0, microTuning); //Pitch fade new note to 0 229 | 230 | if (stopTimerFlag == 1 || noteId == -1) 231 | { 232 | Synth.stopTimer(); 233 | stopTimerFlag = 0; 234 | } 235 | } 236 | } 237 | 238 | function onControl(number, value) 239 | { 240 | switch (number) 241 | { 242 | case btnMute: 243 | notes = [-1, -1]; 244 | Synth.stopTimer(); 245 | setOffsetIntensity(1); 246 | break; 247 | 248 | case knbRate: 249 | //If timer's already started then update its Rate 250 | if (Synth.isTimerRunning()) 251 | { 252 | rate = getRate(); 253 | Synth.startTimer(rate); 254 | } 255 | 256 | knbRate.set("text", "Rate"); //Default 257 | if (knbRate.getValue() == knbRate.get("max")) 258 | { 259 | knbRate.set("text", "Velocity"); 260 | } 261 | break; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /modules/VelocityFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: velocityFilter.js 3 | * Author: David Healey 4 | * Date: 07/09/2017 5 | * License: Public Domain 6 | */ 7 | 8 | Content.setWidth(650); 9 | Content.setHeight(50); 10 | 11 | const var min = Content.addKnob("Min", 0, 0); 12 | min.setRange(1, 127, 1); 13 | min.set("defaultValue", 1); 14 | 15 | const var max = Content.addKnob("Max", 150, 0); 16 | max.setRange(1, 127, 1); 17 | max.set("defaultValue", 127);function onNoteOn() 18 | { 19 | if (Message.getVelocity() < min.getValue() || Message.getVelocity() > max.getValue()) 20 | { 21 | Message.ignoreEvent(true); 22 | } 23 | } 24 | function onNoteOff() 25 | { 26 | 27 | } 28 | function onController() 29 | { 30 | 31 | } 32 | function onTimer() 33 | { 34 | 35 | } 36 | function onControl(number, value) 37 | { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /modules/VelocityMappedRoundRobin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: velocityMappedRoundRobin.js 3 | * Author: David Healey 4 | * Date: 31/10/2017 5 | * Modified: 31/10/2017 6 | * License: Public Domain 7 | */ 8 | 9 | /*Instructions: 10 | Map each round robin sample to a single velocity range i.e 0-1, 2-3, 4-5. 11 | Always start with 0-1 for RR1 and work up from there. 12 | */ 13 | 14 | reg rrStep = -1; 15 | 16 | Content.setWidth(650); 17 | Content.setHeight(50); 18 | 19 | //Knob to lock to a specific RR - essentially disabling the round robin functionality - useful for finding duff samples 20 | const var knbRRLock = Content.addKnob("knbRRLock", 0, 0); 21 | knbRRLock.set("text", "Lock to RR"); 22 | knbRRLock.setRange(0, 50, 1); 23 | 24 | const var btnRandom = Content.addButton("btnRandom", 150, 10); 25 | btnRandom.set("text", "Random"); 26 | 27 | const var knbNumRR = Content.addKnob("knbNumRR", 300, 0); 28 | knbNumRR.set("text", "Num RR"); 29 | knbNumRR.setRange(1, 50, 1); 30 | 31 | function onNoteOn() 32 | { 33 | if (knbRRLock.getValue() == 0) //RR not locked 34 | { 35 | if (!btnRandom.getValue()) 36 | { 37 | rrStep = rrStep + 2; //Each RR covers a velocity range of 2 (0-1, 2-3, 4-5, etc.) 38 | if (rrStep > knbNumRR.getValue()*2-1) rrStep = 1; 39 | } 40 | else 41 | { 42 | rrStep = Math.randInt(1, knbNumRR.getValue()+1); 43 | rrStep = rrStep * 2 - 1; 44 | } 45 | } 46 | else 47 | { 48 | rrStep = knbRRLock.getValue() * 2 - 1; 49 | } 50 | 51 | Message.setVelocity(rrStep); 52 | } 53 | function onNoteOff() 54 | { 55 | 56 | } 57 | function onController() 58 | { 59 | 60 | } 61 | function onTimer() 62 | { 63 | 64 | } 65 | function onControl(number, value) 66 | { 67 | 68 | } 69 | -------------------------------------------------------------------------------- /modules/VelocityScaler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018, 2023 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | Content.setWidth(650); 19 | Content.setHeight(200); 20 | 21 | const var tblVelocity = Content.addTable("Velocity", 10, 10); 22 | tblVelocity.set("width", 180); 23 | tblVelocity.set("height", 180);function onNoteOn() 24 | { 25 | local oldVelocity = Message.getVelocity(); 26 | local newVelocity = 1 + Math.floor(126 * tblVelocity.getTableValue(oldVelocity / 127)); 27 | 28 | Message.setVelocity(newVelocity); 29 | } 30 | function onNoteOff() 31 | { 32 | 33 | } 34 | function onController() 35 | { 36 | 37 | } 38 | function onTimer() 39 | { 40 | 41 | } 42 | function onControl(number, value) 43 | { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /modules/VelocityShaper.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 David Healey 3 | 4 | This file is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This file is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this file. If not, see . 16 | */ 17 | 18 | Content.setWidth(600); 19 | Content.setHeight(100); 20 | 21 | const var dynamicsCC = Synth.getModulator("dynamicsCC"); 22 | 23 | const var btnDynamics = Content.addButton("btnDynamics", 0, 10); 24 | btnDynamics.set("text", "Vel = Dynamics"); 25 | 26 | const tblVel = Content.addTable("tblVel", 150, 0); 27 | tblVel.set("height", 95); 28 | tblVel.set("width", 200); 29 | tblVel.connectToOtherTable("Velocity Modulator", 0);function onNoteOn() 30 | { 31 | Message.setVelocity(Math.max(1, 127 * tblVel.getTableValue(Message.getVelocity()))); //Scale velocity using table 32 | 33 | //Send velocity to dynamics CC if button enabled 34 | if (btnDynamics.getValue()) 35 | { 36 | dynamicsCC.setAttribute(dynamicsCC.DefaultValue, Message.getVelocity()); 37 | } 38 | } 39 | function onNoteOff() 40 | { 41 | 42 | } 43 | function onController() 44 | { 45 | 46 | } 47 | function onTimer() 48 | { 49 | 50 | } 51 | function onControl(number, value) 52 | { 53 | 54 | } 55 | -------------------------------------------------------------------------------- /modules/VelocityToCC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Title: velocityToCC.js 3 | * Author: David Healey 4 | * Date: 15/03/2018 5 | * Modified: 15/03/2018 6 | * License: Public Domain 7 | */ 8 | 9 | Content.setWidth(650); 10 | Content.setHeight(50); 11 | 12 | const var btnBypass = Content.addButton("btnBypass", 0, 10); 13 | btnBypass.set("text", "Bypass"); 14 | 15 | const var knbCC = Content.addKnob("KnbCC", 160, 0); 16 | knbCC.set("text", "CC Number"); 17 | knbCC.setRange(1, 127, 1);function onNoteOn() 18 | { 19 | if (btnBypass.getValue() == 0) 20 | { 21 | Synth.sendController(knbCC.getValue(), Message.getVelocity()); 22 | } 23 | } 24 | 25 | function onNoteOff() 26 | { 27 | 28 | } 29 | function onController() 30 | { 31 | 32 | } 33 | function onTimer() 34 | { 35 | 36 | } 37 | function onControl(number, value) 38 | { 39 | 40 | } 41 | --------------------------------------------------------------------------------