Version 3.0: Major revision.
Incorporated Christoph's randomising system. Added namespace for RRTypes Removed random no repeat mode - I could see no point in keeping it when we have the random full cycle Aimed for simpler code and lower CPU usage so the RR is no longer per note it is for the whole keyboard. Reset timer is still per note though CPU usage of on note callback is now around 0.60 on average on my system (before it was running up to 3.5!) Added sample range knob for synth and hybrid modes. This is used to set how far from the played note the sample borrowed modes will take samples from. So setting it to 1 means the samples 1 down and one up from the played note will be used, as well as the actual note's sample. /** * Multi-Round Robin script v3.2 * Author: David Healey, Christoph Hart * Date: 07/01/2017 * Modified: 20/02/2017 * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html */ //INIT reg noteNumber; reg groupList = []; reg groupIndex; reg offsetList = []; reg offsetIndex; reg offset; namespace RRTypes { const var OFF = 1; const var REAL = 2; const var SYNTH = 3; const var HYBRID = 4; }; namespace RRModes { const var CYCLE = 1; const var RANDOM = 2; const var RANDOM_CYCLE = 3; }; const var timer = Engine.createMidiList(); const var excludedIds = "(elease)"; //Regex string of words in sampler IDs to exclude RR from const var samplerNames = Synth.getIdList("Sampler"); //Get the ids of all child samplers const var samplers = []; //Get child samplers for (samplerName in samplerNames) { if (Engine.matchesRegex(samplerName, excludedIds) == true) continue; //Skip excluded IDs samplers.push(Synth.getSampler(samplerName)); //Add sampler to array samplers[samplers.length-1].enableRoundRobin(false); //Disable default RR behaviour } //GUI Content.setHeight(100); //RR Type and Mode Combo Boxes const var cmbType = Content.addComboBox("Type", 0, 10); cmbType.set("items", ["Off", "Real", "Synthetic", "Hybrid"].join("\n")); const var cmbMode = Content.addComboBox("Mode", 150, 10); cmbMode.set("items", ["Cycle", "Random", "Random Full Cycle"].join("\n")); //Number of groups knob const var knbNumGroups = Content.addKnob("Num Groups", 300, 0); knbNumGroups.setRange(1, 100, 1); //Playable Range controls const var knbLowNote = Content.addKnob("Low Note", 450, 0); knbLowNote.setRange(0, 126, 1); const var knbHighNote = Content.addKnob("High Note", 600, 0); knbHighNote.setRange(1, 127, 1); //Reset timeout knob const var knbReset = Content.addKnob("Reset Time", 0, 50); knbReset.setRange(1, 60, 1); knbReset.set("suffix", " Seconds"); //Microtuning knob const var knbMicroTuning = Content.addKnob("Microtuning", 150, 50); knbMicroTuning.setRange(0, 100, 0.1); //Borrowed Sample Range const var knbSampleRange = Content.addKnob("Sample Range", 300, 50); knbSampleRange.setRange(1, 12, 1); //----------------- //FUNCTIONS /** Swaps two elements in an array randomly. */ inline function swapRandomly(arr) { local firstIndex = Math.randInt(0, arr.length); local secondIndex = Math.randInt(0, arr.length); local temp = arr[firstIndex]; arr[firstIndex] = arr[secondIndex]; arr[secondIndex] = temp; } /** Swaps every element in the array. */ inline function swapEntirely(arr) { local i = 0; while(++i < arr.length) swapRandomly(arr); } inline function resetGroupList(count) { local i; for (i = 0; i < count; i++) { groupList[i] = i; } } inline function resetOffsetList(count) { local i; //Build array going from -value to +value+1 (always need to account for 0) for (i = 0; i < count*2+1; i++) { offsetList[i] = i-count; } } //CALLBACKS function onNoteOn() { noteNumber = Message.getNoteNumber(); if (cmbType.getValue() != RRTypes.OFF && noteNumber >= knbLowNote.getValue() && noteNumber <= knbHighNote.getValue()) { //Group based RR if (knbNumGroups.getValue() > 1 && (cmbType.getValue() == RRTypes.REAL || cmbType.getValue() == RRTypes.HYBRID)) //Real or Hybrid RR { switch (cmbMode.getValue()) { case RRModes.CYCLE: groupIndex = (groupIndex + 1) % groupList.length; break; case RRModes.RANDOM: groupIndex = Math.floor(Math.random() * groupList.length); break; case RRModes.RANDOM_CYCLE: if (++groupIndex >= groupList.length) { groupIndex = 0; swapEntirely(groupList); } break; } } //Reset groupIndex to 0 if only one group is present or the reset time has elapsed if (knbNumGroups.getValue() == 1 || Engine.getUptime() - timer.getValue(noteNumber) > knbReset.getValue()) { groupIndex = 0; resetGroupList(knbNumGroups.getValue()); } //Set active group for each sampler if (samplers.length > 0) { for (sampler in samplers) { sampler.setActiveGroup(1 + groupList[groupIndex]); } } //Synthetic sample borrowed RR if (cmbType.getValue() == RRTypes.SYNTH || cmbType.getValue() == RRTypes.HYBRID) //Synthetic or Hybrid RR { switch (cmbMode.getValue()) { case RRModes.CYCLE: offsetIndex = (offsetIndex + 1) % offsetList.length; break; case RRModes.RANDOM: offsetIndex = Math.floor(Math.random() * offsetList.length); break; case RRModes.RANDOM_CYCLE: if (++offsetIndex >= offsetList.length) { offsetIndex = 0; swapEntirely(offsetList); } break; } //If reset time has elapsed, reset index if (Engine.getUptime() - timer.getValue(noteNumber) > knbReset.getValue()) { offsetIndex = parseInt(Math.floor(offsetList.length / 2)); resetOffsetList(knbSampleRange.getValue()); } //If the played note + the offset are within the playable range if (noteNumber + offsetList[offsetIndex] > knbLowNote.getValue() && noteNumber + offsetList[offsetIndex] < knbHighNote.getValue()) { Message.setTransposeAmount(offsetList[offsetIndex]); //Transpose the note Message.setCoarseDetune(-offsetList[offsetIndex]); //Use coarse detune so setting can be picked up by later scripts } } //Apply random microtuning, if any if (knbMicroTuning.getValue() > 0) { Message.setFineDetune(Math.random() * (knbMicroTuning.getValue() - -knbMicroTuning.getValue() + 1) + -knbMicroTuning.getValue()); } timer.setValue(noteNumber, Engine.getUptime()); //Record time this note was triggered } } function onNoteOff() { } function onController() { } function onTimer() { } function onControl(number, value) { switch(number) { case knbNumGroups: groupList = []; resetGroupList(value); break; case knbSampleRange: offsetList = []; resetOffsetList(value); break; case cmbType: switch (value) { case RRTypes.REAL: knbSampleRange.set("visible", false); if (knbNumGroups.getValue() == 1) { number.setValue(RRTypes.OFF); } break; case RRTypes.SYNTH: knbSampleRange.set("visible", true); break; case RRTypes.HYBRID: knbSampleRange.set("visible", true); if (knbNumGroups.getValue() == 1) //If there are no RR groups then Hybrid defaults to synth { number.setValue(RRTypes.SYNTH); } break; } break; case cmbMode: resetGroupList(knbNumGroups.getValue()); resetOffsetList(knbSampleRange.getValue()); offsetIndex = 0; groupIndex = 0; break; } }