├── .gitignore ├── README.md ├── dependency-reduced-pom.xml ├── pom.xml └── src └── main ├── java └── com │ └── kernicpanel │ ├── RandomizerExtension.java │ └── RandomizerExtensionDefinition.java └── resources └── META-INF └── services └── com.bitwig.extension.ExtensionDefinition /.gitignore: -------------------------------------------------------------------------------- 1 | /${bitwig.extension.directory} 2 | /target 3 | .DS_Store 4 | last-build.bin 5 | fileHashes.lock 6 | /.settings/ 7 | /.classpath 8 | /.project 9 | /.idea 10 | *.iml 11 | /bin/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Randomizer 5 | 6 | Bitwig randomizer 7 | 8 | @polarity did a review for the version 1.0 9 | https://www.youtube.com/watch?v=KXkJyn4Qsho 10 | 11 | ## WARNING 12 | 13 | This WILL lead to unexpected results, be sure to lower your volume before using. 14 | 15 | ## Motivation 16 | 17 | I enjoy surprises, and I think that creativity comes from constraints. 18 | 19 | So I wanted to make a script that would randomly select a Bitwig browser item. 20 | This allows me two main things: 21 | - Select unexpected items, which open up possibilities for me to explore. 22 | - Force me to use a forgotten device, plugin… etc. to try to master it. 23 | 24 | I added a name genarator, wich tries to be inspiring. 25 | It can be used to name devices, tracks, presets, etc., but also to name projects. 26 | 27 | ## Activation 28 | 29 | Copy the released extension to your Bitwig extensions folder. 30 | 31 | Activate it in the settings. 32 | 33 | 34 | https://user-images.githubusercontent.com/720491/178076494-40867f31-c326-4319-8b38-6d4cedb6aeaa.mp4 35 | 36 | 37 | ## Usage 38 | 39 | ### Parameters randomization 40 | 41 | Devices parameters can be randomized. 42 | A ratio can be applied to limit the range of the new value. 43 | We can randomize the current parameters page or all of them. 44 | 45 | 46 | https://user-images.githubusercontent.com/720491/180964015-50166694-b680-47f7-b954-8091a33ea6e3.mp4 47 | 48 | 49 | ### Browser random selection 50 | 51 | This extension allows users to select a random item from the browser. 52 | It could be any browser related item type, like device, plugin, wave file, preset, modulator… 53 | 54 | - `Select random item` opens a browser if none is already opened and selects a random item from the current opened browser. 55 | 56 | - `Add current item` adds the current selected item. 57 | This is the same as `OK` in the browser. 58 | For me, it is convenient as it is close to the random button. 59 | 60 | 61 | https://user-images.githubusercontent.com/720491/178076578-f902a6bb-7716-4c7d-8d67-cb6ed1db0af2.mp4 62 | 63 | 64 | ### Random name generator 65 | 66 | An option is available to prepend the generated filename with the current date. 67 | 68 | https://user-images.githubusercontent.com/720491/178076594-a29e99ac-e81d-426d-8bca-ff4e8bfc7013.mp4 69 | 70 | 71 | Another option lets you customize the date format. The default template is 72 | `yyyy-MM-DD_` which would expand to e.g. `2022-07-31_`. 73 | All the possible patterns are docuemnted 74 | [here](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#patterns). 75 | -------------------------------------------------------------------------------- /dependency-reduced-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.kernicpanel 5 | randomizer 6 | Randomizer 7 | 2.1 8 | 9 | 10 | 11 | maven-compiler-plugin 12 | 3.8.0 13 | 14 | true 15 | true 16 | 1.8 17 | 1.8 18 | UTF-8 19 | 1024m 20 | 21 | 22 | 23 | maven-jar-plugin 24 | 3.2.0 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | maven-shade-plugin 35 | 3.2.4 36 | 37 | 38 | package 39 | 40 | shade 41 | 42 | 43 | 44 | 45 | com.bitwig:extension-api 46 | 47 | 48 | 49 | 50 | net.datafaker:* 51 | 52 | META-INF/* 53 | 54 | 55 | 56 | dk.brics.automaton:* 57 | 58 | META-INF/* 59 | 60 | 61 | 62 | com.github.mifmif:generex:* 63 | 64 | META-INF/* 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ${project.artifactId}-${project.version} 73 | 74 | 75 | 76 | com.coderplus.maven.plugins 77 | copy-rename-maven-plugin 78 | 1.0 79 | 80 | 81 | rename-file 82 | install 83 | 84 | copy 85 | 86 | 87 | ${project.build.directory}/${project.build.finalName}.jar 88 | ${user.home}/Bitwig Studio/Extensions/Randomizer.bwextension 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | bitwig 98 | Bitwig Maven Repository 99 | https://maven.bitwig.com 100 | 101 | 102 | 103 | 104 | com.bitwig 105 | extension-api 106 | 17 107 | compile 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.kernicpanel 6 | randomizer 7 | jar 8 | Randomizer 9 | 2.1 10 | 11 | 12 | 13 | bitwig 14 | Bitwig Maven Repository 15 | https://maven.bitwig.com 16 | 17 | 18 | 19 | 20 | 21 | com.bitwig 22 | extension-api 23 | 17 24 | 25 | 26 | net.datafaker 27 | datafaker 28 | 1.7.0 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-compiler-plugin 37 | 3.8.0 38 | 39 | true 40 | true 41 | 1.8 42 | 1.8 43 | UTF-8 44 | 1024m 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-jar-plugin 51 | 3.2.0 52 | 53 | 54 | 55 | true 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-shade-plugin 64 | 3.2.4 65 | 66 | 67 | package 68 | 69 | shade 70 | 71 | 72 | 73 | 74 | com.bitwig:extension-api 75 | 76 | 77 | 78 | 79 | net.datafaker:* 80 | 81 | META-INF/* 82 | 83 | 84 | 85 | dk.brics.automaton:* 86 | 87 | META-INF/* 88 | 89 | 90 | 91 | com.github.mifmif:generex:* 92 | 93 | META-INF/* 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ${project.artifactId}-${project.version} 102 | 103 | 104 | 105 | 106 | com.coderplus.maven.plugins 107 | copy-rename-maven-plugin 108 | 1.0 109 | 110 | 111 | rename-file 112 | install 113 | 114 | copy 115 | 116 | 117 | ${project.build.directory}/${project.build.finalName}.jar 118 | ${user.home}/Bitwig Studio/Extensions/Randomizer.bwextension 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/main/java/com/kernicpanel/RandomizerExtension.java: -------------------------------------------------------------------------------- 1 | package com.kernicpanel; 2 | 3 | import com.bitwig.extension.callback.NoArgsCallback; 4 | import com.bitwig.extension.controller.api.*; 5 | import com.bitwig.extension.controller.ControllerExtension; 6 | import net.datafaker.Faker; 7 | 8 | import java.time.LocalDate; 9 | import java.time.format.DateTimeFormatter; 10 | import java.time.temporal.UnsupportedTemporalTypeException; 11 | import java.util.Arrays; 12 | import java.util.Locale; 13 | import java.util.Random; 14 | import java.util.stream.IntStream; 15 | 16 | public class RandomizerExtension extends ControllerExtension { 17 | Random rand = new Random(); 18 | Faker faker = new Faker(); 19 | 20 | private SettableBooleanValue useDate; 21 | private SettableStringValue dateFormatTemplate; 22 | private static final String DEFAULT_DATE_FORMAT_TEMPLATE = "yyyy-MM-dd_"; 23 | 24 | private ControllerHost host; 25 | private Application application; 26 | private DocumentState documentState; 27 | private PopupBrowser popupBrowser; 28 | private CursorTrack cursorTrack; 29 | private CursorDevice cursorDevice; 30 | private CursorRemoteControlsPage remoteControlsPage; 31 | private BrowserResultsItemBank resultsItemBank; 32 | private Preferences preferences; 33 | 34 | private SettableStringValue filenameOutput; 35 | private SettableStringValue nameOutput; 36 | private RemoteControl parameter; 37 | private SettableRangedValue randomRatio; 38 | 39 | protected RandomizerExtension( 40 | final RandomizerExtensionDefinition definition, final ControllerHost host) { 41 | super(definition, host); 42 | } 43 | 44 | private void printer(String s) { 45 | host.println(s); 46 | java.lang.System.out.println(s); 47 | } 48 | 49 | @Override 50 | public void init() { 51 | host = getHost(); 52 | printer("RandomizerExtension initialized"); 53 | application = host.createApplication(); 54 | 55 | documentState = host.getDocumentState(); 56 | popupBrowser = host.createPopupBrowser(); 57 | preferences = host.getPreferences(); 58 | popupBrowser.exists().markInterested(); 59 | popupBrowser.resultsColumn().entryCount().markInterested(); 60 | cursorTrack = host.createCursorTrack(0, 0); 61 | preferences = host.getPreferences(); 62 | cursorDevice = cursorTrack.createCursorDevice(); 63 | remoteControlsPage = cursorDevice.createCursorRemoteControlsPage(80); 64 | 65 | remoteControlsPage.pageCount().markInterested(); 66 | remoteControlsPage.pageNames().markInterested(); 67 | remoteControlsPage.selectedPageIndex().markInterested(); 68 | 69 | for (int parameterIndex = 0; parameterIndex < 8; parameterIndex++) { 70 | remoteControlsPage.getParameter(parameterIndex).name().markInterested(); 71 | } 72 | 73 | resultsItemBank = popupBrowser.resultsColumn().createItemBank(100000); 74 | 75 | // Naming settings 76 | useDate = preferences.getBooleanSetting("Prefix filename with date", "Random name", true); 77 | dateFormatTemplate = 78 | preferences.getStringSetting( 79 | "Format string for date prefix", "Random name", 15, DEFAULT_DATE_FORMAT_TEMPLATE); 80 | dateFormatTemplate.addValueObserver( 81 | value -> { 82 | try { 83 | LocalDate.now().format(DateTimeFormatter.ofPattern(value)); 84 | } catch (IllegalArgumentException | UnsupportedTemporalTypeException e) { 85 | dateFormatTemplate.set(DEFAULT_DATE_FORMAT_TEMPLATE); 86 | host.showPopupNotification("Invalid date format template."); 87 | } 88 | }); 89 | 90 | // Naming controls 91 | filenameOutput = documentState.getStringSetting("Filename", "Random name", 50, ""); 92 | nameOutput = documentState.getStringSetting("Name", "Random name", 50, ""); 93 | documentState.getSignalSetting(" ", "Random name", "Generate").addSignalObserver(randomName()); 94 | 95 | // Browser selection controls 96 | documentState 97 | .getSignalSetting("Select", "Randomize browser selection", "Select random item") 98 | .addSignalObserver(selectRandomItem()); 99 | documentState 100 | .getSignalSetting("Add", "Randomize browser selection", "Add current item") 101 | .addSignalObserver(popupBrowser::commit); 102 | 103 | // Parameters controls 104 | randomRatio = 105 | documentState.getNumberSetting("Ratio", "Randomize Device parameters", 0, 100, 1, "%", 15); 106 | documentState 107 | .getSignalSetting(" ", "Randomize Device parameters", "Randomize current page") 108 | .addSignalObserver(randomizeCurrentPageDeviceParameters()); 109 | documentState 110 | .getSignalSetting(" ", "Randomize Device parameters", "Randomize all parameters") 111 | .addSignalObserver(randomizeDeviceParameters()); 112 | } 113 | 114 | private NoArgsCallback selectRandomItem() { 115 | return () -> { 116 | if (!popupBrowser.exists().getAsBoolean()) { 117 | cursorTrack.endOfDeviceChainInsertionPoint().browse(); 118 | } 119 | 120 | host.scheduleTask( 121 | () -> { 122 | Integer random = rand.nextInt(popupBrowser.resultsColumn().entryCount().get()); 123 | resultsItemBank.getItemAt(random).isSelected().set(true); 124 | }, 125 | 300); 126 | }; 127 | } 128 | 129 | private NoArgsCallback randomName() { 130 | return () -> { 131 | String[] prefixes = { 132 | faker.mood().emotion(), 133 | faker.mood().tone(), 134 | faker.mood().feeling(), 135 | faker.color().name(), 136 | faker.coffee().intensifier(), 137 | faker.company().buzzword(), 138 | faker.dungeonsAndDragons().languages(), 139 | faker.size().adjective(), 140 | faker.subscription().subscriptionTerms(), 141 | }; 142 | // printer(Arrays.toString(prefixes)); 143 | String prefix = prefixes[rand.nextInt(prefixes.length)]; 144 | 145 | String[] names = { 146 | // faker.superhero().power(), 147 | // faker.superhero().name(), 148 | faker.hacker().ingverb(), 149 | faker.hacker().noun(), 150 | faker.hacker().verb(), 151 | faker.food().fruit(), 152 | faker.food().ingredient(), 153 | faker.food().vegetable(), 154 | faker.verb().ingForm(), 155 | faker.verb().past(), 156 | faker.verb().pastParticiple(), 157 | faker.appliance().equipment(), 158 | faker.coffee().body(), 159 | faker.darkSoul().classes(), 160 | faker.darkSoul().stats(), 161 | faker.pokemon().type(), 162 | faker.dessert().flavor(), 163 | }; 164 | // printer(Arrays.toString(names)); 165 | String name = names[rand.nextInt(names.length)]; 166 | 167 | String[] suffixes = { 168 | faker.animal().name(), 169 | faker.weather().description(), 170 | faker.address().streetSuffix(), 171 | faker.address().citySuffix(), 172 | faker.battlefield1().classes(), 173 | faker.coffee().descriptor(), 174 | faker.company().profession(), 175 | faker.construction().heavyEquipment(), 176 | faker.construction().materials(), 177 | faker.cosmere().shards(), 178 | faker.cosmere().surges(), 179 | faker.cosmere().knightsRadiant(), 180 | faker.cosmere().metals(), 181 | faker.cosmere().allomancers(), 182 | faker.cosmere().feruchemists(), 183 | faker.dessert().variety(), 184 | faker.dessert().topping(), 185 | faker.dungeonsAndDragons().klasses(), 186 | faker.dungeonsAndDragons().meleeWeapons(), 187 | faker.dungeonsAndDragons().monsters(), 188 | faker.dungeonsAndDragons().races(), 189 | faker.dungeonsAndDragons().rangedWeapons(), 190 | faker.electricalComponents().active(), 191 | faker.electricalComponents().passive(), 192 | faker.electricalComponents().electromechanical(), 193 | faker.house().furniture(), 194 | faker.house().room(), 195 | faker.pokemon().move(), 196 | faker.restaurant().nameSuffix(), 197 | faker.science().element(), 198 | faker.science().quark(), 199 | faker.science().leptons(), 200 | faker.science().bosons(), 201 | faker.science().tool(), 202 | faker.team().creature(), 203 | }; 204 | // printer(Arrays.toString(suffixes)); 205 | String suffix = suffixes[rand.nextInt(suffixes.length)]; 206 | 207 | String[] generators = {prefix, name, suffix}; 208 | String generatedString = String.join(" ", Arrays.asList(generators)); 209 | nameOutput.set(generatedString.toLowerCase(Locale.ROOT)); 210 | 211 | if (useDate.get()) { 212 | LocalDate dateObj = LocalDate.now(); 213 | String formattedDate; 214 | try { 215 | formattedDate = dateObj.format(DateTimeFormatter.ofPattern(dateFormatTemplate.get())); 216 | } catch (IllegalArgumentException | UnsupportedTemporalTypeException e) { 217 | // Should not happen, unless e.g. the config data got corrupted. 218 | dateFormatTemplate.set(DEFAULT_DATE_FORMAT_TEMPLATE); 219 | formattedDate = dateObj.format(DateTimeFormatter.ofPattern(dateFormatTemplate.get())); 220 | } 221 | generatedString = formattedDate + generatedString; 222 | } 223 | 224 | filenameOutput.set(generatedString.replace(" ", "_").toLowerCase(Locale.ROOT)); 225 | }; 226 | } 227 | 228 | private NoArgsCallback randomizeCurrentPageDeviceParameters() { 229 | return () -> { 230 | IntStream.range(0, 8) 231 | .forEach( 232 | parameterIndex -> { 233 | double randomValue = rand.nextDouble(); 234 | parameter = remoteControlsPage.getParameter(parameterIndex); 235 | String parameterName = parameter.name().get(); 236 | 237 | if (!parameterName.trim().isEmpty() 238 | && !parameterName.toLowerCase(Locale.ROOT).equals("output")) { 239 | double newValue = ((2 * randomValue) - 1) * randomRatio.get(); 240 | parameter.inc(newValue); 241 | } 242 | }); 243 | }; 244 | } 245 | 246 | private NoArgsCallback randomizeDeviceParameters() { 247 | return () -> { 248 | IntStream.range(0, remoteControlsPage.pageCount().get()) 249 | .forEachOrdered( 250 | (pageIndex) -> { 251 | randomizeCurrentPageDeviceParameters().call(); 252 | try { 253 | remoteControlsPage.selectNextPage(true); 254 | Thread.sleep(10); 255 | } catch (InterruptedException e) { 256 | e.printStackTrace(); 257 | } 258 | }); 259 | }; 260 | } 261 | 262 | @Override 263 | public void exit() { 264 | // TODO: Perform any cleanup once the driver exits 265 | } 266 | 267 | @Override 268 | public void flush() { 269 | // TODO Send any updates you need here. 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/kernicpanel/RandomizerExtensionDefinition.java: -------------------------------------------------------------------------------- 1 | package com.kernicpanel; 2 | 3 | import java.util.UUID; 4 | 5 | import com.bitwig.extension.api.PlatformType; 6 | import com.bitwig.extension.controller.AutoDetectionMidiPortNamesList; 7 | import com.bitwig.extension.controller.ControllerExtensionDefinition; 8 | import com.bitwig.extension.controller.api.ControllerHost; 9 | 10 | public class RandomizerExtensionDefinition extends ControllerExtensionDefinition { 11 | private static final UUID DRIVER_ID = UUID.fromString("5c4b743f-cc91-41cf-be42-092ecac4a64b"); 12 | 13 | public RandomizerExtensionDefinition() {} 14 | 15 | @Override 16 | public String getName() { 17 | return "Randomizer"; 18 | } 19 | 20 | @Override 21 | public String getAuthor() { 22 | return "kernicPanel"; 23 | } 24 | 25 | @Override 26 | public String getVersion() { 27 | return "2.0"; 28 | } 29 | 30 | @Override 31 | public UUID getId() { 32 | return DRIVER_ID; 33 | } 34 | 35 | @Override 36 | public String getHardwareVendor() { 37 | return "kernicPanel"; 38 | } 39 | 40 | @Override 41 | public String getHardwareModel() { 42 | return "Randomizer"; 43 | } 44 | 45 | @Override 46 | public int getRequiredAPIVersion() { 47 | return 17; 48 | } 49 | 50 | @Override 51 | public int getNumMidiInPorts() { 52 | return 0; 53 | } 54 | 55 | @Override 56 | public int getNumMidiOutPorts() { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public void listAutoDetectionMidiPortNames( 62 | final AutoDetectionMidiPortNamesList list, final PlatformType platformType) {} 63 | 64 | @Override 65 | public RandomizerExtension createInstance(final ControllerHost host) { 66 | return new RandomizerExtension(this, host); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.bitwig.extension.ExtensionDefinition: -------------------------------------------------------------------------------- 1 | com.kernicpanel.RandomizerExtensionDefinition --------------------------------------------------------------------------------