Browse Source

This should conclude the apc mini support. Minus bugs

master
Nils 3 years ago
parent
commit
33bf3eabfb
  1. 2
      engine/api.py
  2. 108
      engine/input_apcmini.py

2
engine/api.py

@ -512,6 +512,8 @@ def setSwingPercent(value:int):
that gets saved. that gets saved.
Our function will use a lookup-table to convert percentage in a musical way. Our function will use a lookup-table to convert percentage in a musical way.
That said, the GUI and the apcMini only go from 0 to 100, the negative range is ignored.
The first 80% will be used for normal musical values. The other 20 for more extreme sounds. The first 80% will be used for normal musical values. The other 20 for more extreme sounds.
""" """
if value < -100 or value > 100: if value < -100 or value > 100:

108
engine/input_apcmini.py

@ -39,7 +39,7 @@ This file is used directly. It is a global part of the engine (see bottom of the
that uses the api directly, which in turn triggers the GUI. that uses the api directly, which in turn triggers the GUI.
""" """
APCmini_MAP = { APCmini_MAP_NOTES = {
64: "arrow_up", #Move the pattern-viewing area. +Shift: Move Track Up 64: "arrow_up", #Move the pattern-viewing area. +Shift: Move Track Up
65: "arrow_down", #Move the pattern-viewing area. +Shift: Move Track Down 65: "arrow_down", #Move the pattern-viewing area. +Shift: Move Track Down
66: "arrow_left", #Move the pattern-viewing area. +Shift: Measure Left 66: "arrow_left", #Move the pattern-viewing area. +Shift: Measure Left
@ -64,8 +64,22 @@ APCmini_MAP = {
98: "shift", #This is just a button. There is no shift layer. 98: "shift", #This is just a button. There is no shift layer.
} }
#Also put in the reverse: #Also put in the reverse:
APCmini_MAP.update({value:key for key,value in APCmini_MAP.items()}) APCmini_MAP_NOTES.update({value:key for key,value in APCmini_MAP_NOTES.items()})
#The CCs have no labels on the hardware. We use the function as value directly
APCmini_MAP_CC = {
48: "",
49: "",
50: "",
51: "",
52: "volume", #because this is below the button with "volume".
53: "",
54: "",
55: "",
56: "swing",
}
APCmini_MAP_CC.update({value:key for key,value in APCmini_MAP_CC.items()})
#APC Color Mappings are velocity: #APC Color Mappings are velocity:
#The colors are only for the note buttons #The colors are only for the note buttons
@ -101,7 +115,7 @@ class ApcMiniInput(MidiInput):
8 8
0 0
For all other buttons please see APCmini_MAP For all other buttons please see APCmini_MAP_NOTES
Because we only have 8*8 buttons, but Patroneo has n*n we have a "ViewArea" for the apcMini Because we only have 8*8 buttons, but Patroneo has n*n we have a "ViewArea" for the apcMini
that can be scrolled with it's arrow keys. that can be scrolled with it's arrow keys.
@ -125,7 +139,7 @@ class ApcMiniInput(MidiInput):
self._blockCurrentTrackSignal = False # to prevent recursive calls self._blockCurrentTrackSignal = False # to prevent recursive calls
self._cacheSubdivisonValue = None #set in callback self._cacheSubdivisonValue = None #set in callback
self.currentTrack = None #track export dict. Set on program start and on every GUI track change and also on our track change. self.currentTrack = None #track export dict. Set on program start and on every GUI track change and also on our track change.
self.prevailingVelocity = None # defaults to average track velocity. But can also be set by the hardware faders self.prevailingVelocity = 90 # defaults to 90 by convention on startup. After that only the hardware fader counts.
self.shiftIsActive = None #For shift momentary press self.shiftIsActive = None #For shift momentary press
self.armRecord = None #Toggle self.armRecord = None #Toggle
self.mute = None #Make music when you press buttons self.mute = None #Make music when you press buttons
@ -140,6 +154,7 @@ class ApcMiniInput(MidiInput):
#self.midiProcessor.notePrinter(True) #self.midiProcessor.notePrinter(True)
self.midiProcessor.register_NoteOn(self.receiveNoteOn) #this also includes note off, which is 0x90 velocity 0 self.midiProcessor.register_NoteOn(self.receiveNoteOn) #this also includes note off, which is 0x90 velocity 0
self.midiProcessor.register_NoteOff(self.receiveNoteOff) self.midiProcessor.register_NoteOff(self.receiveNoteOff)
self.midiProcessor.register_CC(self.receiveCC)
#api.callbacks.setCursor.append(self._setMidiThru) #When the track changes re-route cbox RT midi thru #api.callbacks.setCursor.append(self._setMidiThru) #When the track changes re-route cbox RT midi thru
#api.callbacks._setCursor(destroySelection = False) #Force once to trigger a cursor export which calls our midi thru setter #api.callbacks._setCursor(destroySelection = False) #Force once to trigger a cursor export which calls our midi thru setter
@ -228,9 +243,9 @@ class ApcMiniInput(MidiInput):
def sendDefaultConfig(self): def sendDefaultConfig(self):
"""Setup our default setup. This is not saved but restored on every program startup.""" """Setup our default setup. This is not saved but restored on every program startup."""
pblob = bytes() pblob = bytes()
pblob += cbox.Pattern.serialize_event(1, 0x90, APCmini_MAP["mute"], 1) pblob += cbox.Pattern.serialize_event(1, 0x90, APCmini_MAP_NOTES["mute"], 1)
pblob += cbox.Pattern.serialize_event(1, 0x90, APCmini_MAP["rec_arm"], 1) pblob += cbox.Pattern.serialize_event(1, 0x90, APCmini_MAP_NOTES["rec_arm"], 1)
self.armRecord = True self.armRecord = True
pat = cbox.Document.get_song().pattern_from_blob(pblob, 0) #0 ticks. pat = cbox.Document.get_song().pattern_from_blob(pblob, 0) #0 ticks.
@ -411,66 +426,66 @@ class ApcMiniInput(MidiInput):
if not self.mute: if not self.mute:
api.noteOn(self.currentTrack["id"], patroneoPitch) api.noteOn(self.currentTrack["id"], patroneoPitch)
elif pitch == APCmini_MAP["shift"]: elif pitch == APCmini_MAP_NOTES["shift"]:
self.shiftIsActive = True #The shift button has no color. We just remember the momentary state. self.shiftIsActive = True #The shift button has no color. We just remember the momentary state.
elif pitch == APCmini_MAP["rec_arm"]: elif pitch == APCmini_MAP_NOTES["rec_arm"]:
self.armRecord = not self.armRecord self.armRecord = not self.armRecord
if self.armRecord: if self.armRecord:
cbox.send_midi_event(0x90, APCmini_MAP["rec_arm"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["rec_arm"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
else: else:
cbox.send_midi_event(0x90, APCmini_MAP["rec_arm"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["rec_arm"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP["mute"]: elif pitch == APCmini_MAP_NOTES["mute"]:
self.mute = not self.mute self.mute = not self.mute
if self.mute: if self.mute:
cbox.send_midi_event(0x90, APCmini_MAP["mute"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["mute"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
else: else:
cbox.send_midi_event(0x90, APCmini_MAP["mute"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["mute"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP["arrow_up"]: elif pitch == APCmini_MAP_NOTES["arrow_up"]:
if self.shiftIsActive: if self.shiftIsActive:
api.currentTrackBy(self.currentTrack["id"], -1) api.currentTrackBy(self.currentTrack["id"], -1)
else: else:
self.viewAreaUpDown(+1) self.viewAreaUpDown(+1)
elif pitch == APCmini_MAP["arrow_down"]: elif pitch == APCmini_MAP_NOTES["arrow_down"]:
if self.shiftIsActive: if self.shiftIsActive:
api.currentTrackBy(self.currentTrack["id"], 1) api.currentTrackBy(self.currentTrack["id"], 1)
else: else:
self.viewAreaUpDown(-1) self.viewAreaUpDown(-1)
elif pitch == APCmini_MAP["arrow_left"]: elif pitch == APCmini_MAP_NOTES["arrow_left"]:
if self.shiftIsActive: if self.shiftIsActive:
api.seekMeasureLeft() api.seekMeasureLeft()
else: else:
self.viewAreaLeftRight(+1) #Yes, it is inverted scrolling self.viewAreaLeftRight(+1) #Yes, it is inverted scrolling
elif pitch == APCmini_MAP["arrow_right"]: elif pitch == APCmini_MAP_NOTES["arrow_right"]:
if self.shiftIsActive: if self.shiftIsActive:
api.seekMeasureRight() api.seekMeasureRight()
else: else:
self.viewAreaLeftRight(-1) self.viewAreaLeftRight(-1)
elif pitch == APCmini_MAP["unlabeled_upper"]: #Undo elif pitch == APCmini_MAP_NOTES["unlabeled_upper"]: #Undo
if self.shiftIsActive: #redo if self.shiftIsActive: #redo
api.redo() api.redo()
else: else:
api.undo() api.undo()
elif pitch == APCmini_MAP["unlabeled_lower"]: #Reset View Area and Reset Pattern(!) with shift elif pitch == APCmini_MAP_NOTES["unlabeled_lower"]: #Reset View Area and Reset Pattern(!) with shift
if self.shiftIsActive: #Clear current pattern: if self.shiftIsActive: #Clear current pattern:
api.patternOffAllSteps(self.currentTrack["id"]) api.patternOffAllSteps(self.currentTrack["id"])
else: else:
self.viewAreaUpDownOffset = 0 self.viewAreaUpDownOffset = 0
self.viewAreaLeftRightOffset = 0 self.viewAreaLeftRightOffset = 0
self.sendApcNotePattern(self.currentTrack) self.sendApcNotePattern(self.currentTrack)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_up"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_up"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_down"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_down"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_left"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_left"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP["clip_stop"]: elif pitch == APCmini_MAP_NOTES["clip_stop"]:
api.playPause() api.playPause()
elif pitch == APCmini_MAP["solo"]: elif pitch == APCmini_MAP_NOTES["solo"]:
api.toggleLoop() api.toggleLoop()
elif pitch == APCmini_MAP["stop_all_clips"]: elif pitch == APCmini_MAP_NOTES["stop_all_clips"]:
api.stop() api.stop()
api.rewind() api.rewind()
else: else:
@ -494,18 +509,29 @@ class ApcMiniInput(MidiInput):
self.activeNoteOns.remove(pitch) self.activeNoteOns.remove(pitch)
#else: this is just midi hardware. Button was pressed while connecting and then released, or something like that. #else: this is just midi hardware. Button was pressed while connecting and then released, or something like that.
elif pitch == APCmini_MAP["shift"]: elif pitch == APCmini_MAP_NOTES["shift"]:
self.shiftIsActive = False #The shift button has no color. We just remember the momentary state. self.shiftIsActive = False #The shift button has no color. We just remember the momentary state.
else: else:
logging.info(f"Unhandled APCmini noteOff. Pitch {pitch}, Velocity {velocity}") logging.info(f"Unhandled APCmini noteOff. Pitch {pitch}, Velocity {velocity}")
def receiveCC(self, timestamp, channel, ccnumber, value):
if ccnumber == APCmini_MAP_CC["swing"]:
if value == 0:
api.setSwingPercent(0)
else:
v = int(value/127 * 100)
api.setSwingPercent(v)
elif ccnumber == APCmini_MAP_CC["volume"]:
self.prevailingVelocity = value
def chooseCurrentTrack(self, exportTrack): def chooseCurrentTrack(self, exportTrack):
"""This gets called either by the callback or by an APCmini button. """This gets called either by the callback or by an APCmini button.
The callback has a signal blocker that prevents recursive calls.""" The callback has a signal blocker that prevents recursive calls."""
if self.currentTrack and exportTrack["id"] == self.currentTrack["id"]: return #already current track if self.currentTrack and exportTrack["id"] == self.currentTrack["id"]: return #already current track
self.currentTrack = exportTrack self.currentTrack = exportTrack
api.changeCurrentTrack(exportTrack["id"]) api.changeCurrentTrack(exportTrack["id"])
self.prevailingVelocity = self.currentTrack["averageVelocity"]
self.sendApcNotePattern(exportTrack) self.sendApcNotePattern(exportTrack)
@ -521,11 +547,11 @@ class ApcMiniInput(MidiInput):
return # there is no need for further scrolling. return # there is no need for further scrolling.
if self.viewAreaUpDownOffset: if self.viewAreaUpDownOffset:
cbox.send_midi_event(0x90, APCmini_MAP["arrow_up"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_up"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_down"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_down"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
else: #Indicate that the origin is not 0,0 else: #Indicate that the origin is not 0,0
cbox.send_midi_event(0x90, APCmini_MAP["arrow_up"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_up"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_down"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_down"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
self.sendApcNotePattern(self.currentTrack) self.sendApcNotePattern(self.currentTrack)
@ -542,11 +568,11 @@ class ApcMiniInput(MidiInput):
return # there is no need for further scrolling. return # there is no need for further scrolling.
if self.viewAreaLeftRightOffset: if self.viewAreaLeftRightOffset:
cbox.send_midi_event(0x90, APCmini_MAP["arrow_left"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_left"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_right"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_right"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
else: #Indicate that the origin is not 0,0 else: #Indicate that the origin is not 0,0
cbox.send_midi_event(0x90, APCmini_MAP["arrow_left"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_left"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
cbox.send_midi_event(0x90, APCmini_MAP["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
self.sendApcNotePattern(self.currentTrack) self.sendApcNotePattern(self.currentTrack)
@ -636,15 +662,15 @@ class ApcMiniInput(MidiInput):
def callback_playbackStatusChanged(self, status:bool): def callback_playbackStatusChanged(self, status:bool):
if status: if status:
cbox.send_midi_event(0x90, APCmini_MAP["clip_stop"], APCmini_COLOR["green_blink"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["clip_stop"], APCmini_COLOR["green_blink"], output=self.cboxMidiOutUuid)
else: else:
cbox.send_midi_event(0x90, APCmini_MAP["clip_stop"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["clip_stop"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
def callback_loopChanged(self, measureNumber:int): def callback_loopChanged(self, measureNumber:int):
if measureNumber is None: if measureNumber is None:
cbox.send_midi_event(0x90, APCmini_MAP["solo"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["solo"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
else: else:
cbox.send_midi_event(0x90, APCmini_MAP["solo"], APCmini_COLOR["green_blink"], output=self.cboxMidiOutUuid) cbox.send_midi_event(0x90, APCmini_MAP_NOTES["solo"], APCmini_COLOR["green_blink"], output=self.cboxMidiOutUuid)
apcMiniInput = ApcMiniInput() #global to use in other parts of the program apcMiniInput = ApcMiniInput() #global to use in other parts of the program

Loading…
Cancel
Save