From cda8197669409689be291660f93cb288ab2d31b3 Mon Sep 17 00:00:00 2001 From: Eddy Pedroni Date: Sat, 9 Nov 2024 20:35:56 +0100 Subject: Migrate to project-based structure --- .../test/midi_launchpad_mini_integrationtest.py | 387 +++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 solo-tool-project/test/midi_launchpad_mini_integrationtest.py (limited to 'solo-tool-project/test/midi_launchpad_mini_integrationtest.py') diff --git a/solo-tool-project/test/midi_launchpad_mini_integrationtest.py b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py new file mode 100644 index 0000000..8542aae --- /dev/null +++ b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py @@ -0,0 +1,387 @@ +import pytest + +from solo_tool.midi_controller_launchpad_mini import MidiController +from solo_tool.solo_tool import SoloTool +from player_mock import Player as PlayerMock + +LED_RED = 3 +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 + self.connectedDevice = None + self.sentMessages = list() + + def setCallback(self, callback): + self.callback = callback + + def connect(self, deviceName): + self.connectedDevice = deviceName + + def sendMessage(self, note, velocity, channel): + self.sentMessages.append((note, velocity, channel)) + + 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) + + def getLatestMessage(self): + return self.sentMessages[-1] + +@pytest.fixture +def playerMock(): + return PlayerMock() + +@pytest.fixture +def soloTool(playerMock): + return SoloTool(playerMock) + +@pytest.fixture +def midiWrapperMock(): + return MidiWrapperMock() + +@pytest.fixture +def uut(soloTool, midiWrapperMock): + return MidiController(soloTool, midiWrapperMock) + +def test_connect(uut, midiWrapperMock): + expectedDevice = "Launchpad Mini MIDI 1" + uut.connect() + + assert midiWrapperMock.connectedDevice == expectedDevice + +def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock): + uut.connect() + + assert playerMock.state == PlayerMock.STOPPED + + midiWrapperMock.simulateInput(playPauseButton) + assert playerMock.state == PlayerMock.PLAYING + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0) + + midiWrapperMock.simulateInput(stopButton) + assert playerMock.state == PlayerMock.STOPPED + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0) + + midiWrapperMock.simulateInput(playPauseButton) + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0) + + midiWrapperMock.simulateInput(playPauseButton) + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0) + assert playerMock.state == PlayerMock.PAUSED + + midiWrapperMock.simulateInput(playPauseButton) + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0) + assert playerMock.state == PlayerMock.PLAYING + + midiWrapperMock.simulateInput(playPauseButton) + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0) + + midiWrapperMock.simulateInput(stopButton) + assert playerMock.state == PlayerMock.STOPPED + +def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool): + uut.connect() + + assert playerMock.state == PlayerMock.STOPPED + + playerMock.state = PlayerMock.PLAYING + playerMock.simulatePlayingStateChanged() + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0) + + playerMock.state = PlayerMock.STOPPED + playerMock.simulatePlayingStateChanged() + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0) + + playerMock.state = PlayerMock.PAUSED + playerMock.simulatePlayingStateChanged() + assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0) + + playerMock.state = PlayerMock.PLAYING + 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() + + soloTool.setAbLimits(ab[0], ab[1]) + assert playerMock.position == 0.0 + + midiWrapperMock.simulateInput(jumpToAButton) + assert playerMock.position == ab[0] + +def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock): + songs = [ + "test.flac", + "test.mp3" + ] + for s in songs: + soloTool.addSong(s) + uut.connect() + + assert playerMock.currentSong == None + midiWrapperMock.simulateInput(nextSongButton) + assert playerMock.currentSong == songs[0] + + midiWrapperMock.simulateInput(nextSongButton) + assert playerMock.currentSong == songs[1] + + midiWrapperMock.simulateInput(previousSongButton) + assert playerMock.currentSong == songs[0] + + midiWrapperMock.simulateInput(previousSongButton) + assert playerMock.currentSong == songs[0] + +def test_previousAndNextAbButtons(uut, midiWrapperMock, soloTool, playerMock): + song = "test.flac" + abLimits = [ + [0.2, 0.4], + [0.1, 0.3] + ] + + soloTool.addSong(song) + soloTool.setSong(0) + soloTool.setAbLimitEnable(True) + + for ab in abLimits: + soloTool.storeAbLimits(ab[0], ab[1]) + + uut.connect() + + def checkLimit(aLimit, bLimit): + playerMock.position = bLimit - 0.1 + soloTool.tick() + assert playerMock.position == bLimit - 0.1 + + playerMock.position = bLimit + 0.1 + soloTool.tick() + assert playerMock.position == aLimit + + checkLimit(0.0, 0.0) + + midiWrapperMock.simulateInput(nextLimitButton) + checkLimit(abLimits[0][0], abLimits[0][1]) + + midiWrapperMock.simulateInput(nextLimitButton) + checkLimit(abLimits[1][0], abLimits[1][1]) + + midiWrapperMock.simulateInput(nextLimitButton) + checkLimit(abLimits[1][0], abLimits[1][1]) + + midiWrapperMock.simulateInput(previousLimitButton) + checkLimit(abLimits[0][0], abLimits[0][1]) + + midiWrapperMock.simulateInput(previousLimitButton) + checkLimit(abLimits[0][0], abLimits[0][1]) + +def test_playbackRateButtons(uut, midiWrapperMock, soloTool, playerMock): + playbackRateOptions = { + 16 : (0.5, [LED_YELLOW] * 1 + [LED_OFF] * 7), + 17 : (0.6, [LED_YELLOW] * 2 + [LED_OFF] * 6), + 18 : (0.7, [LED_YELLOW] * 3 + [LED_OFF] * 5), + 19 : (0.8, [LED_YELLOW] * 4 + [LED_OFF] * 4), + 20 : (0.9, [LED_YELLOW] * 5 + [LED_OFF] * 3), + 21 : (1.0, [LED_YELLOW] * 6 + [LED_OFF] * 2), + 22 : (1.1, [LED_YELLOW] * 7 + [LED_OFF] * 1), + 23 : (1.2, [LED_YELLOW] * 8) + } + uut.connect() + assert playerMock.rate == 1.0 + + for t, button in enumerate(playbackRateOptions): + midiWrapperMock.sentMessages.clear() + + midiWrapperMock.simulateInput(button) + assert playerMock.rate == playbackRateOptions[button][0] + + 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.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() + + midiWrapperMock.simulateInput(button) + assert playerMock.volume == playbackVolumeOptions[button][0] + + 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() + + # expect no crash + midiWrapperMock.simulateInput(unassignedButton) + # XXX would be better to assert that nothing changed in the solo tool + +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, 6)] + # volume row + [(i, LED_YELLOW, 0) for i in range(16, 22)] + # playback rate row + [ + (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) + ] + ) + + uut.connect() + + sentMessagesSet = set(midiWrapperMock.sentMessages) + assert sentMessagesSet == expectedMessages + +def test_playingFeedbackWhenChangingSong(uut, midiWrapperMock, soloTool, playerMock): + songs = [ + "test.flac", + "test.mp3" + ] + for s in songs: + soloTool.addSong(s) + uut.connect() + + soloTool.setSong(0) + soloTool.play() + assert playerMock.state == PlayerMock.PLAYING + assert midiWrapperMock.getLatestMessage() == (playPauseButton, LED_GREEN, 0) + + soloTool.nextSong() + assert playerMock.state == PlayerMock.STOPPED + assert midiWrapperMock.getLatestMessage() == (playPauseButton, LED_YELLOW, 0) + -- cgit v1.2.3