From edb2ffc66231702c931429ab44ad5009abb8c128 Mon Sep 17 00:00:00 2001 From: Eddy Pedroni Date: Tue, 4 Jan 2022 13:49:38 +0100 Subject: Added proper volume and playback rate feedback, changed volume steps in MIDI interface --- abcontroller.py | 7 +-- diagram.drawio | 2 +- midi_controller_launchpad_mini.py | 44 ++++++++------ midi_launchpad_mini_integrationtest.py | 102 +++++++++++++++++++++++++++++---- notifier.py | 2 + notifier_unittest.py | 35 +++++++++++ solo_tool.py | 18 ++++++ solo_tool_integrationtest.py | 48 ++++++++++++++++ solo_tool_qt.py | 2 +- 9 files changed, 228 insertions(+), 32 deletions(-) diff --git a/abcontroller.py b/abcontroller.py index 3604a85..5c9bda7 100644 --- a/abcontroller.py +++ b/abcontroller.py @@ -5,7 +5,7 @@ _AB = namedtuple("_AB", ["a", "b"]) class ABController: def __init__(self, enabled=True, callback=None): self._setPositionCallback = callback - self._limits = dict() # dictionary of all songs + self._limits = {} # dictionary of all songs self._songLimits = None # list of limits for selected song self._currentLimits = _AB(0.0, 0.0) # a/b positions of active limit self._loadedIndex = None @@ -13,7 +13,7 @@ class ABController: def _ensureSongExists(self, path): if path not in self._limits: - self._limits[path] = list() + self._limits[path] = [] def setCurrentSong(self, path): self._ensureSongExists(path) @@ -35,7 +35,7 @@ class ABController: def loadLimits(self, index): if not self._songLimits: return - + if index >= 0 and index < len(self._songLimits): self._currentLimits = self._songLimits[index] self._loadedIndex = index @@ -76,4 +76,3 @@ class ABController: def clear(self): self.__init__(enabled=self._enabled, callback=self._setPositionCallback) - diff --git a/diagram.drawio b/diagram.drawio index 8950787..935a914 100644 --- a/diagram.drawio +++ b/diagram.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/midi_controller_launchpad_mini.py b/midi_controller_launchpad_mini.py index cb3bc37..10013b5 100644 --- a/midi_controller_launchpad_mini.py +++ b/midi_controller_launchpad_mini.py @@ -13,9 +13,9 @@ class MidiController: MAX_PLAYBACK_RATE = 1.2 PLAYBACK_RATE_STEP = 0.1 - MIN_PLAYBACK_VOLUME = 0.125 - MAX_PLAYBACK_VOLUME = 1.0 - PLAYBACK_VOLUME_STEP = 0.125 + MIN_PLAYBACK_VOLUME = 0.5 + MAX_PLAYBACK_VOLUME = 1.2 + PLAYBACK_VOLUME_STEP = 0.1 def __init__(self, soloTool, midiWrapperOverride=None): self._soloTool = soloTool @@ -26,6 +26,8 @@ class MidiController: self._registerHandlers() self._soloTool.registerPlayingStateCallback(self._updatePlayPauseButton) + self._soloTool.registerPlaybackVolumeCallback(self._updateVolumeRow) + self._soloTool.registerPlaybackRateCallback(self._updateRateRow) def _registerHandlers(self): self._handlers = { @@ -39,12 +41,12 @@ class MidiController: } for i in range(0, 8): - volume = round(MidiController.MIN_PLAYBACK_VOLUME + i * MidiController.PLAYBACK_VOLUME_STEP, 3) - self._handlers[i] = self._createSetPlaybackVolumeCallback(volume, i) + volume = round(MidiController.MIN_PLAYBACK_VOLUME + MidiController.PLAYBACK_VOLUME_STEP * i, 1) + self._handlers[i] = self._createSetPlaybackVolumeCallback(volume) for i, button in enumerate(range(16, 24)): rate = round(MidiController.MIN_PLAYBACK_RATE + MidiController.PLAYBACK_RATE_STEP * i, 1) - self._handlers[button] = self._createSetPlaybackRateCallback(rate, i) + self._handlers[button] = self._createSetPlaybackRateCallback(rate) def connect(self): self._midiWrapper.setCallback(self._callback) @@ -73,23 +75,35 @@ class MidiController: else: self._setButtonLED(7, 0, MidiController.LED_YELLOW) - def _createSetPlaybackRateCallback(self, rate, column): + def _updateVolumeRow(self): + volume = self._soloTool.getPlaybackVolume() + t1 = int(round(volume / MidiController.PLAYBACK_VOLUME_STEP, 1)) + t2 = int(round(MidiController.MIN_PLAYBACK_VOLUME / MidiController.PLAYBACK_VOLUME_STEP, 1)) + lastColumnLit = t1 - t2 + 1 + self._lightRowUntilColumn(0, lastColumnLit, MidiController.LED_GREEN) + + def _updateRateRow(self): + rate = self._soloTool.getPlaybackRate() + t1 = int(round(rate / MidiController.PLAYBACK_RATE_STEP, 1)) + t2 = int(round(MidiController.MIN_PLAYBACK_RATE / MidiController.PLAYBACK_RATE_STEP, 1)) + lastColumnLit = t1 - t2 + 1 + self._lightRowUntilColumn(1, lastColumnLit, MidiController.LED_YELLOW) + + def _createSetPlaybackRateCallback(self, rate): def f(): self._soloTool.setPlaybackRate(rate) - self._lightRowUntilColumn(1, column, MidiController.LED_YELLOW) return f - def _createSetPlaybackVolumeCallback(self, volume, column): + def _createSetPlaybackVolumeCallback(self, volume): def f(): self._soloTool.setPlaybackVolume(volume) - self._lightRowUntilColumn(0, column, MidiController.LED_GREEN) return f def _setButtonLED(self, row, col, colour): self._midiWrapper.sendMessage(MidiController.BUTTON_MATRIX[row][col], colour, MidiController.LIGHT_CONTROL_CHANNEL) def _lightRowUntilColumn(self, row, column, litColour): - colours = [litColour] * (column + 1) + [MidiController.LED_OFF] * (7 - column) + colours = [litColour] * column + [MidiController.LED_OFF] * (8 - column) for col in range(0, 8): self._setButtonLED(row, col, colours[col]) @@ -102,16 +116,14 @@ class MidiController: self._allLEDsOff() # volume buttons - for col in range(0, 8): - self._setButtonLED(0, col, MidiController.LED_GREEN) + self._updateVolumeRow() # playback rate buttons - for col in range(0, 6): - self._setButtonLED(1, col, MidiController.LED_YELLOW) + self._updateRateRow() # playback control self._setButtonLED(6, 0, MidiController.LED_RED) - self._setButtonLED(7, 0, MidiController.LED_YELLOW) + self._updatePlayPauseButton() # AB control self._setButtonLED(6, 5, MidiController.LED_YELLOW) diff --git a/midi_launchpad_mini_integrationtest.py b/midi_launchpad_mini_integrationtest.py index c10a3f5..35ac087 100644 --- a/midi_launchpad_mini_integrationtest.py +++ b/midi_launchpad_mini_integrationtest.py @@ -211,20 +211,61 @@ def test_playbackRateButtons(uut, midiWrapperMock, soloTool, playerMock): for i, colour in enumerate(playbackRateOptions[button][1]): assert midiWrapperMock.sentMessages[i] == (16 + i, colour, 0) +def test_playbackRateLeds(uut, midiWrapperMock, soloTool, playerMock): + playbackRateOptions = [ + (0.00, [LED_OFF] * 8), + (0.49, [LED_OFF] * 8), + + (0.50, [LED_YELLOW] * 1 + [LED_OFF] * 7), + (0.59, [LED_YELLOW] * 1 + [LED_OFF] * 7), + + (0.60, [LED_YELLOW] * 2 + [LED_OFF] * 6), + (0.69, [LED_YELLOW] * 2 + [LED_OFF] * 6), + + (0.70, [LED_YELLOW] * 3 + [LED_OFF] * 5), + (0.79, [LED_YELLOW] * 3 + [LED_OFF] * 5), + + (0.80, [LED_YELLOW] * 4 + [LED_OFF] * 4), + (0.89, [LED_YELLOW] * 4 + [LED_OFF] * 4), + + (0.90, [LED_YELLOW] * 5 + [LED_OFF] * 3), + (0.99, [LED_YELLOW] * 5 + [LED_OFF] * 3), + + (1.00, [LED_YELLOW] * 6 + [LED_OFF] * 2), + (1.09, [LED_YELLOW] * 6 + [LED_OFF] * 2), + + (1.10, [LED_YELLOW] * 7 + [LED_OFF] * 1), + (1.19, [LED_YELLOW] * 7 + [LED_OFF] * 1), + + (1.2, [LED_YELLOW] * 8), + (1.5, [LED_YELLOW] * 8) + ] + uut.connect() + assert playerMock.rate == 1.0 + + for t, (rate, leds) in enumerate(playbackRateOptions): + midiWrapperMock.sentMessages.clear() + + soloTool.setPlaybackRate(rate) + assert playerMock.rate == rate + + for i, colour in enumerate(leds): + assert midiWrapperMock.sentMessages[i] == (16 + i, colour, 0) + def test_playbackVolumeButtons(uut, midiWrapperMock, soloTool, playerMock): playbackVolumeOptions = { - 0 : (0.125, [LED_GREEN] * 1 + [LED_OFF] * 7), - 1 : (0.250, [LED_GREEN] * 2 + [LED_OFF] * 6), - 2 : (0.375, [LED_GREEN] * 3 + [LED_OFF] * 5), - 3 : (0.500, [LED_GREEN] * 4 + [LED_OFF] * 4), - 4 : (0.625, [LED_GREEN] * 5 + [LED_OFF] * 3), - 5 : (0.750, [LED_GREEN] * 6 + [LED_OFF] * 2), - 6 : (0.875, [LED_GREEN] * 7 + [LED_OFF] * 1), - 7 : (1.000, [LED_GREEN] * 8) + 0 : (0.5, [LED_GREEN] * 1 + [LED_OFF] * 7), + 1 : (0.6, [LED_GREEN] * 2 + [LED_OFF] * 6), + 2 : (0.7, [LED_GREEN] * 3 + [LED_OFF] * 5), + 3 : (0.8, [LED_GREEN] * 4 + [LED_OFF] * 4), + 4 : (0.9, [LED_GREEN] * 5 + [LED_OFF] * 3), + 5 : (1.0, [LED_GREEN] * 6 + [LED_OFF] * 2), + 6 : (1.1, [LED_GREEN] * 7 + [LED_OFF] * 1), + 7 : (1.2, [LED_GREEN] * 8) } uut.connect() assert playerMock.volume == 1.0 - + for t, button in enumerate(playbackVolumeOptions): midiWrapperMock.sentMessages.clear() @@ -234,6 +275,47 @@ def test_playbackVolumeButtons(uut, midiWrapperMock, soloTool, playerMock): for i, colour in enumerate(playbackVolumeOptions[button][1]): assert midiWrapperMock.sentMessages[i] == (i, colour, 0) +def test_playbackVolumeLeds(uut, midiWrapperMock, soloTool, playerMock): + playbackVolumeOptions = [ + (0.00, [LED_OFF] * 8), + (0.49, [LED_OFF] * 8), + + (0.50, [LED_GREEN] * 1 + [LED_OFF] * 7), + (0.59, [LED_GREEN] * 1 + [LED_OFF] * 7), + + (0.60, [LED_GREEN] * 2 + [LED_OFF] * 6), + (0.69, [LED_GREEN] * 2 + [LED_OFF] * 6), + + (0.70, [LED_GREEN] * 3 + [LED_OFF] * 5), + (0.79, [LED_GREEN] * 3 + [LED_OFF] * 5), + + (0.80, [LED_GREEN] * 4 + [LED_OFF] * 4), + (0.89, [LED_GREEN] * 4 + [LED_OFF] * 4), + + (0.90, [LED_GREEN] * 5 + [LED_OFF] * 3), + (0.99, [LED_GREEN] * 5 + [LED_OFF] * 3), + + (1.00, [LED_GREEN] * 6 + [LED_OFF] * 2), + (1.09, [LED_GREEN] * 6 + [LED_OFF] * 2), + + (1.10, [LED_GREEN] * 7 + [LED_OFF] * 1), + (1.19, [LED_GREEN] * 7 + [LED_OFF] * 1), + + (1.2, [LED_GREEN] * 8), + (1.5, [LED_GREEN] * 8) + ] + uut.connect() + assert playerMock.volume == 1.0 + + for t, (volume, leds) in enumerate(playbackVolumeOptions): + midiWrapperMock.sentMessages.clear() + + soloTool.setPlaybackVolume(volume) + assert playerMock.volume == volume + + for i, colour in enumerate(leds): + assert midiWrapperMock.sentMessages[i] == (i, colour, 0) + def test_unassignedButton(uut, midiWrapperMock): unassignedButton = 48 uut.connect() @@ -245,7 +327,7 @@ def test_unassignedButton(uut, midiWrapperMock): def test_initializationMessages(uut, midiWrapperMock): expectedMessages = set( [(int(i / 8) * 16 + (i % 8), LED_OFF, 0) for i in range(0, 64)] + # clear all - [(i, LED_GREEN, 0) for i in range(0, 8)] + # volume row + [(i, LED_GREEN, 0) for i in range(0, 6)] + # volume row [(i, LED_YELLOW, 0) for i in range(16, 22)] + # playback rate row [ (96, LED_RED, 0), diff --git a/notifier.py b/notifier.py index e052b5c..42ab529 100644 --- a/notifier.py +++ b/notifier.py @@ -1,6 +1,8 @@ class Notifier: PLAYING_STATE_EVENT = 0 + PLAYBACK_VOLUME_EVENT = 1 + PLAYBACK_RATE_EVENT = 2 def __init__(self, player): self._callbacks = dict() diff --git a/notifier_unittest.py b/notifier_unittest.py index b840c16..52be51e 100644 --- a/notifier_unittest.py +++ b/notifier_unittest.py @@ -30,6 +30,8 @@ def checkEvent(uut, event): def test_allEvents(uut): checkEvent(uut, Notifier.PLAYING_STATE_EVENT) + checkEvent(uut, Notifier.PLAYBACK_VOLUME_EVENT) + checkEvent(uut, Notifier.PLAYBACK_RATE_EVENT) def test_eventWithoutRegisteredCallbacks(uut): uut.notify(Notifier.PLAYING_STATE_EVENT) @@ -46,3 +48,36 @@ def test_playingStateEventWithMockPlayer(uut, mockPlayer): assert not called mockPlayer.simulatePlayingStateChanged() assert called + +def test_singleEventNotification(uut): + playingStateCalled = False + def playingStateCallback(): + nonlocal playingStateCalled + playingStateCalled = True + + volumeCalled = False + def volumeCallback(): + nonlocal volumeCalled + volumeCalled = True + + uut.registerCallback(Notifier.PLAYING_STATE_EVENT, playingStateCallback) + uut.registerCallback(Notifier.PLAYBACK_VOLUME_EVENT, volumeCallback) + + assert not playingStateCalled + assert not volumeCalled + + uut.notify(Notifier.PLAYING_STATE_EVENT) + assert playingStateCalled + assert not volumeCalled + + playingStateCalled = False + + uut.notify(Notifier.PLAYBACK_VOLUME_EVENT) + assert not playingStateCalled + assert volumeCalled + + volumeCalled = False + + uut.notify(Notifier.PLAYBACK_RATE_EVENT) + assert not playingStateCalled + assert not volumeCalled diff --git a/solo_tool.py b/solo_tool.py index f52e074..7933fce 100644 --- a/solo_tool.py +++ b/solo_tool.py @@ -92,7 +92,13 @@ class SoloTool: return self._player.isPlaying() def setPlaybackRate(self, rate): + previousRate = self._player.getPlaybackRate() self._player.setPlaybackRate(rate) + if previousRate != rate: + self._notifier.notify(Notifier.PLAYBACK_RATE_EVENT) + + def getPlaybackRate(self): + return self._player.getPlaybackRate() def setPlaybackPosition(self, position): self._player.setPlaybackPosition(position) @@ -101,8 +107,20 @@ class SoloTool: return self._player.getPlaybackPosition() def setPlaybackVolume(self, volume): + previousVolume = self._player.getPlaybackVolume() self._player.setPlaybackVolume(volume) + if previousVolume != volume: + self._notifier.notify(Notifier.PLAYBACK_VOLUME_EVENT) + + def getPlaybackVolume(self): + return self._player.getPlaybackVolume() def registerPlayingStateCallback(self, callback): self._notifier.registerCallback(Notifier.PLAYING_STATE_EVENT, callback) + def registerPlaybackVolumeCallback(self, callback): + self._notifier.registerCallback(Notifier.PLAYBACK_VOLUME_EVENT, callback) + + def registerPlaybackRateCallback(self, callback): + self._notifier.registerCallback(Notifier.PLAYBACK_RATE_EVENT, callback) + diff --git a/solo_tool_integrationtest.py b/solo_tool_integrationtest.py index 117e539..98dd31b 100644 --- a/solo_tool_integrationtest.py +++ b/solo_tool_integrationtest.py @@ -291,6 +291,12 @@ def test_getters(uut, mockPlayer): mockPlayer.position = 0.8 assert uut.getPlaybackPosition() == 0.8 + mockPlayer.volume = 0.8 + assert uut.getPlaybackVolume() == 0.8 + + mockPlayer.rate = 0.5 + assert uut.getPlaybackRate() == 0.5 + def test_setTemporaryLimits(uut, mockPlayer): song = "test.flac" abLimits = [ @@ -359,3 +365,45 @@ def test_playingStateNotification(uut, mockPlayer): called = False uut.stop() assert not called + +def test_playbackVolumeNotification(uut, mockPlayer): + song = "test.flac" + uut.addSong(song) + uut.setSong(0) + + called = False + def callback(): + nonlocal called + called = True + + uut.registerPlaybackVolumeCallback(callback) + + assert not called + + uut.setPlaybackVolume(0.0) + assert called + called = False + + uut.setPlaybackVolume(0.0) + assert not called + +def test_playbackRateNotification(uut, mockPlayer): + song = "test.flac" + uut.addSong(song) + uut.setSong(0) + + called = False + def callback(): + nonlocal called + called = True + + uut.registerPlaybackRateCallback(callback) + + assert not called + + uut.setPlaybackRate(0.5) + assert called + called = False + + uut.setPlaybackRate(0.5) + assert not called diff --git a/solo_tool_qt.py b/solo_tool_qt.py index 2ae75a5..bb4ee76 100644 --- a/solo_tool_qt.py +++ b/solo_tool_qt.py @@ -66,7 +66,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.bSlider.setMaximum(POSITION_FACTOR) self.bSlider.sliderReleased.connect(self.abSliderReleased) - self.rateSlider.setRange(int(0.5 * RATE_FACTOR), int(1.5 * RATE_FACTOR)) + self.rateSlider.setRange(int(0.5 * RATE_FACTOR), int(1.2 * RATE_FACTOR)) self.rateSlider.setSingleStep(int(0.1 * RATE_FACTOR)) self.rateSlider.setValue(int(1.0 * RATE_FACTOR)) self.rateSlider.sliderReleased.connect(self.rateSliderReleased) -- cgit v1.2.3