From d3e9055d4977c1dc3ca7426b607dfb72ac75203e Mon Sep 17 00:00:00 2001 From: MaurizioB Date: Fri, 8 Jul 2016 05:16:08 +0200 Subject: [PATCH] Event modifiers, range mode, Editor window updated, variuos fixes. Incoming event can now be transformed directly from the Editor window. NoLaE now can convert any NOTE or CTRL event coming from the Launch Control, ctrl IDs can be changed, ctrl values can become notes (the ctrl value can be a note or a velocity), knobs and faders can send notes too. Every controller has a range mode: buttons can have different press/release values, knobs/faders can have a different range, indipendently from the incoming value. As for the latter, there are 4 range modes: Equal, Single min (minimum value is only at the beginning of the scale), Single max (maximum value is only at the end of the scale), Single ends (minimum and maximum values are only at the beginning and end of the scale); for example, using a range from 0 to 8, value 0 can be reached only on the first step of the controller, the other values are equally divided within the scale. Editor window has been updated accordingly, the Note modifier has a small piano keyboard widget to easily select the preferred fixed note. There was a bug that ignored some mappings in the first template and in the top left knob, now it is fixed along with other minor bugs. Menus have been fixed too, it is also possible to CTRL+s to save the current config file now. --- classes.py | 96 +++++++++++++- const.py | 9 +- editorwin.py | 226 +++++++++++++++++++++++++------ icons.py | 39 ++---- launchcontrol.py | 133 +++++++++++++++---- launchcontrol.qrc | 1 + patch.ui | 331 ++++++++++++++++++++++++++++++++++++++++++---- smallpiano.png | Bin 0 -> 97 bytes 8 files changed, 714 insertions(+), 121 deletions(-) create mode 100644 smallpiano.png diff --git a/classes.py b/classes.py index 7f7193c..77bbf5a 100644 --- a/classes.py +++ b/classes.py @@ -61,14 +61,14 @@ def __init__(self, template, widget, ext=True, mode=Value, dest=Pass, patch=md.P self.led = widget.siblingLed else: self.led = None - elif led is False: + elif isinstance(led, bool) and led == False: self.led = None else: self.led = led widget.siblingLed = led # self.led_basevalue = led_basevalue - if self.led: + if self.led is not None: self.led_setup(led_basevalue) else: self.led_state = self.led_basevalue = 0 @@ -116,7 +116,7 @@ def led_ignore_action(self, event): pass def led_assign(self, action): - if not self.led: + if self.led is None: self.led_action = self.led_ignore_action return if action == Pass: @@ -279,3 +279,93 @@ def __init__(self, parent, text): self.move(center-self.width()/2, parent.y()-10) self.setStyleSheet('background-color: rgba(210,210,210,210); border: 1px solid gray;') self.raise_() + +class PianoKey(QtGui.QWidget): + def __init__(self, parent, id): + QtGui.QWidget.__init__(self, parent) + self.id = id + self.name = md.util.note_name(id) + self.black = True if self.name[1]=='#' else False + self.octave, self.note = divmod(id, 12) + self.hover_color = QtCore.Qt.red + if self.black: + self.color = QtCore.Qt.black + self.setMinimumSize(7, 24) + self.setMaximumSize(7, 24) + if (self.note & 1) == 1: + self.move(self.octave*7*10+self.note/2*10+7, 0) + else: + self.move(self.octave*7*10+(self.note+1)/2*10+7, 0) + else: + if self.id < 21 or self.id > 108: + self.color = QtCore.Qt.gray + elif self.id < 36 or self.id > 96: + self.color = QtGui.QColor(220, 220, 220) + elif self.id == 60: + self.color = QtGui.QColor(230, 230, 230) + else: + self.color = QtCore.Qt.white + self.setMinimumSize(10, 48) + self.setMaximumSize(10, 48) + if (self.note & 1) == 0: + self.move(self.octave*7*10+self.note/2*10, 0) + else: + self.move(self.octave*7*10+(self.note+1)/2*10, 0) + self.lower() + self._color = self.color + self.current_color = self.color + + def showEvent(self, event): + self.update() + + def paintEvent(self, event): + qp = QtGui.QPainter() + qp.begin(self) + self.draw_key(qp) + qp.end() + + def draw_key(self, qp): + qp.setPen(QtGui.QPen(QtCore.Qt.black, 0.5, QtCore.Qt.SolidLine)) + qp.setBrush(self.current_color) + qp.drawRect(0, 0, self.width(), self.height()) + + def mouseReleaseEvent(self, event): + self.parent().done(self.id+1) + + def enterEvent(self, event): + self.current_color = self.hover_color + self.update() + + def leaveEvent(self, event): + self.current_color = self.color + self.update() + +class Piano(QtGui.QDialog): + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + self.setWindowTitle('Note selector') + self.keys = [] + self.highlight = None + for i in range(128): + key = PianoKey(self, i) + self.keys.append(key) + self.setMinimumHeight(key.height()) + self.setMinimumWidth(key.width()*75) + + def exec_(self, highlight=None): + if self.highlight: + key = self.keys[self.highlight] + key.current_color = key.color = key._color + key.repaint() + key.update() + if highlight: + self.highlight = highlight + key = self.keys[self.highlight] + key.current_color = key.color = QtGui.QColor(255, 150, 150) + key.repaint() + key.update() + else: + self.highlight = None + return QtGui.QDialog.exec_(self) + + diff --git a/const.py b/const.py index 17849df..791e32e 100644 --- a/const.py +++ b/const.py @@ -31,10 +31,11 @@ class Const(object): #TODO Split is missing, update function with regex md_replace = ('Ctrl', 'Port', 'Channel', 'Velocity', - 'Note', 'Pitchbend', 'Aftertouch', 'Program', 'SysEx', 'Generator', - 'extra.Harmonize', 'LimitPolyphony', 'MakeMonophonic', 'LatchNotes', 'Panic' - 'Discard', 'Pass', 'Sanitize', 'Print', - 'EVENT' + 'Note', 'Pitchbend', 'Aftertouch', 'Program', 'Generator', + 'event.MidiEvent', 'event.SysExEvent', + 'extra.Harmonize', 'LimitPolyphony', 'MakeMonophonic', 'LatchNotes', 'Panic', + 'Discard', 'Pass', 'Sanitize', 'Print', 'Process', 'Call', + 'EVENT_DATA1', 'EVENT_DATA2', ) patch_colors = (('darkred', 'red'), ('gray', 'black')) diff --git a/editorwin.py b/editorwin.py index 9ff2f6c..a12b650 100644 --- a/editorwin.py +++ b/editorwin.py @@ -48,8 +48,13 @@ def __init__(self, parent=None, mode='control'): self.toggle_add_btn.clicked.connect(self.toggle_value_add) self.toggle_remove_btn.clicked.connect(self.toggle_value_remove) self.toggle_listview.closeEditor = self.toggle_validate - self.convert_chk.toggled.connect(self.convert_enable) + self.range_chk.toggled.connect(self.range_set) + self.convert_chk.toggled.connect(self.convert_set) self.chan_reset_btn.clicked.connect(self.chan_reset) + self.convert_ctrl_radio.toggled.connect(self.convert_group_toggle) + self.convert_note_radio.toggled.connect(self.convert_group_toggle) + self.force_note_change_radio.toggled.connect(self.force_note_change_toggle) + self.convert_piano_btn.clicked.connect(self.piano_show) self.convert_ctrl_radio.id = ToCtrl self.convert_note_radio.id = ToNote @@ -58,12 +63,16 @@ def __init__(self, parent=None, mode='control'): self.models_setup() self.base_group.setEnabled(False) self.patch_group.setEnabled(False) + self.convert_set(False) self.patch_templates_menu_create() self.patch_toolbtn.setMenu(self.patch_templates) - self.toggle_set(False) +# self.toggle_set(False) metrics = QtGui.QFontMetrics(self.patch_edit.font()) self.patch_edit.setTabStopWidth(4*metrics.width(' ')) + self.piano = Piano(self) + self.piano.setModal(True) + def toggle_chk_wheelEvent(self, event): if event.orientation() == QtCore.Qt.Vertical: hbar = self.toggle_listview.horizontalScrollBar() @@ -92,15 +101,17 @@ def toggle_set(self, value): self.toggle_add_btn.setEnabled(value) if not self.current_widget: return - if value and not (self.current_widget.get('toggle') or self.current_widget.get('toggle_values') or self.current_widget.get('toggle_model')): - toggle_model = QtGui.QStandardItemModel() - for i in [0, 127]: - item = QtGui.QStandardItem(str(i)) - item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled) - toggle_model.appendRow(item) + if value: + if not (self.current_widget.get('toggle') or self.current_widget.get('toggle_values') or self.current_widget.get('toggle_model')): + toggle_model = QtGui.QStandardItemModel() + for i in [0, 127]: + item = QtGui.QStandardItem(str(i)) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled) + toggle_model.appendRow(item) self.current_widget['toggle_values'] = (0, 127) self.current_widget['toggle_model'] = toggle_model - self.toggle_listview.setModel(toggle_model) + self.toggle_listview.setModel(toggle_model) + self.range_chk.setChecked(False) self.current_widget['toggle'] = value action = self.current_widget['widget'].toggle_action if action: @@ -136,13 +147,61 @@ def toggle_range_check(self): else: self.toggle_remove_btn.setEnabled(True) - def convert_enable(self, value): + def range_set(self, value): + self.range_min_spin.setEnabled(value) + self.range_max_spin.setEnabled(value) + self.range_combo.setEnabled(True if value and not isinstance(self.current_widget.get('widget'), QtGui.QPushButton) else False) + self.range_min_lbl.setEnabled(value) + self.range_max_lbl.setEnabled(value) + self.range_scale_lbl.setEnabled(True if value and not isinstance(self.current_widget.get('widget'), QtGui.QPushButton) else False) + if value: + self.toggle_chk.setChecked(False) + if not self.current_widget.get('range'): + self.current_widget['range'] = value + self.current_widget['range_values'] = (0, 127, 0) + + def convert_set(self, value): for button in self.convert_group.buttons(): button.setEnabled(value) - - def chan_enable(self, value): - self.chan_spin.setEnabled(value) - + self.force_note_frame.setVisible(value) + self.force_ctrl_frame.setVisible(value) + if self.convert_ctrl_radio.isChecked(): + ctrl = True + else: + ctrl = False + self.force_ctrl_lbl.setEnabled(ctrl) + self.force_ctrl_spin.setEnabled(ctrl) + self.force_note_toggle_chk.setEnabled(not ctrl) + self.force_note_event_lbl.setEnabled(not ctrl) + self.force_note_change_radio.setEnabled(not ctrl) + self.force_vel_change_radio.setEnabled(not ctrl) + self.force_note_lbl.setEnabled(not ctrl) + self.force_note_combo.setEnabled(True if (not ctrl and self.force_vel_change_radio.isChecked()) else False) + self.convert_piano_btn.setEnabled(not ctrl) + self.force_vel_lbl.setEnabled(not ctrl) + self.force_vel_spin.setEnabled(not ctrl) + + def convert_group_toggle(self, value): + if not value: + return + if self.sender() == self.convert_ctrl_radio: + ctrl = True + else: + ctrl = False + self.force_ctrl_lbl.setEnabled(ctrl) + self.force_ctrl_spin.setEnabled(ctrl) + self.force_note_toggle_chk.setEnabled(not ctrl) + self.force_note_event_lbl.setEnabled(not ctrl) + self.force_note_change_radio.setEnabled(not ctrl) + self.force_vel_change_radio.setEnabled(not ctrl) + self.force_note_lbl.setEnabled(not ctrl) + self.force_note_combo.setEnabled(True if (not ctrl and self.force_vel_change_radio.isChecked()) else False) + self.convert_piano_btn.setEnabled(not ctrl) + self.force_vel_lbl.setEnabled(not ctrl) + self.force_vel_spin.setEnabled(not ctrl) + + def force_note_change_toggle(self, value): + self.force_note_combo.setEnabled(not value) def closeEvent(self, event): if self.current_widget: @@ -352,6 +411,22 @@ def create_table(): setBold(self.action_dev_model.item(9)) setBold(self.action_dir_model.item(8)) + #creating notes for force_note_combo + self.note_model = QtGui.QStandardItemModel() + item = QtGui.QStandardItem('(incoming)') + self.note_model.appendRow(item) + for n in range(127): + note_name = md.util.note_name(n).upper() + if '#' in note_name: + note_name = '{} {}'.format(note_name[:2], note_name[2:]) + else: + note_name = '{} {}'.format(note_name[:1], note_name[1:]) + item = QtGui.QStandardItem(note_name) + self.note_model.appendRow(item) + for o in range(11): + setBold(self.note_model.item(o*12+1, 0)) + self.force_note_combo.setModel(self.note_model) + def color_column_check(self, selected, previous): index = self.color_table.selectedIndexes()[0] @@ -394,18 +469,33 @@ def enable_set(self, value): def tool_group_setEnabled(self, value): for button in self.tool_group.buttons(): button.setEnabled(value) - self.toggle_listview.setEnabled(value) - if self.current_widget and not isinstance(self.current_widget['widget'], QtGui.QPushButton): - for button in self.convert_group.buttons(): - button.setEnabled(False) - self.convert_chk.setEnabled(False) - self.convert_chk.setChecked(False) - self.toggle_chk.setEnabled(False) - self.toggle_chk.setChecked(False) - toggle_model = QtGui.QStandardItemModel() - self.toggle_listview.setModel(toggle_model) +# self.toggle_listview.setEnabled(value) +# if self.current_widget: +# if not isinstance(self.current_widget['widget'], QtGui.QPushButton): +# for button in self.convert_group.buttons(): +# button.setEnabled(False) +# self.convert_chk.setEnabled(False) +# self.convert_chk.setChecked(False) +# # self.toggle_chk.setEnabled(False) +# # self.toggle_chk.setChecked(False) +# toggle_model = QtGui.QStandardItemModel() +# self.toggle_listview.setModel(toggle_model) +# else: +# pass if value: self.toggle_range_check() + else: + self.toggle_listview.setEnabled(False) + self.range_min_spin.setEnabled(False) + self.range_max_spin.setEnabled(False) + self.range_combo.setEnabled(False) + self.range_min_lbl.setEnabled(False) + self.range_max_lbl.setEnabled(False) + self.range_scale_lbl.setEnabled(False) +# self.force_ctrl_frame.setEnabled(False) + self.force_ctrl_frame.setVisible(False) +# self.force_note_frame.setEnabled(False) + self.force_note_frame.setVisible(False) def chan_reset(self): self.chan_spin.setValue(0) @@ -525,7 +615,7 @@ def statusbar_create(self): echan_lbl = QtGui.QLabel('Channel', self.statusbar) echan_edit = QtGui.QLabel('', self.statusbar) echan_edit.setFixedWidth(12) - eext_lbl = QtGui.QLabel('Extension', self.statusbar) + eext_lbl = QtGui.QLabel('Range', self.statusbar) eext_edit = QtGui.QLabel('', self.statusbar) eext_edit.setFixedWidth(48) emode_lbl = QtGui.QLabel('Mode', self.statusbar) @@ -572,7 +662,7 @@ def status_update(self, event_data): def widget_save(self, template=None): if not self.current_widget: return - if not template: + if template is None: template = self.main.template widget = self.current_widget.get('widget') enabled = self.current_widget.get('enabled', True) @@ -580,11 +670,22 @@ def widget_save(self, template=None): text = self.current_widget.get('text', '') self.current_widget['chan'] = self.chan_spin.value() convert = self.convert_group.checkedButton().id - #TODO: not important: consider remembering the state, even if disabled if self.convert_chk.isChecked(): - self.current_widget['convert'] = convert - elif self.current_widget.get('convert'): - self.current_widget.pop('convert') + self.current_widget['convert'] = True + else: + self.current_widget['convert'] = False + self.current_widget['convert_type'] = convert + if self.convert_ctrl_radio.isChecked(): + self.current_widget['convert_values'] = self.force_ctrl_spin.value() + else: + if self.force_note_change_radio.isChecked(): + self.current_widget['convert_values'] = (None, self.force_vel_spin.value()) + else: + if self.force_note_combo.currentIndex() == 0: + note = None + else: + note = self.force_note_combo.currentIndex()-1 + self.current_widget['convert_values'] = (note, self.force_vel_spin.value()) patch = self.current_widget.get('patch') if text is not None: @@ -602,9 +703,7 @@ def widget_save(self, template=None): led_index = self.led_combo.currentIndex() if led_index > 0: led_item = self.ledlist_model.item(led_index) - if led_item.led == 0: - self.current_widget['led'] = False - elif led_item.led == widget.siblingLed: + if led_item.led == widget.siblingLed: self.current_widget['led'] = True led_basevalue = int(str(self.led_base_combo.currentText()), 0) if led_basevalue == 0: @@ -628,6 +727,12 @@ def widget_save(self, template=None): toggle_model = self.current_widget.get('toggle_model') if toggle_model: self.current_widget['toggle_values'] = tuple([int(toggle_model.item(i).text()) for i in range(toggle_model.rowCount())]) + vrange = self.current_widget.get('range') + if vrange: + range_start = self.range_min_spin.value() + range_end = self.range_max_spin.value() + range_type = self.range_combo.currentIndex() + self.current_widget['range_values'] = range_start, range_end, range_type self.main.conf_dict[template][widget] = self.current_widget self.widgetSaved.emit(template) @@ -673,6 +778,12 @@ def widget_change(self, widget): self.led_action_combo.setModelColumn(0) self.led_action_combo.setCurrentIndex(1) #other values stuff to reset + self.toggle_chk.setChecked(False) + toggle_model = QtGui.QStandardItemModel() + self.toggle_listview.setModel(toggle_model) + self.range_chk.setChecked(False) + self.convert_chk.setChecked(False) + self.convert_ctrl_radio.setChecked(True) return widget_dict['widget'] = widget @@ -705,16 +816,32 @@ def widget_change(self, widget): self.chan_spin.setValue(chan) #Convert event type - convert = widget_dict.get('convert') - if convert == None: - self.convert_chk.setChecked(False) + convert = widget_dict.get('convert', False) + if isinstance(convert, bool): + convert_type = widget_dict.get('convert_type', ToCtrl) + else: + convert_type = convert + if convert == False: self.convert_ctrl_radio.setChecked(True) + self.convert_chk.setChecked(False) else: self.convert_chk.setChecked(True) - if convert == ToCtrl: - self.convert_ctrl_radio.setChecked(True) + if convert_type == ToCtrl: + self.convert_ctrl_radio.setChecked(True) + self.force_ctrl_spin.setValue(widget_dict.get('convert_values', 0)) + self.force_note_change_radio.setChecked(True) + self.force_note_combo.setCurrentIndex(0) + self.force_vel_spin.setValue(127) + else: + self.convert_note_radio.setChecked(True) + convert_note, convert_vel = widget_dict.get('convert_values', (None, 0)) + if convert_note is None: + self.force_note_change_radio.setChecked(True) + self.force_note_combo.setCurrentIndex(0) else: - self.convert_note_radio.setChecked(True) + self.force_note_combo.setCurrentIndex(convert_note+1) + self.force_vel_spin.setValue(convert_vel) + self.force_ctrl_spin.setValue(0) #Patch patch = widget_dict.get('patch') @@ -730,6 +857,7 @@ def widget_change(self, widget): #LED led = widget_dict.get('led', True) + print led self.led_combo.blockSignals(True) if led == None or (isinstance(led, bool) and led == False): self.led_combo.setCurrentIndex(0) @@ -816,7 +944,15 @@ def widget_change(self, widget): else: self.led_action_combo.lineEdit().setText(led_action) - #Toggle + #Toggle and Range + vrange = self.current_widget.get('range', False) + self.range_chk.setChecked(vrange) + range_start, range_end, range_type = self.current_widget.get('range_values', (0, 127, 0)) + self.range_min_spin.setValue(range_start) + self.range_max_spin.setValue(range_end) + self.range_combo.setCurrentIndex(range_type) + self.range_combo.setEnabled(True if vrange and not isinstance(widget, QtGui.QPushButton) else False) + self.range_scale_lbl.setEnabled(True if vrange and not isinstance(widget, QtGui.QPushButton) else False) if not isinstance(widget, QtGui.QPushButton): self.toggle_chk.setChecked(False) self.toggle_chk.setEnabled(False) @@ -828,6 +964,7 @@ def widget_change(self, widget): self.toggle_chk.setChecked(True) else: self.toggle_chk.setChecked(False) + self.toggle_listview.setEnabled(False) toggle_model = self.current_widget.get('toggle_model') if not toggle_model: toggle_values = self.current_widget.get('toggle_values') @@ -856,3 +993,12 @@ def patch_edit_focusOut(self, event): self.patch_edit.setStyleSheet('color: gray') self.patch_edit.setPlainText('Pass') self.patch_edit.blockSignals(False) + + def piano_show(self): + key = self.force_note_combo.currentIndex() + res = self.piano.exec_(key-1 if key > 0 else None) + if res > 0: + self.force_note_combo.setCurrentIndex(res) + + + diff --git a/icons.py b/icons.py index 85b41cf..38a6962 100644 --- a/icons.py +++ b/icons.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: lun giu 6 23:05:44 2016 +# Created: gio lug 7 17:11:08 2016 # by: The Resource Compiler for PyQt (Qt v4.8.6) # # WARNING! All changes made in this file will be lost! @@ -38,28 +38,15 @@ \xc5\xd2\x80\x5c\x81\xd8\x7d\x9d\x9b\x29\x15\x08\x7d\x6f\x40\xfa\ \x04\xc7\xdf\xc0\x54\x6f\x5c\x2f\x92\x2c\x0c\x50\x46\x3f\xed\x15\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x37\ +\x00\x00\x00\x61\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x00\xd9\x49\x44\x41\x54\x38\x8d\xcd\ -\xd2\x31\x4a\x43\x41\x10\x06\xe0\x4f\xb0\x8a\xd1\xb4\x76\x7a\x00\ -\xc1\xd2\x23\x04\x8b\xf8\xf0\x0a\xf1\x0c\x0a\x7a\xb1\x5c\x41\x14\ -\x62\x67\x3c\x81\x26\x65\x0a\xb5\x31\xc4\xc2\x59\x5c\xc6\xf7\x14\ -\x6c\x74\x60\xd9\xd9\xd9\xff\x9f\x9d\x7f\x76\xf8\x8f\xb6\x83\x0b\ -\xdc\xe2\x05\xaf\xb8\xc3\x15\x06\x3f\x91\x87\x78\xc2\xba\x63\xcd\ -\x71\xdc\x45\x1e\x63\x15\xc0\x7b\x8c\xd0\xc3\x56\xf8\xb3\xb8\x5b\ -\xe1\x2c\x93\x9b\x44\x6e\x2b\x75\x90\x92\x34\xe5\x62\x1f\xcb\xaa\ -\xcc\x51\xc4\x4f\xb1\xc0\x41\x7a\xa8\xe0\x96\xc1\x35\x49\x3a\x7b\ -\x01\x5e\xc4\xf9\x08\x87\x11\xeb\x27\xec\x64\xb3\xa5\xd4\x8d\xb4\ -\x5f\x47\xb2\xdd\x16\xec\xfa\x3b\x09\x4d\x10\xe7\x29\x56\x4b\xd8\ -\xab\xb5\x95\x26\xce\x74\x37\xf1\xc1\x67\x13\x4f\x32\x60\x8c\xb7\ -\x2a\x49\x13\x9a\xfb\xe1\xd7\xe4\x2f\xdf\x58\x6c\x88\x47\xbf\x1c\ -\xa4\x62\xdb\x38\xc7\x0d\x9e\x7d\x8c\xf3\x14\x97\x1d\xd2\xfe\xd8\ -\xde\x01\xea\x5f\x52\xb0\x6d\xdd\x8a\xf4\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x0a\x00\x00\x00\x08\x08\x06\x00\x00\x00\xc0\xfa\x6e\xb6\ +\x00\x00\x00\x28\x49\x44\x41\x54\x18\x57\x63\xf8\xff\xff\xff\x7f\ +\x06\x06\x06\x38\xc6\xc1\x67\x60\x62\x20\x12\xd0\x48\x21\xd4\x19\ +\x38\x69\x06\x06\x52\x4d\x24\x06\x10\xad\x10\x00\x81\x52\x1d\xf1\ +\xe9\x9a\x40\x10\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ " qt_resource_name = "\ @@ -75,17 +62,17 @@ \x02\xbf\xef\x67\ \x00\x61\ \x00\x63\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2d\x00\x75\x00\x6e\x00\x64\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x00\x96\xc8\x47\ -\x00\x65\ -\x00\x79\x00\x65\x00\x2d\x00\x32\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x03\x58\x24\x27\ +\x00\x73\ +\x00\x6d\x00\x61\x00\x6c\x00\x6c\x00\x70\x00\x69\x00\x61\x00\x6e\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\ -\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7b\ \x00\x00\x00\x24\x00\x00\x00\x00\x00\x01\x00\x00\x00\xca\ +\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7b\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ " diff --git a/launchcontrol.py b/launchcontrol.py index 81e7878..e9266d4 100755 --- a/launchcontrol.py +++ b/launchcontrol.py @@ -3,6 +3,7 @@ import sys, argparse import mididings as md +from mididings import extra as mdx from collections import OrderedDict, namedtuple from copy import copy from os.path import basename as path_basename @@ -13,7 +14,7 @@ from utils import * from classes import * -version = 0.5 +version = 0.8 prog_name = 'NoLaE' description = 'A Novation LaunchControl mapper and filter' backend = 'alsa' @@ -100,6 +101,10 @@ def __init__(self, mode='live', map_file=None, config=None, backend='alsa'): # self.template_connect(mode) else: # self.conf_dict = {} + saveAction = QtGui.QAction(self.getIcon(QtGui.QStyle.SP_DialogSaveButton), '&Save config', self) + saveAction.setShortcut('Ctrl+S') + saveAction.triggered.connect(self.config_write) + self.file_menu.addAction(saveAction) self.template_clipboard = None self.template_clipcut = False rename_action = QtGui.QAction('Rename', self) @@ -357,7 +362,7 @@ def template_model_select(self, index): self.temp_id_group.button(item.id).setChecked(True) def template_rename_dialog(self, template=True): - if isinstance(template, int): + if not isinstance(template, bool): default_text = self.template_list[template].name elif template == True: template = self.template @@ -366,6 +371,7 @@ def template_rename_dialog(self, template=True): item = self.template_model.itemFromIndex(self.template_listview.currentIndex()) template = item.id default_text = item.text() + print template template_name, res = QtGui.QInputDialog.getText(self, 'Template Name', 'Enter template name', QtGui.QLineEdit.Normal, default_text) if res: self.templateRenamed.emit(template, str(template_name.toLatin1())) @@ -1290,6 +1296,7 @@ def routing_setup(self): for event, (widget, ext, mode) in self.map_dict[template].items(): if not widget in config_dict: continue + event_chan, event_type, event_id = event patch_data = config_dict[widget] ext = (0, 127) if ext==True else (ext[0], ext[1]) dest = patch_data.get('dest', 1) @@ -1298,38 +1305,97 @@ def routing_setup(self): elif len(out_ports) and dest > len(out_ports): dest = len(out_ports) chan = patch_data.get('chan') - convert = patch_data.get('convert') toggle = patch_data.get('toggle') + pre_patch = None if toggle: toggle_range = MyCycle(patch_data.get('toggle_values', (0, 127))) toggle_patch = md.Process(lambda ev, cycle=toggle_range: md.event.MidiEvent(ev.type, ev.port, ev.channel, ev.data1, cycle.next())) - #TODO finire qui, implementare nell'editor la modalità? - if event[1] == md.CTRL: - if convert == ToNote: - #TODO: AAAh. Ragiona bene su come implementare il convert - pass - toggle_patch = md.CtrlValueFilter(ext[1]) >> toggle_patch + if mode == Toggle: + pre_patch = toggle_patch + elif event_type == md.CTRL: + pre_patch = md.CtrlValueFilter(ext[1]) >> toggle_patch + else: + pre_patch = md.Filter(md.NOTEON) >> toggle_patch + vrange = patch_data.get('range') + if vrange and not pre_patch: + range_start, range_end, range_type = patch_data.get('range_values', (0, 127, 0)) + if isinstance(widget, QtGui.QPushButton): + if event_type == md.CTRL: + pre_patch = [md.CtrlValueFilter(ext[0]) >> Ctrl(event_id, range_start), md.CtrlValueFilter(ext[1]) >> Ctrl(event_id, range_end)] + else: + pre_patch = md.KeyFilter(event_id) >> [md.Filter(md.NOTEOFF) >> md.NoteOff(event_id, range_start), + md.Filter(md.NOTEON) >> md.NoteOn(event_id, range_end)] else: - toggle_patch = md.Filter(md.NOTEON) >> toggle_patch + if range_type == 0: + pre_patch = md.CtrlRange(event_id, range_start, range_end, ext[0], ext[1]+1-(ext[1]+1)/(range_end-range_start+1)) + elif range_type == 1: + pre_patch = md.CtrlRange(event_id, range_end, range_start, ext[0], ext[1]) >> md.CtrlRange(event_id, range_end, range_start, range_start, range_end) + elif range_type == 2: + pre_patch = md.CtrlRange(event_id, range_start, range_end, ext[0], ext[1]) + else: + pre_patch = [md.CtrlValueFilter(ext[0]) >> md.Ctrl(event_id, range_start), + -md.CtrlValueFilter(ext[0]) >> md.CtrlRange(event_id, range_start, range_end, ext[0]+1, ext[1])] + + convert = patch_data.get('convert') + conv_patch = None + if convert: + if convert == ToCtrl: + ctrl_id = patch_data.get('convert_values', event_id) + if (event_type == md.CTRL and event_id != ctrl_id) or event_type == md.NOTE: + conv_patch = md.Ctrl(ctrl_id, md.EVENT_DATA2) + else: + convert_note, convert_vel = patch_data.get('convert_values', (None, 0)) + if convert_note is None: + if event_type == md.NOTE: + conv_patch = [md.Filter(md.NOTEOFF) >> md.NoteOff(md.EVENT_NOTE), + md.Filter(md.NOTEON) >> md.NoteOn(md.EVENT_NOTE, convert_vel if convert_vel > 0 else md.EVENT_VELOCITY)] + else: + if vrange: + start = range_start + elif toggle: + start = 0 + else: + start = ext[0] + conv_patch = [md.CtrlValueFilter(start) >> md.NoteOff(md.EVENT_VALUE, convert_vel), + -md.CtrlValueFilter(start) >> md.NoteOn(md.EVENT_VALUE, convert_vel)] >> mdx.MakeMonophonic() + else: + if event_type == md.NOTE: + conv_patch = [md.Filter(md.NOTEOFF) >> md.NoteOff(convert_note), + md.Filter(md.NOTEON) >> md.NoteOn(convert_note, convert_vel if convert_vel > 0 else md.EVENT_VELOCITY)] + else: + if vrange: + start = range_start + elif toggle: + start = 0 + else: + start = ext[0] + conv_patch = [md.CtrlValueFilter(start) >> md.NoteOff(convert_note, md.EVENT_VALUE), + -md.CtrlValueFilter(start) >> md.NoteOn(convert_note, md.EVENT_VALUE)] + if pre_patch: + if conv_patch: + pre_patch = pre_patch >> conv_patch + elif conv_patch: + pre_patch = conv_patch + patch = patch_data.get('patch') if not patch: - if not toggle: - if not chan or event[0] == chan: + if not pre_patch: + if not chan or event_chan == chan: patch = md.Pass() else: patch = md.Channel(chan) else: - if chan and not event[0] == chan: - patch = md.Channel(chan) >> toggle_patch + if chan and not event_chan == chan: + patch = md.Channel(chan) >> pre_patch else: - patch = toggle_patch + patch = pre_patch else: for rep in md_replace: patch = patch.replace(rep, 'md.'+rep) patch = eval(patch) - if toggle: - patch = toggle_patch >> patch - if chan and not event[0] == chan: + if pre_patch: + patch = pre_patch >> patch + if chan and not event_chan == chan: patch = md.Channel(chan) >> patch text = patch_data.get('text') led = patch_data.get('led', True) @@ -1423,7 +1489,7 @@ def routing_setup(self): #TODO: implement name inside TemplateClass template_id = self.template_list[template].name scenes[template+1] = md.Scene('{} template {}'.format(*template_str(template)) if isinstance(template_id, int) else 'Template {}'.format(template_id), template_scene) -# print template_scene + print template_scene self.map_dict = temp_map_dict return scenes, out_ports @@ -1566,7 +1632,7 @@ def routing_start(self): widget.setVisible(False) widget.siblingLabel.setVisible(False) continue - if signal.led: + if signal.led is not None: led_list.append((signal.led, signal.led_basevalue)) if signal.text: widget.siblingLabel.setText(signal.text) @@ -2273,9 +2339,19 @@ def config_save(self): save_file = QtGui.QFileDialog.getSaveFileName(self, 'Save config to file', self.config if self.config else '', 'LaunchPad config (*.nlc)') if not save_file: return - save_file = str(save_file) - conf_dict = OrderedDict() + self.config_write(str(save_file)) + + def config_write(self, save_file=None): + if self.config: + save_file = self.config + elif not save_file: + self.editor_win.widget_save(self.template) + save_file = QtGui.QFileDialog.getSaveFileName(self, 'Save config to file', self.config if self.config else '', 'LaunchPad config (*.nlc)') + if not save_file: + return + save_file = str(save_file) + conf_dict = OrderedDict() output_list = [] for id in range(self.output_model.rowCount()): output_item = self.output_model.item(id) @@ -2331,7 +2407,16 @@ def config_save(self): if v == 0: widget_data.pop('chan') elif k == 'convert': - pass + if v != True: + widget_data.pop('convert') + try: + widget_data.pop('convert_type') + widget_data.pop('convert_values') + except: + pass + else: + convert_type = widget_data.get('convert_type', ToCtrl) + widget_data['convert'] = convert_type elif k == 'led': if v == True: widget_data.pop('led') @@ -2361,7 +2446,7 @@ def config_save(self): dict_str += '}' with open(save_file, 'w') as fo: fo.write(dict_str) - self.setWindowTitle('{} - Editor {}'.format(prog_name, path_basename(save_file))) + self.setWindowTitle('{} - Editor ({})'.format(prog_name, path_basename(save_file))) self.config = save_file def closeEvent(self, event): diff --git a/launchcontrol.qrc b/launchcontrol.qrc index e6984f6..969ef87 100644 --- a/launchcontrol.qrc +++ b/launchcontrol.qrc @@ -1,6 +1,7 @@ action-undo.png + smallpiano.png eye.png diff --git a/patch.ui b/patch.ui index 164e3c9..e4eb0b2 100644 --- a/patch.ui +++ b/patch.ui @@ -6,8 +6,8 @@ 0 0 - 473 - 385 + 550 + 557 @@ -294,7 +294,7 @@ Event modifiers - + @@ -302,7 +302,7 @@ false - Force to: + Force incoming event tool_group @@ -310,53 +310,144 @@ - + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + + + false - Ctrl - - - true - - - true + Range - convert_group + tool_group - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + false - Note + min - - convert_group - - + + + false + + + 127 + + + + + + + false + + + max + + + + + + + false + + + 127 + + + 127 + + + + + Qt::Horizontal - - QSizePolicy::MinimumExpanding - - 20 + 40 20 + + + + false + + + mode + + + + + + + false + + + + Equal + + + + + Single min + + + + + Single max + + + + + Single ends + + + + @@ -473,6 +564,197 @@ + + + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + + false + + + Ctrl + + + true + + + true + + + convert_group + + + + + + + ID: + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 10 + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + Toggle note ON/OFF + + + + + + + Note + + + + + + + Velocity + + + + + + + source + + + 0 + + + 127 + + + 127 + + + + + + + + + + Qt::Horizontal + + + + + + + velocity + + + convert_note_group + + + + + + + note + + + true + + + convert_note_group + + + + + + + + 20 + 18 + + + + + + + + :/icons/smallpiano.png + + + + true + + + + + + + Incoming event value change: + + + + + + + false + + + Note + + + convert_group + + + + + + + + @@ -483,7 +765,7 @@ 0 0 - 473 + 550 17 @@ -501,5 +783,6 @@ false + diff --git a/smallpiano.png b/smallpiano.png new file mode 100644 index 0000000000000000000000000000000000000000..291ee0dc65fe5b85391618dd3b3206745fb584c3 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+((rU~43P*={_+3+|9UnyHj85i u(-V>ugf4g}UXirOWMd2RRbdlYE5OhgB>VB@EC&Ig9tKZWKbLh*2~7Zj@fxQ9 literal 0 HcmV?d00001