diff options
-rw-r--r-- | MainWindow.py | 21 | ||||
-rw-r--r-- | ablist.py | 22 | ||||
-rw-r--r-- | control.py | 52 | ||||
-rw-r--r-- | mainwindow.ui | 23 | ||||
-rw-r--r-- | midi.py | 7 | ||||
-rw-r--r-- | pacman.txt | 6 | ||||
-rw-r--r-- | playlist.py | 57 | ||||
-rw-r--r-- | requirements.txt | 6 | ||||
-rw-r--r-- | solo-tool.py | 148 |
9 files changed, 229 insertions, 113 deletions
diff --git a/MainWindow.py b/MainWindow.py index 9194943..531d905 100644 --- a/MainWindow.py +++ b/MainWindow.py @@ -45,12 +45,15 @@ class Ui_MainWindow(object): self.horizontalLayout.addWidget(self.abListView) self.formLayout = QtWidgets.QFormLayout() self.formLayout.setObjectName("formLayout") - self.playPauseButton = QtWidgets.QPushButton(self.centralwidget) - self.playPauseButton.setObjectName("playPauseButton") - self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.playPauseButton) + self.playButton = QtWidgets.QPushButton(self.centralwidget) + self.playButton.setObjectName("playButton") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.playButton) + self.pauseButton = QtWidgets.QPushButton(self.centralwidget) + self.pauseButton.setObjectName("pauseButton") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.pauseButton) self.saveAbButton = QtWidgets.QPushButton(self.centralwidget) self.saveAbButton.setObjectName("saveAbButton") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.saveAbButton) + self.formLayout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.saveAbButton) self.saveSessionButton = QtWidgets.QPushButton(self.centralwidget) self.saveSessionButton.setObjectName("saveSessionButton") self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.saveSessionButton) @@ -63,12 +66,13 @@ class Ui_MainWindow(object): self.initMidiButton = QtWidgets.QPushButton(self.centralwidget) self.initMidiButton.setObjectName("initMidiButton") self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.initMidiButton) + self.addSongButton = QtWidgets.QPushButton(self.centralwidget) + self.addSongButton.setObjectName("addSongButton") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.addSongButton) self.horizontalLayout.addLayout(self.formLayout) self.verticalLayout.addLayout(self.horizontalLayout) self.centralWidgetLayout.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) - self.addSongAction = QtWidgets.QAction(MainWindow) - self.addSongAction.setObjectName("addSongAction") self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) @@ -76,10 +80,11 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.playPauseButton.setText(_translate("MainWindow", "Play/pause")) + self.playButton.setText(_translate("MainWindow", "Play")) + self.pauseButton.setText(_translate("MainWindow", "Pause")) 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.initMidiButton.setText(_translate("MainWindow", "Connect MIDI")) - self.addSongAction.setText(_translate("MainWindow", "Add song")) + self.addSongButton.setText(_translate("MainWindow", "Add song")) diff --git a/ablist.py b/ablist.py new file mode 100644 index 0000000..f37905b --- /dev/null +++ b/ablist.py @@ -0,0 +1,22 @@ + +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 new file mode 100644 index 0000000..30fe868 --- /dev/null +++ b/control.py @@ -0,0 +1,52 @@ +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/mainwindow.ui b/mainwindow.ui index b4f6afa..429cbd1 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -59,13 +59,20 @@ <item> <layout class="QFormLayout" name="formLayout"> <item row="0" column="1"> - <widget class="QPushButton" name="playPauseButton"> + <widget class="QPushButton" name="playButton"> <property name="text"> - <string>Play/pause</string> + <string>Play</string> </property> </widget> </item> <item row="1" column="1"> + <widget class="QPushButton" name="pauseButton"> + <property name="text"> + <string>Pause</string> + </property> + </widget> + </item> + <item row="9" column="1"> <widget class="QPushButton" name="saveAbButton"> <property name="text"> <string>Save AB</string> @@ -100,6 +107,13 @@ </property> </widget> </item> + <item row="6" column="1"> + <widget class="QPushButton" name="addSongButton"> + <property name="text"> + <string>Add song</string> + </property> + </widget> + </item> </layout> </item> </layout> @@ -108,11 +122,6 @@ </item> </layout> </widget> - <action name="addSongAction"> - <property name="text"> - <string>Add song</string> - </property> - </action> </widget> <resources/> <connections/> @@ -13,6 +13,8 @@ _outport = None _callbacks_on_press = dict() _callbacks_on_release = dict() +_debug = False + def midi_init(device_name="Launchpad Mini MIDI 1"): global _inport, _outport _inport = mido.open_input(device_name) @@ -21,10 +23,11 @@ def midi_init(device_name="Launchpad Mini MIDI 1"): _inport.callback = _callback def button_on(button, colour): - print(f"outport: {_outport}") + if _debug: print(f"outport: {_outport}") + if _outport: msg = _light_ctrl_msg.copy(note=button, velocity=colour) - print(f"sending {msg}") + if _debug: print(f"sending {msg}") _outport.send(msg) def button_off(button): diff --git a/pacman.txt b/pacman.txt new file mode 100644 index 0000000..cb8a962 --- /dev/null +++ b/pacman.txt @@ -0,0 +1,6 @@ +pkgconf +gst-plugins-good + +qt5-multimedia + +vlc diff --git a/playlist.py b/playlist.py new file mode 100644 index 0000000..12fd9ed --- /dev/null +++ b/playlist.py @@ -0,0 +1,57 @@ +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +import logging +import vlc + +class PlaylistModel(QAbstractListModel): + def __init__(self, medialist, *args, **kwargs): + super(PlaylistModel, self).__init__(*args, **kwargs) + self.medialist = medialist + + def data(self, index, role): + if role == Qt.DisplayRole: + return self.medialist.item_at_index(index.row()).get_mrl() + + def rowCount(self, index): + return self.medialist.count() + +class Playlist: + def __init__(self, window): + self.medialist = vlc.MediaList() + self.model = PlaylistModel(self.medialist) + self.window = window + + window.player.set_media_list(self.medialist) + window.songListView.setModel(self.model) + window.songListView.selectionModel().selectionChanged.connect(self.playlistSelectionChanged) + window.addSongButton.pressed.connect(self.addSong) + + def addSong(self): + path, _ = QFileDialog.getOpenFileName(self.window, "Open file", "", "mp3 Audio (*.mp3);FLAC audio (*.flac);All files (*.*)") + if path: + media = vlc.Media(path) + self.medialist.add_media(media) + self.model.layoutChanged.emit() + logging.debug(f"Added song: {path}") + + def playlistSelectionChanged(self, ix): + i = ix.indexes()[0].row() + self.setCurrent(i) + #path = self.playlist.current.filePath + #self.player.set_media(path) + """ + self.abListModel.abList = self.internalState[path] + self.abListView.selectionModel().clearSelection() + self.abListModel.layoutChanged.emit() + self.aSlider.setValue(0) + self.bSlider.setValue(0) + """ + + def setCurrent(self, index): + media = self.medialist.item_at_index(index) + if not media: + raise Exception(f"Invalid playlist index {index}") + self.medialist.set_media(media) + logging.debug(f"Selected song: {self.medialist.media().get_mrl()}") diff --git a/requirements.txt b/requirements.txt index 62999c0..3dc4a37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ -PyQt5>=5.6 sip mido +python-rtmidi + +PyQt5>=5.6 + +python-vlc diff --git a/solo-tool.py b/solo-tool.py index 26ddca2..3101a55 100644 --- a/solo-tool.py +++ b/solo-tool.py @@ -1,78 +1,30 @@ from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * -from PyQt5.QtMultimedia import * -from PyQt5.QtMultimediaWidgets import * - from MainWindow import Ui_MainWindow -import midi - -class PlaylistModel(QAbstractListModel): - def __init__(self, playlist, *args, **kwargs): - super(PlaylistModel, self).__init__(*args, **kwargs) - self.playlist = playlist - - def data(self, index, role): - if role == Qt.DisplayRole: - media = self.playlist.media(index.row()) - return media.canonicalUrl().fileName() +import vlc - def rowCount(self, index): - return self.playlist.mediaCount() +import logging +LOGLEVEL = logging.DEBUG -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) - -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)) +import midi +import playlist +import control class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) - self.player = QMediaPlayer() - - # Setup the playlist. - self.playlist = QMediaPlaylist() - self.player.setPlaylist(self.playlist) - - self.playlistModel = PlaylistModel(self.playlist) - self.songListView.setModel(self.playlistModel) - self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) - selection_model = self.songListView.selectionModel() - selection_model.selectionChanged.connect(self.playlistSelectionChanged) - - # set button context menu policy - self.songListView.customContextMenuRequested.connect(self.onContextMenu) - - # create context menu - self.songListMenu = QMenu(self) - self.addSongAction.triggered.connect(self.openSoundFile) - self.songListMenu.addAction(self.addSongAction) - - self.playPauseButton.pressed.connect(self.playPause) + self.midiEnabled = False + self.player = vlc.MediaListPlayer() + self.playlist = playlist.Playlist(self) + self.control = control.Control(self) + """ self.player.durationChanged.connect(self.updateDuration) self.player.positionChanged.connect(self.updatePosition) - self.songSlider.valueChanged.connect(self.player.setPosition) self.player.positionChanged.connect(self.positionChanged) @@ -86,21 +38,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.saveSessionButton.pressed.connect(self.saveSession) self.loadSessionButton.pressed.connect(self.loadSession) + self.abRepeatButton.clicked.connect(self.abRepeatToggleClick) + self.initMidiButton.pressed.connect(self.initMidi) - self.midiEnabled = False + """ self.show() - def playlistSelectionChanged(self, ix): - i = ix.indexes()[0].row() - self.playlist.setCurrentIndex(i) - path = self.playlist.currentMedia().canonicalUrl().path() - self.abListModel.abList = self.internalState[path] - self.abListView.selectionModel().clearSelection() - self.abListModel.layoutChanged.emit() - self.aSlider.setValue(0) - self.bSlider.setValue(0) - def playlistPositionChanged(self, i): if i > -1: ix = self.playlistModel.index(i) @@ -119,14 +63,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.abListModel.abList.append(abState) self.abListModel.layoutChanged.emit() - def playPause(self): - if self.player.state() == QMediaPlayer.PlayingState: - self.player.pause() - else: - self.player.play() def updateDuration(self, duration): - self.songSlider.setMaximum(duration) self.aSlider.setMaximum(duration) self.bSlider.setMaximum(duration) @@ -140,25 +78,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): if self.abRepeatButton.isChecked() and position > self.bSlider.value(): self.player.setPosition(self.aSlider.value()) - def onContextMenu(self, point): - self.songListMenu.exec_(self.songListView.mapToGlobal(point)) - - def openSoundFile(self): - path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "mp3 Audio (*.mp3);FLAC audio (*.flac);All files (*.*)") - if path: - self.addSong(path) - - def addSong(self, path): - self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path))) - self.internalState[path] = list() - self.playlistModel.layoutChanged.emit() - def loadSession(self): path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "session (*.json)") if path: import json with open(path, "r") as f: session = json.load(f) + self.reset() for song in session: self.addSong(song) @@ -174,20 +100,52 @@ class MainWindow(QMainWindow, Ui_MainWindow): with open(path, "w") as f: json.dump(self.internalState, f) + def abRepeatToggleClick(self): + midi.button_on(midi.lp_key[1][1], (midi.GREEN if self.abRepeatButton.isChecked() else midi.RED)) + def initMidi(self): - midi.midi_init() + if self.midiEnabled: + return + + try: + midi.midi_init() + except Exception as e: + print(e) + return + self.midiEnabled = True # play pause - midi.button_on(midi.lp_key[0][0], (midi.GREEN if self.player.state() == QMediaPlayer.PlayingState else midi.RED)) - def midiPlayPause(): - midi.button_on(midi.lp_key[0][0], (midi.RED if self.player.state() == QMediaPlayer.PlayingState else midi.GREEN)) - self.playPauseButton.click() + midi.button_on(midi.lp_key[0][1], (midi.GREEN if self.player.state() == QMediaPlayer.PlayingState else midi.RED)) + midi.on_press(midi.lp_key[0][1], self.playPauseButton.click) + + # next song + midi.button_on(midi.lp_key[0][2], midi.YELLOW) + midi.on_press(midi.lp_key[0][2], self.playlist.next) - midi.on_press(midi.lp_key[0][0], midiPlayPause) + # previous song + midi.button_on(midi.lp_key[0][0], midi.YELLOW) + midi.on_press(midi.lp_key[0][0], self.playlist.previous) + # a/b repeat + midi.button_on(midi.lp_key[1][1], (midi.GREEN if self.abRepeatButton.isChecked() else midi.RED)) + midi.on_press(midi.lp_key[1][1], self.abRepeatButton.click) + + # next ab + # TODO continue here + midi.button_on(midi.lp_key[1][2], midi.YELLOW) + #midi.on_press(midi.lp_key[1][2], nextAb) + + + def reset(self): + self.playlist.clear() + self.playlistModel.layoutChanged.emit() + self.abListModel.abList.clear() + self.abListModel.layoutChanged.emit() if __name__ == '__main__': + logging.basicConfig(level=LOGLEVEL) + app = QApplication([]) app.setApplicationName("Solo Tool") app.setStyle("Fusion") |