├── OP-1.control.js └── README.md /OP-1.control.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * OP-1.control.js 4 | * 5 | * Author: Maurice T. Meyer 6 | * E-Mail: maurice@lavireo.com 7 | * 8 | * Date: 25-08-2016 9 | */ 10 | 11 | 12 | loadAPI(1); 13 | 14 | host.defineController("Teenage Engineering", "OP-1", "2.0-beta", "befd66e0-6abd-11e6-bdf4-0800200c9a66"); 15 | host.defineMidiPorts(1, 1); 16 | 17 | var Ctrl = { 18 | position: '1.1.1' 19 | } 20 | 21 | // OP1 22 | var OP1 = { 23 | // 24 | // Number of tracks 25 | NUM_TRACKS: 13, 26 | NUM_ROWS: 1, 27 | NUM_MODES: 4, 28 | 29 | // 30 | // Modes ID's 31 | MODE_CLIP: 0, 32 | MODE_PERFORM: 1, 33 | MODE_ARRANGE: 2, 34 | MODE_MIXER: 3, 35 | 36 | // 37 | // Encoders 38 | ENCODER_1: 1, 39 | ENCODER_2: 2, 40 | ENCODER_3: 3, 41 | ENCODER_4: 4, 42 | 43 | HELP_BUTTON: 5, 44 | METRONOME_BUTTON: 6, 45 | 46 | MODE_1_BUTTON: 7, 47 | MODE_2_BUTTON: 8, 48 | MODE_3_BUTTON: 9, 49 | MODE_4_BUTTON: 10, 50 | 51 | T1_BUTTON: 11, 52 | T2_BUTTON: 12, 53 | T3_BUTTON: 13, 54 | T4_BUTTON: 14, 55 | 56 | ARROW_UP_BUTTON: 15, 57 | ARROW_DOWN_BUTTON: 16, 58 | SCISSOR_BUTTON: 17, 59 | 60 | SS1_BUTTON: 50, 61 | SS2_BUTTON: 51, 62 | SS3_BUTTON: 52, 63 | SS4_BUTTON: 21, 64 | SS5_BUTTON: 22, 65 | SS6_BUTTON: 23, 66 | SS7_BUTTON: 24, 67 | SS8_BUTTON: 25, 68 | 69 | REC_BUTTON: 38, 70 | PLAY_BUTTON: 39, 71 | STOP_BUTTON: 40, 72 | 73 | LEFT_ARROW: 41, 74 | RIGHT_ARROW: 42, 75 | SHIFT_BUTTON: 43, 76 | 77 | MICRO: 48, 78 | COM: 49, 79 | 80 | isMode: 1, 81 | isMetronom: false, 82 | 83 | ENCODER_START_CC: 1, 84 | ENCODER_END_CC: 4, 85 | 86 | LOWEST_CC: 1, 87 | HIGHEST_CC: 119, 88 | 89 | 90 | ID_MIDI: 'f0 7e 7f 06 01 f7', 91 | INIT_MIDI: 'f0 00 20 76 00 01 02 f7', 92 | EXIT_MIDI: 'f0 00 20 76 00 01 00 f7', 93 | TEXT_INIT_MIDI: 'f0 00 20 76 00 03', 94 | TEXT_EXIT_MIDI: 'f7', 95 | TEXT_COLOR_INIT_MIDI: 'f0 00 20 76 00 04' 96 | }; 97 | 98 | function execMidi(sequence) 99 | { 100 | host.getMidiOutPort(0).sendSysex(sequence); 101 | } 102 | 103 | // Initialize 104 | function init() { 105 | // MIDI ports 106 | host.getMidiInPort(0).setMidiCallback(onMidiPort); 107 | generic = host.getMidiInPort(0).createNoteInput("MIDI Keyboard", "??????"); 108 | generic.setShouldConsumeEvents(false); 109 | 110 | // 111 | // Init 112 | execMidi(OP1.INIT_MIDI) 113 | showText(' '); 114 | 115 | // 116 | // Create host objects 117 | application = host.createApplication(); 118 | transport = host.createTransport(); 119 | masterTrack_0 = host.createMasterTrack(0); 120 | cursorTrack = host.createCursorTrackSection(4, 5); 121 | trackBank = host.createTrackBankSection(8, 4, 0); 122 | cursorDevice = host.createCursorDeviceSection(8); 123 | userControls = host.createUserControlsSection(OP1.HIGHEST_CC - OP1.LOWEST_CC + 1 - 8); 124 | 125 | transport.getPosition().addTimeObserver(".", 1, 2, 2, 0, onTimeUpdate); 126 | } 127 | 128 | function isInDeviceParametersRange(cc) 129 | { 130 | return cc >= OP1.ENCODER_START_CC && cc <= OP1.ENCODER_END_CC; 131 | } 132 | 133 | function onMidiPort(status, data1, data2) 134 | { 135 | if (OP1.isMode == OP1.MODE_MIXER) 136 | { 137 | switch (data1) { 138 | case OP1.ENCODER_1: // Volume selected track 139 | cursorTrack.getVolume().set(data2, 128); 140 | break; 141 | 142 | case OP1.ENCODER_2: // Pan selected track 143 | cursorTrack.getPan().set(data2, 128); 144 | break; 145 | 146 | case OP1.ENCODER_3: // Send 1 selected track 147 | cursorTrack.getSend(0).set(data2, 128); 148 | break; 149 | 150 | case OP1.ENCODER_4: // Send 2 selected track 151 | OP1.isShift ? transport.getTempo().setRaw(data2 + 49) : masterTrack_0.getVolume().set(data2, 128); 152 | break; 153 | } 154 | } 155 | 156 | // 157 | // Buttons 158 | if (data2 > 0) 159 | switch (data1) 160 | { 161 | // 162 | // Modes 163 | case OP1.MODE_1_BUTTON: 164 | OP1.isMode = 0; 165 | update_display_clip_mode(); 166 | host.showPopupNotification("Clip Mode"); 167 | break; 168 | 169 | case OP1.MODE_2_BUTTON: 170 | OP1.isMode = 1; 171 | update_display_perform_mode(); 172 | host.showPopupNotification("Performance Mode"); 173 | break; 174 | 175 | case OP1.MODE_3_BUTTON: 176 | OP1.isMode = 2; 177 | update_display_arrange_mode(); 178 | host.showPopupNotification("Arrange Mode"); 179 | break; 180 | 181 | case OP1.MODE_4_BUTTON: 182 | OP1.isMode = 3; 183 | update_display_mixer_mode(); 184 | host.showPopupNotification("Mixer Mode"); 185 | break; 186 | 187 | 188 | // 189 | // Up/Down/Left/Right 190 | case OP1.ARROW_UP_BUTTON: 191 | cursorAction(data1); 192 | break; 193 | 194 | case OP1.ARROW_DOWN_BUTTON: 195 | cursorAction(data1); 196 | break; 197 | 198 | case OP1.LEFT_ARROW: 199 | if (OP1.isShift) 200 | { 201 | 202 | } 203 | break; 204 | 205 | case OP1.RIGHT_ARROW: 206 | if (OP1.isShift) 207 | { 208 | 209 | } 210 | break; 211 | 212 | 213 | // 214 | // Play/Pause/Rec 215 | case OP1.REC_BUTTON: 216 | OP1.isShift ? cursorTrack.getArm().toggle() : transport.record(); 217 | break; 218 | 219 | case OP1.PLAY_BUTTON: 220 | OP1.isShift ? cursorTrack.getSolo().toggle() : transport.play(); 221 | break; 222 | 223 | case OP1.STOP_BUTTON: 224 | OP1.isShift ? cursorTrack.getMute().toggle() : transport.stop() 225 | break; 226 | 227 | 228 | // 229 | // Metronom 230 | case OP1.METRONOME_BUTTON: 231 | OP1.isMetronom = !OP1.isMetronom; 232 | transport.setMetronomeValue(OP1.isMetronom ? 128 : 0, 128); 233 | host.showPopupNotification("Metronom: " + (OP1.isMetronom ? 'On' : 'Off')); 234 | break; 235 | } 236 | 237 | 238 | // 239 | // Shift 240 | switch (data1) 241 | { 242 | case OP1.SHIFT_BUTTON: 243 | OP1.isShift = data2 > 0; 244 | break; 245 | } 246 | } 247 | 248 | function onTimeUpdate(value) 249 | { 250 | Ctrl.position = value; 251 | if (OP1.isMode == OP1.MODE_ARRANGE) { 252 | update_display_arrange_mode(); 253 | } 254 | } 255 | 256 | function showText(text) 257 | { 258 | var d = trim(text); 259 | var l = d.length; 260 | execMidi(OP1.TEXT_INIT_MIDI + ' ' + d2h(l) + d.toHex(l) + OP1.TEXT_EXIT_MIDI); 261 | } 262 | 263 | function cursorAction(cursorButton) 264 | { 265 | // TRACK 266 | if (OP1.isMode == OP1.MODE_ARRANGE) { 267 | switch(cursorButton) 268 | { 269 | case OP1.ARROW_UP_BUTTON: 270 | application.arrowKeyUp(); 271 | break; 272 | 273 | case OP1.ARROW_DOWN_BUTTON: 274 | application.arrowKeyDown(); 275 | break; 276 | } 277 | } 278 | // MIXER 279 | if (OP1.isMode == OP1.MODE_MIXER) 280 | { 281 | switch(cursorButton) 282 | { 283 | case OP1.ARROW_UP_BUTTON: 284 | application.focusPanelAbove(); 285 | break; 286 | 287 | case OP1.ARROW_DOWN_BUTTON: 288 | application.focusPanelBelow(); 289 | break; 290 | } 291 | } 292 | } 293 | 294 | 295 | function update_display_clips() 296 | { 297 | count = 0; 298 | colors = []; 299 | length = []; 300 | sequence = []; 301 | } 302 | 303 | function update_display_perform_mode() 304 | { 305 | showText("perform\rmode"); 306 | } 307 | 308 | function update_display_clip_mode() 309 | { 310 | showText("sel. clip\r"); 311 | } 312 | 313 | function update_display_arrange_mode() 314 | { 315 | showText("song pos,\r" + Ctrl.position); 316 | } 317 | 318 | function update_display_mixer_mode() 319 | { 320 | showText("sel. track\r"); 321 | } 322 | 323 | 324 | 325 | function trim(str) { 326 | str = str.replace(/^\s+/, ''); 327 | for (var i = str.length - 1; i >= 0; i--) { 328 | if (/\S/.test(str.charAt(i))) { 329 | str = str.substring(0, i + 1); 330 | break; 331 | } 332 | } 333 | 334 | return str; 335 | } 336 | 337 | function d2h(d) 338 | { 339 | var hex = Number(d).toString(16); 340 | if (hex.length < 2) { 341 | hex = "0" + hex; 342 | } 343 | 344 | return hex; 345 | } 346 | 347 | function exit() 348 | { 349 | // 350 | // Exit 351 | execMidi(OP1.EXIT_MIDI); 352 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OP1Bitwig 2 | ============== 3 | 4 | Bitwig Studio script to support the OP-1 by Teenage Engineering. 5 | 6 | 7 | > Unsupported!!! 8 | > I will probably continue work on this when I have some more free time. 9 | --------------------------------------------------------------------------------