def draw_cell(self, step, row):
     libseq.isPatternModified()  # Avoid refresh redrawing whole grid
     cellIndex = row * libseq.getSteps(
     ) + step  # Cells are stored in array sequentially: 1st row, 2nd row...
     if cellIndex >= len(self.cells):
         return
     note = self.keymap[row + self.keymap_offset]["note"]
     velocity_colour = libseq.getNoteVelocity(step, note)
     if velocity_colour:
         velocity_colour = 70 + velocity_colour
     elif libseq.getScale() == 1:
         # Draw tramlines for white notes in chromatic scale
         key = note % 12
         if key in (0, 2, 4, 5, 7, 9, 11):  # White notes
             #			if key in (1,3,6,8,10): # Black notes
             velocity_colour += 30
     else:
         # Draw tramlines for odd rows in other scales and maps
         if (row + self.keymap_offset) % 2:
             velocity_colour += 30
     duration = libseq.getNoteDuration(step, note)
     if not duration:
         duration = 1
     fill_colour = "#%02x%02x%02x" % (velocity_colour, velocity_colour,
                                      velocity_colour)
     cell = self.cells[cellIndex]
     coord = self.get_cell(step, row, duration)
     if cell:
         # Update existing cell
         self.grid_canvas.itemconfig(cell, fill=fill_colour)
         self.grid_canvas.coords(cell, coord)
     else:
         # Create new cell
         cell = self.grid_canvas.create_rectangle(
             coord,
             fill=fill_colour,
             width=0,
             tags=("%d,%d" % (step, row), "gridcell", "step%d" % step))
         self.grid_canvas.tag_bind(cell, '<ButtonPress-1>',
                                   self.on_grid_press)
         self.grid_canvas.tag_bind(cell, '<ButtonRelease-1>',
                                   self.on_grid_release)
         self.grid_canvas.tag_bind(cell, '<B1-Motion>', self.on_grid_drag)
         self.cells[cellIndex] = cell
     if step + duration > libseq.getSteps():
         self.grid_canvas.itemconfig("lastnotetext%d" % row,
                                     text="+%d" %
                                     (duration - libseq.getSteps() + step),
                                     state="normal")
 def populate_menu(self):
     self.parent.add_menu({
         'Beats in pattern': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 1,
                 'max': 16,
                 'get_value': libseq.getBeatsInPattern,
                 'on_change': self.on_menu_change,
                 'on_assert': self.assert_beats_in_pattern
             }
         }
     })
     self.parent.add_menu({
         'Steps per beat': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 0,
                 'max': len(STEPS_PER_BEAT) - 1,
                 'get_value': self.get_steps_per_beat_index,
                 'on_change': self.on_menu_change,
                 'on_assert': self.assert_steps_per_beat
             }
         }
     })
     self.parent.add_menu({'-------------------': {}})
     self.parent.add_menu({
         'Copy pattern': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 1,
                 'max': 64872,
                 'get_value': self.get_pattern,
                 'on_change': self.on_menu_change,
                 'on_assert': self.copy_pattern,
                 'on_cancel': self.cancel_copy
             }
         }
     })
     self.parent.add_menu({'Clear pattern': {'method': self.clear_pattern}})
     if libseq.getScale():
         self.parent.add_menu({
             'Transpose pattern': {
                 'method': self.parent.show_param_editor,
                 'params': {
                     'min': -1,
                     'max': 1,
                     'value': 0,
                     'on_change': self.on_menu_change
                 }
             }
         })
     self.parent.add_menu({'-------------------': {}})
     self.parent.add_menu({
         'Vertical zoom': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 1,
                 'max': 127,
                 'get_value': self.get_vertical_zoom,
                 'on_change': self.on_menu_change,
                 'on_assert': self.assert_zoom
             }
         }
     })
     self.parent.add_menu({
         'Scale': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 0,
                 'max': self.get_scales(),
                 'get_value': libseq.getScale,
                 'on_change': self.on_menu_change
             }
         }
     })
     self.parent.add_menu({
         'Tonic': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': -1,
                 'max': 12,
                 'get_value': libseq.getTonic,
                 'on_change': self.on_menu_change
             }
         }
     })
     self.parent.add_menu({
         'Rest note': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': -1,
                 'max': 128,
                 'get_value': libseq.getInputRest,
                 'on_change': self.on_menu_change
             }
         }
     })
     self.parent.add_menu({
         'Input channel': {
             'method': self.parent.show_param_editor,
             'params': {
                 'min': 0,
                 'max': 16,
                 'get_value': self.get_input_channel,
                 'on_change': self.on_menu_change
             }
         }
     })
     self.parent.add_menu({'Export to SMF': {'method': self.export_smf}})
 def load_keymap(self):
     try:
         base_note = int(self.keymap[self.keymap_offset]['note'])
     except:
         base_note = 60
     scale = libseq.getScale()
     tonic = libseq.getTonic()
     name = None
     self.keymap = []
     if scale == 0:
         # Map
         path = None
         for layer in self.zyngui.screens['layer'].layers:
             if layer.midi_chan == self.channel:
                 path = layer.get_presetpath()
                 break
         if path:
             path = path.split('#')[1]
             try:
                 with open(CONFIG_ROOT + "/keymaps.json") as json_file:
                     data = json.load(json_file)
                 if path in data:
                     name = data[path]
                     xml = minidom.parse(CONFIG_ROOT + "/%s.midnam" %
                                         (name))
                     notes = xml.getElementsByTagName('Note')
                     self.scale = []
                     for note in notes:
                         self.keymap.append({
                             'note':
                             int(note.attributes['Number'].value),
                             'name':
                             note.attributes['Name'].value
                         })
             except:
                 logging.warning("Unable to load keymaps.json")
     if name == None:  # Not found map
         # Scale
         if scale > 0:
             scale = scale - 1
         libseq.setScale(scale + 1)  # Use chromatic scale if map not found
         if scale == 0:
             for note in range(0, 128):
                 new_entry = {"note": note}
                 key = note % 12
                 if key in (1, 3, 6, 8, 10):  # Black notes
                     new_entry.update({"colour": "black"})
                 if key == 0:  # 'C'
                     new_entry.update({"name": "C%d" % (note // 12 - 1)})
                 self.keymap.append(new_entry)
                 if note <= base_note:
                     self.keymap_offset = len(self.keymap) - 1
                     self.selected_cell[1] = self.keymap_offset
             name = "Chromatic"
         else:
             with open(CONFIG_ROOT + "/scales.json") as json_file:
                 data = json.load(json_file)
             if len(data) <= scale:
                 scale = 0
             for octave in range(0, 9):
                 for offset in data[scale]['scale']:
                     note = tonic + offset + octave * 12
                     if note > 127:
                         break
                     self.keymap.append({
                         "note":
                         note,
                         "name":
                         "%s%d" % (self.notes[note % 12], note // 12 - 1)
                     })
                     if note <= base_note:
                         self.keymap_offset = len(self.keymap) - 1
                         self.selected_cell[1] = self.keymap_offset
             name = data[scale]['name']
     return name
 def on_menu_change(self, params):
     menu_item = self.parent.param_editor_item
     value = params['value']
     if value < params['min']:
         value = params['min']
     if value > params['max']:
         value = params['max']
     params['value'] = value
     if menu_item == 'Pattern':
         self.pattern = value
         self.copy_source = value
         self.load_pattern(value)
     elif menu_item == 'Copy pattern':
         self.load_pattern(value)
         return "Copy %d => %d ?" % (self.copy_source, value)
     elif menu_item == 'Transpose pattern':
         if libseq.getScale() == 0:
             self.parent.hide_param_editor()
             return
         if libseq.getScale() > 1:
             # Only allow transpose when showing chromatic scale
             libseq.setScale(1)
             self.load_keymap()
             self.redraw_pending = 1
         if (value != 0 and libseq.getScale()):
             libseq.transpose(value)
             self.parent.set_param(menu_item, 'value', 0)
             self.keymap_offset = self.keymap_offset + value
             if self.keymap_offset > 128 - self.zoom:
                 self.keymap_offset = 128 - self.zoom
             elif self.keymap_offset < 0:
                 self.keymap_offset = 0
             else:
                 self.selected_cell[1] = self.selected_cell[1] + value
             self.redraw_pending = 1
             self.select_cell()
         return "Transpose +/-"
     elif menu_item == 'Vertical zoom':
         self.zoom = value
     elif menu_item == 'Steps per beat':
         steps_per_beat = STEPS_PER_BEAT[value]
         #			libseq.setStepsPerBeat(steps_per_beat)
         self.redraw_pending = 2
         value = steps_per_beat
     elif menu_item == 'Scale':
         libseq.setScale(value)
         name = self.load_keymap()
         self.redraw_pending = 1
         return "Keymap: %s" % (name)
     elif menu_item == 'Tonic':
         if value < 0:
             value = 11
         if value > 11:
             value = 0
         self.parent.set_param('Tonic', 'value', value)
         offset = value - libseq.getTonic()
         libseq.setTonic(value)
         self.load_keymap()
         self.redraw_pending = 1
         return "Tonic: %s" % (self.notes[value])
     elif menu_item == 'Input channel':
         if value == 0:
             libseq.setInputChannel(0xFF)
             return 'Input channel: None'
         libseq.setInputChannel(value - 1)
     elif menu_item == 'Rest note':
         if value < 0 or value > 127:
             value = 128
         libseq.setInputRest(value)
         if value > 127:
             return "Rest note: None"
         return "Rest note: %s%d(%d)" % ([
             'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
         ][value % 12], int(value / 12) - 1, value)
     return "%s: %d" % (menu_item, value)