aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--gui-project/pyproject.toml24
-rw-r--r--gui-project/src/MainWindow.py111
-rw-r--r--gui-project/src/mainwindow.ui161
-rw-r--r--gui-project/src/solo_tool_gui.py265
-rw-r--r--pacman.txt6
-rw-r--r--readme.md8
-rw-r--r--requirements.txt1
8 files changed, 9 insertions, 569 deletions
diff --git a/Makefile b/Makefile
index 13e51b6..d9ee657 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ clean:
.venv: .venv/touchfile
-.venv/touchfile: requirements.txt solo-tool-project/pyproject.toml cli-project/pyproject.toml gui-project/pyproject.toml
+.venv/touchfile: requirements.txt solo-tool-project/pyproject.toml cli-project/pyproject.toml web-project/pyproject.toml
rm -rf venv
uv venv
uv pip install -r requirements.txt
diff --git a/gui-project/pyproject.toml b/gui-project/pyproject.toml
deleted file mode 100644
index 1e6fcf4..0000000
--- a/gui-project/pyproject.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-[build-system]
-requires = ["setuptools"]
-build-backend = "setuptools.build_meta"
-
-[project]
-name = "solo_tool_gui"
-authors = [
- { name = "Eddy Pedroni", email = "epedroni@pm.me" },
-]
-description = "A Qt5-based GUI frontend for the solo_tool library"
-requires-python = ">=3.12"
-dependencies = [
- "PyQt5>=5.6",
- "solo_tool"
-]
-dynamic = ["version"]
-
-[project.optional-dependencies]
-dev = [
-]
-
-[project.gui-scripts]
-solo-tool-gui = "solo_tool_gui:main"
-
diff --git a/gui-project/src/MainWindow.py b/gui-project/src/MainWindow.py
deleted file mode 100644
index 137bd33..0000000
--- a/gui-project/src/MainWindow.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'mainwindow.ui'
-#
-# Created by: PyQt5 UI code generator 5.15.6
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic5 is
-# run again. Do not edit this file unless you know what you are doing.
-
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class Ui_MainWindow(object):
- def setupUi(self, MainWindow):
- MainWindow.setObjectName("MainWindow")
- MainWindow.resize(971, 767)
- self.centralwidget = QtWidgets.QWidget(MainWindow)
- self.centralwidget.setObjectName("centralwidget")
- self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
- self.verticalLayout.setObjectName("verticalLayout")
- self.listsLayout = QtWidgets.QHBoxLayout()
- self.listsLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
- self.listsLayout.setSpacing(6)
- self.listsLayout.setObjectName("listsLayout")
- self.songListView = QtWidgets.QListView(self.centralwidget)
- self.songListView.setObjectName("songListView")
- self.listsLayout.addWidget(self.songListView)
- self.abListView = QtWidgets.QListView(self.centralwidget)
- self.abListView.setObjectName("abListView")
- self.listsLayout.addWidget(self.abListView)
- self.verticalLayout.addLayout(self.listsLayout)
- self.slidersLayout = QtWidgets.QVBoxLayout()
- self.slidersLayout.setObjectName("slidersLayout")
- self.songSlider = QtWidgets.QSlider(self.centralwidget)
- self.songSlider.setMinimumSize(QtCore.QSize(0, 0))
- self.songSlider.setOrientation(QtCore.Qt.Horizontal)
- self.songSlider.setObjectName("songSlider")
- self.slidersLayout.addWidget(self.songSlider)
- self.aSlider = QtWidgets.QSlider(self.centralwidget)
- self.aSlider.setOrientation(QtCore.Qt.Horizontal)
- self.aSlider.setObjectName("aSlider")
- self.slidersLayout.addWidget(self.aSlider)
- self.bSlider = QtWidgets.QSlider(self.centralwidget)
- self.bSlider.setOrientation(QtCore.Qt.Horizontal)
- self.bSlider.setObjectName("bSlider")
- self.slidersLayout.addWidget(self.bSlider)
- self.verticalLayout.addLayout(self.slidersLayout)
- self.buttonsLayout = QtWidgets.QGridLayout()
- self.buttonsLayout.setObjectName("buttonsLayout")
- self.pauseButton = QtWidgets.QPushButton(self.centralwidget)
- self.pauseButton.setObjectName("pauseButton")
- self.buttonsLayout.addWidget(self.pauseButton, 0, 1, 1, 1)
- self.initMidiButton = QtWidgets.QPushButton(self.centralwidget)
- self.initMidiButton.setObjectName("initMidiButton")
- self.buttonsLayout.addWidget(self.initMidiButton, 0, 4, 1, 1)
- self.rateSlider = QtWidgets.QSlider(self.centralwidget)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.rateSlider.sizePolicy().hasHeightForWidth())
- self.rateSlider.setSizePolicy(sizePolicy)
- self.rateSlider.setOrientation(QtCore.Qt.Horizontal)
- self.rateSlider.setObjectName("rateSlider")
- self.buttonsLayout.addWidget(self.rateSlider, 2, 0, 1, 1)
- self.playButton = QtWidgets.QPushButton(self.centralwidget)
- self.playButton.setObjectName("playButton")
- self.buttonsLayout.addWidget(self.playButton, 0, 0, 1, 1)
- self.saveSessionButton = QtWidgets.QPushButton(self.centralwidget)
- self.saveSessionButton.setObjectName("saveSessionButton")
- self.buttonsLayout.addWidget(self.saveSessionButton, 0, 2, 1, 1)
- self.storeAbButton = QtWidgets.QPushButton(self.centralwidget)
- self.storeAbButton.setObjectName("storeAbButton")
- self.buttonsLayout.addWidget(self.storeAbButton, 2, 3, 1, 1)
- self.loadSessionButton = QtWidgets.QPushButton(self.centralwidget)
- self.loadSessionButton.setObjectName("loadSessionButton")
- self.buttonsLayout.addWidget(self.loadSessionButton, 0, 3, 1, 1)
- self.abRepeatCheckBox = QtWidgets.QCheckBox(self.centralwidget)
- self.abRepeatCheckBox.setObjectName("abRepeatCheckBox")
- self.buttonsLayout.addWidget(self.abRepeatCheckBox, 2, 1, 1, 1)
- self.addSongButton = QtWidgets.QPushButton(self.centralwidget)
- self.addSongButton.setObjectName("addSongButton")
- self.buttonsLayout.addWidget(self.addSongButton, 2, 4, 1, 1)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.setAButton = QtWidgets.QPushButton(self.centralwidget)
- self.setAButton.setObjectName("setAButton")
- self.horizontalLayout.addWidget(self.setAButton)
- self.setBButton = QtWidgets.QPushButton(self.centralwidget)
- self.setBButton.setObjectName("setBButton")
- self.horizontalLayout.addWidget(self.setBButton)
- self.buttonsLayout.addLayout(self.horizontalLayout, 2, 2, 1, 1)
- self.verticalLayout.addLayout(self.buttonsLayout)
- MainWindow.setCentralWidget(self.centralwidget)
-
- self.retranslateUi(MainWindow)
- QtCore.QMetaObject.connectSlotsByName(MainWindow)
-
- def retranslateUi(self, MainWindow):
- _translate = QtCore.QCoreApplication.translate
- MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
- self.pauseButton.setText(_translate("MainWindow", "Pause"))
- self.initMidiButton.setText(_translate("MainWindow", "Connect MIDI"))
- self.playButton.setText(_translate("MainWindow", "Play"))
- self.saveSessionButton.setText(_translate("MainWindow", "Save session"))
- self.storeAbButton.setText(_translate("MainWindow", "Store AB"))
- self.loadSessionButton.setText(_translate("MainWindow", "Load session"))
- self.abRepeatCheckBox.setText(_translate("MainWindow", "AB repeat"))
- self.addSongButton.setText(_translate("MainWindow", "Add song"))
- self.setAButton.setText(_translate("MainWindow", "Set A"))
- self.setBButton.setText(_translate("MainWindow", "Set B"))
diff --git a/gui-project/src/mainwindow.ui b/gui-project/src/mainwindow.ui
deleted file mode 100644
index ac4d97b..0000000
--- a/gui-project/src/mainwindow.ui
+++ /dev/null
@@ -1,161 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>MainWindow</class>
- <widget class="QMainWindow" name="MainWindow">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>971</width>
- <height>767</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>MainWindow</string>
- </property>
- <widget class="QWidget" name="centralwidget">
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <layout class="QHBoxLayout" name="listsLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <widget class="QListView" name="songListView"/>
- </item>
- <item>
- <widget class="QListView" name="abListView"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QVBoxLayout" name="slidersLayout">
- <item>
- <widget class="QSlider" name="songSlider">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSlider" name="aSlider">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSlider" name="bSlider">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QGridLayout" name="buttonsLayout">
- <item row="0" column="1">
- <widget class="QPushButton" name="pauseButton">
- <property name="text">
- <string>Pause</string>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <widget class="QPushButton" name="initMidiButton">
- <property name="text">
- <string>Connect MIDI</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QSlider" name="rateSlider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QPushButton" name="playButton">
- <property name="text">
- <string>Play</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QPushButton" name="saveSessionButton">
- <property name="text">
- <string>Save session</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="storeAbButton">
- <property name="text">
- <string>Store AB</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QPushButton" name="loadSessionButton">
- <property name="text">
- <string>Load session</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="abRepeatCheckBox">
- <property name="text">
- <string>AB repeat</string>
- </property>
- </widget>
- </item>
- <item row="2" column="4">
- <widget class="QPushButton" name="addSongButton">
- <property name="text">
- <string>Add song</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QPushButton" name="setAButton">
- <property name="text">
- <string>Set A</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="setBButton">
- <property name="text">
- <string>Set B</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/gui-project/src/solo_tool_gui.py b/gui-project/src/solo_tool_gui.py
deleted file mode 100644
index cd73c9a..0000000
--- a/gui-project/src/solo_tool_gui.py
+++ /dev/null
@@ -1,265 +0,0 @@
-import sys
-
-from PyQt5.QtGui import *
-from PyQt5.QtWidgets import *
-from PyQt5.QtCore import *
-from MainWindow import Ui_MainWindow
-
-from solo_tool import SoloTool
-from solo_tool.midi_controller_launchpad_mini import MidiController
-
-POSITION_FACTOR = 100000
-RATE_FACTOR = 10
-UI_REFRESH_PERIOD_MS = 500
-
-CHANGE_GUI = 0
-CHANGE_INTERNAL = 1
-
-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:
- from pathlib import Path
- path = Path(self.soloTool.getSongs()[index.row()])
- return path.name
-
- 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.getStoredAbLimits()[index.row()]
- return f"{ab[0]} - {ab[1]}"
-
- def rowCount(self, index):
- return len(self.soloTool.getStoredAbLimits())
-
-class MainWindow(QMainWindow, Ui_MainWindow):
- songChangeSignal = pyqtSignal(int)
- abLimitsChangeSignal = pyqtSignal(int)
-
- 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.midiController = MidiController(self.soloTool)
-
- self.playlistModel = PlaylistModel(self.soloTool)
- self.songListView.setModel(self.playlistModel)
- self.songListView.selectionModel().selectionChanged.connect(self.playlistSelectionChanged)
- self.songChangePending = None
- self.songChangeSignal.connect(self.currentSongChanged)
- self.soloTool.registerCurrentSongCallback(self.songChangeSignal.emit)
-
- self.abListModel = ABListModel(self.soloTool)
- self.abListView.setModel(self.abListModel)
- self.abListView.selectionModel().selectionChanged.connect(self.abListSelectionChanged)
- self.abLimitsChangePending = None
- self.abLimitsChangeSignal.connect(self.currentAbLimitsChanged)
- self.soloTool.registerCurrentAbLimitsCallback(self.abLimitsChangeSignal.emit)
-
- self.songSlider.setMaximum(POSITION_FACTOR)
- self.songSlider.sliderPressed.connect(self.songSliderPressed)
- self.songSlider.sliderReleased.connect(self.songSliderReleased)
-
- self.aSlider.setMaximum(POSITION_FACTOR)
- self.aSlider.sliderReleased.connect(self.abSliderReleased)
- self.bSlider.setMaximum(POSITION_FACTOR)
- self.bSlider.sliderReleased.connect(self.abSliderReleased)
-
- self.rateSlider.setRange(int(0.5 * RATE_FACTOR), int(1.2 * RATE_FACTOR))
- self.rateSlider.setSingleStep(int(0.1 * RATE_FACTOR))
- self.rateSlider.setValue(int(1.0 * RATE_FACTOR))
- self.rateSlider.sliderReleased.connect(self.rateSliderReleased)
-
- self.playButton.pressed.connect(self.soloTool.play)
- self.pauseButton.pressed.connect(self.soloTool.pause)
- self.storeAbButton.pressed.connect(self.storeAbLimits)
- self.setAButton.pressed.connect(self.setA)
- self.setBButton.pressed.connect(self.setB)
- self.saveSessionButton.pressed.connect(self.saveSession)
- self.loadSessionButton.pressed.connect(self.loadSession)
- self.addSongButton.pressed.connect(self.addSong)
- self.abRepeatCheckBox.clicked.connect(self.toggleAbRepeat)
- self.initMidiButton.pressed.connect(self.initMidi)
-
- self.timer.start()
-
- if len(sys.argv) > 1:
- self.loadSession(sys.argv[1])
-
- self.show()
-
- 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 storeAbLimits(self):
- a = self.aSlider.value() / float(POSITION_FACTOR)
- b = self.bSlider.value() / float(POSITION_FACTOR)
- self.soloTool.storeAbLimits(a, b)
- self.abListModel.layoutChanged.emit()
-
- def setA(self):
- position = self.songSlider.value()
- self.aSlider.setValue(position)
- self.abSliderReleased()
-
- def setB(self):
- position = self.songSlider.value()
- self.bSlider.setValue(position)
- self.abSliderReleased()
-
- def toggleAbRepeat(self):
- enable = self.abRepeatCheckBox.isChecked()
- self.soloTool.setAbLimitEnable(enable)
-
- def saveSession(self):
- path, _ = QFileDialog.getSaveFileName(self, "Open file", "", "session file (*.json)")
- if path:
- self.soloTool.saveSession(path)
-
- def loadSession(self, path=None):
- if path is None:
- path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "session file (*.json)")
- if path is not None:
- 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 clearListViewSelection(self, listView):
- i = listView.selectionModel().currentIndex()
- listView.selectionModel().select(i, QItemSelectionModel.Deselect)
-
- def abSliderReleased(self):
- a = self.aSlider.value() / float(POSITION_FACTOR)
- b = self.bSlider.value() / float(POSITION_FACTOR)
- self.soloTool.setAbLimits(a, b)
- self.clearListViewSelection(self.abListView)
-
- def rateSliderReleased(self):
- rate = self.rateSlider.value() / float(RATE_FACTOR)
- self.soloTool.setPlaybackRate(rate)
-
- def playlistSelectionChanged(self, i):
- if self.songChangePending == CHANGE_INTERNAL:
- self.songChangePending = None
- else:
- assert self.songChangePending is None
- self.songChangePending = CHANGE_GUI
- index = i.indexes()[0].row()
- self.soloTool.setSong(index)
-
- self.clearListViewSelection(self.abListView)
- self.abListModel.layoutChanged.emit()
-
- def currentSongChanged(self, songIndex):
- if self.songChangePending == CHANGE_GUI:
- self.songChangePending = None
- else:
- assert self.songChangePending is None
- self.songChangePending = CHANGE_INTERNAL
- i = self.playlistModel.createIndex(songIndex, 0)
- self.songListView.selectionModel().select(i, QItemSelectionModel.ClearAndSelect)
-
- def abListSelectionChanged(self, i):
- if self.abLimitsChangePending == CHANGE_INTERNAL:
- print("Ack internal change")
- self.abLimitsChangePending = None
- else:
- assert self.abLimitsChangePending is None
- if i is not None and not i.isEmpty():
- print("Processing GUI change")
- self.abLimitsChangePending = CHANGE_GUI
- index = i.indexes()[0].row()
- ab = self.soloTool.getStoredAbLimits()[index]
- self.soloTool.loadAbLimits(index)
- self.aSlider.setValue(int(ab[0] * POSITION_FACTOR))
- self.bSlider.setValue(int(ab[1] * POSITION_FACTOR))
-
- def currentAbLimitsChanged(self, abIndex):
- if self.abLimitsChangePending == CHANGE_GUI:
- print("Ack GUI change")
- self.abLimitsChangePending = None
- else:
- assert self.abLimitsChangePending is None
- print("Processing internal change")
- self.abLimitsChangePending = CHANGE_INTERNAL
- i = self.abListModel.createIndex(abIndex, 0)
- self.abListView.selectionModel().select(i, QItemSelectionModel.ClearAndSelect)
- ab = self.soloTool.getStoredAbLimits()[abIndex]
- self.aSlider.setValue(int(ab[0] * POSITION_FACTOR))
- self.bSlider.setValue(int(ab[1] * POSITION_FACTOR))
-
- def initMidi(self):
- try:
- self.midiController.connect()
- except Exception as e:
- print("Error: could not connect to MIDI controller")
- print(e)
-
- def keyPressEvent(self, event):
- if event.key() == Qt.Key_Super_L:
- self.soloTool.jumpToA()
-
- def closeEvent(self, event):
- self.midiController.disconnect()
- event.accept()
-
-def main():
- app = QApplication([])
- app.setApplicationName("Solo Tool")
- app.setStyle("Fusion")
-
- # Fusion dark palette from https://gist.github.com/QuantumCD/6245215.
- palette = QPalette()
- palette.setColor(QPalette.Window, QColor(53, 53, 53))
- palette.setColor(QPalette.WindowText, Qt.white)
- palette.setColor(QPalette.Base, QColor(25, 25, 25))
- palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
- palette.setColor(QPalette.ToolTipBase, Qt.white)
- palette.setColor(QPalette.ToolTipText, Qt.white)
- palette.setColor(QPalette.Text, Qt.white)
- palette.setColor(QPalette.Button, QColor(53, 53, 53))
- palette.setColor(QPalette.ButtonText, Qt.white)
- palette.setColor(QPalette.BrightText, Qt.red)
- palette.setColor(QPalette.Link, QColor(42, 130, 218))
- palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
- palette.setColor(QPalette.HighlightedText, Qt.black)
- app.setPalette(palette)
- app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }")
-
- window = MainWindow()
- app.exec_()
-
-if __name__ == '__main__':
- main()
diff --git a/pacman.txt b/pacman.txt
index cb8a962..5b57d72 100644
--- a/pacman.txt
+++ b/pacman.txt
@@ -1,6 +1,8 @@
pkgconf
gst-plugins-good
-qt5-multimedia
-
+For VLC backend:
vlc
+
+For mplayer backend:
+mplayer
diff --git a/readme.md b/readme.md
index 20f15b6..be2269b 100644
--- a/readme.md
+++ b/readme.md
@@ -10,16 +10,16 @@ Non-Python dependencies are listed in pacman.txt and should be manually installe
## Usage
-To set up the environment and run the tests, just use `make`:
+To set up the environment and run the tests, run `make`:
```
make
```
-The GUI can then be executed in the venv:
+The web GUI can also be run with `make`:
```
-./venv/bin/solo_tool_gui
+make gui
```
Alternatively, the tool can be executed in headless mode. In this case all it does is load the provided session and connect to the MIDI controller:
@@ -30,7 +30,7 @@ Alternatively, the tool can be executed in headless mode. In this case all it do
## MIDI
-It is currently possible to control the tool with MIDI. With the device plugged in, a connection can be established by clicking on "Connect MIDI" in the GUI or running the headless binary. Currently the only device supported is the Novation Launchpad Mini Mk II.
+It is currently possible to control the tool with MIDI. With the device plugged in, a connection is automatically established by the CLI. Currently the only device supported is the Novation Launchpad Mini Mk II.
The MIDI device button mapping is documented in `doc/diagram.drawio`.
diff --git a/requirements.txt b/requirements.txt
index 7c19832..459ff68 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
-e solo-tool-project[dev]
-e cli-project[dev]
--e gui-project[dev]
-e web-project[dev]