import mido from . import handlers from .solo_tool import SoloTool class MidiWrapper: def __init__(self): self._inPort = None self._outPort = None def connect(self, deviceName, callback): if self._inPort is None and self._outPort is None: self._inPort = mido.open_input(deviceName) self._inPort.callback = callback self._outPort = mido.open_output(deviceName) def disconnect(self): if self._inPort is not None: self._inPort.close() self._inPort = None if self._outPort is not None: self._outPort.reset() self._outPort.close() self._outPort = None def sendNoteOn(self, note, velocity, channel): if self._outPort is not None: msg = mido.Message("note_on", channel=channel, velocity=velocity, note=note) self._outPort.send(msg) class LaunchpadMiniController: DEVICE_NAME = "Launchpad Mini MIDI 1" LIGHT_CONTROL_CHANNEL = 0 LED_GREEN = 124 LED_YELLOW = 126 LED_RED = 3 LED_OFF = 0 BUTTON_MATRIX = [[x for x in range(y * 16, y * 16 + 8)] for y in range(0,8)] # address as [row][col] MIN_PLAYBACK_RATE = 0.5 MAX_PLAYBACK_RATE = 1.2 PLAYBACK_RATE_STEP = 0.1 MIN_PLAYBACK_VOLUME = 0.5 MAX_PLAYBACK_VOLUME = 1.2 PLAYBACK_VOLUME_STEP = 0.1 def __init__(self, soloTool: SoloTool, midiWrapperOverride=None): self._soloTool = soloTool if midiWrapperOverride is not None: self._midiWrapper = midiWrapperOverride else: self._midiWrapper = MidiWrapper() self._registerHandlers() self._soloTool.registerPlayingStateCallback(self._updatePlayPauseButton) self._soloTool.registerVolumeCallback(self._updateVolumeRow) self._soloTool.registerRateCallback(self._updateRateRow) def _registerHandlers(self): self._handlers = { 96 : handlers.seekAbsolute(self._soloTool, 0.0), 114 : self._soloTool.jump, 112 : handlers.playPause(self._soloTool), 118 : handlers.keyPointRelative(self._soloTool, -1), 119 : handlers.keyPointRelative(self._soloTool, 1), 117 : handlers.positionToKeyPoint(self._soloTool), 48 : handlers.songRelative(self._soloTool, -1), 49 : handlers.seekRelative(self._soloTool, -0.25), 50 : handlers.seekRelative(self._soloTool, -0.05), 51 : handlers.seekRelative(self._soloTool, -0.01), 52 : handlers.seekRelative(self._soloTool, 0.01), 53 : handlers.seekRelative(self._soloTool, 0.05), 54 : handlers.seekRelative(self._soloTool, 0.25), 55 : handlers.songRelative(self._soloTool, 1), } for i in range(0, 8): volume = round(LaunchpadMiniController.MIN_PLAYBACK_VOLUME + LaunchpadMiniController.PLAYBACK_VOLUME_STEP * i, 1) self._handlers[i] = handlers.volumeAbsolute(self._soloTool, volume) for i, button in enumerate(range(16, 24)): rate = round(LaunchpadMiniController.MIN_PLAYBACK_RATE + LaunchpadMiniController.PLAYBACK_RATE_STEP * i, 1) self._handlers[button] = handlers.rateAbsolute(self._soloTool, rate) def connect(self): self._midiWrapper.connect(LaunchpadMiniController.DEVICE_NAME, self._callback) self._initialiseButtonLEDs() def disconnect(self): self._setAllLEDs(LaunchpadMiniController.LED_OFF) self._midiWrapper.disconnect() def _callback(self, msg): if msg.type != "note_on" or msg.velocity < 127: return if msg.note in self._handlers: handler = self._handlers[msg.note]() def _updatePlayPauseButton(self, playing): if playing: self._setButtonLED(7, 0, LaunchpadMiniController.LED_GREEN) else: self._setButtonLED(7, 0, LaunchpadMiniController.LED_YELLOW) def _updateVolumeRow(self, volume): t1 = int(round(volume / LaunchpadMiniController.PLAYBACK_VOLUME_STEP, 1)) t2 = int(round(LaunchpadMiniController.MIN_PLAYBACK_VOLUME / LaunchpadMiniController.PLAYBACK_VOLUME_STEP, 1)) lastColumnLit = t1 - t2 + 1 self._lightRowUntilColumn(0, lastColumnLit, LaunchpadMiniController.LED_GREEN) def _updateRateRow(self, rate): t1 = int(round(rate / LaunchpadMiniController.PLAYBACK_RATE_STEP, 1)) t2 = int(round(LaunchpadMiniController.MIN_PLAYBACK_RATE / LaunchpadMiniController.PLAYBACK_RATE_STEP, 1)) lastColumnLit = t1 - t2 + 1 self._lightRowUntilColumn(1, lastColumnLit, LaunchpadMiniController.LED_YELLOW) def _setButtonLED(self, row, col, colour): self._midiWrapper.sendNoteOn(LaunchpadMiniController.BUTTON_MATRIX[row][col], colour, LaunchpadMiniController.LIGHT_CONTROL_CHANNEL) def _lightRowUntilColumn(self, row, column, litColour): colours = [litColour] * column + [LaunchpadMiniController.LED_OFF] * (8 - column) for col in range(0, 8): self._setButtonLED(row, col, colours[col]) def _setAllLEDs(self, colour): for row in range(0, 8): for col in range(0, 8): self._setButtonLED(row, col, colour) def _initialiseButtonLEDs(self): self._setAllLEDs(LaunchpadMiniController.LED_OFF) # volume buttons self._updateVolumeRow(self._soloTool.volume) # playback rate buttons self._updateRateRow(self._soloTool.rate) # playback control self._setButtonLED(6, 0, LaunchpadMiniController.LED_YELLOW) self._updatePlayPauseButton(self._soloTool.playing) # Key point control self._setButtonLED(7, 2, LaunchpadMiniController.LED_YELLOW) self._setButtonLED(7, 6, LaunchpadMiniController.LED_RED) self._setButtonLED(7, 7, LaunchpadMiniController.LED_GREEN) self._setButtonLED(7, 5, LaunchpadMiniController.LED_YELLOW) # Song control self._setButtonLED(3, 0, LaunchpadMiniController.LED_RED) self._setButtonLED(3, 1, LaunchpadMiniController.LED_RED) self._setButtonLED(3, 2, LaunchpadMiniController.LED_RED) self._setButtonLED(3, 3, LaunchpadMiniController.LED_RED) self._setButtonLED(3, 4, LaunchpadMiniController.LED_GREEN) self._setButtonLED(3, 5, LaunchpadMiniController.LED_GREEN) self._setButtonLED(3, 6, LaunchpadMiniController.LED_GREEN) self._setButtonLED(3, 7, LaunchpadMiniController.LED_GREEN)