diff options
-rw-r--r-- | abcontroller.py | 3 | ||||
-rw-r--r-- | abcontroller_unittest.py | 4 | ||||
-rw-r--r-- | diagram.drawio | 2 | ||||
-rw-r--r-- | known-issues.md | 1 | ||||
-rw-r--r-- | midi_controller_launchpad_mini.py | 21 | ||||
-rw-r--r-- | midi_launchpad_mini_integrationtest.py | 57 | ||||
-rw-r--r-- | notifier.py | 1 | ||||
-rw-r--r-- | notifier_unittest.py | 1 | ||||
-rw-r--r-- | solo_tool.py | 10 | ||||
-rw-r--r-- | solo_tool_cli.py | 10 | ||||
-rw-r--r-- | solo_tool_cli_integrationtest.py | 4 | ||||
-rw-r--r-- | solo_tool_integrationtest.py | 38 |
12 files changed, 124 insertions, 28 deletions
diff --git a/abcontroller.py b/abcontroller.py index 99bd9d7..cec9fb2 100644 --- a/abcontroller.py +++ b/abcontroller.py @@ -66,6 +66,9 @@ class ABController: def setEnable(self, enable): self._enabled = enable + def isEnabled(self): + return self._enabled + def getStoredLimits(self, song): return self._limits.get(song) diff --git a/abcontroller_unittest.py b/abcontroller_unittest.py index f9e947d..9fdcbc3 100644 --- a/abcontroller_unittest.py +++ b/abcontroller_unittest.py @@ -103,11 +103,15 @@ def test_disableAbRepeat(): uut.loadLimits(0) assert uut.getLoadedIndex() == 0 + assert uut.isEnabled() + uut.setEnable(False) checkLimits(uut, abLimits.a, abLimits.b, fail=True) + assert not uut.isEnabled() uut.setEnable(True) checkLimits(uut, abLimits.a, abLimits.b) + assert uut.isEnabled() def test_storeLimitsToSpecificSong(): song = "/path/to/song" diff --git a/diagram.drawio b/diagram.drawio index 935a914..e3d1846 100644 --- a/diagram.drawio +++ b/diagram.drawio @@ -1 +1 @@ -<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 +<mxfile host="app.diagrams.net" modified="2022-02-03T17:57:29.948Z" agent="5.0 (X11)" etag="JSVR_wePj1eIi_OTfR3Q" version="16.5.3" type="device" pages="3"><diagram id="g-wcGVps3MkI6_XAwNEs" name="Core">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</diagram><diagram id="yK3rgzEW7m2RTtpwjvJ6" name="MIDI">7Vxtc6JIEP41Vt19MAUD8vJRxWRzm9TuxaRuc1+uEEYdgwwHozH762+QQV5mNLIKmpxWqsI02EA/3c9090zSUvrz1U1oB9N77EKvBSR31VKsFgCgY5j0Vyx5SySGCRLBJERuIpIywRD9hIlQTqUL5MKIyRIRwdgjKCgKHez70CEFmR2G+LV42Rh7bkEQ2BPICYaO7fHSv5BLpkwqa2Z24gtEkym7tQH05MTcTi9mbxJNbRe/5kTKoKX0Q4xJcjRf9aEXGy+1y7ev1gCtrN5ovvrz/ubh5kmefWsnyq6rfGXzCiH0yS+r/joLB+5iOXuy76578O3e+mM6asvMCkvbWzCDsZclb6kFoe92YyDoyPHsKEJOS+lNydyjApkehnjhuzC+i0RH9BHDtx/5wXM8uAKddGyt8metNzba8yWZMaBbgJ298g3Ec0iV0gteM7A7DMBpDuZUFkLPJmhZdBab+dxko25zh+8Y0ccDEouPNtCZIhYeQNWLOiK8CB3IvpaHp6xJlt/RROxwAgmniR7kXjwTreGv4Aqpmx/RFVaI/MgdJ46gd9gw84N48LHdgAPPkH7RDTiHMqSrTrOOIHOOcH9r3VLJaBFxLkHgihSdwPbQxI89hCIIQypYwpAgyshddmKOXDf+ei+EEfppj9aqYuyD+JXWL9nptTpWrGtBcJTMKbHqiIT4Bfaxh6ley8d+rGWMPK8kquZE8fPB1U432qCjlNDpMGxyfqYL/AxI212qgF9lsAAHlgWXyIEcUMXYfJ0iAoeB7cRnX+nUX8Qwb9MWUFwbGmOHA4Ce0RwDjsZ1mlwtm1xWr3ijy6rA6rLUKcfOrxjesYLVPysT2H/3LWI8yY8vtw9tATlSLhqyIQ7JFE+wb3uDTFqix+yaO4wDZvgZJOSNZVGx8xdhSRlVrsKo22Zk/bgzcsJwyXXj3sQbPszQg9HtL+/Nu4HVC9PrEvbacZ2yJ7UfyNlGya1MtS6WFfqPwsUtZUhvZDsvQse6s0c0Ma+NZ3modzk9F72b9J3dpJXPkIVRTRNCXVcKCKQOciCucolq8XgcwUMh3OXQOQipxfFhxFsbjcpSyd/TcY5EDRGHKmptHKqeD4eC3RwaUTYgfOq7Fl8jb6M7y49ZJkIl7HwNjNoQUyrllFQBV0buYzZKnB0u6oZxeNN6PQiQPzlj9lSPyZ6SqRfZExyHPcVaj0qmQuNo58MF7+RTBS5gYZ4nAqlIBBlbZFxQnenPhgu0IhcoknSl5T9Ko2Sgc2Tw3bPp418H9iKiNdB1RKgrAM2La9RRSI8m8REkzhnzhHZMntBqYQm1pLWk4Sg0cY2CexBa3Wft1ne+dmbPvj5sS0LcTkITl4xhb5YAmn6l5D6pVx6fJXa9dTFRRzwtoDjEx/ahzZNqmO2fw5tFo8qmKWqEAGESX18j5DQRmfY0NoM9VxnEPel3sv96gvDdRsi+Pe4jN0LUkoZ9W9eg1APvKKVKvOZcgC/HI8qlZzzPpwsux5jnZc0oGv8o83y7BGm7RCL1zfMyX+ZxSE4ojQQHkm1lW+ulCkkGAg42BRysAnO7+fdl4Nndgkx9R37sPiyedXP0ZfRtkxMVTJVMbS5apnPbEHv4EWMvPUXvlDt72IQ3idObNO9JV8nXgxeaaU/ZII2wMLEJF2Bxpi6Knd3+8f7seTpgtlUnJVtvX0Lz4FhkqVMsoB2KQqk7m042QOGiR7SSc4z1MyFE/P6H70nleMEoW+NUOYxEa+m1YSTzM/twXdZfMMp2CfAYNRpHMl9jJW3ZgDJesqolhTa5BFa+dyYLUge5UdS2NdNzqAXUhARh/yMgN8Y+yW9OWH+aRFQVIKo0iiifc3RdN74TVTeivz00R+SC5ftY6jIHpWh9tDYkBduwBv7a0BmYc+xeGDVfjGngxIwKeEZN4i/CgrXJM4Tq1GEnS3zc1bWjTowgz6D9KcZxTfBhQGwMLJMv30Qldn1g8fVbksAssbeYX7gxn5vo2mknNIWf0DaB9eGyk8YiTBHMaFKTsKmAg+0O2+spDUbRpTDYC8a0Fj8VT6oqB+LQXsILiNVSkxODqHEgPiLBfuELdIL44ye/uvpj4jfgseNwO8l6Vqe0niX6ixZxCZXOTMe3FWeqz7Zssm2HF48SQ8Xki6JG3ZfPAT7dMkllTOQ0hBtYFxE+sqDl/smWRapj0uExaTRQBHnWZ18GqQ6SwXcNauvSCZ9ZtLkk2RYRBbZfQEb7dxH/gX/PSYzTjQGcjH6jT0Z/6N2l3NHv2eWbbReFbsRm60Vymy17L87QFw7N3yr7yGaLcb4NaPAuIhs1uQjfBfz03ffKICl8QdRsHPPdv0/bqq0MjsanjHVVq+In/j+2+/ZHqYZ+Hh1m/40m2YiZ/U8fZfAf</diagram><diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad">5ZxNc6M4EIZ/jY9bRUt8SMdMNjNzmLlsDlO7N2Jkm1lsuTD+yP76xTY4RC1XkSrbTTunQEuB8LZCP91Aj+TjfPetTJeznzYzxUgE2W4k/xwJAaEI6h97y+vRorQ4GqZlnjWT3gzP+X+mMTa/N13nmVm9m1hZW1T58r1xbBcLM67e2dKytNv30ya2eH/WZTo1yPA8Tgts/ZVn1ay5CpG82b+bfDprzwyxPo7M03ZycyWrWZrZbcckn0bysbS2Om7Nd4+m2IvX6vJP9XtinuR6O/lr9XW2+DX58fePP44H+/qRXzldQmkW1WUPLZuL3aTF2pwuLS7q03x5Keut6X5rY4v13GB7VM+NGnGq11bx0q4XmdmfFeo521lemedlOt6Pbus1Vttm1bxohmu3V2m+MGW9H9T7k7woHm1hy8OxZBYZlYW1fVWV9l/TGVHiRcZxPdJTnEbEjSkrs+ssjUasb8bOTVW+1lOa0bBxe7Puk2Z321lEjWnWWT+tLW2W7fR03DfX1BuNdz7gqfbP6XgKYuyS1dLU0hN4ajIx8Xjs81SW6JcguJ2nIEqIXQXIVVJcVvwbSSnCiFhKgaQMFUspZbsEyKSUSMo4ZCllCJpYyhBJ2Z6LmZRREBNLGSEptS+sVXZ53fClxsYfvl5UFEa3dIkOiV0SY9IAgX2yLNLX/ZnwQLpemU8CG7ECWm8pfC+C/gQf3zPBgxwWwit8r4OkN8Jf31WE/1Wuq8gZXuGboJQ8QryrJTnEqwRpGWqeWpJTvFJIy/jCd4VbaUmO8QoXv9pozk1Lco7XuDylE6ZaUgO4xvUjAKbBh5yPNS57eHKZc3yc3DMfn55sDISPtSeVUb35+PquIuRj11XkfKxxLiOZVBNdLcn5WONcI2JSTnS1JOdjjXONOOapJTkfa5xrKMFTS3o+xrmGZvIgC2lJzccQeJ6FA9PoQw7IEAgcyvsTsrpnQpYwLEKGAGcz+yjRE5Gv7ytCRHZ9RY7IEOB8RjKp1blikjMyBDjhiJgU61wxySEZApxxxEyqda6Y5JQMAU45FJNqnSsmOSZDgHOONo5l+aYNZPoU8+qzdOyeqb/X82XvyZWtT/2AA+e5A3yOSDoA4MfZEwDTUEoP/ICfL4T9gV/fM/CHwcCAH3By5ntD/wzwX99XpG9iDQ34AWdnkknx0RWTHvgBZ0+R4CkmPfADzp5iJuVHV0x64AecPSkm1UdXTHrgB5w9neq77NQkB2XA6dNpuXJTkx6U21J9NwT1B+XDMr5fUtYDI2WBsxrfm/BnSPkGzqJEZT00VBY4r5FMKpCumPSoLHDeETGpQLpi0qOywHlH2xKAm5j0qCxw3qEufJe9lZj0qCxw3uGpYUMAH6h3T6eF6T394csgSuMDoh564hee/AmYRtIBED9+0OD5Gvks8cPlIfJa2kdqYPzefjjQ5XfRn98ZS09P49LTsYRJedAVk57GW4Lt0jiT8qArJj2NS5zatHcqbmLS07j09C1hUhx0xaSncYlTm8OrtKgbRmk2uV2v8MjDld8goWtpgrxFjsnS19TE83HZeW+t7GL6WfxFD+ISP8jwtMo4C+KCDw3GydBAHOdAvg8CzoE4Y+npQdzT5bF1Bzcx6UHc04cxYroy6UHc04kxYfL+vSsmPYh7ejEqJjU7V0x6EPd0Yzx8J+VGpoXZVTeHcMJaNfIUOYR7mj2C7/M1v6euDuAD8tUVAbzefetZfhjrdH6XT/8D</diagram></mxfile>
\ No newline at end of file diff --git a/known-issues.md b/known-issues.md index b1bac9e..ae248f5 100644 --- a/known-issues.md +++ b/known-issues.md @@ -27,6 +27,7 @@ * Key mapping in Qt to jump to A * Space bar in principle * Not so easy to do actually, used Super L instead +* AB repeat toggle in MIDI controller # Use Cases diff --git a/midi_controller_launchpad_mini.py b/midi_controller_launchpad_mini.py index 6790cec..0cc952c 100644 --- a/midi_controller_launchpad_mini.py +++ b/midi_controller_launchpad_mini.py @@ -28,12 +28,14 @@ class MidiController: self._soloTool.registerPlayingStateCallback(self._updatePlayPauseButton) self._soloTool.registerPlaybackVolumeCallback(self._updateVolumeRow) self._soloTool.registerPlaybackRateCallback(self._updateRateRow) + self._soloTool.registerAbLimitEnabledCallback(self._updateToggleAbLimitEnableButton) def _registerHandlers(self): self._handlers = { - 96 : self._stop, + 96 : self._soloTool.stop, + 99 : self._soloTool.jumpToA, 112 : self._playPause, - 101 : self._soloTool.jumpToA, + 101 : self._toggleAbLimitEnable, 102 : self._soloTool.previousStoredAbLimits, 103 : self._soloTool.nextStoredAbLimits, 118 : self._soloTool.previousSong, @@ -66,8 +68,8 @@ class MidiController: else: self._soloTool.play() - def _stop(self): - self._soloTool.stop() + def _toggleAbLimitEnable(self): + self._soloTool.setAbLimitEnable(not self._soloTool.isAbLimitEnabled()) def _updatePlayPauseButton(self, playing): if playing: @@ -75,6 +77,12 @@ class MidiController: else: self._setButtonLED(7, 0, MidiController.LED_YELLOW) + def _updateToggleAbLimitEnableButton(self, enabled): + if enabled: + self._setButtonLED(6, 5, MidiController.LED_GREEN) + else: + self._setButtonLED(6, 5, MidiController.LED_RED) + def _updateVolumeRow(self, volume): t1 = int(round(volume / MidiController.PLAYBACK_VOLUME_STEP, 1)) t2 = int(round(MidiController.MIN_PLAYBACK_VOLUME / MidiController.PLAYBACK_VOLUME_STEP, 1)) @@ -123,8 +131,11 @@ class MidiController: self._setButtonLED(6, 0, MidiController.LED_RED) self._updatePlayPauseButton(self._soloTool.isPlaying()) + # AB repeat toggle + self._updateToggleAbLimitEnableButton(self._soloTool.isAbLimitEnabled()) + # AB control - self._setButtonLED(6, 5, MidiController.LED_YELLOW) + self._setButtonLED(6, 3, MidiController.LED_YELLOW) self._setButtonLED(6, 6, MidiController.LED_RED) self._setButtonLED(6, 7, MidiController.LED_GREEN) diff --git a/midi_launchpad_mini_integrationtest.py b/midi_launchpad_mini_integrationtest.py index 615ef49..e9bc6b5 100644 --- a/midi_launchpad_mini_integrationtest.py +++ b/midi_launchpad_mini_integrationtest.py @@ -9,6 +9,15 @@ LED_YELLOW = 126 LED_GREEN = 124 LED_OFF = 0 +nextSongButton = 119 +previousSongButton = 118 +playPauseButton = 112 +stopButton = 96 +nextLimitButton = 103 +previousLimitButton = 102 +abToggleButton = 101 +jumpToAButton = 99 + class MidiWrapperMock: def __init__(self): self.callback = None @@ -56,8 +65,6 @@ def test_connect(uut, midiWrapperMock): assert midiWrapperMock.connectedDevice == expectedDevice def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock): - playPauseButton = 112 - stopButton = 96 uut.connect() assert playerMock.state == PlayerMock.STOPPED @@ -88,8 +95,6 @@ def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock): assert playerMock.state == PlayerMock.STOPPED def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool): - playPauseButton = 112 - stopButton = 96 uut.connect() assert playerMock.state == PlayerMock.STOPPED @@ -110,6 +115,26 @@ def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool): playerMock.simulatePlayingStateChanged() assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0) +def test_abToggleButton(uut, midiWrapperMock, soloTool): + uut.connect() + + midiWrapperMock.simulateInput(abToggleButton) + assert soloTool.isAbLimitEnabled() + assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0) + + midiWrapperMock.simulateInput(abToggleButton) + assert not soloTool.isAbLimitEnabled() + assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0) + +def test_abToggleButtonLed(uut, midiWrapperMock, soloTool): + uut.connect() + + soloTool.setAbLimitEnable(True) + assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0) + + soloTool.setAbLimitEnable(False) + assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0) + def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock): ab = (0.5, 0.6) uut.connect() @@ -117,12 +142,10 @@ def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock): soloTool.setAbLimits(ab[0], ab[1]) assert playerMock.position == 0.0 - midiWrapperMock.simulateInput(101) + midiWrapperMock.simulateInput(jumpToAButton) assert playerMock.position == ab[0] def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock): - nextSongButton = 119 - previousSongButton = 118 songs = [ "test.flac", "test.mp3" @@ -145,8 +168,6 @@ def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock): assert playerMock.currentSong == songs[0] def test_previousAndNextAbButtons(uut, midiWrapperMock, soloTool, playerMock): - nextLimitButton = 103 - previousLimitButton = 102 song = "test.flac" abLimits = [ [0.2, 0.4], @@ -330,13 +351,14 @@ def test_initializationMessages(uut, midiWrapperMock): [(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), - (101, LED_YELLOW, 0), - (102, LED_RED, 0), - (103, LED_GREEN, 0), - (112, LED_YELLOW, 0), - (118, LED_RED, 0), - (119, LED_GREEN, 0) + (stopButton, LED_RED, 0), + (playPauseButton, LED_YELLOW, 0), + (abToggleButton, LED_RED, 0), + (jumpToAButton, LED_YELLOW, 0), + (previousLimitButton, LED_RED, 0), + (nextLimitButton, LED_GREEN, 0), + (previousSongButton, LED_RED, 0), + (nextSongButton, LED_GREEN, 0) ] ) @@ -346,9 +368,6 @@ def test_initializationMessages(uut, midiWrapperMock): assert sentMessagesSet == expectedMessages def test_playingFeedbackWhenChangingSong(uut, midiWrapperMock, soloTool, playerMock): - nextSongButton = 119 - previousSongButton = 118 - playPauseButton = 112 songs = [ "test.flac", "test.mp3" diff --git a/notifier.py b/notifier.py index 06b4434..fae41b9 100644 --- a/notifier.py +++ b/notifier.py @@ -5,6 +5,7 @@ class Notifier: PLAYBACK_RATE_EVENT = 2 CURRENT_SONG_EVENT = 3 CURRENT_AB_EVENT = 4 + AB_LIMIT_ENABLED_EVENT = 5 def __init__(self, player): self._callbacks = dict() diff --git a/notifier_unittest.py b/notifier_unittest.py index 4aff8f4..b27a17f 100644 --- a/notifier_unittest.py +++ b/notifier_unittest.py @@ -38,6 +38,7 @@ def test_allEvents(uut): checkEvent(uut, Notifier.PLAYBACK_RATE_EVENT) checkEvent(uut, Notifier.CURRENT_SONG_EVENT) checkEvent(uut, Notifier.CURRENT_AB_EVENT) + checkEvent(uut, Notifier.AB_LIMIT_ENABLED_EVENT) def test_eventWithoutRegisteredCallbacks(uut): uut.notify(Notifier.PLAYING_STATE_EVENT, 0) diff --git a/solo_tool.py b/solo_tool.py index 4ea081d..6cb1a77 100644 --- a/solo_tool.py +++ b/solo_tool.py @@ -74,7 +74,14 @@ class SoloTool: return list() def setAbLimitEnable(self, enable): + previous = self._abController.isEnabled() self._abController.setEnable(enable) + new = self._abController.isEnabled() + if previous != new: + self._notifier.notify(Notifier.AB_LIMIT_ENABLED_EVENT, new) + + def isAbLimitEnabled(self): + return self._abController.isEnabled() def nextStoredAbLimits(self): previous = self._abController.getLoadedIndex() @@ -152,3 +159,6 @@ class SoloTool: def registerCurrentAbLimitsCallback(self, callback): self._notifier.registerCallback(Notifier.CURRENT_AB_EVENT, callback) + def registerAbLimitEnabledCallback(self, callback): + self._notifier.registerCallback(Notifier.AB_LIMIT_ENABLED_EVENT, callback) + diff --git a/solo_tool_cli.py b/solo_tool_cli.py index 8d53a33..43e3a9d 100644 --- a/solo_tool_cli.py +++ b/solo_tool_cli.py @@ -1,10 +1,12 @@ import sys +import time +import threading from solo_tool import SoloTool from midi_controller_launchpad_mini import MidiController class SoloToolCLI: - def __init__(self, sessionJson, soloToolOverride=None, midiOverride=None): + def __init__(self, sessionJson, soloToolOverride=None, midiOverride=None, tickEnable=True): self._soloTool = SoloTool() if soloToolOverride is None else soloToolOverride self._soloTool.loadSession(sessionJson) self._midiController = MidiController(self._soloTool) if midiOverride is None else midiOverride @@ -12,6 +14,8 @@ class SoloToolCLI: "song" : self._song, "midi" : self._midi } + if tickEnable: + self._tick() def input(self, commandString): split = commandString.strip().split(" ") @@ -34,6 +38,10 @@ class SoloToolCLI: else: print("Supported device: Novation Launchpad Mini MkII") + def _tick(self): + self._soloTool.tick() + threading.Timer(0.1, self._tick).start() + def main(args): if len(args) == 0: print("Please provide path to session file") diff --git a/solo_tool_cli_integrationtest.py b/solo_tool_cli_integrationtest.py index 9134dcf..25ef131 100644 --- a/solo_tool_cli_integrationtest.py +++ b/solo_tool_cli_integrationtest.py @@ -27,7 +27,7 @@ def mockMidi(soloTool): @pytest.fixture def uut(soloTool, mockMidi): - return SoloToolCLI("test_session.json", soloToolOverride=soloTool, midiOverride=mockMidi) + return SoloToolCLI("test_session.json", soloToolOverride=soloTool, midiOverride=mockMidi, tickEnable=False) def test_songSelection(uut, soloTool, mockPlayer): expectedOutput = """\ @@ -56,7 +56,6 @@ Songs: uut.input("song") - assert buf.getvalue() == expectedOutput def test_connectMidi(uut, mockMidi): @@ -73,3 +72,4 @@ Connecting to MIDI device... assert mockMidi.connected assert buf.getvalue() == expectedOutput + diff --git a/solo_tool_integrationtest.py b/solo_tool_integrationtest.py index ee99303..5731eac 100644 --- a/solo_tool_integrationtest.py +++ b/solo_tool_integrationtest.py @@ -137,6 +137,15 @@ def test_addAndSetAbLimits(uut, mockPlayer): uut.tick() assert mockPlayer.position == 0.1 +def test_abLimitEnabledGetter(uut): + assert not uut.isAbLimitEnabled() + + uut.setAbLimitEnable(True) + assert uut.isAbLimitEnabled() + + uut.setAbLimitEnable(False) + assert not uut.isAbLimitEnabled() + def test_multipleSongsAndAbLimits(uut, mockPlayer): songs = [ "test.flac", @@ -554,3 +563,32 @@ def test_currentAbNotification(uut): uut.nextStoredAbLimits() assert not called + +def test_abLimitEnabledNotification(uut): + called = False + receivedValue = None + def callback(value): + nonlocal called, receivedValue + called = True + receivedValue = value + + uut.registerAbLimitEnabledCallback(callback) + assert not called + + uut.setAbLimitEnable(False) + assert not called + assert receivedValue is None + + uut.setAbLimitEnable(True) + assert called + assert receivedValue == True + called = False + receivedValue = None + + uut.setAbLimitEnable(True) + assert not called + assert receivedValue is None + + uut.setAbLimitEnable(False) + assert called + assert receivedValue == False |