aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--abcontroller.py7
-rw-r--r--abcontroller_unittest.py17
-rw-r--r--diagram.drawio2
-rw-r--r--midi_controller_launchpad_mini.py40
-rw-r--r--midi_launchpad_mini_unittest.py107
-rw-r--r--midi_wrapper_mido.py21
-rw-r--r--player_mock.py3
-rw-r--r--player_vlc.py4
-rw-r--r--session_manager.py2
-rw-r--r--session_manager_unittest.py6
-rw-r--r--solo_tool.py12
-rw-r--r--solo_tool_integrationtest.py22
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]
+