From 59b13af09a5e35ea1364eb1031be4ce9410f6f03 Mon Sep 17 00:00:00 2001 From: Eddy Pedroni Date: Wed, 22 Dec 2021 17:06:53 +0100 Subject: Added MVP Qt implementation, known issues tracking, removed old files --- MainWindow.py | 8 +-- ablist.py | 22 -------- control.py | 52 ------------------ known-issues | 4 ++ mainwindow.ui | 2 +- playlist.py | 3 -- solo_tool.py | 18 ++++++- solo_tool_integrationtest.py | 18 +++++-- solo_tool_qt.py | 122 +++++++++++++++++++++++++++++++++++++++++-- 9 files changed, 158 insertions(+), 91 deletions(-) delete mode 100644 ablist.py delete mode 100644 control.py create mode 100644 known-issues diff --git a/MainWindow.py b/MainWindow.py index 531d905..5cec554 100644 --- a/MainWindow.py +++ b/MainWindow.py @@ -60,9 +60,9 @@ class Ui_MainWindow(object): self.loadSessionButton = QtWidgets.QPushButton(self.centralwidget) self.loadSessionButton.setObjectName("loadSessionButton") self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.loadSessionButton) - self.abRepeatButton = QtWidgets.QCheckBox(self.centralwidget) - self.abRepeatButton.setObjectName("abRepeatButton") - self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.abRepeatButton) + self.abRepeatCheckBox = QtWidgets.QCheckBox(self.centralwidget) + self.abRepeatCheckBox.setObjectName("abRepeatCheckBox") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.abRepeatCheckBox) self.initMidiButton = QtWidgets.QPushButton(self.centralwidget) self.initMidiButton.setObjectName("initMidiButton") self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.initMidiButton) @@ -85,6 +85,6 @@ class Ui_MainWindow(object): self.saveAbButton.setText(_translate("MainWindow", "Save AB")) self.saveSessionButton.setText(_translate("MainWindow", "Save session")) self.loadSessionButton.setText(_translate("MainWindow", "Load session")) - self.abRepeatButton.setText(_translate("MainWindow", "AB repeat")) + self.abRepeatCheckBox.setText(_translate("MainWindow", "AB repeat")) self.initMidiButton.setText(_translate("MainWindow", "Connect MIDI")) self.addSongButton.setText(_translate("MainWindow", "Add song")) diff --git a/ablist.py b/ablist.py deleted file mode 100644 index f37905b..0000000 --- a/ablist.py +++ /dev/null @@ -1,22 +0,0 @@ - -def hhmmss(ms): - # s = 1000 - # m = 60000 - # h = 360000 - h, r = divmod(ms, 36000) - m, r = divmod(r, 60000) - s, _ = divmod(r, 1000) - return ("%d:%02d:%02d" % (h,m,s)) if h else ("%d:%02d" % (m,s)) - -class AbListModel(QAbstractListModel): - def __init__(self, *args, **kwargs): - super(AbListModel, self).__init__(*args, **kwargs) - self.abList = list() - - def data(self, index, role): - if role == Qt.DisplayRole: - ab = self.abList[index.row()] - return f"{hhmmss(ab[0])} - {hhmmss(ab[1])}" - - def rowCount(self, index): - return len(self.abList) diff --git a/control.py b/control.py deleted file mode 100644 index 30fe868..0000000 --- a/control.py +++ /dev/null @@ -1,52 +0,0 @@ -from PyQt5.QtCore import * - -import midi -import logging - -MIDI_PLAY = midi.lp_key[0][1] - -class Control: - def __init__(self, window): - self.window = window - self.player = window.player - self.playlist = window.playlist - - window.playButton.pressed.connect(self.play) - window.pauseButton.pressed.connect(self.pause) - window.songSlider.setMaximum(100000) - window.songSlider.sliderPressed.connect(self.sliderPressed) - window.songSlider.sliderReleased.connect(self.sliderReleased) - - self.timer = QTimer(window) - self.timer.setInterval(1000) - self.timer.timeout.connect(self.updateSongSlider) - - def pause(self): - self.player.pause() - - self.updateMidi() - - def play(self): - selected = self.playlist.medialist.media() - self.player.play_item(selected) - self.timer.start() - - self.updateMidi() - - def updateSongSlider(self): - position = self.player.get_media_player().get_position() * 100000 - self.window.songSlider.setValue(position) - - def sliderPressed(self): - self.timer.stop() - - def sliderReleased(self): - position = self.window.songSlider.value() / 100000.0 - logging.info(f"Setting position: {position}") - self.player.get_media_player().set_position(position) - self.timer.start() - - def updateMidi(self): - if self.window.midiEnabled: - midi.button_on(MIDI_PLAY, (midi.GREEN if self.player.is_playing() else midi.RED)) - diff --git a/known-issues b/known-issues new file mode 100644 index 0000000..35ebdff --- /dev/null +++ b/known-issues @@ -0,0 +1,4 @@ +* Moving AB sliders does not set AB limit. Instead need to save AB limit and then select it to apply +* Loading session is additive, should clear the state first +* AB limits are displayed as p.u., should be timestamps +* Songs are displayed as full path, should be file name or ideally title from metadata diff --git a/mainwindow.ui b/mainwindow.ui index 429cbd1..771d66d 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -94,7 +94,7 @@ - + AB repeat diff --git a/playlist.py b/playlist.py index 5c52774..084fd21 100644 --- a/playlist.py +++ b/playlist.py @@ -1,4 +1,3 @@ -import logging class Playlist: def __init__(self, callback): @@ -8,13 +7,11 @@ class Playlist: def addSong(self, path): self._songList.append(path) - logging.debug(f"Added song: {path}") def setCurrentSong(self, index): if index >= 0 and index < len(self._songList): self._currentSong = index self._setSongCallback(self._songList[index]) - logging.debug(f"Selected song: {self._currentSong}") def getCurrentSong(self): index = self._currentSong diff --git a/solo_tool.py b/solo_tool.py index 538d558..565943c 100644 --- a/solo_tool.py +++ b/solo_tool.py @@ -30,11 +30,23 @@ class SoloTool: def setSong(self, index): self._playlist.setCurrentSong(index) + # XXX untested + def getSongs(self): + return self._playlist.getSongs() + def addAbLimit(self, aLimit, bLimit): self._abController.addLimits(aLimit, bLimit) def setAbLimit(self, index): - self._abController.setCurrentLimits(0) + self._abController.setCurrentLimits(index) + + # XXX untested + def getAbLimits(self): + currentSong = self._playlist.getCurrentSong() + if currentSong is not None: + return self._abController.getLimits(currentSong) + else: + return list() def setAbLimitEnable(self, enable): self._abController.setEnable(enable) @@ -62,6 +74,10 @@ class SoloTool: def setPlaybackPosition(self, position): self._player.setPlaybackPosition(position) + # XXX untested + def getPlaybackPosition(self): + return self._player.getPlaybackPosition() + def setPlaybackVolume(self, volume): self._player.setPlaybackVolume(volume) diff --git a/solo_tool_integrationtest.py b/solo_tool_integrationtest.py index 2ed2449..927bd2c 100644 --- a/solo_tool_integrationtest.py +++ b/solo_tool_integrationtest.py @@ -84,16 +84,20 @@ def test_addAndSetSongs(): uut.setSong(i) assert mockPlayer.currentSong == songs[i] -def test_addAndSetAbLimit(): +def test_addAndSetAbLimits(): song = "test.flac" - abLimit = [0.2, 0.4] + abLimits = [ + [0.2, 0.4], + [0.1, 0.3] + ] mockPlayer = MockPlayer() uut = SoloTool(mockPlayer) uut.addSong(song) uut.setSong(0) - uut.addAbLimit(abLimit[0], abLimit[1]) + uut.addAbLimit(abLimits[0][0], abLimits[0][1]) + uut.addAbLimit(abLimits[1][0], abLimits[1][1]) mockPlayer.position = 0.0 uut.tick() @@ -116,6 +120,14 @@ def test_addAndSetAbLimit(): uut.tick() assert mockPlayer.position == 0.2 + uut.setAbLimit(1) + uut.tick() + assert mockPlayer.position == 0.2 + + mockPlayer.position = 0.8 + uut.tick() + assert mockPlayer.position == 0.1 + def test_multipleSongsAndAbLimits(): songs = [ "test.flac", diff --git a/solo_tool_qt.py b/solo_tool_qt.py index 8697764..14c0fe1 100644 --- a/solo_tool_qt.py +++ b/solo_tool_qt.py @@ -3,21 +3,133 @@ from PyQt5.QtWidgets import * from PyQt5.QtCore import * from MainWindow import Ui_MainWindow -import logging -LOGLEVEL = logging.DEBUG +from solo_tool import SoloTool -import solo_tool +POSITION_FACTOR = 100000 +UI_REFRESH_PERIOD_MS = 500 + +class PlaylistModel(QAbstractListModel): + def __init__(self, soloTool, *args, **kwargs): + super(PlaylistModel, self).__init__(*args, **kwargs) + self.soloTool = soloTool + + def data(self, index, role): + if role == Qt.DisplayRole: + return self.soloTool.getSongs()[index.row()] + + def rowCount(self, index): + return len(self.soloTool.getSongs()) + +class ABListModel(QAbstractListModel): + def __init__(self, soloTool, *args, **kwargs): + super(ABListModel, self).__init__(*args, **kwargs) + self.soloTool = soloTool + + def data(self, index, role): + if role == Qt.DisplayRole: + ab = self.soloTool.getAbLimits()[index.row()] + return f"{ab[0]} - {ab[1]}" + + def rowCount(self, index): + return len(self.soloTool.getAbLimits()) class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) + + self.timer = QTimer(self) + self.timer.setInterval(UI_REFRESH_PERIOD_MS) + self.timer.timeout.connect(self.timerCallback) + + self.soloTool = SoloTool() + + self.playlistModel = PlaylistModel(self.soloTool) + self.songListView.setModel(self.playlistModel) + self.songListView.selectionModel().selectionChanged.connect(self.playlistSelectionChanged) + + self.abListModel = ABListModel(self.soloTool) + self.abListView.setModel(self.abListModel) + self.abListView.selectionModel().selectionChanged.connect(self.abListSelectionChanged) + + self.songSlider.setMaximum(POSITION_FACTOR) + self.songSlider.sliderPressed.connect(self.songSliderPressed) + self.songSlider.sliderReleased.connect(self.songSliderReleased) + + self.aSlider.setMaximum(POSITION_FACTOR) + self.bSlider.setMaximum(POSITION_FACTOR) + + self.playButton.pressed.connect(self.soloTool.play) + self.pauseButton.pressed.connect(self.soloTool.pause) + self.saveAbButton.pressed.connect(self.saveAbLimits) + self.saveSessionButton.pressed.connect(self.saveSession) + self.loadSessionButton.pressed.connect(self.loadSession) + self.addSongButton.pressed.connect(self.addSong) + #self.initMidiButton.pressed.connect() + self.abRepeatCheckBox.clicked.connect(self.toggleAbRepeat) + self.timer.start() self.show() -if __name__ == '__main__': - logging.basicConfig(level=LOGLEVEL) + def timerCallback(self): + position = self.soloTool.getPlaybackPosition() * POSITION_FACTOR + self.songSlider.setValue(int(position)) + self.soloTool.tick() + + def addSong(self): + path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "mp3 Audio (*.mp3);FLAC audio (*.flac);All files (*.*)") + if path: + self.soloTool.addSong(path) + self.playlistModel.layoutChanged.emit() + + def saveAbLimits(self): + a = self.aSlider.value() / float(POSITION_FACTOR) + b = self.bSlider.value() / float(POSITION_FACTOR) + self.soloTool.addAbLimit(a, b) + self.abListModel.layoutChanged.emit() + + def toggleAbRepeat(self): + enable = self.abRepeatCheckBox.isChecked() + self.soloTool.setAbLimitEnable(enable) + + def saveSession(self): + path, _ = QFileDialog.getSaveFileName(self, "Open file", "", "session file (*.json);All files (*.*)") + if path: + self.soloTool.saveSession(path) + + def loadSession(self): + path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "session file (*.json);All files (*.*)") + if path: + self.soloTool.loadSession(path) + self.playlistModel.layoutChanged.emit() + self.abListModel.layoutChanged.emit() + + def songSliderPressed(self): + self.timer.stop() + def songSliderReleased(self): + position = self.songSlider.value() / float(POSITION_FACTOR) + self.soloTool.setPlaybackPosition(position) + self.timer.start() + + def playlistSelectionChanged(self, i): + index = i.indexes()[0].row() + self.soloTool.setSong(index) + self.abListModel.layoutChanged.emit() + self.abListSelectionChanged(None) + + def abListSelectionChanged(self, i): + if i is not None: + index = i.indexes()[0].row() + ab = self.soloTool.getAbLimits()[index] + self.soloTool.setAbLimit(index) + else: + ab = (0, 0) + self.aSlider.setValue(int(ab[0] * POSITION_FACTOR)) + self.bSlider.setValue(int(ab[1] * POSITION_FACTOR)) + + +if __name__ == '__main__': app = QApplication([]) app.setApplicationName("Solo Tool") app.setStyle("Fusion") -- cgit v1.2.3