kitstarter 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kitstarter-0.2.0 → kitstarter-0.2.2}/PKG-INFO +1 -1
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/__init__.py +1 -1
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/main_window.py +76 -66
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/samples_widget.py +2 -3
- {kitstarter-0.2.0 → kitstarter-0.2.2}/pyproject.toml +1 -1
- kitstarter-0.2.0/bin/kitstarter +0 -6
- {kitstarter-0.2.0 → kitstarter-0.2.2}/.gitignore +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/LICENSE +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/README.md +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/__init__.py +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/main_window.ui +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/pindb.py +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-down-disabled.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-down-enabled.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-up-disabled.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-up-enabled.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/delete.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/empty.sfz +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/inst-complete.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/inst-incomplete.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/pin.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/sample-mismatch.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/sample-okay.svg +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/starter_kits.py +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/tests/pindb.py +0 -0
- {kitstarter-0.2.0 → kitstarter-0.2.2}/tests/samples_widget.py +0 -0
|
@@ -6,6 +6,7 @@ import os, logging, platform, subprocess, tempfile
|
|
|
6
6
|
from os.path import join, dirname, basename, abspath, splitext
|
|
7
7
|
from functools import lru_cache
|
|
8
8
|
from collections import namedtuple
|
|
9
|
+
from itertools import chain
|
|
9
10
|
|
|
10
11
|
from PyQt5 import uic
|
|
11
12
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QPoint, QDir, QItemSelection, QTimer
|
|
@@ -17,7 +18,7 @@ import soundfile as sf
|
|
|
17
18
|
from soundfile import LibsndfileError
|
|
18
19
|
from midi_notes import MIDI_DRUM_NAMES
|
|
19
20
|
from liquiphy import LiquidSFZ
|
|
20
|
-
from conn_jack import JackConnectionManager
|
|
21
|
+
from conn_jack import JackConnectionManager, JACK_PORT_IS_INPUT
|
|
21
22
|
from jack_audio_player import JackAudioPlayer
|
|
22
23
|
from qt_extras import SigBlock, ShutUpQT
|
|
23
24
|
from sfzen.drumkits import Drumkit, iter_pitch_by_group
|
|
@@ -39,12 +40,15 @@ MESSAGE_TIMEOUT = 3000
|
|
|
39
40
|
|
|
40
41
|
class MainWindow(QMainWindow):
|
|
41
42
|
|
|
42
|
-
sig_ports_complete = pyqtSignal()
|
|
43
|
+
sig_ports_complete = pyqtSignal() # \
|
|
44
|
+
sig_sources_changed = pyqtSignal() # Used to decouple JackConnectionManager callbacks
|
|
45
|
+
sig_sinks_changed = pyqtSignal() # /
|
|
43
46
|
|
|
44
47
|
def __init__(self, filename):
|
|
45
48
|
super().__init__()
|
|
46
49
|
self.sfz_filename = filename
|
|
47
50
|
self.kit = StarterKit()
|
|
51
|
+
self.closing = False
|
|
48
52
|
# Setup GUI
|
|
49
53
|
with ShutUpQT():
|
|
50
54
|
uic.loadUi(join(dirname(__file__), 'main_window.ui'), self)
|
|
@@ -64,16 +68,16 @@ class MainWindow(QMainWindow):
|
|
|
64
68
|
# Setup JackConnectionManager
|
|
65
69
|
self.conn_man = JackConnectionManager()
|
|
66
70
|
self.conn_man.on_error(self.jack_error)
|
|
67
|
-
self.conn_man.on_xrun(self.jack_xrun)
|
|
68
71
|
self.conn_man.on_shutdown(self.jack_shutdown)
|
|
69
72
|
self.conn_man.on_client_registration(self.jack_client_registration)
|
|
70
73
|
self.conn_man.on_port_registration(self.jack_port_registration)
|
|
71
74
|
# Setup tempfile, synth, audio player, pindb
|
|
72
|
-
self.pindb = PinDatabase()
|
|
73
75
|
_, self.tempfile = tempfile.mkstemp(suffix='.sfz')
|
|
74
76
|
self.synth = JackLiquidSFZ(self.tempfile)
|
|
77
|
+
self.synth_ports_complete = False
|
|
75
78
|
self.audio_player = None # Instantiated after initial paint delay
|
|
76
|
-
|
|
79
|
+
self.pindb = PinDatabase()
|
|
80
|
+
# Setup tree browser
|
|
77
81
|
root_path = settings().value(KEY_FILES_ROOT, QDir.homePath())
|
|
78
82
|
current_path = settings().value(KEY_FILES_CURRENT, QDir.homePath())
|
|
79
83
|
self.files_model = QFileSystemModel()
|
|
@@ -105,13 +109,17 @@ class MainWindow(QMainWindow):
|
|
|
105
109
|
widget.deleteLater()
|
|
106
110
|
# Setup statusbar
|
|
107
111
|
self.cmb_midi_srcs = QComboBox(self.statusbar)
|
|
112
|
+
self.cmb_midi_srcs.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
108
113
|
self.statusbar.addPermanentWidget(QLabel('Src:', self.statusbar))
|
|
109
114
|
self.statusbar.addPermanentWidget(self.cmb_midi_srcs)
|
|
110
115
|
self.cmb_audio_sinks = QComboBox(self.statusbar)
|
|
116
|
+
self.cmb_audio_sinks.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
111
117
|
self.statusbar.addPermanentWidget(QLabel('Sink:', self.statusbar))
|
|
112
118
|
self.statusbar.addPermanentWidget(self.cmb_audio_sinks)
|
|
113
119
|
# Connect signals
|
|
114
|
-
self.sig_ports_complete.connect(self.slot_ports_complete)
|
|
120
|
+
self.sig_ports_complete.connect(self.slot_ports_complete, type = Qt.QueuedConnection)
|
|
121
|
+
self.sig_sources_changed.connect(self.slot_sources_changed, type = Qt.QueuedConnection)
|
|
122
|
+
self.sig_sinks_changed.connect(self.slot_sinks_changed, type = Qt.QueuedConnection)
|
|
115
123
|
self.lst_instruments.currentRowChanged.connect(self.stk_samples_widgets.setCurrentIndex)
|
|
116
124
|
self.lst_instruments.currentRowChanged.connect(self.slot_instrument_changed)
|
|
117
125
|
self.tree_files.selectionModel().selectionChanged.connect(self.slot_files_selection_changed)
|
|
@@ -124,16 +132,13 @@ class MainWindow(QMainWindow):
|
|
|
124
132
|
self.lst_samples.mouseReleaseEvent = self.samples_mouse_release
|
|
125
133
|
self.lst_samples.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
126
134
|
self.lst_samples.customContextMenuRequested.connect(self.slot_samples_context_menu)
|
|
127
|
-
self.cmb_midi_srcs.currentTextChanged.connect(self.
|
|
128
|
-
self.cmb_audio_sinks.currentTextChanged.connect(self.
|
|
135
|
+
self.cmb_midi_srcs.currentTextChanged.connect(self.slot_midi_src_selected)
|
|
136
|
+
self.cmb_audio_sinks.currentTextChanged.connect(self.slot_audio_sink_selected)
|
|
129
137
|
self.action_new.triggered.connect(self.slot_new)
|
|
130
138
|
self.action_open.triggered.connect(self.slot_open)
|
|
131
139
|
self.action_save.triggered.connect(self.slot_save)
|
|
132
140
|
self.action_save_as.triggered.connect(self.slot_save_as)
|
|
133
141
|
self.action_exit.triggered.connect(self.close)
|
|
134
|
-
# Fill sink/source menus:
|
|
135
|
-
self.fill_cmb_sources()
|
|
136
|
-
self.fill_cmb_sinks()
|
|
137
142
|
# Set currently selected file
|
|
138
143
|
QTimer.singleShot(250, self.layout_complete)
|
|
139
144
|
|
|
@@ -154,6 +159,7 @@ class MainWindow(QMainWindow):
|
|
|
154
159
|
self.setWindowTitle(title)
|
|
155
160
|
|
|
156
161
|
def closeEvent(self, _):
|
|
162
|
+
self.closing = True
|
|
157
163
|
self.synth.quit()
|
|
158
164
|
self.save_geometry()
|
|
159
165
|
os.unlink(self.tempfile)
|
|
@@ -164,105 +170,109 @@ class MainWindow(QMainWindow):
|
|
|
164
170
|
def jack_error(self, error_message):
|
|
165
171
|
logging.error('JACK ERROR: %s', error_message)
|
|
166
172
|
|
|
167
|
-
def jack_xrun(self, xruns):
|
|
168
|
-
pass
|
|
169
|
-
|
|
170
173
|
def jack_shutdown(self):
|
|
171
174
|
logging.error('JACK is shutting down')
|
|
172
175
|
self.close()
|
|
173
176
|
|
|
174
177
|
def jack_client_registration(self, client_name, action):
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if self.cmb_audio_sinks.findText(client_name, Qt.MatchStartsWith) > -1:
|
|
180
|
-
self.fill_cmb_sinks()
|
|
181
|
-
elif self.cmb_midi_srcs.findText(client_name, Qt.MatchStartsWith) > -1:
|
|
182
|
-
self.fill_cmb_sources()
|
|
178
|
+
if not self.closing and self.synth_ports_complete:
|
|
179
|
+
self.sig_sinks_changed.emit()
|
|
180
|
+
elif action and client_name.startswith(SYNTH_NAME):
|
|
181
|
+
self.synth.client_name = client_name
|
|
183
182
|
|
|
184
183
|
def jack_port_registration(self, port, action):
|
|
185
|
-
if
|
|
184
|
+
if not self.closing and self.synth_ports_complete:
|
|
185
|
+
self.sig_sources_changed.emit()
|
|
186
|
+
elif action and self.synth.client_name in port.name:
|
|
186
187
|
if port.is_input and port.is_midi:
|
|
187
188
|
self.synth.input_port = port
|
|
188
189
|
elif port.is_output and port.is_audio:
|
|
189
190
|
self.synth.output_ports.append(port)
|
|
190
|
-
else:
|
|
191
|
-
logging.error('Incorrect port type: %s', port)
|
|
192
191
|
if self.synth.input_port and len(self.synth.output_ports) == 2:
|
|
193
192
|
self.sig_ports_complete.emit()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
elif port.is_input and port.is_audio:
|
|
198
|
-
self.fill_cmb_sinks()
|
|
193
|
+
|
|
194
|
+
# -----------------------------------------------------------------
|
|
195
|
+
# Source / sink management
|
|
199
196
|
|
|
200
197
|
@pyqtSlot()
|
|
201
198
|
def slot_ports_complete(self):
|
|
202
199
|
"""
|
|
203
|
-
|
|
204
|
-
"jack_port_registration" is triggered from another thread, not the GUI thread.
|
|
200
|
+
Triggered by sig_ports_complete, emitted from another thread.
|
|
205
201
|
"""
|
|
202
|
+
self.synth_ports_complete = True
|
|
206
203
|
self.connect_midi_source()
|
|
207
204
|
self.connect_audio_sink()
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
@pyqtSlot()
|
|
207
|
+
def slot_sources_changed(self):
|
|
208
|
+
"""
|
|
209
|
+
Triggered by sig_sources_changed, emitted from another thread.
|
|
210
|
+
"""
|
|
211
|
+
self.connect_midi_source()
|
|
212
|
+
|
|
213
|
+
@pyqtSlot()
|
|
214
|
+
def slot_sinks_changed(self):
|
|
215
|
+
"""
|
|
216
|
+
Triggered by sig_sinks_changed, emitted from another thread.
|
|
217
|
+
"""
|
|
218
|
+
self.connect_audio_sink()
|
|
211
219
|
|
|
212
|
-
def
|
|
220
|
+
def connect_midi_source(self):
|
|
221
|
+
midi_src = settings().value(KEY_MIDI_SOURCE)
|
|
222
|
+
for port_name in self.conn_man.get_port_connections_names(self.synth.input_port):
|
|
223
|
+
if port_name != midi_src:
|
|
224
|
+
self.conn_man.disconnect_by_name(port_name, self.synth.input_port.name)
|
|
225
|
+
connected = False
|
|
226
|
+
if midi_src:
|
|
227
|
+
if src_port := self.conn_man.get_port_by_name(midi_src):
|
|
228
|
+
self.conn_man.connect(src_port, self.synth.input_port)
|
|
229
|
+
connected = True
|
|
213
230
|
with SigBlock(self.cmb_midi_srcs):
|
|
214
231
|
self.cmb_midi_srcs.clear()
|
|
215
232
|
self.cmb_midi_srcs.addItem('')
|
|
216
233
|
for port in self.conn_man.output_ports():
|
|
217
234
|
if port.is_midi:
|
|
218
235
|
self.cmb_midi_srcs.addItem(port.name)
|
|
219
|
-
if
|
|
220
|
-
|
|
221
|
-
self.cmb_midi_srcs.setCurrentText(midi_src)
|
|
236
|
+
if connected and midi_src:
|
|
237
|
+
self.cmb_midi_srcs.setCurrentText(midi_src)
|
|
222
238
|
|
|
223
|
-
def
|
|
239
|
+
def connect_audio_sink(self):
|
|
240
|
+
audio_sink = settings().value(KEY_AUDIO_SINK)
|
|
241
|
+
for output_port in chain(self.synth.output_ports, self.audio_player.output_ports):
|
|
242
|
+
output_port = self.conn_man.get_port_by_name(output_port.name)
|
|
243
|
+
for port_name in self.conn_man.get_port_connections_names(output_port):
|
|
244
|
+
if port_name.split(':')[0] != audio_sink:
|
|
245
|
+
self.conn_man.disconnect_by_name(output_port.name, port_name)
|
|
246
|
+
connected = False
|
|
247
|
+
if audio_sink:
|
|
248
|
+
audio_sink_ports = self.conn_man.get_ports(JACK_PORT_IS_INPUT,
|
|
249
|
+
port_name_pattern = f'{audio_sink}:*')
|
|
250
|
+
if audio_sink_ports:
|
|
251
|
+
for src_port, dest_port in zip(self.synth.output_ports, audio_sink_ports):
|
|
252
|
+
self.conn_man.connect(src_port, dest_port)
|
|
253
|
+
for src_port, dest_port in zip(self.audio_player.output_ports, audio_sink_ports):
|
|
254
|
+
self.conn_man.connect(src_port, dest_port)
|
|
255
|
+
connected = True
|
|
224
256
|
with SigBlock(self.cmb_audio_sinks):
|
|
225
257
|
self.cmb_audio_sinks.clear()
|
|
226
258
|
self.cmb_audio_sinks.addItem('')
|
|
227
259
|
valid_clients = set(port.client_name \
|
|
228
|
-
for port in self.conn_man.input_ports()
|
|
229
|
-
if port.is_audio)
|
|
260
|
+
for port in self.conn_man.input_ports() if port.is_audio)
|
|
230
261
|
for client in valid_clients:
|
|
231
262
|
self.cmb_audio_sinks.addItem(client)
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
self.cmb_audio_sinks.setCurrentText(audio_sink)
|
|
263
|
+
if connected and audio_sink:
|
|
264
|
+
self.cmb_audio_sinks.setCurrentText(audio_sink)
|
|
235
265
|
|
|
236
266
|
@pyqtSlot(str)
|
|
237
|
-
def
|
|
238
|
-
if midi_src := settings().value(KEY_MIDI_SOURCE):
|
|
239
|
-
self.conn_man.disconnect_by_name(midi_src, self.synth.input_port.name)
|
|
267
|
+
def slot_midi_src_selected(self, value):
|
|
240
268
|
settings().setValue(KEY_MIDI_SOURCE, value)
|
|
241
269
|
self.connect_midi_source()
|
|
242
270
|
|
|
243
271
|
@pyqtSlot(str)
|
|
244
|
-
def
|
|
245
|
-
if audio_sink := settings().value(KEY_AUDIO_SINK):
|
|
246
|
-
for src_port in self.synth.output_ports:
|
|
247
|
-
for dest_port in self.conn_man.get_port_connections(src_port):
|
|
248
|
-
self.conn_man.disconnect(src_port, dest_port)
|
|
272
|
+
def slot_audio_sink_selected(self, value):
|
|
249
273
|
settings().setValue(KEY_AUDIO_SINK, value)
|
|
250
274
|
self.connect_audio_sink()
|
|
251
275
|
|
|
252
|
-
def connect_midi_source(self):
|
|
253
|
-
if midi_src := settings().value(KEY_MIDI_SOURCE):
|
|
254
|
-
self.conn_man.connect_by_name(midi_src, self.synth.input_port.name)
|
|
255
|
-
|
|
256
|
-
def connect_audio_sink(self):
|
|
257
|
-
if audio_sink := settings().value(KEY_AUDIO_SINK):
|
|
258
|
-
audio_sink_ports = [ port for port \
|
|
259
|
-
in self.conn_man.input_ports() \
|
|
260
|
-
if port.client_name == audio_sink ]
|
|
261
|
-
for src_port, dest_port in zip(self.synth.output_ports, audio_sink_ports):
|
|
262
|
-
self.conn_man.connect(src_port, dest_port)
|
|
263
|
-
for src_port, dest_port in zip(self.audio_player.output_ports, audio_sink_ports):
|
|
264
|
-
self.conn_man.connect(src_port, dest_port)
|
|
265
|
-
|
|
266
276
|
# -----------------------------------------------------------------
|
|
267
277
|
# Instrument list management
|
|
268
278
|
|
|
@@ -572,7 +572,7 @@ class SamplesWidget(QWidget):
|
|
|
572
572
|
self.grid.delete_row(self.grid.inhabited_row_indexes()[1])
|
|
573
573
|
self.instrument = instrument
|
|
574
574
|
with SigBlock(self.sld_pan):
|
|
575
|
-
self.sld_pan.setValue(int(self.instrument.pan
|
|
575
|
+
self.sld_pan.setValue(int(self.instrument.pan))
|
|
576
576
|
for sample in self.instrument.samples.values():
|
|
577
577
|
self._add_sample(sample)
|
|
578
578
|
self.update_ui()
|
|
@@ -722,7 +722,7 @@ class SamplesWidget(QWidget):
|
|
|
722
722
|
|
|
723
723
|
@pyqtSlot(int)
|
|
724
724
|
def slot_pan_changed(self, value):
|
|
725
|
-
self.instrument.pan = value
|
|
725
|
+
self.instrument.pan = value
|
|
726
726
|
self.slot_value_changed()
|
|
727
727
|
|
|
728
728
|
@pyqtSlot()
|
|
@@ -798,7 +798,6 @@ class SamplesWidget(QWidget):
|
|
|
798
798
|
def update_ui(self):
|
|
799
799
|
frames = self.button_frames()
|
|
800
800
|
has_samples = bool(frames)
|
|
801
|
-
self.sld_pan.setEnabled(has_samples)
|
|
802
801
|
self.spread_button.setEnabled(has_samples)
|
|
803
802
|
self.chk_crossfade.setEnabled(has_samples)
|
|
804
803
|
self.chk_snap.setEnabled(has_samples)
|
|
@@ -27,7 +27,7 @@ requires = ["flit_core >=3.2,<4"]
|
|
|
27
27
|
build-backend = "flit_core.buildapi"
|
|
28
28
|
|
|
29
29
|
[bumpver]
|
|
30
|
-
current_version = "0.2.
|
|
30
|
+
current_version = "0.2.2"
|
|
31
31
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
32
32
|
commit_message = "Bump version {old_version} -> {new_version}"
|
|
33
33
|
commit = true
|
kitstarter-0.2.0/bin/kitstarter
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|