matchpatch 0.4.0__tar.gz → 0.6.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.
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.github/workflows/release.yml +5 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.pre-commit-config.yaml +2 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/PKG-INFO +1 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/dev/architecture.md +11 -2
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/dev/commands.md +12 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/developer-notes.md +18 -4
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/musician-guide.md +17 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/quick-start.md +3 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/troubleshooting.md +27 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/matchpatch.iss +62 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/pyinstaller/build_support.py +19 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/pyinstaller/matchpatch-gui.spec +2 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/smoke/smoke_installed.ps1 +4 -2
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/smoke/smoke_payload.ps1 +2 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/pyproject.toml +1 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/release.py +12 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/cli.py +5 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/config.py +29 -5
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/devices/helix.py +54 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/device_panels.py +0 -27
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/dialogs.py +21 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/main_window.py +9 -2
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/measure.py +4 -4
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/normalize.py +22 -13
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_cli.py +14 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_config.py +45 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_gui.py +69 -9
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_gui_dialogs.py +36 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_helix.py +45 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_installer_metadata.py +26 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_normalize.py +93 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/uv.lock +1 -1
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.gitattributes +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.github/dependabot.yml +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.github/workflows/quality.yml +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.gitignore +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/.python-version +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/AGENTS.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/LICENSE +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/adjust_gain.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/decrypt_hls.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/encrypt_hls.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/list_cab_presets.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/preset_handling.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/remove_inactive_blocks.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/replace_amp.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/reset_output_levels.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/Python/stereofy.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/README.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/audio/reference-di/DI_Strandberg_Boden_Fusion_Bridge_Humbucker.wav +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/_static/matchpatch-docs.css +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/matchmatch-icon-512.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/matchmatch-icon.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/matchmatch-logo.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/backend-selector.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/completed-results-table.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/failed-measurement-row.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/hardware-routing.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/loaded-setlist.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/normalization-ongoing.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/optimization-dialog.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/parameter-study-setup.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/assets/screenshots/timing-tab.png +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/backends.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/crest-factor.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/lufs-and-loudness.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/measurement-and-adjusted-files.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/reading-results.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/reference-di.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/routing-and-levels.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/snapshots-solos-and-ignored.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/concepts/timing.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/conf.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/dev/file-formats.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/dev/release.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/faq.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/glossary.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/index.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/custom-adjustments.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/hardware-measurement.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/manual-editing-and-csv.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/normalize-setlist.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/normalize-single-preset.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/optimize-timing.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/save-and-import.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/select-changed-presets.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/docs/workflows/test-without-hardware.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/installer/README.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/build-docs.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/build-windows-installer-from-wsl.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/build-windows-installer.cmd +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/build-windows-payload.cmd +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/check_commit_msg.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/measure-windows-from-wsl.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/run_hook_with_hint.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/stage-installer-docs.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/sync-windows-from-wsl.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/sync-windows.cmd +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/sync-wsl.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/test-gui.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/test-windows-installer-from-wsl.sh +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/scripts/test-windows-installer.cmd +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/__init__.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/analysis.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/app.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/audio.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/custom_adjustments.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/devices/__init__.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/devices/base.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/devices/registry.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/__init__.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/app.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/help.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/snapshot_header.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/gui/worker.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/measurement_optimizer.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/progress.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/src/matchpatch/workflow.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/README.md +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_analysis.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_app.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_audio.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_check_commit_msg.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_devices.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_gui_app.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_gui_help.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_measure.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_measurement_optimizer.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_preset_handling.py +0 -0
- {matchpatch-0.4.0 → matchpatch-0.6.0}/tests/test_progress.py +0 -0
|
@@ -8,6 +8,9 @@ on:
|
|
|
8
8
|
permissions:
|
|
9
9
|
contents: read
|
|
10
10
|
|
|
11
|
+
env:
|
|
12
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
13
|
+
|
|
11
14
|
jobs:
|
|
12
15
|
publish:
|
|
13
16
|
name: Publish to PyPI
|
|
@@ -26,6 +29,7 @@ jobs:
|
|
|
26
29
|
- name: Install uv and Python
|
|
27
30
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
28
31
|
with:
|
|
32
|
+
enable-cache: false
|
|
29
33
|
python-version: "3.14"
|
|
30
34
|
|
|
31
35
|
- name: Verify tag matches package version
|
|
@@ -81,6 +85,7 @@ jobs:
|
|
|
81
85
|
- name: Install uv and Python
|
|
82
86
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
83
87
|
with:
|
|
88
|
+
enable-cache: false
|
|
84
89
|
python-version: "3.12"
|
|
85
90
|
|
|
86
91
|
- name: Resolve package version
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matchpatch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Audio processor preset gain normalization and measurement tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/noseglasses/MatchPatch
|
|
6
6
|
Project-URL: Issues, https://github.com/noseglasses/MatchPatch/issues
|
|
@@ -223,8 +223,17 @@ inside the GUI.
|
|
|
223
223
|
|
|
224
224
|
## Configuration
|
|
225
225
|
|
|
226
|
-
`matchpatch.config` reads TOML using the standard library.
|
|
227
|
-
|
|
226
|
+
`matchpatch.config` reads TOML using the standard library. When no `--config`
|
|
227
|
+
is supplied, it checks `default_config_paths()` in order and loads the first
|
|
228
|
+
existing file:
|
|
229
|
+
|
|
230
|
+
- Windows: `%APPDATA%\MatchPatch\config.toml`, then
|
|
231
|
+
`%USERPROFILE%\.config\matchpatch\config.toml`;
|
|
232
|
+
- Linux/WSL/macOS: `$XDG_CONFIG_HOME/matchpatch/config.toml` when
|
|
233
|
+
`XDG_CONFIG_HOME` is set, otherwise `~/.config/matchpatch/config.toml`.
|
|
234
|
+
|
|
235
|
+
`--config` points at one explicit file and raises an error if that file is
|
|
236
|
+
missing.
|
|
228
237
|
|
|
229
238
|
Configuration is layered with command-line values preferred over config file
|
|
230
239
|
values. For normalize commands, selected environment variables override matching
|
|
@@ -112,6 +112,18 @@ Export default config:
|
|
|
112
112
|
matchpatch --export-default-config ~/.config/matchpatch/config.toml
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
Installed Windows builds automatically look first in the roaming profile:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
MatchPatch.exe --cli --export-default-config "$env:APPDATA\MatchPatch\config.toml"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
When `--config` is omitted, MatchPatch searches for config files in this order:
|
|
122
|
+
`%APPDATA%\MatchPatch\config.toml`, then
|
|
123
|
+
`%USERPROFILE%\.config\matchpatch\config.toml` on Windows; on Linux/WSL/macOS,
|
|
124
|
+
`$XDG_CONFIG_HOME/matchpatch/config.toml` when `XDG_CONFIG_HOME` is set,
|
|
125
|
+
otherwise `~/.config/matchpatch/config.toml`.
|
|
126
|
+
|
|
115
127
|
Normalize without hardware:
|
|
116
128
|
|
|
117
129
|
```bash
|
|
@@ -136,10 +136,18 @@ When adding a new term, update [Glossary](glossary.md).
|
|
|
136
136
|
Most musicians should configure MatchPatch from the GUI. Use TOML only for
|
|
137
137
|
durable machine defaults, repeated CLI runs, or advanced setup notes.
|
|
138
138
|
|
|
139
|
-
MatchPatch loads
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
MatchPatch automatically loads the first existing default config file from this
|
|
140
|
+
search path:
|
|
141
|
+
|
|
142
|
+
- Windows: `%APPDATA%\MatchPatch\config.toml`
|
|
143
|
+
- Windows fallback: `%USERPROFILE%\.config\matchpatch\config.toml`
|
|
144
|
+
- Linux/WSL/macOS with `XDG_CONFIG_HOME`: `$XDG_CONFIG_HOME/matchpatch/config.toml`
|
|
145
|
+
- Linux/WSL/macOS fallback: `~/.config/matchpatch/config.toml`
|
|
146
|
+
|
|
147
|
+
Use `--config PATH` or the GUI Files tab to load another file. Command-line
|
|
148
|
+
options override the file. The `MATCHPATCH_BACKEND`,
|
|
149
|
+
`MATCHPATCH_WINDOWS_PYTHON`, and `MATCHPATCH_REFERENCE_DI` environment
|
|
150
|
+
variables override matching file values.
|
|
143
151
|
|
|
144
152
|
Use `--snapshot-count N` to override `policy.measured_snapshots` for one run.
|
|
145
153
|
Line 6 Helix supports between `1` and `8` measured snapshots; the default is
|
|
@@ -151,6 +159,12 @@ Export the default config:
|
|
|
151
159
|
matchpatch --export-default-config ~/.config/matchpatch/config.toml
|
|
152
160
|
```
|
|
153
161
|
|
|
162
|
+
On installed Windows builds, the matching default export target is:
|
|
163
|
+
|
|
164
|
+
```powershell
|
|
165
|
+
MatchPatch.exe --cli --export-default-config "$env:APPDATA\MatchPatch\config.toml"
|
|
166
|
+
```
|
|
167
|
+
|
|
154
168
|
Example config:
|
|
155
169
|
|
|
156
170
|
```toml
|
|
@@ -84,6 +84,23 @@ Use hardware for final rehearsal or gig-ready results.
|
|
|
84
84
|
|
|
85
85
|
See [Backends](concepts/backends.md).
|
|
86
86
|
|
|
87
|
+
## Saved Configuration
|
|
88
|
+
|
|
89
|
+
Most users can set options directly in the GUI. If you want MatchPatch to load
|
|
90
|
+
the same machine defaults every time, save a TOML configuration file.
|
|
91
|
+
|
|
92
|
+
When no config file is selected in Advanced > Files, MatchPatch automatically
|
|
93
|
+
uses the first config file it finds in this search path:
|
|
94
|
+
|
|
95
|
+
- Windows installed app: `%APPDATA%\MatchPatch\config.toml`
|
|
96
|
+
- Windows compatibility fallback: `%USERPROFILE%\.config\matchpatch\config.toml`
|
|
97
|
+
- Linux/WSL/macOS with `XDG_CONFIG_HOME`: `$XDG_CONFIG_HOME/matchpatch/config.toml`
|
|
98
|
+
- Linux/WSL/macOS fallback: `~/.config/matchpatch/config.toml`
|
|
99
|
+
|
|
100
|
+
To use a different file for one session, open Advanced > Files and choose it in
|
|
101
|
+
the Config field. A selected Config path takes priority over the automatic
|
|
102
|
+
search path.
|
|
103
|
+
|
|
87
104
|
(help-opening-files)=
|
|
88
105
|
## Opening Files
|
|
89
106
|
|
|
@@ -16,6 +16,9 @@ Helix results, use hardware mode.
|
|
|
16
16
|
- Decide which backend to use:
|
|
17
17
|
- loopback for a safe no-hardware test;
|
|
18
18
|
- hardware for real Helix measurement.
|
|
19
|
+
- Optional: save a config file if you want the same defaults every time. On
|
|
20
|
+
installed Windows builds, the automatic config path is
|
|
21
|
+
`%APPDATA%\MatchPatch\config.toml`.
|
|
19
22
|
|
|
20
23
|
> Warning:
|
|
21
24
|
> Keep a backup of your original Helix file before saving changes.
|
|
@@ -114,6 +114,33 @@ The file path in Advanced > Files points to a missing or moved WAV.
|
|
|
114
114
|
|
|
115
115
|
See [Reference DI](concepts/reference-di.md).
|
|
116
116
|
|
|
117
|
+
## Config File Not Applied
|
|
118
|
+
|
|
119
|
+
### What You See
|
|
120
|
+
|
|
121
|
+
MatchPatch starts with different backend, routing, Reference DI, or timing
|
|
122
|
+
settings than you expected.
|
|
123
|
+
|
|
124
|
+
### Likely Cause
|
|
125
|
+
|
|
126
|
+
The config file is not in the automatic search path, or another config file is
|
|
127
|
+
selected in Advanced > Files.
|
|
128
|
+
|
|
129
|
+
### What To Try
|
|
130
|
+
|
|
131
|
+
1. Open Advanced > Files.
|
|
132
|
+
2. If the Config field points to a file, confirm it is the file you meant to
|
|
133
|
+
use.
|
|
134
|
+
3. If the Config field is empty, put your default config in the automatic
|
|
135
|
+
location for your system.
|
|
136
|
+
|
|
137
|
+
Automatic config search path:
|
|
138
|
+
|
|
139
|
+
- Windows installed app: `%APPDATA%\MatchPatch\config.toml`
|
|
140
|
+
- Windows compatibility fallback: `%USERPROFILE%\.config\matchpatch\config.toml`
|
|
141
|
+
- Linux/WSL/macOS with `XDG_CONFIG_HOME`: `$XDG_CONFIG_HOME/matchpatch/config.toml`
|
|
142
|
+
- Linux/WSL/macOS fallback: `~/.config/matchpatch/config.toml`
|
|
143
|
+
|
|
117
144
|
## Reference DI Sample Rate Mismatch
|
|
118
145
|
|
|
119
146
|
### What You See
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
#endif
|
|
12
12
|
|
|
13
13
|
#define AppName "MatchPatch"
|
|
14
|
+
#define AppUninstallRegistryKey "Software\Microsoft\Windows\CurrentVersion\Uninstall\{15537D18-AE3B-4B79-A046-9B95C60E2DB4}_is1"
|
|
14
15
|
#define AppPublisher "noseglasses/MatchPatch"
|
|
15
16
|
#define AppURL "https://github.com/noseglasses/MatchPatch"
|
|
17
|
+
#define UninstallerBaseName "Uninstall-MatchPatch"
|
|
18
|
+
#define UninstallerExeName "Uninstall-MatchPatch.exe"
|
|
16
19
|
|
|
17
20
|
[Setup]
|
|
18
21
|
AppId={{15537D18-AE3B-4B79-A046-9B95C60E2DB4}
|
|
@@ -46,8 +49,66 @@ Source: "{#SourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
|
|
46
49
|
[Icons]
|
|
47
50
|
Name: "{group}\MatchPatch"; Filename: "{app}\MatchPatch.exe"; WorkingDir: "{app}"; IconFilename: "{app}\installer-assets\matchpatch.ico"
|
|
48
51
|
Name: "{group}\MatchPatch Documentation"; Filename: "{app}\docs_html\index.html"; WorkingDir: "{app}\docs_html"
|
|
49
|
-
Name: "{group}\Uninstall MatchPatch"; Filename: "{
|
|
52
|
+
Name: "{group}\Uninstall MatchPatch"; Filename: "{app}\{#UninstallerExeName}"
|
|
50
53
|
Name: "{autodesktop}\MatchPatch"; Filename: "{app}\MatchPatch.exe"; WorkingDir: "{app}"; IconFilename: "{app}\installer-assets\matchpatch.ico"; Tasks: desktopicon
|
|
51
54
|
|
|
52
55
|
[Run]
|
|
53
56
|
Filename: "{app}\MatchPatch.exe"; Description: "{cm:LaunchProgram,MatchPatch}"; Flags: nowait postinstall skipifsilent unchecked
|
|
57
|
+
|
|
58
|
+
[Code]
|
|
59
|
+
const
|
|
60
|
+
UninstallerBaseName = '{#UninstallerBaseName}';
|
|
61
|
+
UninstallerExeName = '{#UninstallerExeName}';
|
|
62
|
+
UninstallRegistryKey = '{#AppUninstallRegistryKey}';
|
|
63
|
+
|
|
64
|
+
procedure RenameUninstallerFile(SourceName: string; TargetName: string);
|
|
65
|
+
var
|
|
66
|
+
SourcePath: string;
|
|
67
|
+
TargetPath: string;
|
|
68
|
+
begin
|
|
69
|
+
SourcePath := ExpandConstant('{app}\') + SourceName;
|
|
70
|
+
TargetPath := ExpandConstant('{app}\') + TargetName;
|
|
71
|
+
|
|
72
|
+
if FileExists(SourcePath) then
|
|
73
|
+
begin
|
|
74
|
+
if FileExists(TargetPath) then
|
|
75
|
+
begin
|
|
76
|
+
DeleteFile(TargetPath);
|
|
77
|
+
end;
|
|
78
|
+
|
|
79
|
+
if not RenameFile(SourcePath, TargetPath) then
|
|
80
|
+
begin
|
|
81
|
+
RaiseException('Could not rename ' + SourcePath + ' to ' + TargetPath);
|
|
82
|
+
end;
|
|
83
|
+
end;
|
|
84
|
+
end;
|
|
85
|
+
|
|
86
|
+
procedure UpdateUninstallRegistry;
|
|
87
|
+
var
|
|
88
|
+
UninstallerPath: string;
|
|
89
|
+
begin
|
|
90
|
+
UninstallerPath := ExpandConstant('{app}\') + UninstallerExeName;
|
|
91
|
+
RegWriteStringValue(
|
|
92
|
+
HKEY_LOCAL_MACHINE,
|
|
93
|
+
UninstallRegistryKey,
|
|
94
|
+
'UninstallString',
|
|
95
|
+
'"' + UninstallerPath + '"'
|
|
96
|
+
);
|
|
97
|
+
RegWriteStringValue(
|
|
98
|
+
HKEY_LOCAL_MACHINE,
|
|
99
|
+
UninstallRegistryKey,
|
|
100
|
+
'QuietUninstallString',
|
|
101
|
+
'"' + UninstallerPath + '" /SILENT'
|
|
102
|
+
);
|
|
103
|
+
end;
|
|
104
|
+
|
|
105
|
+
procedure CurStepChanged(CurStep: TSetupStep);
|
|
106
|
+
begin
|
|
107
|
+
if CurStep = ssPostInstall then
|
|
108
|
+
begin
|
|
109
|
+
RenameUninstallerFile('unins000.exe', UninstallerExeName);
|
|
110
|
+
RenameUninstallerFile('unins000.dat', UninstallerBaseName + '.dat');
|
|
111
|
+
RenameUninstallerFile('unins000.msg', UninstallerBaseName + '.msg');
|
|
112
|
+
UpdateUninstallRegistry;
|
|
113
|
+
end;
|
|
114
|
+
end;
|
|
@@ -19,6 +19,16 @@ PYINSTALLER_ASSETS_ROOT = PYINSTALLER_WORK_ROOT / "installer-assets"
|
|
|
19
19
|
INSTALLER_ASSETS_ROOT = PAYLOAD_ROOT / "installer-assets"
|
|
20
20
|
ICON_SOURCE = PROJECT_ROOT / "docs" / "assets" / "matchmatch-icon-512.png"
|
|
21
21
|
LOGO_SOURCE = PROJECT_ROOT / "docs" / "assets" / "matchmatch-logo.png"
|
|
22
|
+
REFERENCE_DI_SOURCE = (
|
|
23
|
+
PROJECT_ROOT / "audio" / "reference-di" / "DI_Strandberg_Boden_Fusion_Bridge_Humbucker.wav"
|
|
24
|
+
)
|
|
25
|
+
PAYLOAD_RUNTIME_FILES = [
|
|
26
|
+
(PROJECT_ROOT / "Python" / "preset_handling.py", Path("Python") / "preset_handling.py"),
|
|
27
|
+
(
|
|
28
|
+
REFERENCE_DI_SOURCE,
|
|
29
|
+
Path("audio") / "reference-di" / "DI_Strandberg_Boden_Fusion_Bridge_Humbucker.wav",
|
|
30
|
+
),
|
|
31
|
+
]
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
def project_version() -> str:
|
|
@@ -41,6 +51,8 @@ def git_sha() -> str:
|
|
|
41
51
|
|
|
42
52
|
def asset_datas() -> list[tuple[str, str]]:
|
|
43
53
|
return [
|
|
54
|
+
(str(PROJECT_ROOT / "Python" / "preset_handling.py"), "Python"),
|
|
55
|
+
(str(REFERENCE_DI_SOURCE), "audio/reference-di"),
|
|
44
56
|
(str(PROJECT_ROOT / "docs" / "assets" / "matchmatch-icon.png"), "docs/assets"),
|
|
45
57
|
(str(PROJECT_ROOT / "docs" / "assets" / "matchmatch-icon-512.png"), "docs/assets"),
|
|
46
58
|
(str(PROJECT_ROOT / "docs" / "assets" / "matchmatch-logo.png"), "docs/assets"),
|
|
@@ -95,6 +107,13 @@ def stage_installer_assets(
|
|
|
95
107
|
shutil.copytree(source_root, target_root)
|
|
96
108
|
|
|
97
109
|
|
|
110
|
+
def stage_runtime_files(payload_root: Path = PAYLOAD_ROOT) -> None:
|
|
111
|
+
for source, relative_target in PAYLOAD_RUNTIME_FILES:
|
|
112
|
+
target = payload_root / relative_target
|
|
113
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
shutil.copy2(source, target)
|
|
115
|
+
|
|
116
|
+
|
|
98
117
|
def write_build_info(payload_root: Path = PAYLOAD_ROOT) -> None:
|
|
99
118
|
payload_root.mkdir(parents=True, exist_ok=True)
|
|
100
119
|
build_info = {
|
|
@@ -16,6 +16,7 @@ from build_support import (
|
|
|
16
16
|
prepare_pyinstaller_paths,
|
|
17
17
|
stage_installer_assets,
|
|
18
18
|
stage_docs,
|
|
19
|
+
stage_runtime_files,
|
|
19
20
|
write_build_info,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -65,5 +66,6 @@ coll = COLLECT(
|
|
|
65
66
|
)
|
|
66
67
|
|
|
67
68
|
stage_installer_assets()
|
|
69
|
+
stage_runtime_files()
|
|
68
70
|
stage_docs()
|
|
69
71
|
write_build_info()
|
|
@@ -78,10 +78,11 @@ Invoke-SetupProcess -Path $installer -Arguments $installArgs -FailureMessage "In
|
|
|
78
78
|
$guiExe = Join-Path $InstallDir "MatchPatch.exe"
|
|
79
79
|
$docsIndex = Join-Path $InstallDir "docs_html\index.html"
|
|
80
80
|
$buildInfoPath = Join-Path $InstallDir "build-info.json"
|
|
81
|
-
$uninstaller = Join-Path $InstallDir "
|
|
81
|
+
$uninstaller = Join-Path $InstallDir "Uninstall-MatchPatch.exe"
|
|
82
82
|
$installerIcon = Join-Path $InstallDir "installer-assets\matchpatch.ico"
|
|
83
83
|
$wizardLogo = Join-Path $InstallDir "installer-assets\wizard-logo.bmp"
|
|
84
84
|
$wizardSmallLogo = Join-Path $InstallDir "installer-assets\wizard-small-logo.bmp"
|
|
85
|
+
$referenceDi = Join-Path $InstallDir "audio\reference-di\DI_Strandberg_Boden_Fusion_Bridge_Humbucker.wav"
|
|
85
86
|
|
|
86
87
|
Assert-FileExists $guiExe
|
|
87
88
|
Assert-FileExists $docsIndex
|
|
@@ -90,6 +91,7 @@ Assert-FileExists $uninstaller
|
|
|
90
91
|
Assert-FileExists $installerIcon
|
|
91
92
|
Assert-FileExists $wizardLogo
|
|
92
93
|
Assert-FileExists $wizardSmallLogo
|
|
94
|
+
Assert-FileExists $referenceDi
|
|
93
95
|
|
|
94
96
|
$buildInfo = Get-Content -LiteralPath $buildInfoPath -Raw | ConvertFrom-Json
|
|
95
97
|
if ($buildInfo.version -ne $ExpectedVersion) {
|
|
@@ -112,7 +114,7 @@ Invoke-SetupProcess -Path $uninstaller -Arguments $uninstallArgs -FailureMessage
|
|
|
112
114
|
Start-Sleep -Seconds 2
|
|
113
115
|
if (Test-Path -LiteralPath $InstallDir) {
|
|
114
116
|
$leftovers = @(Get-ChildItem -LiteralPath $InstallDir -Force)
|
|
115
|
-
$unexpected = @($leftovers | Where-Object { $_.Name -notmatch "^
|
|
117
|
+
$unexpected = @($leftovers | Where-Object { $_.Name -notmatch "^Uninstall-MatchPatch\.(dat|msg|log)$" })
|
|
116
118
|
if ($unexpected.Count -gt 0) {
|
|
117
119
|
throw "Install directory contains unexpected leftovers after uninstall: $($unexpected.FullName -join ', ')"
|
|
118
120
|
}
|
|
@@ -49,6 +49,7 @@ $buildInfoPath = Join-Path $payload "build-info.json"
|
|
|
49
49
|
$installerIcon = Join-Path $payload "installer-assets\matchpatch.ico"
|
|
50
50
|
$wizardLogo = Join-Path $payload "installer-assets\wizard-logo.bmp"
|
|
51
51
|
$wizardSmallLogo = Join-Path $payload "installer-assets\wizard-small-logo.bmp"
|
|
52
|
+
$referenceDi = Join-Path $payload "audio\reference-di\DI_Strandberg_Boden_Fusion_Bridge_Humbucker.wav"
|
|
52
53
|
|
|
53
54
|
Assert-FileExists $guiExe
|
|
54
55
|
Assert-FileExists $docsIndex
|
|
@@ -56,6 +57,7 @@ Assert-FileExists $buildInfoPath
|
|
|
56
57
|
Assert-FileExists $installerIcon
|
|
57
58
|
Assert-FileExists $wizardLogo
|
|
58
59
|
Assert-FileExists $wizardSmallLogo
|
|
60
|
+
Assert-FileExists $referenceDi
|
|
59
61
|
|
|
60
62
|
$buildInfo = Get-Content -LiteralPath $buildInfoPath -Raw | ConvertFrom-Json
|
|
61
63
|
if ($buildInfo.version -ne $ExpectedVersion) {
|
|
@@ -566,7 +566,18 @@ def verify_public_release(version: str, tag: str, notes_file: str | None) -> Non
|
|
|
566
566
|
assets = {asset["name"] for asset in release.get("assets", [])}
|
|
567
567
|
if expected_asset not in assets:
|
|
568
568
|
raise ReleaseError(f"GitHub Release is missing installer asset: {expected_asset}")
|
|
569
|
-
run(
|
|
569
|
+
run(
|
|
570
|
+
[
|
|
571
|
+
sys.executable,
|
|
572
|
+
"-c",
|
|
573
|
+
(
|
|
574
|
+
"import json, sys, urllib.request; "
|
|
575
|
+
"data = json.load(urllib.request.urlopen('https://pypi.org/pypi/matchpatch/json')); "
|
|
576
|
+
"sys.exit(0 if sys.argv[1] in data['releases'] else 1)"
|
|
577
|
+
),
|
|
578
|
+
version,
|
|
579
|
+
]
|
|
580
|
+
)
|
|
570
581
|
|
|
571
582
|
|
|
572
583
|
def parse_args() -> argparse.Namespace:
|
|
@@ -29,6 +29,11 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
29
29
|
|
|
30
30
|
normalize_main(args[1:])
|
|
31
31
|
return
|
|
32
|
+
if args and args[0] == "measure":
|
|
33
|
+
from matchpatch.measure import main as measure_main
|
|
34
|
+
|
|
35
|
+
measure_main(args[1:])
|
|
36
|
+
return
|
|
32
37
|
|
|
33
38
|
parser = argparse.ArgumentParser(description="Normalize gain across audio processor presets")
|
|
34
39
|
parser.add_argument(
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
5
7
|
import tomllib
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any
|
|
@@ -12,17 +14,39 @@ from matchpatch.devices.base import NormalizationPolicy
|
|
|
12
14
|
Config = dict[str, Any]
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def default_config_paths() -> list[Path]:
|
|
18
|
+
home = Path.home()
|
|
19
|
+
legacy_path = home / ".config" / "matchpatch" / "config.toml"
|
|
20
|
+
|
|
21
|
+
if sys.platform == "win32":
|
|
22
|
+
appdata = os.getenv("APPDATA")
|
|
23
|
+
if appdata:
|
|
24
|
+
primary_path = Path(appdata) / "MatchPatch" / "config.toml"
|
|
25
|
+
else:
|
|
26
|
+
primary_path = home / "AppData" / "Roaming" / "MatchPatch" / "config.toml"
|
|
27
|
+
return [primary_path, legacy_path]
|
|
28
|
+
|
|
29
|
+
xdg_config_home = os.getenv("XDG_CONFIG_HOME")
|
|
30
|
+
if xdg_config_home:
|
|
31
|
+
return [Path(xdg_config_home) / "matchpatch" / "config.toml", legacy_path]
|
|
32
|
+
return [legacy_path]
|
|
33
|
+
|
|
34
|
+
|
|
15
35
|
def default_config_path() -> Path:
|
|
16
|
-
return
|
|
36
|
+
return default_config_paths()[0]
|
|
17
37
|
|
|
18
38
|
|
|
19
39
|
def load_config(path: str | Path | None) -> Config:
|
|
20
|
-
|
|
40
|
+
if path is None:
|
|
41
|
+
for config_path in default_config_paths():
|
|
42
|
+
if config_path.is_file():
|
|
43
|
+
with config_path.open("rb") as config_file:
|
|
44
|
+
return tomllib.load(config_file)
|
|
45
|
+
return {}
|
|
21
46
|
|
|
22
|
-
|
|
23
|
-
if path is None:
|
|
24
|
-
return {}
|
|
47
|
+
config_path = Path(path).expanduser()
|
|
25
48
|
|
|
49
|
+
if not config_path.is_file():
|
|
26
50
|
raise ValueError(f"MatchPatch config file does not exist: {config_path}")
|
|
27
51
|
|
|
28
52
|
with config_path.open("rb") as config_file:
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import csv
|
|
7
|
+
import io
|
|
6
8
|
import json
|
|
9
|
+
import runpy
|
|
7
10
|
import subprocess
|
|
8
11
|
import sys
|
|
9
12
|
import tempfile
|
|
@@ -38,6 +41,9 @@ class HelixPatchFileHandler(PatchFileHandler):
|
|
|
38
41
|
capture: bool = False,
|
|
39
42
|
log_output: bool = True,
|
|
40
43
|
) -> subprocess.CompletedProcess[str]:
|
|
44
|
+
if getattr(sys, "frozen", False):
|
|
45
|
+
return self._run_in_process(*args, capture=capture, log_output=log_output)
|
|
46
|
+
|
|
41
47
|
should_capture = capture or self.log_callback is not None
|
|
42
48
|
|
|
43
49
|
try:
|
|
@@ -59,6 +65,54 @@ class HelixPatchFileHandler(PatchFileHandler):
|
|
|
59
65
|
self._log_output(completed.stderr)
|
|
60
66
|
return completed
|
|
61
67
|
|
|
68
|
+
def _run_in_process(
|
|
69
|
+
self,
|
|
70
|
+
*args: object,
|
|
71
|
+
capture: bool = False,
|
|
72
|
+
log_output: bool = True,
|
|
73
|
+
) -> subprocess.CompletedProcess[str]:
|
|
74
|
+
should_capture = capture or self.log_callback is not None
|
|
75
|
+
command = [str(self.script), *(str(arg) for arg in args)]
|
|
76
|
+
original_argv = sys.argv
|
|
77
|
+
stdout = io.StringIO()
|
|
78
|
+
stderr = io.StringIO()
|
|
79
|
+
sys.argv = command
|
|
80
|
+
try:
|
|
81
|
+
stdout_context = (
|
|
82
|
+
contextlib.redirect_stdout(stdout) if should_capture else contextlib.nullcontext()
|
|
83
|
+
)
|
|
84
|
+
stderr_context = (
|
|
85
|
+
contextlib.redirect_stderr(stderr) if should_capture else contextlib.nullcontext()
|
|
86
|
+
)
|
|
87
|
+
with stdout_context, stderr_context:
|
|
88
|
+
try:
|
|
89
|
+
runpy.run_path(str(self.script), run_name="__main__")
|
|
90
|
+
returncode = 0
|
|
91
|
+
except SystemExit as exc:
|
|
92
|
+
returncode = exc.code if isinstance(exc.code, int) else 1
|
|
93
|
+
finally:
|
|
94
|
+
sys.argv = original_argv
|
|
95
|
+
|
|
96
|
+
output = stdout.getvalue() if should_capture else None
|
|
97
|
+
error = stderr.getvalue() if should_capture else None
|
|
98
|
+
completed = subprocess.CompletedProcess(command, returncode, stdout=output, stderr=error)
|
|
99
|
+
if completed.returncode:
|
|
100
|
+
exc = subprocess.CalledProcessError(
|
|
101
|
+
completed.returncode,
|
|
102
|
+
command,
|
|
103
|
+
output=completed.stdout,
|
|
104
|
+
stderr=completed.stderr,
|
|
105
|
+
)
|
|
106
|
+
if log_output:
|
|
107
|
+
self._log_output(exc.stdout)
|
|
108
|
+
self._log_output(exc.stderr)
|
|
109
|
+
raise exc
|
|
110
|
+
|
|
111
|
+
if log_output:
|
|
112
|
+
self._log_output(completed.stdout)
|
|
113
|
+
self._log_output(completed.stderr)
|
|
114
|
+
return completed
|
|
115
|
+
|
|
62
116
|
def _log_output(self, output: str | None) -> None:
|
|
63
117
|
if self.log_callback is None or not output:
|
|
64
118
|
return
|
|
@@ -72,9 +72,6 @@ class HelixSettingsPanel(QWidget):
|
|
|
72
72
|
self.steering_output = QLineEdit()
|
|
73
73
|
self.steering_channel = QSpinBox()
|
|
74
74
|
self.steering_channel.setRange(0, 15)
|
|
75
|
-
self.preset_wait = QLineEdit()
|
|
76
|
-
self.snapshot_wait = QLineEdit()
|
|
77
|
-
self.measurement_wait = QLineEdit()
|
|
78
75
|
steering.addRow(
|
|
79
76
|
_label("MIDI output", "MIDI port substring used to find the connected Helix."),
|
|
80
77
|
self.steering_output,
|
|
@@ -83,20 +80,6 @@ class HelixSettingsPanel(QWidget):
|
|
|
83
80
|
_label("MIDI channel", "Zero-based MIDI channel used for preset and snapshot changes."),
|
|
84
81
|
self.steering_channel,
|
|
85
82
|
)
|
|
86
|
-
steering.addRow(
|
|
87
|
-
_label("Preset wait (s)", "Pause after switching presets before continuing."),
|
|
88
|
-
self.preset_wait,
|
|
89
|
-
)
|
|
90
|
-
steering.addRow(
|
|
91
|
-
_label("Snapshot wait (s)", "Pause after switching snapshots before continuing."),
|
|
92
|
-
self.snapshot_wait,
|
|
93
|
-
)
|
|
94
|
-
steering.addRow(
|
|
95
|
-
_label(
|
|
96
|
-
"Measurement wait (s)", "Pause before capturing loudness after a snapshot change."
|
|
97
|
-
),
|
|
98
|
-
self.measurement_wait,
|
|
99
|
-
)
|
|
100
83
|
|
|
101
84
|
def populate(self, args: argparse.Namespace) -> None:
|
|
102
85
|
self.audio_device.setText(_text(args.audio_device or "Helix"))
|
|
@@ -106,13 +89,6 @@ class HelixSettingsPanel(QWidget):
|
|
|
106
89
|
self.blocksize.setValue(args.blocksize or 0)
|
|
107
90
|
self.steering_output.setText(_text(args.steering_output or "Helix"))
|
|
108
91
|
self.steering_channel.setValue(args.steering_channel or 0)
|
|
109
|
-
self.preset_wait.setText(_text(args.preset_wait if args.preset_wait is not None else 0.5))
|
|
110
|
-
self.snapshot_wait.setText(
|
|
111
|
-
_text(args.snapshot_wait if args.snapshot_wait is not None else 0.2)
|
|
112
|
-
)
|
|
113
|
-
self.measurement_wait.setText(
|
|
114
|
-
_text(args.measurement_wait if args.measurement_wait is not None else 0.1)
|
|
115
|
-
)
|
|
116
92
|
|
|
117
93
|
def append_arguments(self, argv: list[str]) -> None:
|
|
118
94
|
_append(argv, "--audio-device", self.audio_device.text())
|
|
@@ -122,9 +98,6 @@ class HelixSettingsPanel(QWidget):
|
|
|
122
98
|
_append(argv, "--blocksize", self.blocksize.value())
|
|
123
99
|
_append(argv, "--steering-output", self.steering_output.text())
|
|
124
100
|
_append(argv, "--steering-channel", self.steering_channel.value())
|
|
125
|
-
_append(argv, "--preset-wait", self.preset_wait.text())
|
|
126
|
-
_append(argv, "--snapshot-wait", self.snapshot_wait.text())
|
|
127
|
-
_append(argv, "--measurement-wait", self.measurement_wait.text())
|
|
128
101
|
|
|
129
102
|
|
|
130
103
|
def _append(argv: list[str], name: str, value: object) -> None:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from PySide6.QtCore import Qt
|
|
@@ -21,7 +22,26 @@ from matchpatch import __version__
|
|
|
21
22
|
from matchpatch.gui.help import HelpId, resolve_help_url
|
|
22
23
|
|
|
23
24
|
PROJECT_URL = "https://github.com/noseglasses/MatchPatch"
|
|
24
|
-
|
|
25
|
+
SOURCE_ROOT = Path(__file__).resolve().parents[3]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resource_path(*parts: str) -> Path:
|
|
29
|
+
relative_path = Path(*parts)
|
|
30
|
+
candidates: list[Path] = []
|
|
31
|
+
meipass = getattr(sys, "_MEIPASS", None)
|
|
32
|
+
if getattr(sys, "frozen", False):
|
|
33
|
+
if meipass:
|
|
34
|
+
candidates.append(Path(meipass) / relative_path)
|
|
35
|
+
candidates.append(Path(sys.executable).resolve().parent / relative_path)
|
|
36
|
+
candidates.append(SOURCE_ROOT / relative_path)
|
|
37
|
+
|
|
38
|
+
for candidate in candidates:
|
|
39
|
+
if candidate.exists():
|
|
40
|
+
return candidate
|
|
41
|
+
return candidates[0]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ASSETS_DIR = resource_path("docs", "assets")
|
|
25
45
|
|
|
26
46
|
|
|
27
47
|
def _about_icon_blue(size: int) -> QColor:
|
|
@@ -101,7 +101,14 @@ from PySide6.QtWidgets import (
|
|
|
101
101
|
QWidget,
|
|
102
102
|
)
|
|
103
103
|
|
|
104
|
-
from matchpatch.config import
|
|
104
|
+
from matchpatch.config import (
|
|
105
|
+
Config,
|
|
106
|
+
config_value,
|
|
107
|
+
default_config,
|
|
108
|
+
default_config_path,
|
|
109
|
+
export_config,
|
|
110
|
+
load_config,
|
|
111
|
+
)
|
|
105
112
|
from matchpatch.custom_adjustments import CustomAdjustments, load_custom_adjustments_file
|
|
106
113
|
from matchpatch.devices import get_device_profile, list_device_profiles
|
|
107
114
|
from matchpatch.devices.base import (
|
|
@@ -2646,7 +2653,7 @@ class MainWindow(QMainWindow):
|
|
|
2646
2653
|
dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
|
|
2647
2654
|
dialog.setFileMode(QFileDialog.FileMode.AnyFile)
|
|
2648
2655
|
dialog.setNameFilter("TOML (*.toml)")
|
|
2649
|
-
dialog.selectFile(str(Path(self.config_path.text().strip() or
|
|
2656
|
+
dialog.selectFile(str(Path(self.config_path.text().strip() or default_config_path())))
|
|
2650
2657
|
dialog.setLabelText(QFileDialog.DialogLabel.Accept, "Save")
|
|
2651
2658
|
save_default = QCheckBox("Save default configuration", dialog)
|
|
2652
2659
|
save_default.setChecked(False)
|