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.
Files changed (26) hide show
  1. {kitstarter-0.2.0 → kitstarter-0.2.2}/PKG-INFO +1 -1
  2. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/__init__.py +1 -1
  3. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/main_window.py +76 -66
  4. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/samples_widget.py +2 -3
  5. {kitstarter-0.2.0 → kitstarter-0.2.2}/pyproject.toml +1 -1
  6. kitstarter-0.2.0/bin/kitstarter +0 -6
  7. {kitstarter-0.2.0 → kitstarter-0.2.2}/.gitignore +0 -0
  8. {kitstarter-0.2.0 → kitstarter-0.2.2}/LICENSE +0 -0
  9. {kitstarter-0.2.0 → kitstarter-0.2.2}/README.md +0 -0
  10. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/__init__.py +0 -0
  11. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/gui/main_window.ui +0 -0
  12. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/pindb.py +0 -0
  13. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-down-disabled.svg +0 -0
  14. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-down-enabled.svg +0 -0
  15. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-up-disabled.svg +0 -0
  16. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/arrow-up-enabled.svg +0 -0
  17. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/delete.svg +0 -0
  18. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/empty.sfz +0 -0
  19. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/inst-complete.svg +0 -0
  20. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/inst-incomplete.svg +0 -0
  21. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/pin.svg +0 -0
  22. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/sample-mismatch.svg +0 -0
  23. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/res/sample-okay.svg +0 -0
  24. {kitstarter-0.2.0 → kitstarter-0.2.2}/kitstarter/starter_kits.py +0 -0
  25. {kitstarter-0.2.0 → kitstarter-0.2.2}/tests/pindb.py +0 -0
  26. {kitstarter-0.2.0 → kitstarter-0.2.2}/tests/samples_widget.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kitstarter
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: kitstarter is a program you can use to "sketch in" a drumkit SFZ file.
5
5
  Author-email: Leon Dionne <ldionne@dridesign.sh.cn>
6
6
  Description-Content-Type: text/markdown
@@ -15,7 +15,7 @@ from PyQt5.QtWidgets import QApplication, QWidget, QSplitter
15
15
  from qt_extras import DevilBox
16
16
  from conn_jack import JackConnectError
17
17
 
18
- __version__ = "0.2.0"
18
+ __version__ = "0.2.2"
19
19
 
20
20
 
21
21
  APPLICATION_NAME = "KitStarter"
@@ -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
- # Startup paths
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.slot_midi_src_changed)
128
- self.cmb_audio_sinks.currentTextChanged.connect(self.slot_audio_sink_changed)
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 action:
176
- if SYNTH_NAME in client_name:
177
- self.synth.client_name = client_name
178
- else:
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 action and self.synth.client_name in port.name:
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
- else:
195
- if port.is_output and port.is_midi:
196
- self.fill_cmb_sources()
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
- Called in response to sig_ports_complete since sig_ports_complete, emitted in
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
- # Source / sink management
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 fill_cmb_sources(self):
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 midi_src := settings().value(KEY_MIDI_SOURCE):
220
- if self.cmb_midi_srcs.findText(midi_src, Qt.MatchExactly):
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 fill_cmb_sinks(self):
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 audio_sink := settings().value(KEY_AUDIO_SINK):
233
- if self.cmb_audio_sinks.findText(audio_sink, Qt.MatchExactly):
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 slot_midi_src_changed(self, value):
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 slot_audio_sink_changed(self, value):
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 * 100))
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 / 100
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.0"
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
@@ -1,6 +0,0 @@
1
- #!/usr/bin/python3
2
- # -*- coding: utf-8 -*-
3
- if __name__ == '__main__':
4
- import sys
5
- from kitstarter import main
6
- sys.exit(main())
File without changes
File without changes
File without changes
File without changes