kitbash 1.0.1__py2.py3-none-any.whl
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/__init__.py +113 -0
- kitbash/gui/__init__.py +92 -0
- kitbash/gui/main_window.py +1263 -0
- kitbash/gui/main_window.ui +433 -0
- kitbash/gui/samples_explorer.py +200 -0
- kitbash/gui/samples_explorer.ui +126 -0
- kitbash/res/group_expanded.svg +2 -0
- kitbash/res/group_hidden.svg +2 -0
- kitbash/res/kitbash-icon.png +0 -0
- kitbash/scripts/bash_project.py +97 -0
- kitbash/styles/light.css +135 -0
- kitbash/styles/system.css +65 -0
- kitbash-1.0.1.dist-info/METADATA +25 -0
- kitbash-1.0.1.dist-info/RECORD +17 -0
- kitbash-1.0.1.dist-info/WHEEL +5 -0
- kitbash-1.0.1.dist-info/entry_points.txt +4 -0
- kitbash-1.0.1.dist-info/licenses/LICENSE +619 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ui version="4.0">
|
|
3
|
+
<class>MainWindow</class>
|
|
4
|
+
<widget class="QMainWindow" name="MainWindow">
|
|
5
|
+
<property name="geometry">
|
|
6
|
+
<rect>
|
|
7
|
+
<x>0</x>
|
|
8
|
+
<y>0</y>
|
|
9
|
+
<width>982</width>
|
|
10
|
+
<height>768</height>
|
|
11
|
+
</rect>
|
|
12
|
+
</property>
|
|
13
|
+
<property name="windowTitle">
|
|
14
|
+
<string>kitbash</string>
|
|
15
|
+
</property>
|
|
16
|
+
<widget class="QWidget" name="centralwidget">
|
|
17
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
|
18
|
+
<property name="spacing">
|
|
19
|
+
<number>2</number>
|
|
20
|
+
</property>
|
|
21
|
+
<property name="leftMargin">
|
|
22
|
+
<number>1</number>
|
|
23
|
+
</property>
|
|
24
|
+
<property name="topMargin">
|
|
25
|
+
<number>0</number>
|
|
26
|
+
</property>
|
|
27
|
+
<property name="rightMargin">
|
|
28
|
+
<number>1</number>
|
|
29
|
+
</property>
|
|
30
|
+
<item>
|
|
31
|
+
<widget class="QFrame" name="frm_controls">
|
|
32
|
+
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
33
|
+
<property name="spacing">
|
|
34
|
+
<number>9</number>
|
|
35
|
+
</property>
|
|
36
|
+
<property name="leftMargin">
|
|
37
|
+
<number>4</number>
|
|
38
|
+
</property>
|
|
39
|
+
<property name="topMargin">
|
|
40
|
+
<number>4</number>
|
|
41
|
+
</property>
|
|
42
|
+
<property name="rightMargin">
|
|
43
|
+
<number>4</number>
|
|
44
|
+
</property>
|
|
45
|
+
<property name="bottomMargin">
|
|
46
|
+
<number>2</number>
|
|
47
|
+
</property>
|
|
48
|
+
<item>
|
|
49
|
+
<widget class="QPushButton" name="b_save_kit">
|
|
50
|
+
<property name="sizePolicy">
|
|
51
|
+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
52
|
+
<horstretch>0</horstretch>
|
|
53
|
+
<verstretch>0</verstretch>
|
|
54
|
+
</sizepolicy>
|
|
55
|
+
</property>
|
|
56
|
+
<property name="text">
|
|
57
|
+
<string/>
|
|
58
|
+
</property>
|
|
59
|
+
<property name="icon">
|
|
60
|
+
<iconset theme="document-save">
|
|
61
|
+
<normaloff>.</normaloff>.</iconset>
|
|
62
|
+
</property>
|
|
63
|
+
</widget>
|
|
64
|
+
</item>
|
|
65
|
+
<item>
|
|
66
|
+
<widget class="QLabel" name="lbl_bashed_sfz_filename">
|
|
67
|
+
<property name="minimumSize">
|
|
68
|
+
<size>
|
|
69
|
+
<width>35</width>
|
|
70
|
+
<height>0</height>
|
|
71
|
+
</size>
|
|
72
|
+
</property>
|
|
73
|
+
<property name="text">
|
|
74
|
+
<string/>
|
|
75
|
+
</property>
|
|
76
|
+
</widget>
|
|
77
|
+
</item>
|
|
78
|
+
<item>
|
|
79
|
+
<widget class="QPushButton" name="b_copy_path">
|
|
80
|
+
<property name="enabled">
|
|
81
|
+
<bool>true</bool>
|
|
82
|
+
</property>
|
|
83
|
+
<property name="text">
|
|
84
|
+
<string/>
|
|
85
|
+
</property>
|
|
86
|
+
<property name="icon">
|
|
87
|
+
<iconset theme="edit-copy"/>
|
|
88
|
+
</property>
|
|
89
|
+
</widget>
|
|
90
|
+
</item>
|
|
91
|
+
<item>
|
|
92
|
+
<spacer name="horizontalSpacer">
|
|
93
|
+
<property name="orientation">
|
|
94
|
+
<enum>Qt::Horizontal</enum>
|
|
95
|
+
</property>
|
|
96
|
+
<property name="sizeHint" stdset="0">
|
|
97
|
+
<size>
|
|
98
|
+
<width>287</width>
|
|
99
|
+
<height>20</height>
|
|
100
|
+
</size>
|
|
101
|
+
</property>
|
|
102
|
+
</spacer>
|
|
103
|
+
</item>
|
|
104
|
+
<item>
|
|
105
|
+
<layout class="QHBoxLayout" name="hz1">
|
|
106
|
+
<property name="spacing">
|
|
107
|
+
<number>3</number>
|
|
108
|
+
</property>
|
|
109
|
+
<item>
|
|
110
|
+
<widget class="QLabel" name="lbl1">
|
|
111
|
+
<property name="text">
|
|
112
|
+
<string>Source:</string>
|
|
113
|
+
</property>
|
|
114
|
+
</widget>
|
|
115
|
+
</item>
|
|
116
|
+
<item>
|
|
117
|
+
<widget class="QComboBox" name="cmb_midi_srcs"/>
|
|
118
|
+
</item>
|
|
119
|
+
</layout>
|
|
120
|
+
</item>
|
|
121
|
+
<item>
|
|
122
|
+
<layout class="QHBoxLayout" name="hz2">
|
|
123
|
+
<property name="spacing">
|
|
124
|
+
<number>3</number>
|
|
125
|
+
</property>
|
|
126
|
+
<item>
|
|
127
|
+
<widget class="QLabel" name="lbl2">
|
|
128
|
+
<property name="text">
|
|
129
|
+
<string>Sink:</string>
|
|
130
|
+
</property>
|
|
131
|
+
</widget>
|
|
132
|
+
</item>
|
|
133
|
+
<item>
|
|
134
|
+
<widget class="QComboBox" name="cmb_audio_sinks"/>
|
|
135
|
+
</item>
|
|
136
|
+
</layout>
|
|
137
|
+
</item>
|
|
138
|
+
<item>
|
|
139
|
+
<layout class="QHBoxLayout" name="hz3">
|
|
140
|
+
<property name="spacing">
|
|
141
|
+
<number>3</number>
|
|
142
|
+
</property>
|
|
143
|
+
<item>
|
|
144
|
+
<widget class="QLabel" name="lbl3">
|
|
145
|
+
<property name="text">
|
|
146
|
+
<string>Velocity:</string>
|
|
147
|
+
</property>
|
|
148
|
+
</widget>
|
|
149
|
+
</item>
|
|
150
|
+
<item>
|
|
151
|
+
<widget class="QSpinBox" name="spn_velocity">
|
|
152
|
+
<property name="maximum">
|
|
153
|
+
<number>127</number>
|
|
154
|
+
</property>
|
|
155
|
+
<property name="value">
|
|
156
|
+
<number>80</number>
|
|
157
|
+
</property>
|
|
158
|
+
</widget>
|
|
159
|
+
</item>
|
|
160
|
+
</layout>
|
|
161
|
+
</item>
|
|
162
|
+
<item>
|
|
163
|
+
<layout class="QHBoxLayout" name="hz4">
|
|
164
|
+
<property name="spacing">
|
|
165
|
+
<number>3</number>
|
|
166
|
+
</property>
|
|
167
|
+
<item>
|
|
168
|
+
<widget class="QLabel" name="lbl4">
|
|
169
|
+
<property name="text">
|
|
170
|
+
<string>Xruns:</string>
|
|
171
|
+
</property>
|
|
172
|
+
</widget>
|
|
173
|
+
</item>
|
|
174
|
+
<item>
|
|
175
|
+
<widget class="QPushButton" name="b_xruns">
|
|
176
|
+
<property name="maximumSize">
|
|
177
|
+
<size>
|
|
178
|
+
<width>42</width>
|
|
179
|
+
<height>16777215</height>
|
|
180
|
+
</size>
|
|
181
|
+
</property>
|
|
182
|
+
<property name="text">
|
|
183
|
+
<string>0</string>
|
|
184
|
+
</property>
|
|
185
|
+
</widget>
|
|
186
|
+
</item>
|
|
187
|
+
</layout>
|
|
188
|
+
</item>
|
|
189
|
+
</layout>
|
|
190
|
+
</widget>
|
|
191
|
+
</item>
|
|
192
|
+
<item>
|
|
193
|
+
<widget class="Line" name="line">
|
|
194
|
+
<property name="orientation">
|
|
195
|
+
<enum>Qt::Horizontal</enum>
|
|
196
|
+
</property>
|
|
197
|
+
</widget>
|
|
198
|
+
</item>
|
|
199
|
+
<item>
|
|
200
|
+
<widget class="QScrollArea" name="drums_scroller">
|
|
201
|
+
<property name="sizePolicy">
|
|
202
|
+
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
|
203
|
+
<horstretch>0</horstretch>
|
|
204
|
+
<verstretch>0</verstretch>
|
|
205
|
+
</sizepolicy>
|
|
206
|
+
</property>
|
|
207
|
+
<property name="frameShape">
|
|
208
|
+
<enum>QFrame::NoFrame</enum>
|
|
209
|
+
</property>
|
|
210
|
+
<property name="widgetResizable">
|
|
211
|
+
<bool>true</bool>
|
|
212
|
+
</property>
|
|
213
|
+
<widget class="QWidget" name="kits_area">
|
|
214
|
+
<property name="geometry">
|
|
215
|
+
<rect>
|
|
216
|
+
<x>0</x>
|
|
217
|
+
<y>0</y>
|
|
218
|
+
<width>980</width>
|
|
219
|
+
<height>696</height>
|
|
220
|
+
</rect>
|
|
221
|
+
</property>
|
|
222
|
+
</widget>
|
|
223
|
+
</widget>
|
|
224
|
+
</item>
|
|
225
|
+
</layout>
|
|
226
|
+
</widget>
|
|
227
|
+
<widget class="QMenuBar" name="menubar">
|
|
228
|
+
<property name="geometry">
|
|
229
|
+
<rect>
|
|
230
|
+
<x>0</x>
|
|
231
|
+
<y>0</y>
|
|
232
|
+
<width>982</width>
|
|
233
|
+
<height>22</height>
|
|
234
|
+
</rect>
|
|
235
|
+
</property>
|
|
236
|
+
<widget class="QMenu" name="menu_File">
|
|
237
|
+
<property name="title">
|
|
238
|
+
<string>&File</string>
|
|
239
|
+
</property>
|
|
240
|
+
<widget class="QMenu" name="menu_recent_project">
|
|
241
|
+
<property name="title">
|
|
242
|
+
<string>&Recent Projects ...</string>
|
|
243
|
+
</property>
|
|
244
|
+
</widget>
|
|
245
|
+
<widget class="QMenu" name="menu_recent_drumkits">
|
|
246
|
+
<property name="title">
|
|
247
|
+
<string>Recent &Drumkits</string>
|
|
248
|
+
</property>
|
|
249
|
+
</widget>
|
|
250
|
+
<addaction name="action_new_project"/>
|
|
251
|
+
<addaction name="action_open_project"/>
|
|
252
|
+
<addaction name="action_save_project"/>
|
|
253
|
+
<addaction name="action_save_project_as"/>
|
|
254
|
+
<addaction name="menu_recent_project"/>
|
|
255
|
+
<addaction name="separator"/>
|
|
256
|
+
<addaction name="action_save_bashed_kit"/>
|
|
257
|
+
<addaction name="action_save_kit_as"/>
|
|
258
|
+
<addaction name="separator"/>
|
|
259
|
+
<addaction name="menu_recent_drumkits"/>
|
|
260
|
+
<addaction name="separator"/>
|
|
261
|
+
<addaction name="action_quit"/>
|
|
262
|
+
</widget>
|
|
263
|
+
<widget class="QMenu" name="menu_View">
|
|
264
|
+
<property name="title">
|
|
265
|
+
<string>&View</string>
|
|
266
|
+
</property>
|
|
267
|
+
<widget class="QMenu" name="menu_style">
|
|
268
|
+
<property name="title">
|
|
269
|
+
<string>St&yle</string>
|
|
270
|
+
</property>
|
|
271
|
+
</widget>
|
|
272
|
+
<addaction name="action_collapse_kits"/>
|
|
273
|
+
<addaction name="separator"/>
|
|
274
|
+
<addaction name="menu_style"/>
|
|
275
|
+
<addaction name="action_reload_style"/>
|
|
276
|
+
</widget>
|
|
277
|
+
<widget class="QMenu" name="menu_Edit">
|
|
278
|
+
<property name="title">
|
|
279
|
+
<string>&Edit</string>
|
|
280
|
+
</property>
|
|
281
|
+
<addaction name="action_add_drumkit"/>
|
|
282
|
+
<addaction name="action_remove_all_kits"/>
|
|
283
|
+
</widget>
|
|
284
|
+
<addaction name="menu_File"/>
|
|
285
|
+
<addaction name="menu_Edit"/>
|
|
286
|
+
<addaction name="menu_View"/>
|
|
287
|
+
</widget>
|
|
288
|
+
<action name="action_add_drumkit">
|
|
289
|
+
<property name="text">
|
|
290
|
+
<string>Load Drum&Kit</string>
|
|
291
|
+
</property>
|
|
292
|
+
<property name="toolTip">
|
|
293
|
+
<string>Load an SFZ drumkit</string>
|
|
294
|
+
</property>
|
|
295
|
+
<property name="shortcut">
|
|
296
|
+
<string>Ctrl+O</string>
|
|
297
|
+
</property>
|
|
298
|
+
</action>
|
|
299
|
+
<action name="action_quit">
|
|
300
|
+
<property name="text">
|
|
301
|
+
<string>&Quit</string>
|
|
302
|
+
</property>
|
|
303
|
+
<property name="toolTip">
|
|
304
|
+
<string>Quit after being prompted to save changes</string>
|
|
305
|
+
</property>
|
|
306
|
+
<property name="shortcut">
|
|
307
|
+
<string>Ctrl+Q</string>
|
|
308
|
+
</property>
|
|
309
|
+
</action>
|
|
310
|
+
<action name="action_new_project">
|
|
311
|
+
<property name="text">
|
|
312
|
+
<string>&New</string>
|
|
313
|
+
</property>
|
|
314
|
+
<property name="toolTip">
|
|
315
|
+
<string>Create a new project</string>
|
|
316
|
+
</property>
|
|
317
|
+
<property name="shortcut">
|
|
318
|
+
<string>Ctrl+N</string>
|
|
319
|
+
</property>
|
|
320
|
+
</action>
|
|
321
|
+
<action name="action_save_project">
|
|
322
|
+
<property name="text">
|
|
323
|
+
<string>Save &Project</string>
|
|
324
|
+
</property>
|
|
325
|
+
<property name="toolTip">
|
|
326
|
+
<string>Save the current project</string>
|
|
327
|
+
</property>
|
|
328
|
+
</action>
|
|
329
|
+
<action name="action_reload_style">
|
|
330
|
+
<property name="text">
|
|
331
|
+
<string>Reload style</string>
|
|
332
|
+
</property>
|
|
333
|
+
<property name="toolTip">
|
|
334
|
+
<string>Reload the current style, reflecting changes</string>
|
|
335
|
+
</property>
|
|
336
|
+
<property name="shortcut">
|
|
337
|
+
<string>F5</string>
|
|
338
|
+
</property>
|
|
339
|
+
</action>
|
|
340
|
+
<action name="action_collapse_kits">
|
|
341
|
+
<property name="text">
|
|
342
|
+
<string>Collapse &Kits</string>
|
|
343
|
+
</property>
|
|
344
|
+
<property name="toolTip">
|
|
345
|
+
<string>Collapse all drumkit widgets</string>
|
|
346
|
+
</property>
|
|
347
|
+
<property name="shortcut">
|
|
348
|
+
<string>F4</string>
|
|
349
|
+
</property>
|
|
350
|
+
</action>
|
|
351
|
+
<action name="action_open_project">
|
|
352
|
+
<property name="text">
|
|
353
|
+
<string>&Open project</string>
|
|
354
|
+
</property>
|
|
355
|
+
<property name="toolTip">
|
|
356
|
+
<string>Open a kitbash project</string>
|
|
357
|
+
</property>
|
|
358
|
+
<property name="shortcut">
|
|
359
|
+
<string>Ctrl+Shift+O</string>
|
|
360
|
+
</property>
|
|
361
|
+
</action>
|
|
362
|
+
<action name="action_save_bashed_kit">
|
|
363
|
+
<property name="text">
|
|
364
|
+
<string>&Save bashed kit</string>
|
|
365
|
+
</property>
|
|
366
|
+
<property name="toolTip">
|
|
367
|
+
<string>Save the current instrument selections as a new .sfz</string>
|
|
368
|
+
</property>
|
|
369
|
+
<property name="shortcut">
|
|
370
|
+
<string>Ctrl+S</string>
|
|
371
|
+
</property>
|
|
372
|
+
</action>
|
|
373
|
+
<action name="action_save_project_as">
|
|
374
|
+
<property name="text">
|
|
375
|
+
<string>Save Project As</string>
|
|
376
|
+
</property>
|
|
377
|
+
<property name="toolTip">
|
|
378
|
+
<string>Save the kitbash project under a different name</string>
|
|
379
|
+
</property>
|
|
380
|
+
<property name="shortcut">
|
|
381
|
+
<string>Ctrl+Shift+A</string>
|
|
382
|
+
</property>
|
|
383
|
+
</action>
|
|
384
|
+
<action name="action_remove_all_kits">
|
|
385
|
+
<property name="text">
|
|
386
|
+
<string>Remove All Drumkits</string>
|
|
387
|
+
</property>
|
|
388
|
+
</action>
|
|
389
|
+
<action name="action_save_kit_as">
|
|
390
|
+
<property name="text">
|
|
391
|
+
<string>Save Bashed Kit &As ...</string>
|
|
392
|
+
</property>
|
|
393
|
+
<property name="shortcut">
|
|
394
|
+
<string>Ctrl+Shift+S</string>
|
|
395
|
+
</property>
|
|
396
|
+
</action>
|
|
397
|
+
</widget>
|
|
398
|
+
<resources/>
|
|
399
|
+
<connections>
|
|
400
|
+
<connection>
|
|
401
|
+
<sender>action_quit</sender>
|
|
402
|
+
<signal>triggered()</signal>
|
|
403
|
+
<receiver>MainWindow</receiver>
|
|
404
|
+
<slot>close()</slot>
|
|
405
|
+
<hints>
|
|
406
|
+
<hint type="sourcelabel">
|
|
407
|
+
<x>-1</x>
|
|
408
|
+
<y>-1</y>
|
|
409
|
+
</hint>
|
|
410
|
+
<hint type="destinationlabel">
|
|
411
|
+
<x>482</x>
|
|
412
|
+
<y>227</y>
|
|
413
|
+
</hint>
|
|
414
|
+
</hints>
|
|
415
|
+
</connection>
|
|
416
|
+
<connection>
|
|
417
|
+
<sender>b_save_kit</sender>
|
|
418
|
+
<signal>clicked()</signal>
|
|
419
|
+
<receiver>action_save_bashed_kit</receiver>
|
|
420
|
+
<slot>trigger()</slot>
|
|
421
|
+
<hints>
|
|
422
|
+
<hint type="sourcelabel">
|
|
423
|
+
<x>111</x>
|
|
424
|
+
<y>38</y>
|
|
425
|
+
</hint>
|
|
426
|
+
<hint type="destinationlabel">
|
|
427
|
+
<x>-1</x>
|
|
428
|
+
<y>-1</y>
|
|
429
|
+
</hint>
|
|
430
|
+
</hints>
|
|
431
|
+
</connection>
|
|
432
|
+
</connections>
|
|
433
|
+
</ui>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# kitbash/gui/samples_explorer.py
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 liyang <liyang@veronica>
|
|
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
|
+
import os, sys, logging
|
|
21
|
+
from functools import lru_cache
|
|
22
|
+
|
|
23
|
+
from PyQt5 import uic
|
|
24
|
+
from PyQt5.QtCore import Qt, pyqtSlot, QPoint, QDir, QItemSelection
|
|
25
|
+
from PyQt5.QtGui import QIcon
|
|
26
|
+
from PyQt5.QtWidgets import QDialog, QListWidget, QListWidgetItem, QFileSystemModel, \
|
|
27
|
+
QMenu, QApplication
|
|
28
|
+
|
|
29
|
+
import soundfile as sf
|
|
30
|
+
from midi_notes import MIDI_DRUM_NAMES, MIDI_DRUM_IDS
|
|
31
|
+
from jack import JackError
|
|
32
|
+
from jack_audio_player import JackAudioPlayer
|
|
33
|
+
from qt_extras import ShutUpQT
|
|
34
|
+
from sfzen.drumkits import Drumkit
|
|
35
|
+
|
|
36
|
+
from kitbash import settings, set_application_style, \
|
|
37
|
+
KEY_SAMPLE_XPLORE_ROOT, KEY_SAMPLE_XPLORE_CURR
|
|
38
|
+
from kitbash.gui import GeometrySaver
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SamplesExplorer(QDialog, GeometrySaver):
|
|
42
|
+
|
|
43
|
+
def __init__(self, parent):
|
|
44
|
+
super().__init__(parent)
|
|
45
|
+
with ShutUpQT():
|
|
46
|
+
uic.loadUi(os.path.join(os.path.dirname(__file__), 'samples_explorer.ui'), self)
|
|
47
|
+
self.restore_geometry()
|
|
48
|
+
self.finished.connect(self.save_geometry)
|
|
49
|
+
self.audio_player = JackAudioPlayer()
|
|
50
|
+
root_path = settings().value(KEY_SAMPLE_XPLORE_ROOT, QDir.homePath())
|
|
51
|
+
current_path = settings().value(KEY_SAMPLE_XPLORE_CURR, QDir.homePath())
|
|
52
|
+
self.files_model = QFileSystemModel()
|
|
53
|
+
self.files_model.setRootPath(root_path)
|
|
54
|
+
self.files_model.setNameFilters(['*.sfz'])
|
|
55
|
+
self.tree_files.setModel(self.files_model)
|
|
56
|
+
self.tree_files.hideColumn(1)
|
|
57
|
+
self.tree_files.hideColumn(2)
|
|
58
|
+
self.tree_files.hideColumn(3)
|
|
59
|
+
self.tree_files.setRootIndex(self.files_model.index(root_path))
|
|
60
|
+
index = self.files_model.index(current_path)
|
|
61
|
+
self.tree_files.setCurrentIndex(index)
|
|
62
|
+
self.tree_files.scrollTo(index, 1)
|
|
63
|
+
for pitch, inst_id in MIDI_DRUM_IDS.items():
|
|
64
|
+
list_item = QListWidgetItem(self.lst_instruments)
|
|
65
|
+
list_item.setData(Qt.UserRole, inst_id)
|
|
66
|
+
list_item.setText(MIDI_DRUM_NAMES[pitch])
|
|
67
|
+
self.instrument_list_ids = [
|
|
68
|
+
list_item.data(Qt.UserRole) \
|
|
69
|
+
for list_item in self.iterate_instrument_list()
|
|
70
|
+
]
|
|
71
|
+
self.instrument_list_drumkit_paths = [
|
|
72
|
+
[] for row in range(self.lst_instruments.count())
|
|
73
|
+
]
|
|
74
|
+
self.reset_instrument_selections()
|
|
75
|
+
# Connect signals
|
|
76
|
+
self.tree_files.selectionModel().selectionChanged.connect(self.slot_tree_selection_changed)
|
|
77
|
+
self.tree_files.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
78
|
+
self.tree_files.customContextMenuRequested.connect(self.slot_files_context_menu)
|
|
79
|
+
self.lst_instruments.currentItemChanged.connect(self.slot_inst_current_changed)
|
|
80
|
+
self.lst_samples.itemPressed.connect(self.slot_sample_pressed)
|
|
81
|
+
self.lst_samples.mouseReleaseEvent = self.samples_mouse_release
|
|
82
|
+
self.lst_samples.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
83
|
+
self.lst_samples.customContextMenuRequested.connect(self.slot_samples_context_menu)
|
|
84
|
+
# Populate samples, if applicable
|
|
85
|
+
#self.slot_tree_selection_changed(index)
|
|
86
|
+
|
|
87
|
+
def iterate_instrument_list(self):
|
|
88
|
+
for row in range(self.lst_instruments.count()):
|
|
89
|
+
yield self.lst_instruments.item(row)
|
|
90
|
+
|
|
91
|
+
@lru_cache
|
|
92
|
+
def drumkit(self, path):
|
|
93
|
+
return Drumkit(path)
|
|
94
|
+
|
|
95
|
+
@pyqtSlot(QPoint)
|
|
96
|
+
def slot_files_context_menu(self, position):
|
|
97
|
+
indexes = self.tree_files.selectedIndexes()
|
|
98
|
+
if len(indexes):
|
|
99
|
+
menu = QMenu(self)
|
|
100
|
+
menu.addAction('Copy path' if len(indexes) == 1 else 'Copy paths')
|
|
101
|
+
if menu.exec(self.tree_files.mapToGlobal(position)):
|
|
102
|
+
QApplication.instance().clipboard().setText("\n".join(
|
|
103
|
+
self.files_model.filePath(index) for index in indexes
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
def reset_instrument_selections(self):
|
|
107
|
+
for row in range(self.lst_instruments.count()):
|
|
108
|
+
self.lst_instruments.item(row).setFlags(Qt.NoItemFlags)
|
|
109
|
+
self.instrument_list_drumkit_paths[row] = []
|
|
110
|
+
|
|
111
|
+
@pyqtSlot(QItemSelection, QItemSelection)
|
|
112
|
+
def slot_tree_selection_changed(self, *_):
|
|
113
|
+
QApplication.setOverrideCursor(Qt.WaitCursor)
|
|
114
|
+
self.lst_samples.clear()
|
|
115
|
+
self.reset_instrument_selections()
|
|
116
|
+
indexes = self.tree_files.selectedIndexes()
|
|
117
|
+
for index in indexes:
|
|
118
|
+
path = self.files_model.filePath(index)
|
|
119
|
+
if self.files_model.isDir(index):
|
|
120
|
+
settings().setValue(KEY_SAMPLE_XPLORE_CURR, path)
|
|
121
|
+
else:
|
|
122
|
+
settings().setValue(KEY_SAMPLE_XPLORE_CURR, os.path.dirname(path))
|
|
123
|
+
for inst in self.drumkit(path).instruments():
|
|
124
|
+
row = self.instrument_list_ids.index(inst.inst_id)
|
|
125
|
+
self.instrument_list_drumkit_paths[row].append(path)
|
|
126
|
+
self.lst_instruments.item(row).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
127
|
+
selected_items = self.lst_instruments.selectedItems()
|
|
128
|
+
if len(selected_items):
|
|
129
|
+
self.populate_samples(selected_items.pop())
|
|
130
|
+
QApplication.restoreOverrideCursor()
|
|
131
|
+
|
|
132
|
+
@pyqtSlot(QListWidgetItem, QListWidgetItem)
|
|
133
|
+
def slot_inst_current_changed(self, list_item, _):
|
|
134
|
+
if list_item:
|
|
135
|
+
self.populate_samples(list_item)
|
|
136
|
+
|
|
137
|
+
def populate_samples(self, list_item):
|
|
138
|
+
self.lst_samples.clear()
|
|
139
|
+
inst_id = list_item.data(Qt.UserRole)
|
|
140
|
+
row = self.lst_instruments.row(list_item)
|
|
141
|
+
for path in self.instrument_list_drumkit_paths[row]:
|
|
142
|
+
for sample in self.drumkit(path).instrument(inst_id).samples():
|
|
143
|
+
existing_items = self.lst_samples.findItems(sample.basename, Qt.MatchExactly)
|
|
144
|
+
if len(existing_items):
|
|
145
|
+
for existing_item in existing_items:
|
|
146
|
+
if existing_item.data(Qt.UserRole).name == sample.abspath:
|
|
147
|
+
return
|
|
148
|
+
list_item = QListWidgetItem(self.lst_samples)
|
|
149
|
+
list_item.setText(sample.basename)
|
|
150
|
+
soundfile = sf.SoundFile(sample.abspath)
|
|
151
|
+
list_item.setData(Qt.UserRole, soundfile)
|
|
152
|
+
s_samp = soundfile.name + \
|
|
153
|
+
f'\nThis file has a sample rate of {soundfile.samplerate} Hz,\n'
|
|
154
|
+
if soundfile.samplerate != self.audio_player.client.samplerate:
|
|
155
|
+
list_item.setIcon(QIcon.fromTheme('face-sad'))
|
|
156
|
+
list_item.setToolTip(s_samp + \
|
|
157
|
+
f'while the JACK server is running at {self.audio_player.client.samplerate} Hz')
|
|
158
|
+
else:
|
|
159
|
+
list_item.setIcon(QIcon.fromTheme('face-cool'))
|
|
160
|
+
list_item.setToolTip(s_samp + 'the same as the JACK server')
|
|
161
|
+
|
|
162
|
+
@pyqtSlot(QPoint)
|
|
163
|
+
def slot_samples_context_menu(self, position):
|
|
164
|
+
list_item = self.lst_samples.currentItem()
|
|
165
|
+
if list_item is not None:
|
|
166
|
+
menu = QMenu(self)
|
|
167
|
+
menu.addAction('Copy path')
|
|
168
|
+
if menu.exec(self.lst_samples.mapToGlobal(position)):
|
|
169
|
+
QApplication.instance().clipboard().setText(list_item.data(Qt.UserRole).name)
|
|
170
|
+
|
|
171
|
+
@pyqtSlot(QListWidgetItem)
|
|
172
|
+
def slot_sample_pressed(self, list_item):
|
|
173
|
+
if QApplication.mouseButtons() == Qt.LeftButton:
|
|
174
|
+
soundfile = list_item.data(Qt.UserRole)
|
|
175
|
+
soundfile.seek(0)
|
|
176
|
+
self.audio_player.play_python_soundfile(soundfile)
|
|
177
|
+
|
|
178
|
+
def samples_mouse_release(self, event):
|
|
179
|
+
self.audio_player.stop()
|
|
180
|
+
super(QListWidget, self.lst_samples).mouseReleaseEvent(event)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
logging.basicConfig(
|
|
185
|
+
level = logging.DEBUG,
|
|
186
|
+
format = "[%(filename)24s:%(lineno)-4d] %(levelname)-8s %(message)s"
|
|
187
|
+
)
|
|
188
|
+
app = QApplication([])
|
|
189
|
+
set_application_style()
|
|
190
|
+
try:
|
|
191
|
+
dialog = SamplesExplorer(None)
|
|
192
|
+
except JackError as err:
|
|
193
|
+
print('Could not connect to JACK server. Is it running?')
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
else:
|
|
196
|
+
dialog.exec_()
|
|
197
|
+
sys.exit(0)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# end kitbash/gui/samples_explorer.py
|