From 9d92121a67188995ced4aa2cc26d320475bb8c94 Mon Sep 17 00:00:00 2001
From: Eddy Pedroni <eddy@0xf7.com>
Date: Sun, 21 Nov 2021 18:03:11 +0100
Subject: Fixed some bugs, added some midi buttons

---
 MainWindow.py    |  21 +++++---
 ablist.py        |  22 +++++++++
 control.py       |  52 +++++++++++++++++++
 mainwindow.ui    |  23 ++++++---
 midi.py          |   7 ++-
 pacman.txt       |   6 +++
 playlist.py      |  57 +++++++++++++++++++++
 requirements.txt |   6 ++-
 solo-tool.py     | 148 ++++++++++++++++++++-----------------------------------
 9 files changed, 229 insertions(+), 113 deletions(-)
 create mode 100644 ablist.py
 create mode 100644 control.py
 create mode 100644 pacman.txt
 create mode 100644 playlist.py

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/>
diff --git a/midi.py b/midi.py
index 81b6703..7eca87e 100644
--- a/midi.py
+++ b/midi.py
@@ -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")
-- 
cgit v1.2.3