aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEddy Pedroni <eddy@0xf7.com>2021-12-22 17:06:53 +0100
committerEddy Pedroni <eddy@0xf7.com>2021-12-22 17:10:08 +0100
commit59b13af09a5e35ea1364eb1031be4ce9410f6f03 (patch)
treeac2cd68dd28541d38fec91041c7c4175dbd6731e
parentb2772136b6d1813150bf7a2e0a0a98085db6af0b (diff)
Added MVP Qt implementation, known issues tracking, removed old files
-rw-r--r--MainWindow.py8
-rw-r--r--ablist.py22
-rw-r--r--control.py52
-rw-r--r--known-issues4
-rw-r--r--mainwindow.ui2
-rw-r--r--playlist.py3
-rw-r--r--solo_tool.py18
-rw-r--r--solo_tool_integrationtest.py18
-rw-r--r--solo_tool_qt.py122
9 files changed, 158 insertions, 91 deletions
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 @@
</widget>
</item>
<item row="2" column="1">
- <widget class="QCheckBox" name="abRepeatButton">
+ <widget class="QCheckBox" name="abRepeatCheckBox">
<property name="text">
<string>AB repeat</string>
</property>
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")