diff options
-rw-r--r-- | abcontroller.py | 7 | ||||
-rw-r--r-- | abcontroller_unittest.py | 17 | ||||
-rw-r--r-- | diagram.drawio | 2 | ||||
-rw-r--r-- | midi_controller_launchpad_mini.py | 40 | ||||
-rw-r--r-- | midi_launchpad_mini_unittest.py | 107 | ||||
-rw-r--r-- | midi_wrapper_mido.py | 21 | ||||
-rw-r--r-- | player_mock.py | 3 | ||||
-rw-r--r-- | player_vlc.py | 4 | ||||
-rw-r--r-- | session_manager.py | 2 | ||||
-rw-r--r-- | session_manager_unittest.py | 6 | ||||
-rw-r--r-- | solo_tool.py | 12 | ||||
-rw-r--r-- | solo_tool_integrationtest.py | 22 |
12 files changed, 225 insertions, 18 deletions
diff --git a/abcontroller.py b/abcontroller.py index 0a10078..c04b8db 100644 --- a/abcontroller.py +++ b/abcontroller.py @@ -42,16 +42,17 @@ class ABController: self._currentLimits = _AB(aLimit, bLimit) def positionChanged(self, position): - if not self._currentLimits: - return if position > self._currentLimits.b and self._setPositionCallback and self._enabled: self._setPositionCallback(self._currentLimits.a) def setEnable(self, enable): self._enabled = enable - def getLimits(self, song): + def getStoredLimits(self, song): return self._limits.get(song) + def getCurrentLimits(self): + return self._currentLimits + def clear(self): self.__init__(enabled=self._enabled, callback=self._setPositionCallback) diff --git a/abcontroller_unittest.py b/abcontroller_unittest.py index fbe7ae1..b2b2a67 100644 --- a/abcontroller_unittest.py +++ b/abcontroller_unittest.py @@ -29,6 +29,9 @@ def checkLimits(uut, aLimit, bLimit, fail=False): TCase(bLimit + 0.1, aLimit if not fail else None) ] _checkLimits(uut, tests) + if not fail: + assert uut.getCurrentLimits()[0] == aLimit + assert uut.getCurrentLimits()[1] == bLimit def checkDefaultLimits(uut): tests = [ @@ -47,7 +50,7 @@ def test_oneSetOfLimits(): uut.loadLimits(0) checkLimits(uut, abLimits.a, abLimits.b) - assert uut.getLimits(song) == [abLimits] + assert uut.getStoredLimits(song) == [abLimits] def test_multipleSetsOfLimits(): song = "/path/to/song" @@ -66,7 +69,7 @@ def test_multipleSetsOfLimits(): uut.loadLimits(i) checkLimits(uut, l.a, l.b) - assert uut.getLimits(song) == abLimits + assert uut.getStoredLimits(song) == abLimits def test_multipleSongs(): songs = [ @@ -86,7 +89,7 @@ def test_multipleSongs(): uut.loadLimits(0) checkLimits(uut, abLimits[i].a, abLimits[i].b) - assert uut.getLimits(s) == [abLimits[i]] + assert uut.getStoredLimits(s) == [abLimits[i]] def test_disableAbRepeat(): song = "/path/to/song" @@ -147,10 +150,10 @@ def test_storeLimitsToCurrentSongButDoNotSetCurrentLimits(): checkLimits(uut, abLimits.a, abLimits.b) -def test_getLimitsOfInexistentSong(): +def test_getStoredLimitsOfInexistentSong(): song = "/path/to/song" uut = ABController() - assert uut.getLimits(song) == None + assert uut.getStoredLimits(song) == None def test_clearAbController(): songsWithLimits = [ @@ -163,12 +166,12 @@ def test_clearAbController(): uut.storeLimits(s[1].a, s[1].b, s[0]) for i, s in enumerate(songsWithLimits): - assert uut.getLimits(s[0]) == [s[1]] + assert uut.getStoredLimits(s[0]) == [s[1]] uut.clear() for i, s in enumerate(songsWithLimits): - assert uut.getLimits(s[0]) == None + assert uut.getStoredLimits(s[0]) == None def test_setTemporaryLimits(): abLimits = [ diff --git a/diagram.drawio b/diagram.drawio index ecbf149..9b04ef2 100644 --- a/diagram.drawio +++ b/diagram.drawio @@ -1 +1 @@ -<mxfile host="Electron" modified="2021-12-29T16:06:37.706Z" 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="SEIFEGhcxgz9P1gqrZCB" 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">5ZxLc9owFIV/Dct2LAk/tExp0i7STbPItDuDBTg1lseIAP31FcHmIdnTZCZwdZVV7Gshwzke6zuyowEbLTbf6rSa/5CZKAY0yDYD9nVAKaEs1n92le2+EvNgX5jVedY0OhYe8r+iKbbNVnkmlmcNlZSFyqvz4kSWpZios1pa13J93mwqi/OzVulMWIWHSVrY1cc8U/N9NaHxsf5d5LN5e2YS8f2RRdo2bn7Jcp5mcn1SYrcDNqqlVPutxWYkip14rS6/1dNU3LLVevpzeTcvH6f3v+4/7Tu7e8tHDj+hFqV6365Z82Of02LVCPYsi9VCDGhU6HN9Gdd6a7bbIvRzOKBho4XatgLXclVmYncSolut57kSD1U62R1d60tK1+ZqUTSHtcsqzUtR6/1A70/zohjJQtYvfbEsFEk21PWlquUfcXIkoWMWRfpI831FrcTGcPg/8pCDZ/piF3IhVL3Vn2t6GTYut5d5s7s+uWaa0vzkcmlraXOVzg79Hp3QG40ZbzCm/TonxiwroWW2fAmDS7synYpoMulyJYv5OAiu5woJY2BbiGXL+0p/JSHpMAQWkvohJGMUWEjmh5BDwoGFHPohZBhEwEKG9tClZHXZISqZiO4hapyEw/Ca8vMhsPyRJX9VpNtdpxY7VOlqKS5qjDtEFyUE1pjEvsH0sTb1mbQJcwu1k477VTdqRz6jtmkLOGsn9o0Mx4BsKgkO20nsiZLgtJ0knigJjtuJPfOEU0lw3m4nZPErCY3OHOvsjqkkOOtye1aij3VZ7PW88uHxgSO0y+0U0kO7sc+0a9oCTrvcTiE47j2mkuC0y7HmBlNJcNrlWHODqSQ47XKsucFUEp52seYGS0lo2iUB1uBgSgmOuySglpR9vHv55/WAtMuIW7RLAjuI9OBu4jPumr6A4y4JsD5uNaUE510SYI0OppTgwEsCrNnBlBKceEmANTyYUoIjLwmwpgdLSnjmxRofTCnhmZfYs+V9zBv5/u6wY9RL7DjSQ73cZ+o1fYGnXoL3bU3XqJdgDRCmlPDUS7AGCFNKeOolWAOEKSU89RKsAcKSEpx6CdYAYUoJT73t1PMrqDf2mnm5Y8xL7TjSw7wvDz78hV7uGvS2Nxt8dx/uGvRStPmBuwa9FG1+4K5BL0WbH7hr0Evt/PC0WlT2IKakbnXzUQYxeISmaNMIdw6hX7/oROL3y8Fh4hhEMzvd9EE08RmiTWPgIZph/d8EU0p4iG7RE72U8BDNsOYRU0p4iGZY84gpJTxEMzuPVLV4zuVqaQ9kN18uO4jBrUxhGQMO0axjbYpeY5aynH0Ua+ChnNmPCHpXgrv8/Ckgk0exa0xux6U+Jqc+M7lpDDyTdyzSh2PINqWEZ3K0C+uZUsIzOdql9Uwp4Zkc7eJ6ppTwTN6xvF4pNurqPO4QWcDzeMdSfd2mXJzFHbLlgiyud4/rRb8cO1l1m93+Aw==</diagram></mxfile>
\ No newline at end of file +<mxfile host="Electron" modified="2021-12-31T17:40:45.116Z" 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="sPgYuBUoq8Zz1AG5Rgi6" 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+7RUt8SMdJJjN7mL1sDlO7N2Jkm1lsuTCOnf31i21wsFquYqpiN81cUtDCEN5W0U83oifycbn/WqbrxZ82M8VEBNl+Ij9PhFBxXP89GN5OhigRJ8O8zLOTCd4Nz/l/pjEGjXWbZ2ZzcWBlbVHl60vj1K5WZlpd2NKytLvLw2a2uLzqOp0bZHiepgW2fs+zatHclkje7X+YfL5orwyxPo0s0/bg5k42izSzu45JPk3kY2ltddpa7h9NcdCu1eWf6sfMPMntbvbX5sti9X327e9vv51O9uVnfnK+hdKsqo89tWxu9jUttuZ8a3FRX+bhpay35oetV1tslwbbQfweTUTUyFO9tZqXdrvKzOG6UB+1W+SVeV6n08Porp5ktW1RLYtmuHZ8leYrU9b7Qb0/y4vi0Ra2PJ5LZpFRWVjbN1Vp/zWdESVeZD075UNPeRoZX01ZmX1ncjRyfTV2aaryrT6kGQ0bxzczP2l2d51p1JgWnRnU2tJm4s7P5313Tr3R+OcnfNX+Ox1fQYydslmbWnpkjoJbe2o2M/F06vNUluiXILifpyBKiF0FyFVSfKz4d5JShBGxlAJJGSqWUsp2CpBJKZGUcchSyhA0sZQhkrK9FjMpoyAmljJCUmpfWKvs+rbhS02NP3y9qCiM7ukSHRK7JMakAQL7ZF2kb4cr4YF0uzE39dZwsDBWQOsthZ9F0J/hxZgJHuSwEF7hZx0kvRE+HjPCu64iZ3iFH4JS8gjxrpbkEK8SpGWoeWpJTvFKIS3jD34q3EtLcoxXuPzVRnNuWpJzvMblKZ0w1ZIawDWuHwEwDT7kfKxx2cOTy1zjY5mMusZ9frsxEELWnmRG9SbkZMyE7LqKnJA1zmYkk3qiqyU5IWucbURMCoquluSErHG20b7F5qYlOSFrnG0owVNLekLG2YZm8ioLaUlNyBB43oYD0+hDjsgQCBzK+zPy7dcWEBKyhGERMgQ4nzlEiZ6IrMaMyK6vyBEZApzPSCbVOldMckaGACccEZNynSsmOSRDgDOOmEm9zhWTnJIhwCmHYlKvc8Ukx2QIcM7RojM7Mek5GScdAEwjED0nAy7Mh/05OR77eumBkTLgrMa3vP0KKesxk7LrK3pSBpzWSCZVO1dMelIGnHZEgqeY9KQMOO2ImdTtXDHpSRlw2qGYlO1cMelJGXDacS6MslOTHJUB5x3n6cpNTXpUbmvc3RDUH5WTUYOyHhgoC5zW+NaQXwHl4yNnvKSsh0bKAqc1kknlzhWTnpQFTjsiJpU7V0x6UhY47YiZVO5cMelJWeC0Q33wU/ZeYtKTsvB80hZ4QtyP7XKNrZWtf/rpV4lw9CQuPHkNMA1xAyBx/ArA84HtNRJX414AHamBsbjEedNhvy+Lw5hZ3HUWPYtLT6cPJrVBV0x6Fm/5tcviTGqDrpj0LC5xYtM+27iJSc/i0tPvg0ll0BWTnsUlTmyOC1BRF4nSvOZ2u8Ejnx5uG+HoWoEgb5GzuPQ1A/F8knXdWxu7mv8q/qKnfYnfYnhaTFxt6Xf7Wi4h7MfJ0GAfp2a+hffXYF+MGfZdZ9HDvqcDY+vAoVOAKyY97Ht6JEZMypuumPSw7+mSmDBZGe+KSQ/7nj6Jiknx0RWTHvY9nRKPXzC5sWxl9tXdQX9AKEIP+p5GjOD7sMzvqZtD/oB8dUPIr3ffO4ofxzpt2eXT/w==</diagram></mxfile>
\ No newline at end of file diff --git a/midi_controller_launchpad_mini.py b/midi_controller_launchpad_mini.py new file mode 100644 index 0000000..16c819c --- /dev/null +++ b/midi_controller_launchpad_mini.py @@ -0,0 +1,40 @@ +from midi_wrapper_mido import MidiWrapper + +class MidiController: + DEVICE_NAME = "Launchpad Mini MIDI 1" + + def __init__(self, soloTool, midiWrapperOverride=None): + self._soloTool = soloTool + if midiWrapperOverride is not None: + self._midiWrapper = midiWrapperOverride + else: + self._midiWrapper = MidiWrapper() + + self._handlers = { + 112 : self._playPause, + 96 : self._stop, + 101 : self._jumpToA + } + + def connect(self): + self._midiWrapper.connect(MidiController.DEVICE_NAME) + self._midiWrapper.setCallback(self._callback) + + def _callback(self, msg): + if msg.velocity < 127: + return + + if msg.note in self._handlers: + handler = self._handlers[msg.note]() + + def _playPause(self): + if self._soloTool.isPlaying(): + self._soloTool.pause() + else: + self._soloTool.play() + + def _stop(self): + self._soloTool.stop() + + def _jumpToA(self): + self._soloTool.jumpToA() diff --git a/midi_launchpad_mini_unittest.py b/midi_launchpad_mini_unittest.py new file mode 100644 index 0000000..09f12df --- /dev/null +++ b/midi_launchpad_mini_unittest.py @@ -0,0 +1,107 @@ +from midi_controller_launchpad_mini import MidiController + +class MidiWrapperMock: + def __init__(self): + self.callback = None + self.connectedDevice = None + self.lastMessageSent = None + + def setCallback(self, callback): + self.callback = callback + + def connect(self, deviceName): + self.connectedDevice = deviceName + + def sendMessage(self, note, velocity=127, channel=0): + pass + + def simulateInput(self, note, velocity=127, channel=0): + if self.callback is not None: + from mido import Message + msg = Message("note_on", note=note, velocity=velocity, channel=channel) + self.callback(msg) + +class SoloToolMock: + STOPPED = 0 + PLAYING = 1 + PAUSED = 2 + + def __init__(self): + self.state = SoloToolMock.STOPPED + self.position = 0.0 + self.currentAbLimit = (0.0, 0.0) + + def play(self): + self.state = SoloToolMock.PLAYING + + def pause(self): + self.state = SoloToolMock.PAUSED + + def stop(self): + self.state = SoloToolMock.STOPPED + + def isPlaying(self): + return self.state == SoloToolMock.PLAYING + + def jumpToA(self): + self.position = self.currentAbLimit[0] + +def test_connect(): + expectedDevice = "Launchpad Mini MIDI 1" + + soloToolMock = SoloToolMock() + midiWrapperMock = MidiWrapperMock() + uut = MidiController(soloToolMock, midiWrapperMock) + uut.connect() + + assert midiWrapperMock.connectedDevice == expectedDevice + +def test_startStopAndPauseButtons(): + playPauseButton = 112 + stopButton = 96 + + soloToolMock = SoloToolMock() + midiWrapperMock = MidiWrapperMock() + uut = MidiController(soloToolMock, midiWrapperMock) + uut.connect() + + assert soloToolMock.state == SoloToolMock.STOPPED + + midiWrapperMock.simulateInput(playPauseButton) + assert soloToolMock.state == SoloToolMock.PLAYING + + midiWrapperMock.simulateInput(stopButton) + assert soloToolMock.state == SoloToolMock.STOPPED + + midiWrapperMock.simulateInput(playPauseButton) + midiWrapperMock.simulateInput(playPauseButton) + assert soloToolMock.state == SoloToolMock.PAUSED + + midiWrapperMock.simulateInput(playPauseButton) + assert soloToolMock.state == SoloToolMock.PLAYING + + midiWrapperMock.simulateInput(playPauseButton) + midiWrapperMock.simulateInput(stopButton) + assert soloToolMock.state == SoloToolMock.STOPPED + +def test_jumpToAButton(): + ab = (0.5, 0.6) + soloToolMock = SoloToolMock() + midiWrapperMock = MidiWrapperMock() + uut = MidiController(soloToolMock, midiWrapperMock) + uut.connect() + + soloToolMock.currentAbLimit = (ab[0], ab[1]) + assert soloToolMock.position == 0.0 + + midiWrapperMock.simulateInput(101) + assert soloToolMock.position == ab[0] + +def test_unprogrammedButton(): + unusedButton = 48 + midiWrapperMock = MidiWrapperMock() + uut = MidiController(None, midiWrapperMock) + uut.connect() + + # expect no crash + midiWrapperMock.simulateInput(48) diff --git a/midi_wrapper_mido.py b/midi_wrapper_mido.py new file mode 100644 index 0000000..ee0d6d5 --- /dev/null +++ b/midi_wrapper_mido.py @@ -0,0 +1,21 @@ +import mido + +class MidiWrapper: + def __init__(self): + self._inPort = None + self._outPort = None + self._callback = None + + def setCallback(self, callback): + self._callback = callback + + def connect(self, deviceName): + self._inPort = mido.open_input(deviceName) + self._inPort.callback = self._callback + self._outPort = mido.open_output(deviceName) + + def sendMessage(note, velocity=127, channel=0): + if self._outPort is not None: + msg = mido.Message('note_on', channel=channel, velocity=velocity, note=note) + self.outPort.send(msg) + diff --git a/player_mock.py b/player_mock.py index 2fcb725..5aad769 100644 --- a/player_mock.py +++ b/player_mock.py @@ -19,6 +19,9 @@ class Player(): def pause(self): self.state = Player.PAUSED + def isPlaying(self): + return self.state == Player.PLAYING + def setPlaybackRate(self, rate): self.rate = rate diff --git a/player_vlc.py b/player_vlc.py index 1418dd0..c0eadf9 100644 --- a/player_vlc.py +++ b/player_vlc.py @@ -13,6 +13,10 @@ class Player: def pause(self): self._player.pause() + def isPlaying(self): + playing = self._player.is_playing() == 1 + return playing + def setPlaybackRate(self, rate): self._player.set_rate(rate) diff --git a/session_manager.py b/session_manager.py index 640188d..718e864 100644 --- a/session_manager.py +++ b/session_manager.py @@ -34,7 +34,7 @@ class SessionManager: for s in songs: entry = { "path": s, - "ab_limits" : self._abController.getLimits(s) + "ab_limits" : self._abController.getStoredLimits(s) } session.append(entry) diff --git a/session_manager_unittest.py b/session_manager_unittest.py index 30283a1..169aa17 100644 --- a/session_manager_unittest.py +++ b/session_manager_unittest.py @@ -45,7 +45,7 @@ class ABControllerMock: self.limits[song] = list() self.limits[song].append([aLimit, bLimit]) - def getLimits(self, song): + def getStoredLimits(self, song): return self.limits.get(song) def clear(self): @@ -143,7 +143,7 @@ def test_loadAndSaveEmptySession(): songs = playlistMock.getSongs() assert songs == list() for s in songs: - assert abControllerMock.getLimits(s) == None + assert abControllerMock.getStoredLimits(s) == None def test_loadSessionNotAdditive(): playlistMock = PlaylistMock() @@ -157,7 +157,7 @@ def test_loadSessionNotAdditive(): songs = playlistMock.getSongs() assert len(songs) == len(set(songs)) for s in songs: - abLimits = abControllerMock.getLimits(s) + abLimits = abControllerMock.getStoredLimits(s) if abLimits is not None: abLimitStr = [f"[{l[0]}, {l[1]}] " for l in abLimits] assert len(abLimitStr) == len(set(abLimitStr)) diff --git a/solo_tool.py b/solo_tool.py index db28c45..c32d3e8 100644 --- a/solo_tool.py +++ b/solo_tool.py @@ -42,16 +42,21 @@ class SoloTool: def setAbLimits(self, aLimit, bLimit): self._abController.setLimits(aLimit, bLimit) - def getAbLimits(self): + def getStoredAbLimits(self): currentSong = self._playlist.getCurrentSong() if currentSong is not None: - return self._abController.getLimits(currentSong) + return self._abController.getStoredLimits(currentSong) else: return list() def setAbLimitEnable(self, enable): self._abController.setEnable(enable) + def jumpToA(self): + a = self._abController.getCurrentLimits()[0] + # XXX assumes that player.setPlaybackPosition is thread-safe! + self._player.setPlaybackPosition(a) + def loadSession(self, path): with open(path, "r") as f: self._sessionManager.loadSession(f) @@ -69,6 +74,9 @@ class SoloTool: def stop(self): self._player.stop() + def isPlaying(self): + return self._player.isPlaying() + def setPlaybackRate(self, rate): self._player.setPlaybackRate(rate) diff --git a/solo_tool_integrationtest.py b/solo_tool_integrationtest.py index a927bfb..af0a921 100644 --- a/solo_tool_integrationtest.py +++ b/solo_tool_integrationtest.py @@ -8,12 +8,16 @@ def test_playerControls(): uut = SoloTool(mockPlayer) assert mockPlayer.state == MockPlayer.STOPPED + assert uut.isPlaying() == False uut.play() assert mockPlayer.state == MockPlayer.PLAYING + assert uut.isPlaying() == True uut.pause() assert mockPlayer.state == MockPlayer.PAUSED + assert uut.isPlaying() == False uut.stop() assert mockPlayer.state == MockPlayer.STOPPED + assert uut.isPlaying() == False assert mockPlayer.rate == 1.0 uut.setPlaybackRate(0.5) @@ -221,7 +225,7 @@ def test_getters(): assert uut.getSongs() == [song] - limits = uut.getAbLimits() + limits = uut.getStoredAbLimits() assert len(limits) == 1 assert limits[0][0] == abLimit[0] assert limits[0][1] == abLimit[1] @@ -250,3 +254,19 @@ def test_setTemporaryLimits(): uut.tick() assert mockPlayer.position == abLimits[1][0] +def test_jumpToA(): + song = "test.flac" + abLimits = (0.2, 0.4) + initialPosition = 0.8 + mockPlayer = MockPlayer() + uut = SoloTool(mockPlayer) + + mockPlayer.position = initialPosition + + uut.jumpToA() + assert mockPlayer.position == 0.0 # default AB controller A limit + + uut.setAbLimits(abLimits[0], abLimits[1]) + uut.jumpToA() + assert mockPlayer.position == abLimits[0] + |