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)