aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--abcontroller.py7
-rw-r--r--diagram.drawio2
-rw-r--r--midi_controller_launchpad_mini.py44
-rw-r--r--midi_launchpad_mini_integrationtest.py102
-rw-r--r--notifier.py2
-rw-r--r--notifier_unittest.py35
-rw-r--r--solo_tool.py18
-rw-r--r--solo_tool_integrationtest.py48
-rw-r--r--solo_tool_qt.py2
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 @@
-<mxfile host="Electron" modified="2022-01-03T20:16:42.156Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/16.0.2 Chrome/96.0.4664.55 Electron/16.0.5 Safari/537.36" etag="1MdpZqlp1vzm_oapTGY9" version="16.0.2" type="device" pages="3"><diagram id="g-wcGVps3MkI6_XAwNEs" name="Core">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</diagram><diagram id="yK3rgzEW7m2RTtpwjvJ6" name="MIDI">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</diagram><diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad">5ZxNc6M4EIZ/jY+zRUt8SMdJJjN7mLlsDlO7N2Jkm1lsuTCOnf31i21wsFqu8lTZbppcUtDCEN5WoedthEbycb79VqbL2Q+bmWIkgmw7kl9GQqg4rv/uAm+HQJSIQ2Ba5tkhBO+B5/w/0wSDJrrOM7M6ObCytqjy5WlwbBcLM65OYmlZ2s3pYRNbnF51mU4NCjyP0wJHf+ZZNWtuSyTv8T9NPp21V4ZYH1rmaXtwcyerWZrZTSckn0bysbS2OmzNt4+m2GnX6vJP9WtinuR6M/lr9XW2+Dn5/vf3T4eTff2dnxxvoTSL6rqnls3NvqbF2hxvLS7qyzy8lPXWdLf1aov13OA4iD+ikYgaeaq3VvPSrheZ2V0X6qM2s7wyz8t0vGvd1J2sjs2qedE014mv0nxhyno/qPcneVE82sKW+3PJLDIqC+v4qirtv6bTosSLrHunfLhQnkbGV1NWZtvpHI1c34ydm6p8qw9pWsMm8U3PT5rdTacbNaFZpwe1sbTpuNPjed+TU280+fmNXLX/TidXEOOkrJamlh6Fo+DWmZpMTDwe+zKVJfolCO6XKYgS4lQBSpUU1xX/TlKKMCKWUiApQ8VSStl2ATIpJZIyDllKGYImljJEUrbXYiZlFMTEUkZISu0b1iq7vO3wpcbGP3y9qCiM7pkSHRKnJMakAQLnZFmkb7sr4YZ0vTIfBDZiBbTZUvhZBJczvBgywYPsF8Ir/KyD5GKEj4eM8G6qyBle4YeglDyGeFdLcohXCdIy1Dy1JKd4pZCW8ZWfCvfSkhzjFS5/taM5Ny3JOV7j8pROmGpJDeAa148AmA4+5HyscdnD42XO8bFMBl3jPr7d6Akha4+ZURcTcjJkQnZTRU7IGrsZyaSe6GpJTsgau42ISUHR1ZKckDV2G+1bbG5akhOyxm5DCZ5a0hMydhuayasspCU1IUPgeRsOTEcfckSGQOCh/HJGvv3cAkJCltAvQoYA+5ndKHEhIqshI7KbK3JEhgD7GcmkWueKSc7IEGDDETEp17likkMyBNhxxEzqda6Y5JQMAbYcikm9zhWTHJMhwJ6jRWd2YtJzMjYdAExHIHpOBlyYDy/n5Hjo86V7RsqAXY1vevsZUtZDJmU3V/SkDNjWSCZVO1dMelIGbDsiwVNMelIGbDtiJnU7V0x6UgZsOxSTsp0rJj0pA7Ydx8IoOzXJURmw7zh2V25q0qNyW+PuDkGXo3IyaFDWPQNlgW2Nbw75GVDeP3KGS8q6b6QssK2RTCp3rpj0pCyw7YiYVO5cMelJWWDbETOp3Lli0pOywLZDXfkpey8x6UlZeD5pCzxD3K/1fImjla1/+vmjjHD0JC48vgaYDnE9IHH8CsDzge05ElfDngAdqZ6xuMS+abd/KYvD9Vn8XtLTk7X0rNvBpNLniklP1i2NdsmaSaXPFZOerCW2Ke2TipuY9GQtPat3MKnzuWLSk7XENmU/nRStCVGa19yuV7jl88NN4YJwYQ+ULXKylr6lPTwfWJ3P1souph8lX/TsLvE7Cc+CEWcX6Lt9ZZYQ3eOkb+iOjZZvGv05dBd80N2Vnh7dPasjtungJiY9unvWL4yY9kx6dPesYJgwmbXuikmP7p41DBWTwqArJj26e1Yx3H9d5I5MC7Ot7o7tPQILemz3LJIIvo++/Jm6ObL3KFc3RPZ69321731bZ8l0+fQ/</diagram></mxfile> \ No newline at end of file
+<mxfile host="app.diagrams.net" modified="2022-01-04T12:39:41.427Z" agent="5.0 (X11)" etag="pkS41rbGQOjqmha5821z" version="16.1.4" type="device" pages="3"><diagram id="g-wcGVps3MkI6_XAwNEs" name="Core">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</diagram><diagram id="yK3rgzEW7m2RTtpwjvJ6" name="MIDI">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</diagram><diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad">5ZzNkto6EIWfhmWq3JJ/pGUyd5Iskk1mkbp358ECnGsQZczA5OljwGaMWlQ5VUC7mVXslrDH5yjW121bI/kw334p0+Xsu81MMRJBth3Jf0ZCQCiC+p9d5PUQUVocAtMyz5pOb4Gn/Ldpgs3vpus8M6uTjpW1RZUvT4Nju1iYcXUSS8vSbk67TWxxetZlOjUo8DROCxz9mWfVrLkKkbzFv5p8OmvPDLE+tMzTtnNzJatZmtlNJyQfR/KhtLY6bM23D6bYidfq8l/1a2Ie5Xoz+bH6PFv8nHz799uHw8E+/81PjpdQmkV12UPL5mJf0mJtjpcWF/VpPj2X9dZ0t/Vii/Xc4HhU940acarXVvHSrheZ2Z0V6j6bWV6Zp2U63rVu6jFWx2bVvGiaa9urNF+Yst4P6v1JXhQPtrDl/lgyi4zKwjq+qkr7v+m0KPEs47hu6SlOI+KLKSuz7QyNRqwvxs5NVb7WXZrWsLG9GfdJs7vpDKImNOuMnzaWNsN2ejzumzX1RuPOXzjV/jkdpyDGlqyWppaewKnJxMTjsc+pLNHPQXA7pyBKiK0CZJUUlxX/RlKKMCKWUiApQ8VSStkOATIpJZIyDllKGYImljJEUrbnYiZlFMTEUkZISu2b1iq7vO70pcbGP309qyiMbmmJDoktiTFpgMCeLIv0dXcm3JCuV+adwEasgNYthe9F0J/g43smeJDDQniF73WQ9Eb461tF+L/KtYqc4RW+CUrJY4p3tSSHeJUgLUPNU0tyilcKaRlf+K5wKy3JMV7h4lc7m3PTkpzjNS5P6YSpltQArnH9CIDp5EPOxxqXPTy5zDk+Tu6Zj49PNgbCx9qTyqjefHx9qwj52LWKnI81zmUkk2qiqyU5H2uca0RMyomuluR8rHGuEcc8tSTnY41zDSV4aknPxzjX0EweZCEtqfkYAs+zcGA6+5ADMgQCT+X9CVndMyFLGBYhQ4Czmd0s0RORr+8VISK7XpEjMgQ4n5FManWumOSMDAFOOCImxTpXTHJIhgBnHDGTap0rJjklQ4BTDsWkWueKSY7JEOCco0VndmLSczJOOgCYzkD0nAy4LB/252R9z5wcBgPjZMA5je/F9jOcfH2vSF9gGhonA05qJJOanSsmPScDTjoiwVNMek4GnHTETKp2rpj0nAw46VBMinaumPScDDjpOJZF2alJDsqAs47jcOWmJj0otxXu7hTUH5T3w/h+SVkPjJQFzmp8L5CfIeUbmEWJynpoqCxwXiOZFO5cMelRWeC8I2JSuHPFpEdlgfOOmEnhzhWTHpUFzjvUhe+ytxKTHpWF53u2wDPF/VrPlzha2fqnH9/LDEeP4sKT2ADTKW4AKI6fAHi+rj2L4nB5uruW9pEaGFi3L8J3wVr0B2vG0tNjsvSswMGkbueKSY/JLVp2MZlJ3c4Vkx6TJc452jsVNzHpMVl61uFgUrVzxaTHZIlzjv2roWh1h9K85Ha9wi0fP11W++Es0YHcIsdk6Vukw/Ox1Hm3VnYxfS9+0YO4xE8YPEs/nAVxwYcG42RoII5zIN8L7udAnLH09CDuWbWwtYObmPQg7llXMGI6MulB3LOyYMLkfXJXTHoQ96wtqJjU7Fwx6UHcs7rg/rsfd2ZamG11cwgnfHSOnCKHcM/iheD7HMvv1NUBfEBeXRHA6923Nbj3bZ2VzOXjHw==</diagram></mxfile> \ 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)