Browse Source

This should conclude the apc mini support. Minus bugs

master
Nils 12 months 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.
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.
"""
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.
"""
APCmini_MAP = {
APCmini_MAP_NOTES = {
64: "arrow_up", #Move the pattern-viewing area. +Shift: Move Track Up
65: "arrow_down", #Move the pattern-viewing area. +Shift: Move Track Down
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.
}
#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:
#The colors are only for the note buttons
@ -101,7 +115,7 @@ class ApcMiniInput(MidiInput):
8
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
that can be scrolled with it's arrow keys.
@ -125,7 +139,7 @@ class ApcMiniInput(MidiInput):
self._blockCurrentTrackSignal = False # to prevent recursive calls
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.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.armRecord = None #Toggle
self.mute = None #Make music when you press buttons
@ -140,6 +154,7 @@ class ApcMiniInput(MidiInput):
#self.midiProcessor.notePrinter(True)
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_CC(self.receiveCC)
#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
@ -228,9 +243,9 @@ class ApcMiniInput(MidiInput):
def sendDefaultConfig(self):
"""Setup our default setup. This is not saved but restored on every program startup."""
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
pat = cbox.Document.get_song().pattern_from_blob(pblob, 0) #0 ticks.
@ -411,66 +426,66 @@ class ApcMiniInput(MidiInput):
if not self.mute:
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.
elif pitch == APCmini_MAP["rec_arm"]:
elif pitch == APCmini_MAP_NOTES["rec_arm"]:
self.armRecord = not 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:
cbox.send_midi_event(0x90, APCmini_MAP["rec_arm"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP["mute"]:
cbox.send_midi_event(0x90, APCmini_MAP_NOTES["rec_arm"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP_NOTES["mute"]:
self.mute = not 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:
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:
api.currentTrackBy(self.currentTrack["id"], -1)
else:
self.viewAreaUpDown(+1)
elif pitch == APCmini_MAP["arrow_down"]:
elif pitch == APCmini_MAP_NOTES["arrow_down"]:
if self.shiftIsActive:
api.currentTrackBy(self.currentTrack["id"], 1)
else:
self.viewAreaUpDown(-1)
elif pitch == APCmini_MAP["arrow_left"]:
elif pitch == APCmini_MAP_NOTES["arrow_left"]:
if self.shiftIsActive:
api.seekMeasureLeft()
else:
self.viewAreaLeftRight(+1) #Yes, it is inverted scrolling
elif pitch == APCmini_MAP["arrow_right"]:
elif pitch == APCmini_MAP_NOTES["arrow_right"]:
if self.shiftIsActive:
api.seekMeasureRight()
else:
self.viewAreaLeftRight(-1)
elif pitch == APCmini_MAP["unlabeled_upper"]: #Undo
elif pitch == APCmini_MAP_NOTES["unlabeled_upper"]: #Undo
if self.shiftIsActive: #redo
api.redo()
else:
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:
api.patternOffAllSteps(self.currentTrack["id"])
else:
self.viewAreaUpDownOffset = 0
self.viewAreaLeftRightOffset = 0
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["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["arrow_right"], 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_NOTES["arrow_down"], 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_NOTES["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
elif pitch == APCmini_MAP["clip_stop"]:
elif pitch == APCmini_MAP_NOTES["clip_stop"]:
api.playPause()
elif pitch == APCmini_MAP["solo"]:
elif pitch == APCmini_MAP_NOTES["solo"]:
api.toggleLoop()
elif pitch == APCmini_MAP["stop_all_clips"]:
elif pitch == APCmini_MAP_NOTES["stop_all_clips"]:
api.stop()
api.rewind()
else:
@ -494,18 +509,29 @@ class ApcMiniInput(MidiInput):
self.activeNoteOns.remove(pitch)
#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.
else:
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):
"""This gets called either by the callback or by an APCmini button.
The callback has a signal blocker that prevents recursive calls."""
if self.currentTrack and exportTrack["id"] == self.currentTrack["id"]: return #already current track
self.currentTrack = exportTrack
api.changeCurrentTrack(exportTrack["id"])
self.prevailingVelocity = self.currentTrack["averageVelocity"]
self.sendApcNotePattern(exportTrack)
@ -521,11 +547,11 @@ class ApcMiniInput(MidiInput):
return # there is no need for further scrolling.
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["arrow_down"], 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_NOTES["arrow_down"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
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["arrow_down"], 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_NOTES["arrow_down"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
self.sendApcNotePattern(self.currentTrack)
@ -542,11 +568,11 @@ class ApcMiniInput(MidiInput):
return # there is no need for further scrolling.
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["arrow_right"], 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_NOTES["arrow_right"], APCmini_COLOR["green"], output=self.cboxMidiOutUuid)
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["arrow_right"], 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_NOTES["arrow_right"], APCmini_COLOR["off"], output=self.cboxMidiOutUuid)
self.sendApcNotePattern(self.currentTrack)
@ -636,15 +662,15 @@ class ApcMiniInput(MidiInput):
def callback_playbackStatusChanged(self, status:bool):
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:
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):
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:
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

Loading…
Cancel
Save