From e7aee333e053617131a3b3ef03fad598d6aa7130 Mon Sep 17 00:00:00 2001 From: Sparadox Date: Wed, 2 Sep 2015 01:30:03 -0400 Subject: [PATCH] Add double-click feature to quick-select a device Also, notifications settings have been muted programmatically (even though I'm not completely sure what that does :) ) and navigation accross nested devices has been made easier. Sadly, the `selectParent` function we use doesn't seem to work :( --- TODO List.md | 9 ++++---- launchcontrolxl/controller.js | 13 +++++++++++ launchcontrolxl/device_board.js | 41 +++++++++++++++++++++++++++++++-- launchcontrolxl/mixer_board.js | 26 ++++++++++++++++++++- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/TODO List.md b/TODO List.md index 631b554..0a7b5ef 100644 --- a/TODO List.md +++ b/TODO List.md @@ -1,7 +1,7 @@ TODO List --------- -Feel free to pick up a task and implementing if you don't have anything else to do :) +Feel free to pick up a task and implement it. New Features ============ @@ -16,11 +16,12 @@ New Features Launchcontrol). That would be cool for a mixing session where one might want to be able to switch between effects only. Extending the existing UserMode should be a good approach. * Track navigation synchronized with the Novation Launchpad (make a PR against their repo). -* Scroll in sends (should work out of the box the the Bitwig 1.1 minor release). +* Scroll in sends: investigate what doesn't work and fix it * Save the state of the launchpad when the script is exited and retrieve it when it wakes up (state = physical values of all the knobs and faders for a direct takeover when starting the - script). Apparently the new Bitwig API has bindings to write on disk, otherwise a TCP - service will be required. + script). Since Bitwig's API doesn't seem to provide a good way to write on DIsk, I was thinking + of hiding the state in controller parameters on `exit()` and to fetch them on `start()` and then + get them deleted to stop the end user from witnessing this dirty trick in the UI. Code improvements diff --git a/launchcontrolxl/controller.js b/launchcontrolxl/controller.js index e9ba21b..1600bf7 100644 --- a/launchcontrolxl/controller.js +++ b/launchcontrolxl/controller.js @@ -12,6 +12,19 @@ Controller = function(bw_host){ this.boards = []; this.current_board_number = -1; + // Let's set the notification levels + log_info("Setting notification settings"); + this.notification_settings = this.host.getNotificationSettings(); + this.notification_settings.getUserNotificationsEnabled().set(true); + this.notification_settings.setShouldShowSelectionNotifications(false); + this.notification_settings.setShouldShowChannelSelectionNotifications(false); + this.notification_settings.setShouldShowTrackSelectionNotifications(false); + this.notification_settings.setShouldShowDeviceSelectionNotifications(false); + this.notification_settings.setShouldShowDeviceLayerSelectionNotifications(false); + this.notification_settings.setShouldShowPresetNotifications(false); + this.notification_settings.setShouldShowMappingNotifications(false); + this.notification_settings.setShouldShowValueNotifications(false); + // Unfortunately we can have only one control section per controller so we // have to create it here once and for all for the 8 user channels this.user_control_count = Board.CONTROL_COUNT*8 + 2*LiveBoard.USER_CONTROL_COUNT; diff --git a/launchcontrolxl/device_board.js b/launchcontrolxl/device_board.js index f385b02..a9c835a 100644 --- a/launchcontrolxl/device_board.js +++ b/launchcontrolxl/device_board.js @@ -45,6 +45,10 @@ DeviceBoard = function(controller, channel){ } }; + this.slot_names = []; + this.select_slot = -1; + this.cursor_slot = null; + // Let's register our observers var board = this; for(var i=0; i<8; i++){ @@ -104,6 +108,22 @@ DeviceBoard = function(controller, channel){ SoftTakeoverBoard.prototype.valueChangedCallback.call(board, ["buttons", 0, 4], yes? 127 : 0); }); + + // Callbacks for navigation accross slots + this.controller.cursor_device.addSlotsObserver(function(slots){ + board.slot_names = slots; + }); + + this.controller.cursor_device.hasSlots().addValueObserver(function(has_slots){ + if(has_slots) { + board.selected_slot = 0; + board.cursor_slot = board.controller.cursor_device.getCursorSlot(); + board.cursor_slot.selectSlot(board.slot_names[board.selected_slot]); + } else { + board.selected_slot = -1; + board.cursor_slot = null; + } + }); } }; @@ -175,6 +195,25 @@ DeviceBoard.prototype.onMidi = function(status, data1, data2){ } } + // Nested navigation + if(path[0] == "action" && Math.floor(status/16) != 8 && this.cursor_slot !== null){ + switch(path[1]){ + case "mute": + this.controller.cursor_device.selectParent(); + break; + case "solo": + this.controller.cursor_device.selectFirstInSlot( + this.slot_names[this.selected_slot]); + break; + case "record": + this.selected_slot++; + if(this.selected_slot >= this.slot_names.length) + this.selected_slot = 0; + this.cursor_slot.selectSlot(this.slot_names[this.selected_slot]); + break; + } + } + // Tweak the right control if(this.hasControl(path)){ if(path[0] == "faders"){ @@ -275,8 +314,6 @@ DeviceBoard.prototype.getWeakColorBits = function(path){ } } - // TODO: Action buttons - // The buttons if(path[0] == "buttons"){ if (path[2] < 4) diff --git a/launchcontrolxl/mixer_board.js b/launchcontrolxl/mixer_board.js index 73550fb..f158e9b 100644 --- a/launchcontrolxl/mixer_board.js +++ b/launchcontrolxl/mixer_board.js @@ -45,6 +45,8 @@ MixerBoard = function(controller, channel){ this.device_mode = false; this.selected_track_index = 0; + this.last_hit = null; + this.consecutive_hits = 0; this.button_states = { "mute": [false, false, false, false, false, false, false, false], @@ -177,7 +179,12 @@ MixerBoard.prototype.onMidi = function(status, data1, data2){ } if(path[0] == "buttons" && path[1] === 0 && Math.floor(status/16) != 8){ - this.controller.track_bank.getTrack(path[2]).select(); + if(this.last_hit !== null && this.last_hit === path[2]) + this.enableDeviceMode(); + else { + this.controller.track_bank.getTrack(path[2]).select(); + this.bowLastHitString(path[2]); + } } // And let's update the "hasControl" (in case we caught up) @@ -202,6 +209,21 @@ MixerBoard.prototype.disable = function(){ Board.prototype.disable.call(this); }; +MixerBoard.prototype.bowLastHitString = function(button_number){ + if(this.last_hit === button_number) + this.consecutive_hits++; + else + this.consecutive_hits = 1; + this.last_hit = button_number; + var board = this; + this.controller.host.scheduleTask(function(btn_nb){ + if(board.last_hit === btn_nb) + board.consecutive_hits--; + if(board.consecutive_hits === 0) + board.last_hit = null; + }, [button_number], 500); +}; + //////////////////////////////////////////////////////////////////////////////// ///////////// Led color and manipulations /////////////// //////////////////////////////////////////////////////////////////////////////// @@ -300,6 +322,7 @@ MixerBoard.prototype.showSelectedMode = function(){ MixerBoard.prototype.enableDeviceMode = function(){ this.setSoftValue(["action", "device"], 127); + this.updateLed(["action", "device"]); this.disableAssignmentVisualFeedback(); this.device_mode = true; this.enableAssignmentVisualFeedback(); @@ -308,6 +331,7 @@ MixerBoard.prototype.enableDeviceMode = function(){ MixerBoard.prototype.enableMixerMode = function(){ this.setSoftValue(["action", "device"], 0); + this.updateLed(["action", "device"]); this.disableAssignmentVisualFeedback(); this.device_mode = false; this.enableAssignmentVisualFeedback();