diff options
author | Eddy Pedroni <epedroni@pm.me> | 2024-11-10 12:23:21 +0100 |
---|---|---|
committer | Eddy Pedroni <epedroni@pm.me> | 2024-11-10 12:23:21 +0100 |
commit | b015f93a58f8c0d4bc36b504a55b88468640b141 (patch) | |
tree | f3f76e510d5a3fc892b5f9c3a874653cbc80c93c | |
parent | 85b0116f80515c71e0250e4910c721b9e4fabca1 (diff) |
MIDI controller interface seeking
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | doc/diagram.drawio | 18 | ||||
-rw-r--r-- | doc/known-issues.md | 6 | ||||
-rw-r--r-- | solo-tool-project/src/solo_tool/midi_controller_launchpad_mini.py | 23 | ||||
-rw-r--r-- | solo-tool-project/test/midi_launchpad_mini_integrationtest.py | 58 |
5 files changed, 84 insertions, 22 deletions
@@ -2,3 +2,4 @@ venv/ **/*.egg-info **/build +**/*.bkp diff --git a/doc/diagram.drawio b/doc/diagram.drawio index 51dd11f..f900292 100644 --- a/doc/diagram.drawio +++ b/doc/diagram.drawio @@ -1,6 +1,6 @@ -<mxfile host="Electron" modified="2024-11-09T22:49:49.440Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="Lsuc8pY-LfMLzD1PEqfs" version="22.1.2" type="device" pages="3"> +<mxfile host="Electron" modified="2024-11-10T11:22:07.508Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="iuxE9_e737GxBbJf21Ic" version="22.1.2" type="device" pages="3"> <diagram id="g-wcGVps3MkI6_XAwNEs" name="Core"> - <mxGraphModel dx="846" dy="395" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> + <mxGraphModel dx="1561" dy="946" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> <root> <mxCell id="0" /> <mxCell id="1" parent="0" /> @@ -369,7 +369,7 @@ </mxGraphModel> </diagram> <diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad"> - <mxGraphModel dx="846" dy="395" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> + <mxGraphModel dx="1561" dy="946" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0"> <root> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-0" /> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-1" parent="ZtjfeE3uwfRsFhnWfLYL-0" /> @@ -406,7 +406,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-86" value="33" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="130" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-87" value="49<br>-1 min" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-87" value="49<br>-25%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="130" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-88" value="65" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> @@ -430,7 +430,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-95" value="34" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="220" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-96" value="50<br>-30 s" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-96" value="50<br>-5%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="220" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-97" value="66" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> @@ -454,7 +454,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-104" value="35" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="310" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-105" value="51<br>-5 s" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-105" value="51<br>-1%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#f8cecc;strokeColor=#b85450;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="310" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-106" value="67" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> @@ -478,7 +478,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-113" value="36" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="400" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-114" value="52<br>+5 s" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-114" value="52<br>+1%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="400" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-115" value="68" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> @@ -502,7 +502,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-122" value="37" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="490" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-123" value="53<br>+30 s" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-123" value="53<br>+5%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="490" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-124" value="69" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> @@ -526,7 +526,7 @@ <mxCell id="ZtjfeE3uwfRsFhnWfLYL-131" value="38" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="580" y="255" width="80" height="80" as="geometry" /> </mxCell> - <mxCell id="ZtjfeE3uwfRsFhnWfLYL-132" value="54<br>+1 min" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> + <mxCell id="ZtjfeE3uwfRsFhnWfLYL-132" value="54<br>+25%" style="rounded=1;whiteSpace=wrap;html=1;container=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> <mxGeometry x="580" y="342" width="80" height="80" as="geometry" /> </mxCell> <mxCell id="ZtjfeE3uwfRsFhnWfLYL-133" value="70" style="rounded=1;whiteSpace=wrap;html=1;container=0;" parent="ZtjfeE3uwfRsFhnWfLYL-1" vertex="1"> diff --git a/doc/known-issues.md b/doc/known-issues.md index 2a43f53..f41bfc8 100644 --- a/doc/known-issues.md +++ b/doc/known-issues.md @@ -13,7 +13,7 @@ * sometimes crashes when selecting limits with MIDI controller * MIDI controller feature requests: - * skip ahead and behind by steps of a few seconds + * play head location indicator # Closed Issues @@ -34,10 +34,10 @@ * CLI feature requests: * close application without crashing - * wipe LED state when application closes * set A and B points at current play head position (during playback) -* GUI feature requests: +* MIDI controller feature requests: + * skip ahead and behind by steps of a few seconds * wipe LED state when application closes # Use Cases diff --git a/solo-tool-project/src/solo_tool/midi_controller_launchpad_mini.py b/solo-tool-project/src/solo_tool/midi_controller_launchpad_mini.py index ee77d21..fb6e385 100644 --- a/solo-tool-project/src/solo_tool/midi_controller_launchpad_mini.py +++ b/solo-tool-project/src/solo_tool/midi_controller_launchpad_mini.py @@ -44,7 +44,13 @@ class MidiController: 116 : self._setALimit, 117 : self._setBLimit, 48 : self._soloTool.previousSong, - 55 : self._soloTool.nextSong + 49 : self._createSeekHandler(-0.25), + 50 : self._createSeekHandler(-0.05), + 51 : self._createSeekHandler(-0.01), + 52 : self._createSeekHandler(0.01), + 53 : self._createSeekHandler(0.05), + 54 : self._createSeekHandler(0.25), + 55 : self._soloTool.nextSong, } for i in range(0, 8): @@ -75,7 +81,14 @@ class MidiController: self._soloTool.pause() else: self._soloTool.play() - + + def _createSeekHandler(self, delta): + def f(): + newPosition = self._soloTool.getPlaybackPosition() + delta + newPosition = min(1.0, max(0.0, newPosition)) + self._soloTool.setPlaybackPosition(newPosition) + return f + def _setALimit(self): self._aLimit = self._soloTool.getPlaybackPosition() self._soloTool.setAbLimits(self._aLimit, self._bLimit) @@ -159,5 +172,11 @@ class MidiController: # Song control self._setButtonLED(3, 0, MidiController.LED_RED) + self._setButtonLED(3, 1, MidiController.LED_RED) + self._setButtonLED(3, 2, MidiController.LED_RED) + self._setButtonLED(3, 3, MidiController.LED_RED) + self._setButtonLED(3, 4, MidiController.LED_GREEN) + self._setButtonLED(3, 5, MidiController.LED_GREEN) + self._setButtonLED(3, 6, MidiController.LED_GREEN) self._setButtonLED(3, 7, MidiController.LED_GREEN) diff --git a/solo-tool-project/test/midi_launchpad_mini_integrationtest.py b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py index e7c1315..8fd09bb 100644 --- a/solo-tool-project/test/midi_launchpad_mini_integrationtest.py +++ b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py @@ -1,4 +1,5 @@ import pytest +from mido import Message from solo_tool.midi_controller_launchpad_mini import MidiController from solo_tool.solo_tool import SoloTool @@ -10,12 +11,12 @@ LED_GREEN = 124 LED_OFF = 0 nextSongButton = 55 -plus1MinuteButton = 54 -plus30SecondsButton = 53 -plus5SecondsButton = 52 -minus5SecondsButton = 51 -minus30SecondsButton = 50 -minus1MinuteButton = 49 +fwd25PcButton = 54 +fwd5PcButton = 53 +fwd1PcButton = 52 +rwd1PcButton = 51 +rwd5PcButton = 50 +rwd25PcButton = 49 previousSongButton = 48 playPauseButton = 112 @@ -46,7 +47,6 @@ class MidiWrapperMock: 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) @@ -365,7 +365,13 @@ def test_connectDisconnect(uut, midiWrapperMock): (setAButton, LED_YELLOW, 0), (setBButton, LED_YELLOW, 0), (previousSongButton, LED_RED, 0), - (nextSongButton, LED_GREEN, 0) + (rwd1PcButton, LED_RED, 0), + (rwd5PcButton, LED_RED, 0), + (rwd25PcButton, LED_RED, 0), + (nextSongButton, LED_GREEN, 0), + (fwd1PcButton, LED_GREEN, 0), + (fwd5PcButton, LED_GREEN, 0), + (fwd25PcButton, LED_GREEN, 0), ]) teardownMessages = [(int(i / 8) * 16 + (i % 8), LED_OFF, 0) for i in range(0, 64)] # clear all @@ -436,3 +442,39 @@ def test_setAbButtons(uut, midiWrapperMock, soloTool, playerMock): midiWrapperMock.simulateInput(nextLimitButton) checkLimit(abLimits[0], abLimits[1]) +def test_seekButtons(uut, midiWrapperMock, soloTool, playerMock): + song = "test.flac" + soloTool.addSong(song) + soloTool.setSong(0) + + uut.connect() + + assert playerMock.position == 0.0 + + midiWrapperMock.simulateInput(fwd25PcButton) + assert playerMock.position == 0.25 + + midiWrapperMock.simulateInput(fwd5PcButton) + assert playerMock.position == 0.30 + + midiWrapperMock.simulateInput(fwd1PcButton) + assert playerMock.position == 0.31 + + midiWrapperMock.simulateInput(fwd25PcButton) + midiWrapperMock.simulateInput(fwd25PcButton) + midiWrapperMock.simulateInput(fwd25PcButton) + assert playerMock.position == 1.0 + + midiWrapperMock.simulateInput(rwd25PcButton) + assert playerMock.position == 0.75 + + midiWrapperMock.simulateInput(rwd5PcButton) + assert playerMock.position == 0.70 + + midiWrapperMock.simulateInput(rwd1PcButton) + assert playerMock.position == 0.69 + + midiWrapperMock.simulateInput(rwd25PcButton) + midiWrapperMock.simulateInput(rwd25PcButton) + midiWrapperMock.simulateInput(rwd25PcButton) + assert playerMock.position == 0.0 |