kitbash 1.5.2__tar.gz → 1.7.0__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.
- {kitbash-1.5.2 → kitbash-1.7.0}/PKG-INFO +6 -8
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/__init__.py +10 -2
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/__main__.py +4 -8
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/gui/drumkit_widget.py +14 -5
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/gui/kit_save_dialog.py +0 -1
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/gui/main_window.py +52 -81
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/gui/main_window.ui +13 -13
- kitbash-1.7.0/kitbash/jack_audio.py +78 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/worker_threads.py +3 -3
- {kitbash-1.5.2 → kitbash-1.7.0}/pyproject.toml +6 -8
- kitbash-1.5.2/kitbash/jack_audio.py +0 -243
- {kitbash-1.5.2 → kitbash-1.7.0}/.gitignore +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/LICENSE +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/README.md +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/gui/__init__.py +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/res/empty.sfz +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/res/group_expanded.svg +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/res/group_hidden.svg +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/res/kitbash-icon.png +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/scripts/bash_project.py +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/styles/light.css +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/kitbash/styles/system.css +0 -0
- {kitbash-1.5.2 → kitbash-1.7.0}/tests/file_save_dialog.py +0 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kitbash
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Provides universal settings, styles, cached icons, and pixmaps, and the base
|
|
5
5
|
Author-email: Leon Dionne <ldionne@dridesign.sh.cn>
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
8
|
+
Requires-Dist: log_soso >= 1.0.3
|
|
8
9
|
Requires-Dist: PyQt5
|
|
9
|
-
Requires-Dist:
|
|
10
|
-
Requires-Dist:
|
|
11
|
-
Requires-Dist:
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist: midi_notes >= 1.1.0
|
|
14
|
-
Requires-Dist: recent_items_list >= 1.1.1
|
|
15
|
-
Requires-Dist: soso_qt_extras >= 1.2.0
|
|
10
|
+
Requires-Dist: qt_liquid_pool >= 1.2.0
|
|
11
|
+
Requires-Dist: recent_items_list >= 1.1.2
|
|
12
|
+
Requires-Dist: sfzen >= 2.3.1
|
|
13
|
+
Requires-Dist: soso_qt_extras >= 1.7.0
|
|
16
14
|
Project-URL: Home, https://github.com/Zen-Master-SoSo/kitbash
|
|
17
15
|
|
|
18
16
|
# KitBash
|
|
@@ -28,14 +28,14 @@ from functools import lru_cache
|
|
|
28
28
|
from PyQt5.QtCore import QSettings
|
|
29
29
|
from PyQt5.QtWidgets import QApplication, QSplitter
|
|
30
30
|
from qt_extras import DevilBox
|
|
31
|
-
from conn_jack import JackConnectError
|
|
32
31
|
|
|
33
|
-
__version__ = "1.
|
|
32
|
+
__version__ = "1.7.0"
|
|
34
33
|
|
|
35
34
|
APPLICATION_NAME = "kitbash"
|
|
36
35
|
PACKAGE_DIR = dirname(__file__)
|
|
37
36
|
DEFAULT_STYLE = 'system'
|
|
38
37
|
AUDIO_ICON_SIZE = 16
|
|
38
|
+
KEY_PREVIEW_VELOCITY = 'PreviewVelocity'
|
|
39
39
|
KEY_STYLE = 'Style'
|
|
40
40
|
KEY_SAMPLES_MODE = 'KitSaveDialog/SamplesMode'
|
|
41
41
|
KEY_RECENT_DRUMKIT_FOLDER = 'RecentDrumkitFolder'
|
|
@@ -44,8 +44,16 @@ KEY_RECENT_PROJECT_FOLDER = 'RecentProjectFolder'
|
|
|
44
44
|
KEY_RECENT_PROJECTS = 'RecentProjects'
|
|
45
45
|
KEY_SAMPLE_XPLORE_ROOT = 'SampleExplorer/Root'
|
|
46
46
|
KEY_SAMPLE_XPLORE_CURR = 'SampleExplorer/Current'
|
|
47
|
+
KEY_MIDI_SOURCE = 'MIDISource'
|
|
48
|
+
KEY_AUDIO_SINK = 'AudioSink'
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
def audio():
|
|
52
|
+
if not hasattr(audio, 'instance'):
|
|
53
|
+
from kitbash.jack_audio import Audio
|
|
54
|
+
audio.instance = Audio()
|
|
55
|
+
return audio.instance
|
|
56
|
+
|
|
49
57
|
@lru_cache
|
|
50
58
|
def __settings():
|
|
51
59
|
return QSettings('ZenSoSo', APPLICATION_NAME)
|
|
@@ -22,9 +22,8 @@ kitbash is a program you can use to combine parts of various SFZ files into a
|
|
|
22
22
|
new SFZ with instruments "borrowed" from the originals.
|
|
23
23
|
"""
|
|
24
24
|
import sys, os, argparse, logging
|
|
25
|
-
from PyQt5.QtWidgets import QApplication
|
|
26
|
-
from qt_extras import
|
|
27
|
-
from conn_jack import JackConnectError
|
|
25
|
+
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
|
26
|
+
from qt_extras import exceptions_hook
|
|
28
27
|
from kitbash.gui.main_window import MainWindow
|
|
29
28
|
|
|
30
29
|
|
|
@@ -61,11 +60,8 @@ def main():
|
|
|
61
60
|
#-----------------------------------------------------------------------
|
|
62
61
|
|
|
63
62
|
app = QApplication([])
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
except JackConnectError:
|
|
67
|
-
DevilBox('Could not connect to JACK server. Is it running?')
|
|
68
|
-
sys.exit(1)
|
|
63
|
+
sys.excepthook = exceptions_hook
|
|
64
|
+
main_window = MainWindow(options)
|
|
69
65
|
main_window.show()
|
|
70
66
|
sys.exit(app.exec())
|
|
71
67
|
|
|
@@ -29,7 +29,7 @@ from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QLabel,
|
|
|
29
29
|
QFrame, QSizePolicy, QPushButton, QCheckBox)
|
|
30
30
|
from qt_extras import SigBlock
|
|
31
31
|
from sfzen.drumkits import PercussionInstrument
|
|
32
|
-
from kitbash import PACKAGE_DIR
|
|
32
|
+
from kitbash import PACKAGE_DIR, KEY_PREVIEW_VELOCITY, get_setting, audio
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@lru_cache
|
|
@@ -64,9 +64,9 @@ class DrumkitWidget(QFrame):
|
|
|
64
64
|
self.sfz_filename = filename
|
|
65
65
|
self.moniker = basename(self.sfz_filename)
|
|
66
66
|
self.drumkit = None
|
|
67
|
-
self.
|
|
68
|
-
self.initial_height = None
|
|
67
|
+
self.synth = None
|
|
69
68
|
|
|
69
|
+
self.initial_height = None
|
|
70
70
|
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
|
|
71
71
|
|
|
72
72
|
main_layout = QVBoxLayout()
|
|
@@ -130,6 +130,7 @@ class DrumkitWidget(QFrame):
|
|
|
130
130
|
Fills the groups and instruments of this the DrumkitWidget.
|
|
131
131
|
"""
|
|
132
132
|
self.drumkit = drumkit
|
|
133
|
+
self.synth = audio().create_synth(self.sfz_filename)
|
|
133
134
|
for group in self.drumkit.percussion_groups.values():
|
|
134
135
|
if group.is_empty():
|
|
135
136
|
continue
|
|
@@ -162,14 +163,21 @@ class DrumkitWidget(QFrame):
|
|
|
162
163
|
"""
|
|
163
164
|
Triggered by InstrumentButton mouse press.
|
|
164
165
|
"""
|
|
165
|
-
self.
|
|
166
|
+
self.synth.noteon(
|
|
167
|
+
self.drumkit.midi_channel,
|
|
168
|
+
inst.pitch,
|
|
169
|
+
get_setting(KEY_PREVIEW_VELOCITY, 80, int)
|
|
170
|
+
)
|
|
166
171
|
|
|
167
172
|
@pyqtSlot(PercussionInstrument)
|
|
168
173
|
def slot_instrument_released(self, inst):
|
|
169
174
|
"""
|
|
170
175
|
Triggered by InstrumentButton mouse relase.
|
|
171
176
|
"""
|
|
172
|
-
self.
|
|
177
|
+
self.synth.noteoff(
|
|
178
|
+
self.drumkit.midi_channel,
|
|
179
|
+
inst.pitch
|
|
180
|
+
)
|
|
173
181
|
|
|
174
182
|
@pyqtSlot(QPushButton)
|
|
175
183
|
def slot_instrument_toggled(self, button):
|
|
@@ -187,6 +195,7 @@ class DrumkitWidget(QFrame):
|
|
|
187
195
|
"""
|
|
188
196
|
Triggered by the "remove" button click event.
|
|
189
197
|
"""
|
|
198
|
+
audio().delete_synth(self.synth)
|
|
190
199
|
self.sig_remove_drumkit.emit(self)
|
|
191
200
|
|
|
192
201
|
@pyqtSlot(bool)
|
|
@@ -31,18 +31,18 @@ from PyQt5.QtCore import Qt, QObject, pyqtSlot, QTimer, QThreadPool, QPoint, QCo
|
|
|
31
31
|
from PyQt5.QtWidgets import (
|
|
32
32
|
QApplication, QMainWindow, QMessageBox, QFileDialog, QAction, QActionGroup, QMenu)
|
|
33
33
|
from PyQt5.QtGui import QIcon
|
|
34
|
-
from qt_extras import ShutUpQT,
|
|
34
|
+
from qt_extras import ShutUpQT, DevilBox
|
|
35
35
|
from qt_extras.list_layout import VListLayout
|
|
36
36
|
from recent_items_list import RecentItemsList
|
|
37
37
|
from sfzen import SAMPLES_ABSPATH
|
|
38
38
|
from sfzen.drumkits import Drumkit
|
|
39
39
|
from kitbash import (
|
|
40
40
|
APPLICATION_NAME, PACKAGE_DIR, DEFAULT_STYLE,
|
|
41
|
-
styles, set_application_style, get_setting, set_setting, GeometrySaver,
|
|
42
|
-
KEY_STYLE, KEY_SAMPLES_MODE,
|
|
41
|
+
styles, set_application_style, get_setting, set_setting, GeometrySaver, audio,
|
|
42
|
+
KEY_PREVIEW_VELOCITY, KEY_STYLE, KEY_SAMPLES_MODE,
|
|
43
|
+
KEY_RECENT_DRUMKIT_FOLDER, KEY_RECENT_DRUMKITS,
|
|
43
44
|
KEY_RECENT_PROJECT_FOLDER, KEY_RECENT_PROJECTS)
|
|
44
45
|
from kitbash.worker_threads import KitLoader, KitBasher
|
|
45
|
-
from kitbash.jack_audio import Audio
|
|
46
46
|
from kitbash.gui.drumkit_widget import DrumkitWidget
|
|
47
47
|
from kitbash.gui.kit_save_dialog import KitSaveDialog
|
|
48
48
|
|
|
@@ -94,7 +94,18 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
94
94
|
# Setup background threadpool for KitLoader and KitBasher workers
|
|
95
95
|
self.background_threadpool = QThreadPool()
|
|
96
96
|
# Setup jack audio
|
|
97
|
-
self.audio =
|
|
97
|
+
self.audio = audio()
|
|
98
|
+
# Setup source/sink combo boxes:
|
|
99
|
+
self.cmb_midi_sources = self.audio.midi_in_combo_box(self)
|
|
100
|
+
self.lo_source_combo.replaceWidget(self.cmb_midi_src_placeholder, self.cmb_midi_sources)
|
|
101
|
+
self.cmb_midi_src_placeholder.setVisible(False)
|
|
102
|
+
self.cmb_midi_src_placeholder.deleteLater()
|
|
103
|
+
del self.cmb_midi_src_placeholder
|
|
104
|
+
self.cmb_audio_sinks = self.audio.audio_out_combo_box(self)
|
|
105
|
+
self.lo_sink_combo.replaceWidget(self.cmb_audio_sink_placeholder, self.cmb_audio_sinks)
|
|
106
|
+
self.cmb_audio_sink_placeholder.setVisible(False)
|
|
107
|
+
self.cmb_audio_sink_placeholder.deleteLater()
|
|
108
|
+
del self.cmb_audio_sink_placeholder
|
|
98
109
|
# Setup GUI elements
|
|
99
110
|
self.fill_style_menu()
|
|
100
111
|
self.setup_kits_area()
|
|
@@ -122,7 +133,7 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
122
133
|
self.action_save_project.triggered.connect(self.slot_save_project)
|
|
123
134
|
self.action_save_project_as.triggered.connect(self.slot_save_project_as)
|
|
124
135
|
self.action_save_bashed_kit.triggered.connect(self.slot_save_kit)
|
|
125
|
-
self.
|
|
136
|
+
self.action_save_bashed_kit_as.triggered.connect(self.slot_save_kit_as)
|
|
126
137
|
self.action_add_drumkit.triggered.connect(self.slot_add_drumkit)
|
|
127
138
|
self.action_remove_all_kits.triggered.connect(self.slot_remove_all_kits)
|
|
128
139
|
self.action_reload_style.triggered.connect(self.slot_reload_style)
|
|
@@ -137,14 +148,8 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
137
148
|
self.b_copy_path.clicked.connect(self.slot_copy_kit_path)
|
|
138
149
|
self.b_add_drumkit.clicked.connect(self.slot_add_drumkit)
|
|
139
150
|
self.b_xruns.clicked.connect(self.slot_xruns_clicked)
|
|
140
|
-
self.
|
|
141
|
-
|
|
142
|
-
self.cmb_audio_sinks.currentTextChanged.connect(
|
|
143
|
-
self.audio.slot_audio_sink_selected)
|
|
144
|
-
self.audio.sig_sources_changed.connect(
|
|
145
|
-
self.slot_sources_changed)
|
|
146
|
-
self.audio.sig_sinks_changed.connect(
|
|
147
|
-
self.slot_sinks_changed)
|
|
151
|
+
self.spn_velocity.valueChanged.connect(self.slot_velocity_value_changed)
|
|
152
|
+
self.audio.sig_jack_ready.connect(self.slot_jack_ready)
|
|
148
153
|
|
|
149
154
|
def update_ui(self):
|
|
150
155
|
title = APPLICATION_NAME \
|
|
@@ -158,7 +163,11 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
158
163
|
self.action_new_project.setEnabled(has_kits)
|
|
159
164
|
self.action_save_project.setEnabled(has_kits and self.dirty)
|
|
160
165
|
self.action_save_project_as.setEnabled(has_kits)
|
|
161
|
-
self.action_save_bashed_kit.setEnabled(has_kits)
|
|
166
|
+
self.action_save_bashed_kit.setEnabled(has_kits and self.saved_sfz_filename is not None)
|
|
167
|
+
self.action_save_bashed_kit.setText(
|
|
168
|
+
'Save bashed kit' if self.saved_sfz_filename is None
|
|
169
|
+
else f'Save "{self.saved_sfz_filename}"')
|
|
170
|
+
self.action_save_bashed_kit_as.setEnabled(has_kits)
|
|
162
171
|
self.b_save_project.setEnabled(has_kits)
|
|
163
172
|
self.b_save_kit.setEnabled(has_kits)
|
|
164
173
|
self.b_copy_path.setVisible(bool(self.saved_sfz_filename))
|
|
@@ -218,7 +227,6 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
218
227
|
Starts project load; saves recent file name.
|
|
219
228
|
Permission to clear must already have been given.
|
|
220
229
|
"""
|
|
221
|
-
logging.debug('load_project %s', filename)
|
|
222
230
|
if exists(filename):
|
|
223
231
|
try:
|
|
224
232
|
with open(filename, 'r', encoding = 'utf-8') as fh:
|
|
@@ -251,18 +259,6 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
251
259
|
self.set_dirty(False)
|
|
252
260
|
self.statusbar.showMessage(f'Saved project at {self.project_filename}', MESSAGE_TIMEOUT)
|
|
253
261
|
|
|
254
|
-
def save_kit(self):
|
|
255
|
-
kit = self.bashed_kit.simplified()
|
|
256
|
-
kit.default_path = dirname(self.saved_sfz_filename)
|
|
257
|
-
try:
|
|
258
|
-
kit.save_as(self.saved_sfz_filename, self.saved_sfz_samples_mode)
|
|
259
|
-
logging.debug('Saved bashed SFZ at %s', self.saved_sfz_filename)
|
|
260
|
-
self.statusbar.showMessage(f'Saved {self.saved_sfz_filename}', MESSAGE_TIMEOUT)
|
|
261
|
-
except OSError as e:
|
|
262
|
-
DevilBox('Hardlinks between devices are not allowed.\n' +\
|
|
263
|
-
'Choose a different path or sample mode.' if e.errno == 18 \
|
|
264
|
-
else str(e))
|
|
265
|
-
|
|
266
262
|
def register_recent_project(self):
|
|
267
263
|
self.recent_projects.bump(self.project_filename)
|
|
268
264
|
set_setting(KEY_RECENT_PROJECT_FOLDER, dirname(self.project_filename))
|
|
@@ -332,41 +328,10 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
332
328
|
# -----------------------------------------------------------------
|
|
333
329
|
# JACK audio / source / sink management
|
|
334
330
|
|
|
335
|
-
@pyqtSlot(int)
|
|
336
|
-
def slot_jack_ready(self,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
@pyqtSlot()
|
|
340
|
-
def slot_jack_down(self):
|
|
341
|
-
self.lbl_jack_state.setText('JACK is down')
|
|
342
|
-
|
|
343
|
-
@pyqtSlot()
|
|
344
|
-
def slot_sources_changed(self):
|
|
345
|
-
with SigBlock(self.cmb_midi_srcs):
|
|
346
|
-
self.cmb_midi_srcs.clear()
|
|
347
|
-
self.cmb_midi_srcs.addItem('')
|
|
348
|
-
for port in self.audio.conn_man.output_ports():
|
|
349
|
-
if port.is_midi:
|
|
350
|
-
self.cmb_midi_srcs.addItem(port.name)
|
|
351
|
-
if self.audio.synth.connected_midi_src_port:
|
|
352
|
-
self.cmb_midi_srcs.setCurrentText(self.audio.midi_src)
|
|
353
|
-
|
|
354
|
-
@pyqtSlot()
|
|
355
|
-
def slot_sinks_changed(self):
|
|
356
|
-
with SigBlock(self.cmb_audio_sinks):
|
|
357
|
-
self.cmb_audio_sinks.clear()
|
|
358
|
-
self.cmb_audio_sinks.addItem('')
|
|
359
|
-
valid_clients = set(
|
|
360
|
-
port.client_name for port in self.audio.conn_man.input_ports()
|
|
361
|
-
if port.is_audio )
|
|
362
|
-
for client in valid_clients:
|
|
363
|
-
self.cmb_audio_sinks.addItem(client)
|
|
364
|
-
if self.audio.synth.connected_audio_sink_ports:
|
|
365
|
-
self.cmb_audio_sinks.setCurrentText(self.audio.audio_sink)
|
|
366
|
-
|
|
367
|
-
@pyqtSlot(str)
|
|
368
|
-
def slot_midi_connected(self, port_name):
|
|
369
|
-
self.statusbar.showMessage(f'Connected to "{port_name}"', MESSAGE_TIMEOUT)
|
|
331
|
+
@pyqtSlot(bool, int)
|
|
332
|
+
def slot_jack_ready(self, state, _):
|
|
333
|
+
if not state:
|
|
334
|
+
DevilBox('JACK is down')
|
|
370
335
|
|
|
371
336
|
# -----------------------------------------------------------------
|
|
372
337
|
# Drumkit load / delete / instrument selection
|
|
@@ -402,8 +367,6 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
402
367
|
"""
|
|
403
368
|
drumkit_widget.set_drumkit(drumkit)
|
|
404
369
|
drumkit_widget.sig_inst_toggle.connect(self.slot_inst_toggle)
|
|
405
|
-
drumkit_widget.sig_note_on.connect(self.slot_note_on)
|
|
406
|
-
drumkit_widget.sig_note_off.connect(self.slot_note_off)
|
|
407
370
|
drumkit_widget.sig_remove_drumkit.connect(self.slot_remove_drumkit)
|
|
408
371
|
if self.project_loading:
|
|
409
372
|
drumkit_widget.apply_selections(
|
|
@@ -468,26 +431,23 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
468
431
|
Triggered from KitBasher signal when bashing is finished.
|
|
469
432
|
"""
|
|
470
433
|
self.bashed_kit = bashed_kit
|
|
471
|
-
|
|
472
|
-
self.bashed_kit.write(fob)
|
|
473
|
-
self.audio.synth.load(self.tempfile) # pylint: disable = no-member
|
|
434
|
+
self.audio.load_kit(self.bashed_kit)
|
|
474
435
|
self.statusbar.showMessage('Drumkit updated', MESSAGE_TIMEOUT)
|
|
475
436
|
|
|
476
|
-
@pyqtSlot(int)
|
|
477
|
-
def slot_note_on(self, pitch):
|
|
478
|
-
self.audio.synth.noteon(0, pitch, self.spn_velocity.value())
|
|
479
|
-
|
|
480
|
-
@pyqtSlot(int)
|
|
481
|
-
def slot_note_off(self, pitch):
|
|
482
|
-
self.audio.synth.noteoff(0, pitch)
|
|
483
|
-
|
|
484
437
|
# -----------------------------------------------------------------
|
|
485
438
|
# UI handling slots:
|
|
486
439
|
|
|
440
|
+
@pyqtSlot(int)
|
|
441
|
+
def slot_velocity_value_changed(self, value):
|
|
442
|
+
"""
|
|
443
|
+
Triggered by spn_velocity.valueChanged
|
|
444
|
+
"""
|
|
445
|
+
set_setting(KEY_PREVIEW_VELOCITY, value)
|
|
446
|
+
|
|
487
447
|
@pyqtSlot()
|
|
488
448
|
def slot_xruns_clicked(self):
|
|
489
449
|
"""
|
|
490
|
-
Triggered by b_xruns.click
|
|
450
|
+
Triggered by b_xruns.click
|
|
491
451
|
"""
|
|
492
452
|
self.base_xruns = self.current_xruns
|
|
493
453
|
self.b_xruns.setText('0')
|
|
@@ -616,15 +576,26 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
616
576
|
@pyqtSlot()
|
|
617
577
|
def slot_save_kit(self):
|
|
618
578
|
if self.saved_sfz_filename is None:
|
|
619
|
-
self.slot_save_kit_as()
|
|
579
|
+
return self.slot_save_kit_as()
|
|
580
|
+
try:
|
|
581
|
+
self.bashed_kit.save_as(
|
|
582
|
+
self.saved_sfz_filename, samples_mode = self.saved_sfz_samples_mode)
|
|
583
|
+
except OSError as e:
|
|
584
|
+
if e.errno == 18:
|
|
585
|
+
DevilBox('Hardlinks between devices are not allowed.\n' +
|
|
586
|
+
'Choose a different path or sample mode.')
|
|
587
|
+
elif e.errno == 2:
|
|
588
|
+
return self.slot_save_kit_as()
|
|
589
|
+
else:
|
|
590
|
+
DevilBox(str(e))
|
|
620
591
|
else:
|
|
621
|
-
self.
|
|
592
|
+
self.statusbar.showMessage(f'Saved {self.saved_sfz_filename}', MESSAGE_TIMEOUT)
|
|
593
|
+
self.update_ui()
|
|
622
594
|
|
|
623
595
|
@pyqtSlot()
|
|
624
596
|
def slot_save_kit_as(self):
|
|
625
597
|
"""
|
|
626
598
|
Triggered by "File -> Save bashed kit" menu
|
|
627
|
-
See also: slot_drumkit_bashed
|
|
628
599
|
"""
|
|
629
600
|
dlg = KitSaveDialog(self,
|
|
630
601
|
int(get_setting(KEY_SAMPLES_MODE, SAMPLES_ABSPATH)) \
|
|
@@ -633,7 +604,7 @@ class MainWindow(QMainWindow, GeometrySaver):
|
|
|
633
604
|
if dlg.exec_() and dlg.selected_file:
|
|
634
605
|
self.saved_sfz_filename = dlg.selected_file
|
|
635
606
|
self.saved_sfz_samples_mode = dlg.samples_mode
|
|
636
|
-
self.
|
|
607
|
+
self.slot_save_kit()
|
|
637
608
|
|
|
638
609
|
@pyqtSlot()
|
|
639
610
|
def slot_add_drumkit(self):
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
</spacer>
|
|
181
181
|
</item>
|
|
182
182
|
<item>
|
|
183
|
-
<layout class="QHBoxLayout" name="
|
|
183
|
+
<layout class="QHBoxLayout" name="lo_source_combo">
|
|
184
184
|
<property name="spacing">
|
|
185
185
|
<number>3</number>
|
|
186
186
|
</property>
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
</widget>
|
|
193
193
|
</item>
|
|
194
194
|
<item>
|
|
195
|
-
<widget class="QComboBox" name="
|
|
195
|
+
<widget class="QComboBox" name="cmb_midi_src_placeholder">
|
|
196
196
|
<property name="toolTip">
|
|
197
197
|
<string>Get MIDI input from this JACK client</string>
|
|
198
198
|
</property>
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
</layout>
|
|
205
205
|
</item>
|
|
206
206
|
<item>
|
|
207
|
-
<layout class="QHBoxLayout" name="
|
|
207
|
+
<layout class="QHBoxLayout" name="lo_sink_combo">
|
|
208
208
|
<property name="spacing">
|
|
209
209
|
<number>3</number>
|
|
210
210
|
</property>
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
</widget>
|
|
217
217
|
</item>
|
|
218
218
|
<item>
|
|
219
|
-
<widget class="QComboBox" name="
|
|
219
|
+
<widget class="QComboBox" name="cmb_audio_sink_placeholder">
|
|
220
220
|
<property name="toolTip">
|
|
221
221
|
<string>Send audio out to this JACK client</string>
|
|
222
222
|
</property>
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
<x>0</x>
|
|
315
315
|
<y>0</y>
|
|
316
316
|
<width>767</width>
|
|
317
|
-
<height>
|
|
317
|
+
<height>389</height>
|
|
318
318
|
</rect>
|
|
319
319
|
</property>
|
|
320
320
|
</widget>
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
<height>22</height>
|
|
332
332
|
</rect>
|
|
333
333
|
</property>
|
|
334
|
-
<widget class="QMenu" name="
|
|
334
|
+
<widget class="QMenu" name="menu_file">
|
|
335
335
|
<property name="title">
|
|
336
336
|
<string>&File</string>
|
|
337
337
|
</property>
|
|
@@ -351,11 +351,11 @@
|
|
|
351
351
|
<addaction name="menu_recent_project"/>
|
|
352
352
|
<addaction name="separator"/>
|
|
353
353
|
<addaction name="action_save_bashed_kit"/>
|
|
354
|
-
<addaction name="
|
|
354
|
+
<addaction name="action_save_bashed_kit_as"/>
|
|
355
355
|
<addaction name="separator"/>
|
|
356
356
|
<addaction name="action_quit"/>
|
|
357
357
|
</widget>
|
|
358
|
-
<widget class="QMenu" name="
|
|
358
|
+
<widget class="QMenu" name="menu_view">
|
|
359
359
|
<property name="title">
|
|
360
360
|
<string>&View</string>
|
|
361
361
|
</property>
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
<addaction name="menu_style"/>
|
|
374
374
|
<addaction name="action_reload_style"/>
|
|
375
375
|
</widget>
|
|
376
|
-
<widget class="QMenu" name="
|
|
376
|
+
<widget class="QMenu" name="menu_edit">
|
|
377
377
|
<property name="title">
|
|
378
378
|
<string>&Edit</string>
|
|
379
379
|
</property>
|
|
@@ -391,9 +391,9 @@
|
|
|
391
391
|
<addaction name="separator"/>
|
|
392
392
|
<addaction name="action_remove_all_kits"/>
|
|
393
393
|
</widget>
|
|
394
|
-
<addaction name="
|
|
395
|
-
<addaction name="
|
|
396
|
-
<addaction name="
|
|
394
|
+
<addaction name="menu_file"/>
|
|
395
|
+
<addaction name="menu_edit"/>
|
|
396
|
+
<addaction name="menu_view"/>
|
|
397
397
|
</widget>
|
|
398
398
|
<widget class="QStatusBar" name="statusbar"/>
|
|
399
399
|
<action name="action_add_drumkit">
|
|
@@ -537,7 +537,7 @@
|
|
|
537
537
|
<string>Remove All Drumkits</string>
|
|
538
538
|
</property>
|
|
539
539
|
</action>
|
|
540
|
-
<action name="
|
|
540
|
+
<action name="action_save_bashed_kit_as">
|
|
541
541
|
<property name="text">
|
|
542
542
|
<string>Save Bashed Kit &As ...</string>
|
|
543
543
|
</property>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# kitbash/kitbash/jack_audio.py
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Leon Dionne <ldionne@dridesign.sh.cn>
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
18
|
+
# MA 02110-1301, USA.
|
|
19
|
+
#
|
|
20
|
+
"""
|
|
21
|
+
Provides MainWindow of the kitbash application.
|
|
22
|
+
"""
|
|
23
|
+
import logging # pylint: disable = unused-import
|
|
24
|
+
from tempfile import mkstemp
|
|
25
|
+
from os import unlink
|
|
26
|
+
from qt_liquid_pool import LiquidPool
|
|
27
|
+
from kitbash import get_setting, set_setting, KEY_MIDI_SOURCE, KEY_AUDIO_SINK
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Audio(LiquidPool):
|
|
31
|
+
"""
|
|
32
|
+
Handles audio, including hosting LiquidSFZ instances.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
super().__init__()
|
|
37
|
+
_, self.tempfile = mkstemp(suffix='.sfz')
|
|
38
|
+
self.kit_synth = None
|
|
39
|
+
self.sig_jack_ready.connect(self.slot_jack_ready)
|
|
40
|
+
|
|
41
|
+
def quit(self):
|
|
42
|
+
super().quit()
|
|
43
|
+
unlink(self.tempfile)
|
|
44
|
+
|
|
45
|
+
def slot_jack_ready(self, state):
|
|
46
|
+
if state:
|
|
47
|
+
with open(self.tempfile, 'w', encoding = 'utf-8') as fob:
|
|
48
|
+
fob.write('// Empty\n')
|
|
49
|
+
self.kit_synth = self.create_synth(self.tempfile)
|
|
50
|
+
|
|
51
|
+
def get_preferred_midi_source(self):
|
|
52
|
+
return get_setting(KEY_MIDI_SOURCE)
|
|
53
|
+
|
|
54
|
+
def set_preferred_midi_source(self, value):
|
|
55
|
+
set_setting(KEY_MIDI_SOURCE, value)
|
|
56
|
+
super().set_preferred_midi_source(value)
|
|
57
|
+
|
|
58
|
+
def get_preferred_audio_sink(self):
|
|
59
|
+
return get_setting(KEY_AUDIO_SINK)
|
|
60
|
+
|
|
61
|
+
def set_preferred_audio_sink(self, value):
|
|
62
|
+
set_setting(KEY_AUDIO_SINK, value)
|
|
63
|
+
super().set_preferred_audio_sink(value)
|
|
64
|
+
|
|
65
|
+
def connect_midi_source(self, port):
|
|
66
|
+
"""
|
|
67
|
+
Override in order to prevent all but the main window bashed drumkit synth from
|
|
68
|
+
connecting to the midi source.
|
|
69
|
+
"""
|
|
70
|
+
self.conn_man.connect(port, self.kit_synth.input_port)
|
|
71
|
+
|
|
72
|
+
def load_kit(self, drumkit):
|
|
73
|
+
with open(self.tempfile, 'w', encoding = 'utf-8') as fob:
|
|
74
|
+
drumkit.write(fob)
|
|
75
|
+
self.kit_synth.load(self.tempfile) # pylint: disable = no-member
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# end kitbash/kitbash/jack_audio.py
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
# MA 02110-1301, USA.
|
|
19
19
|
#
|
|
20
20
|
"""
|
|
21
|
-
Provides
|
|
21
|
+
Provides worker threads that load and bash drumkits.
|
|
22
22
|
"""
|
|
23
|
-
import logging
|
|
24
23
|
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QRunnable
|
|
25
24
|
from sfzen.drumkits import Drumkit
|
|
26
25
|
|
|
@@ -36,7 +35,7 @@ class KitWorkerSignals(QObject):
|
|
|
36
35
|
|
|
37
36
|
class KitLoader(QRunnable):
|
|
38
37
|
"""
|
|
39
|
-
Loads a Drumkit in a background thread
|
|
38
|
+
Loads a Drumkit in a background thread.
|
|
40
39
|
"""
|
|
41
40
|
|
|
42
41
|
def __init__(self, drumkit_widget):
|
|
@@ -47,6 +46,7 @@ class KitLoader(QRunnable):
|
|
|
47
46
|
@pyqtSlot()
|
|
48
47
|
def run(self):
|
|
49
48
|
drumkit = Drumkit(self.drumkit_widget.sfz_filename)
|
|
49
|
+
drumkit.midi_channel = 10 if 'lochan' in drumkit.opcodes_used() else 0
|
|
50
50
|
self.signals.sig_loaded.emit(self.drumkit_widget, drumkit)
|
|
51
51
|
|
|
52
52
|
|
|
@@ -6,14 +6,12 @@ license = {file = "LICENSE"}
|
|
|
6
6
|
classifiers = ["License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"]
|
|
7
7
|
dynamic = ["version", "description"]
|
|
8
8
|
dependencies = [
|
|
9
|
+
"log_soso >= 1.0.3",
|
|
9
10
|
"PyQt5",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"midi_notes >= 1.1.0",
|
|
15
|
-
"recent_items_list >= 1.1.1",
|
|
16
|
-
"soso_qt_extras >= 1.2.0"
|
|
11
|
+
"qt_liquid_pool >= 1.2.0",
|
|
12
|
+
"recent_items_list >= 1.1.2",
|
|
13
|
+
"sfzen >= 2.3.1",
|
|
14
|
+
"soso_qt_extras >= 1.7.0"
|
|
17
15
|
]
|
|
18
16
|
|
|
19
17
|
[project.urls]
|
|
@@ -28,7 +26,7 @@ requires = ["flit_core >=3.2,<4"]
|
|
|
28
26
|
build-backend = "flit_core.buildapi"
|
|
29
27
|
|
|
30
28
|
[bumpver]
|
|
31
|
-
current_version = "1.
|
|
29
|
+
current_version = "1.7.0"
|
|
32
30
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
33
31
|
commit_message = "Bump version {old_version} -> {new_version}"
|
|
34
32
|
commit = true
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
# kitbash/kitbash/jack_audio.py
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2026 Leon Dionne <ldionne@dridesign.sh.cn>
|
|
4
|
-
#
|
|
5
|
-
# This program is free software; you can redistribute it and/or modify
|
|
6
|
-
# it under the terms of the GNU General Public License as published by
|
|
7
|
-
# the Free Software Foundation; either version 2 of the License, or
|
|
8
|
-
# (at your option) any later version.
|
|
9
|
-
#
|
|
10
|
-
# This program is distributed in the hope that it will be useful,
|
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
# GNU General Public License for more details.
|
|
14
|
-
#
|
|
15
|
-
# You should have received a copy of the GNU General Public License
|
|
16
|
-
# along with this program; if not, write to the Free Software
|
|
17
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
18
|
-
# MA 02110-1301, USA.
|
|
19
|
-
#
|
|
20
|
-
"""
|
|
21
|
-
Provides MainWindow of the kitstarter application.
|
|
22
|
-
"""
|
|
23
|
-
import logging, tempfile
|
|
24
|
-
from os import unlink
|
|
25
|
-
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QTimer
|
|
26
|
-
from liquiphy import LiquidSFZ
|
|
27
|
-
from conn_jack import JackConnectionManager, JackConnectError
|
|
28
|
-
from kitstarter import get_setting, set_setting, KEY_MIDI_SOURCE, KEY_AUDIO_SINK
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
SYNTH_NAME = 'liquidsfz'
|
|
32
|
-
AUDIO_PLAYER_CLIENT = 'kitstarter_jack_player'
|
|
33
|
-
CONNECT_RETRY_INTERVAL = 1776
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class Audio(QObject):
|
|
37
|
-
"""
|
|
38
|
-
Handles audio, including hosting an instance of liquidsfz, and playing samples.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
sig_ports_complete = pyqtSignal() # \
|
|
42
|
-
sig_sources_changed = pyqtSignal() # Used to decouple JackConnectionManager callbacks
|
|
43
|
-
sig_sinks_changed = pyqtSignal() # /
|
|
44
|
-
sig_jack_down = pyqtSignal() #
|
|
45
|
-
sig_jack_ready = pyqtSignal(int)
|
|
46
|
-
sig_midi_connected = pyqtSignal(str)
|
|
47
|
-
|
|
48
|
-
def __init__(self):
|
|
49
|
-
super().__init__()
|
|
50
|
-
self.conn_man = None
|
|
51
|
-
self.synth = None
|
|
52
|
-
_, self.tempfile = tempfile.mkstemp(suffix='.sfz')
|
|
53
|
-
self.sig_ports_complete.connect(self.slot_ports_complete, type = Qt.QueuedConnection)
|
|
54
|
-
self.sig_sources_changed.connect(self.slot_sources_changed, type = Qt.QueuedConnection)
|
|
55
|
-
self.sig_sinks_changed.connect(self.slot_sinks_changed, type = Qt.QueuedConnection)
|
|
56
|
-
self.sig_jack_down.connect(self.slot_jack_down, type = Qt.QueuedConnection)
|
|
57
|
-
self.connect_retry_timer = QTimer()
|
|
58
|
-
self.connect_retry_timer.setInterval(CONNECT_RETRY_INTERVAL)
|
|
59
|
-
self.connect_retry_timer.setSingleShot(True)
|
|
60
|
-
self.connect_retry_timer.timeout.connect(self.connect)
|
|
61
|
-
|
|
62
|
-
def connect(self):
|
|
63
|
-
try:
|
|
64
|
-
self.conn_man = JackConnectionManager()
|
|
65
|
-
except JackConnectError:
|
|
66
|
-
self.connect_retry_timer.start()
|
|
67
|
-
else:
|
|
68
|
-
self.conn_man.on_error(self.jack_error)
|
|
69
|
-
self.conn_man.on_shutdown(self.jack_shutdown)
|
|
70
|
-
self.conn_man.on_client_registration(self.jack_client_registration)
|
|
71
|
-
self.conn_man.on_port_registration(self.jack_port_registration)
|
|
72
|
-
self.synth = JackLiquidSFZ(self.tempfile)
|
|
73
|
-
self.sig_jack_ready.emit(self.conn_man.samplerate)
|
|
74
|
-
self.synth.start()
|
|
75
|
-
|
|
76
|
-
def quit(self):
|
|
77
|
-
if self.synth and hasattr(self.synth, 'quit'):
|
|
78
|
-
self.synth.ports_ready = False
|
|
79
|
-
self.synth.quit() # pylint: disable = no-member
|
|
80
|
-
unlink(self.tempfile)
|
|
81
|
-
|
|
82
|
-
# -----------------------------------------------------------------
|
|
83
|
-
# JACK callbacks
|
|
84
|
-
|
|
85
|
-
def jack_client_registration(self, client_name, action):
|
|
86
|
-
if action and not self.synth.client_name and client_name.startswith(SYNTH_NAME):
|
|
87
|
-
self.synth.client_name = client_name
|
|
88
|
-
|
|
89
|
-
def jack_port_registration(self, port, action):
|
|
90
|
-
if action and self.synth.client_name and port.name.startswith(self.synth.client_name + ':'):
|
|
91
|
-
if port.is_input and port.is_midi:
|
|
92
|
-
self.synth.input_port = port
|
|
93
|
-
elif port.is_output and port.is_audio:
|
|
94
|
-
self.synth.output_ports.append(port)
|
|
95
|
-
if self.synth.input_port and len(self.synth.output_ports) == 2:
|
|
96
|
-
self.synth.ports_ready = True
|
|
97
|
-
self.sig_ports_complete.emit()
|
|
98
|
-
if port.is_input:
|
|
99
|
-
self.sig_sinks_changed.emit()
|
|
100
|
-
else:
|
|
101
|
-
self.sig_sources_changed.emit()
|
|
102
|
-
|
|
103
|
-
def jack_error(self, error_message):
|
|
104
|
-
logging.error('JACK ERROR: "%s"', error_message)
|
|
105
|
-
|
|
106
|
-
def jack_shutdown(self):
|
|
107
|
-
logging.warning('JACK is shutting down')
|
|
108
|
-
self.sig_jack_down.emit()
|
|
109
|
-
|
|
110
|
-
@pyqtSlot()
|
|
111
|
-
def slot_jack_down(self):
|
|
112
|
-
"""
|
|
113
|
-
Triggered by sig_jack_down emitted from another thread in "jack_shutdown"
|
|
114
|
-
"""
|
|
115
|
-
self.conn_man.close()
|
|
116
|
-
self.conn_man = None
|
|
117
|
-
self.connect_retry_timer.start()
|
|
118
|
-
|
|
119
|
-
# -----------------------------------------------------------------
|
|
120
|
-
# Source / sink management
|
|
121
|
-
|
|
122
|
-
@pyqtSlot()
|
|
123
|
-
def slot_ports_complete(self):
|
|
124
|
-
"""
|
|
125
|
-
Triggered by sig_ports_complete, emitted in "jack_port_registration".
|
|
126
|
-
"""
|
|
127
|
-
self.connect_midi_source()
|
|
128
|
-
self.connect_audio_sinks()
|
|
129
|
-
|
|
130
|
-
@pyqtSlot(str)
|
|
131
|
-
def slot_midi_src_selected(self, value):
|
|
132
|
-
"""
|
|
133
|
-
Triggered from combo box selection
|
|
134
|
-
"""
|
|
135
|
-
self.midi_src = value
|
|
136
|
-
|
|
137
|
-
@pyqtSlot(str)
|
|
138
|
-
def slot_audio_sink_selected(self, value):
|
|
139
|
-
"""
|
|
140
|
-
Triggered from combo box selection
|
|
141
|
-
"""
|
|
142
|
-
self.audio_sink = value
|
|
143
|
-
|
|
144
|
-
@pyqtSlot()
|
|
145
|
-
def slot_sources_changed(self):
|
|
146
|
-
"""
|
|
147
|
-
Triggered by sig_sources_changed, emitted in "jack_port_registration".
|
|
148
|
-
"""
|
|
149
|
-
if self.synth.ports_ready:
|
|
150
|
-
self.connect_midi_source()
|
|
151
|
-
|
|
152
|
-
@pyqtSlot()
|
|
153
|
-
def slot_sinks_changed(self):
|
|
154
|
-
"""
|
|
155
|
-
Triggered by sig_sinks_changed, emitted in "jack_port_registration".
|
|
156
|
-
"""
|
|
157
|
-
if self.synth.ports_ready:
|
|
158
|
-
self.connect_audio_sinks()
|
|
159
|
-
|
|
160
|
-
@property
|
|
161
|
-
def midi_src(self):
|
|
162
|
-
return get_setting(KEY_MIDI_SOURCE)
|
|
163
|
-
|
|
164
|
-
@midi_src.setter
|
|
165
|
-
def midi_src(self, value):
|
|
166
|
-
set_setting(KEY_MIDI_SOURCE, value)
|
|
167
|
-
self.connect_midi_source()
|
|
168
|
-
|
|
169
|
-
@property
|
|
170
|
-
def audio_sink(self):
|
|
171
|
-
return get_setting(KEY_AUDIO_SINK)
|
|
172
|
-
|
|
173
|
-
@audio_sink.setter
|
|
174
|
-
def audio_sink(self, value):
|
|
175
|
-
set_setting(KEY_AUDIO_SINK, value)
|
|
176
|
-
self.connect_audio_sinks()
|
|
177
|
-
|
|
178
|
-
def connect_midi_source(self):
|
|
179
|
-
if self.synth and self.synth.ports_ready:
|
|
180
|
-
# Look for source port if midi_src has a str value:
|
|
181
|
-
if self.midi_src:
|
|
182
|
-
src_port = self.conn_man.get_port_by_name(self.midi_src)
|
|
183
|
-
# No need to disconnect / reconnect if they are equal:
|
|
184
|
-
if src_port == self.synth.connected_midi_src_port:
|
|
185
|
-
return
|
|
186
|
-
else:
|
|
187
|
-
src_port = None
|
|
188
|
-
# Disconnect existing:
|
|
189
|
-
if self.synth.connected_midi_src_port:
|
|
190
|
-
self.conn_man.disconnect(self.synth.connected_midi_src_port, self.synth.input_port)
|
|
191
|
-
# Connect if midi_src has a str value:
|
|
192
|
-
if src_port:
|
|
193
|
-
self.conn_man.connect(src_port, self.synth.input_port)
|
|
194
|
-
self.sig_midi_connected.emit(src_port.name)
|
|
195
|
-
# Update connected port (may be none):
|
|
196
|
-
self.synth.connected_midi_src_port = src_port
|
|
197
|
-
|
|
198
|
-
def connect_audio_sinks(self):
|
|
199
|
-
if self.synth and self.synth.ports_ready:
|
|
200
|
-
# Look for target ports if audio_sink has a str value:
|
|
201
|
-
if self.audio_sink:
|
|
202
|
-
tgt_ports = [ port for port in self.conn_man.get_client_ports(self.audio_sink)
|
|
203
|
-
if port.is_audio and port.is_input ]
|
|
204
|
-
# No need to disconnect / reconnect if they are equal:
|
|
205
|
-
if tgt_ports == self.synth.connected_audio_sink_ports:
|
|
206
|
-
return
|
|
207
|
-
else:
|
|
208
|
-
tgt_ports = []
|
|
209
|
-
# Disconnect existing:
|
|
210
|
-
if self.synth.connected_audio_sink_ports:
|
|
211
|
-
for src, tgt in zip(
|
|
212
|
-
self.synth.output_ports, self.synth.connected_audio_sink_ports):
|
|
213
|
-
self.conn_man.disconnect(src, tgt)
|
|
214
|
-
# Connect if audio_sink has a str value:
|
|
215
|
-
if tgt_ports:
|
|
216
|
-
for src, tgt in zip(self.synth.output_ports, tgt_ports):
|
|
217
|
-
self.conn_man.connect(src, tgt)
|
|
218
|
-
# Update connected ports (may be none):
|
|
219
|
-
self.synth.connected_audio_sink_ports = tgt_ports
|
|
220
|
-
|
|
221
|
-
def load_kit(self, kit):
|
|
222
|
-
with open(self.tempfile, 'w', encoding = 'utf-8') as fob:
|
|
223
|
-
kit.write(fob)
|
|
224
|
-
self.synth.load(self.tempfile) # pylint: disable = no-member
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
class JackLiquidSFZ(LiquidSFZ):
|
|
228
|
-
"""
|
|
229
|
-
Wraps a LiquidSFZ instance in order to hold references to jacklib ports created
|
|
230
|
-
by JackConnectionManager.
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
def __init__(self, filename):
|
|
234
|
-
self.client_name = None
|
|
235
|
-
self.ports_ready = False
|
|
236
|
-
self.input_port = None
|
|
237
|
-
self.output_ports = []
|
|
238
|
-
self.connected_midi_src_port = None
|
|
239
|
-
self.connected_audio_sink_ports = []
|
|
240
|
-
super().__init__(filename, defer_start = True)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# end kitbash/kitbash/jack_audio.py
|
|
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
|