muscriptor 0.2.1__tar.gz → 0.3.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.
- {muscriptor-0.2.1 → muscriptor-0.3.0}/.dockerignore +1 -0
- muscriptor-0.3.0/.github/workflows/deploy.yml +28 -0
- muscriptor-0.3.0/.github/workflows/id_ed25519.pub +1 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/.gitignore +3 -0
- muscriptor-0.3.0/.python-version +1 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/Dockerfile +4 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/PKG-INFO +94 -16
- {muscriptor-0.2.1 → muscriptor-0.3.0}/README.md +87 -13
- muscriptor-0.3.0/muscriptor/accelerator.py +69 -0
- muscriptor-0.3.0/muscriptor/events.py +294 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/main.py +103 -19
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/models/lm.py +30 -12
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/modules/conditioners.py +3 -4
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/modules/transformer.py +31 -16
- muscriptor-0.3.0/muscriptor/server.py +451 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/tokenizer/mt3.py +61 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/tokenizer/notes.py +70 -37
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/transcription_model.py +177 -22
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/auralization.py +23 -11
- muscriptor-0.3.0/muscriptor/utils/beats.py +367 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/download.py +36 -2
- muscriptor-0.3.0/muscriptor/utils/midi.py +55 -0
- muscriptor-0.3.0/muscriptor/web_dist/assets/index-ClK01VrS.js +223 -0
- muscriptor-0.3.0/muscriptor/web_dist/assets/index-DfGntwya.css +2 -0
- muscriptor-0.3.0/muscriptor/web_dist/headache_by_lost_deposit_1min.mp3 +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/index.html +3 -3
- muscriptor-0.2.1/web/public/muscriptor-mark-v4-pink.svg → muscriptor-0.3.0/muscriptor/web_dist/muscriptor-mark-v4.svg +3 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/pyproject.toml +15 -3
- {muscriptor-0.2.1 → muscriptor-0.3.0}/swarm.yml +6 -1
- muscriptor-0.3.0/tests/test_beats.py +243 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_cli.py +64 -1
- muscriptor-0.3.0/tests/test_download.py +26 -0
- muscriptor-0.3.0/tests/test_midi.py +174 -0
- muscriptor-0.3.0/tests/test_prelude_forcing.py +427 -0
- muscriptor-0.3.0/tests/test_server.py +463 -0
- muscriptor-0.3.0/tests/test_strict_instruments.py +190 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_transcription_model.py +31 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/uv.lock +1895 -109
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/index.html +1 -1
- muscriptor-0.3.0/web/public/headache_by_lost_deposit_1min.mp3 +0 -0
- muscriptor-0.2.1/muscriptor/web_dist/muscriptor-mark-v4-pink.svg → muscriptor-0.3.0/web/public/muscriptor-mark-v4.svg +3 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/App.tsx +111 -36
- muscriptor-0.3.0/web/src/analytics.ts +91 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/audio.ts +29 -1
- muscriptor-0.3.0/web/src/components/Button.tsx +43 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/ConditioningPanel.tsx +14 -8
- muscriptor-0.3.0/web/src/components/ConsentBanner.tsx +49 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/Controls.tsx +5 -4
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/DropOverlay.tsx +2 -9
- muscriptor-0.3.0/web/src/components/Faq.tsx +143 -0
- muscriptor-0.3.0/web/src/components/FeedbackLine.tsx +68 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/Footer.tsx +27 -18
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/InstrumentList.tsx +17 -13
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/OutputBar.tsx +24 -13
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/PianoRollCanvas.tsx +1 -1
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/WelcomeScreen.tsx +71 -30
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/hooks/useTranscription.ts +78 -12
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/main.tsx +3 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/pianoroll.ts +309 -44
- muscriptor-0.3.0/web/src/sse.ts +138 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/style.css +29 -0
- muscriptor-0.3.0/web/src/vite-env.d.ts +10 -0
- muscriptor-0.2.1/.python-version +0 -1
- muscriptor-0.2.1/muscriptor/events.py +0 -208
- muscriptor-0.2.1/muscriptor/server.py +0 -279
- muscriptor-0.2.1/muscriptor/utils/midi.py +0 -25
- muscriptor-0.2.1/muscriptor/web_dist/assets/index-BZbHOTyZ.js +0 -223
- muscriptor-0.2.1/muscriptor/web_dist/assets/index-b8IJOPe2.css +0 -2
- muscriptor-0.2.1/tests/test_midi.py +0 -63
- muscriptor-0.2.1/tests/test_server.py +0 -220
- muscriptor-0.2.1/web/src/sse.ts +0 -68
- muscriptor-0.2.1/web/src/vite-env.d.ts +0 -1
- {muscriptor-0.2.1 → muscriptor-0.3.0}/.github/workflows/pypi.yml +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/.pre-commit-config.yaml +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/LICENSE +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/deploy.sh +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/__main__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/models/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/modules/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/modules/mel_spectrogram.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/modules/streaming.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/soundfonts.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/tokenizer/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/audio.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/resample.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/utils/sampling.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/assets/spessasynth_processor.min-tdOdhV7p.js +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/favicon.png +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/kyutai-logo.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/mirelo-logo.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/muscriptor-logo-v2.png +0 -0
- /muscriptor-0.2.1/muscriptor/web_dist/muscriptor-logo-v4-pink.svg → /muscriptor-0.3.0/muscriptor/web_dist/muscriptor-logo-v4.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/muscriptor/web_dist/muscriptor-wave.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/__init__.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/conftest.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/encode_helpers.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_conditioners.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_events.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_integration.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_notes.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_sampling.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/tests/test_transformer.py +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/logo_muscriptor_final.png +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/package-lock.json +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/package.json +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/pnpm-lock.yaml +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/public/favicon.png +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/public/kyutai-logo.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/public/mirelo-logo.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/public/muscriptor-logo-v2.png +0 -0
- /muscriptor-0.2.1/web/public/muscriptor-logo-v4-pink.svg → /muscriptor-0.3.0/web/public/muscriptor-logo-v4.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/public/muscriptor-wave.svg +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/components/icons.tsx +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/hooks/useAudioEngine.ts +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/instruments.ts +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/src/progress.ts +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/tsconfig.json +0 -0
- {muscriptor-0.2.1 → muscriptor-0.3.0}/web/vite.config.ts +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Deploy demo
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
concurrency:
|
|
7
|
+
group: deploy-demo
|
|
8
|
+
cancel-in-progress: false
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
deploy:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- name: Set up SSH access to the deployment host
|
|
16
|
+
run: |
|
|
17
|
+
mkdir -p ~/.ssh
|
|
18
|
+
chmod 700 ~/.ssh
|
|
19
|
+
# The public key is at .github/workflows/id_ed25519.pub
|
|
20
|
+
# If the machine changes, add this key to the authorized_keys of the new machine.
|
|
21
|
+
echo "${{ secrets.DEPLOYMENT_SSH_KEY }}" > ~/.ssh/id_ed25519
|
|
22
|
+
chmod 600 ~/.ssh/id_ed25519
|
|
23
|
+
ssh-keyscan muscriptor.kyutai.org >> ~/.ssh/known_hosts
|
|
24
|
+
echo "${{ secrets.DEPLOYMENT_REGISTRY_SECRET }}" | docker login rg.fr-par.scw.cloud -u nologin --password-stdin
|
|
25
|
+
- name: Deploy
|
|
26
|
+
env:
|
|
27
|
+
HF_TOKEN: ${{ secrets.DEPLOYMENT_HF_TOKEN }}
|
|
28
|
+
run: ./deploy.sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTt3l4T8vJXJWxDPQrWjXFNOf3Z4ww/vtUXBbn5reJU root@tf-muscriptor-0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -9,6 +9,10 @@ COPY web/package.json web/pnpm-lock.yaml ./
|
|
|
9
9
|
RUN pnpm install --frozen-lockfile
|
|
10
10
|
|
|
11
11
|
COPY web/ ./
|
|
12
|
+
# The GA4 measurement ID is inlined into the JS bundle by Vite; left unset
|
|
13
|
+
# (the default), the frontend is built with analytics disabled entirely.
|
|
14
|
+
ARG VITE_GA_MEASUREMENT_ID=
|
|
15
|
+
ENV VITE_GA_MEASUREMENT_ID=$VITE_GA_MEASUREMENT_ID
|
|
12
16
|
# vite outDir is ../muscriptor/web_dist, i.e. /muscriptor/web_dist in this stage.
|
|
13
17
|
RUN pnpm run build
|
|
14
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muscriptor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Audio-to-MIDI transcription using a transformer language model
|
|
5
5
|
Project-URL: Homepage, https://github.com/muscriptor/muscriptor
|
|
6
6
|
Project-URL: Repository, https://github.com/muscriptor/muscriptor
|
|
@@ -18,16 +18,20 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: beat-this>=1.1
|
|
21
22
|
Requires-Dist: einops>=0.4
|
|
22
23
|
Requires-Dist: fastapi>=0.136.3
|
|
23
24
|
Requires-Dist: httpx>=0.28.1
|
|
24
25
|
Requires-Dist: huggingface-hub>=0.13
|
|
25
26
|
Requires-Dist: mido>=1.3
|
|
26
|
-
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: numpy<2,>=1.24; sys_platform == 'darwin' and platform_machine == 'x86_64'
|
|
28
|
+
Requires-Dist: numpy>=1.24; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
|
29
|
+
Requires-Dist: packaging>=20.0
|
|
27
30
|
Requires-Dist: python-multipart>=0.0.29
|
|
28
31
|
Requires-Dist: safetensors>=0.4
|
|
29
32
|
Requires-Dist: soundfile>=0.14.0
|
|
30
|
-
Requires-Dist: torch
|
|
33
|
+
Requires-Dist: torch<2.3,>=2.0; sys_platform == 'darwin' and platform_machine == 'x86_64'
|
|
34
|
+
Requires-Dist: torch>=2.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
|
31
35
|
Requires-Dist: typer>=0.10
|
|
32
36
|
Requires-Dist: uvicorn[standard]>=0.48.0
|
|
33
37
|
Description-Content-Type: text/markdown
|
|
@@ -50,6 +54,30 @@ MuScriptor is the first music transcription model that has been trained on a lar
|
|
|
50
54
|
</p>
|
|
51
55
|
-->
|
|
52
56
|
|
|
57
|
+
## HuggingFace login (required)
|
|
58
|
+
|
|
59
|
+
The model weights are hosted on [HuggingFace](https://huggingface.co/MuScriptor)
|
|
60
|
+
and gated behind their CC BY-NC 4.0 license, so downloading them — including
|
|
61
|
+
on the first `uvx muscriptor` run — requires a (free) HuggingFace account:
|
|
62
|
+
|
|
63
|
+
1. Accept the model license on the model page, e.g.
|
|
64
|
+
[muscriptor-medium](https://huggingface.co/MuScriptor/muscriptor-medium)
|
|
65
|
+
(access is granted automatically).
|
|
66
|
+
2. Authenticate on your machine:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uvx hf auth login
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
or set a token (create one at
|
|
73
|
+
[huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export HF_TOKEN=hf_...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The weights are then downloaded on first use and cached locally.
|
|
80
|
+
|
|
53
81
|
## Try it locally
|
|
54
82
|
|
|
55
83
|
You can try it locally with the web UI with:
|
|
@@ -58,12 +86,29 @@ You can try it locally with the web UI with:
|
|
|
58
86
|
uvx muscriptor serve
|
|
59
87
|
```
|
|
60
88
|
|
|
89
|
+
On windows, the default PyTorch backend is `cpu`, so to use the GPU on Windows, you'll need to run:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uvx --torch-backend=cu128 muscriptor serve
|
|
93
|
+
```
|
|
94
|
+
Use the `--torch-backend=cu128` flag every time you run a `uvx` command on Windows.
|
|
95
|
+
|
|
61
96
|
or with the CLI:
|
|
62
97
|
|
|
63
98
|
```bash
|
|
64
99
|
uvx muscriptor transcribe
|
|
65
100
|
```
|
|
66
101
|
|
|
102
|
+
> **Intel Macs:** PyTorch stopped shipping Intel-mac (x86_64) wheels after
|
|
103
|
+
> torch 2.2.2, which supports Python ≤ 3.12, so you must tell uvx to use
|
|
104
|
+
> Python 3.12:
|
|
105
|
+
>
|
|
106
|
+
> ```bash
|
|
107
|
+
> uvx --python 3.12 muscriptor serve
|
|
108
|
+
> ```
|
|
109
|
+
>
|
|
110
|
+
> If you install with pip/uv instead, use Python 3.10–3.12.
|
|
111
|
+
|
|
67
112
|
|
|
68
113
|
## Installation
|
|
69
114
|
|
|
@@ -92,7 +137,9 @@ weights are downloaded and cached automatically. The architecture is a transform
|
|
|
92
137
|
|
|
93
138
|
`small` is the practical choice on CPU-only machines, `medium` is the default
|
|
94
139
|
speed/accuracy trade-off, and `large` is the most accurate but really wants a
|
|
95
|
-
GPU.
|
|
140
|
+
GPU. On Apple Silicon the model runs on Metal (MPS) automatically, in float16
|
|
141
|
+
by default (`--dtype float32` to override) — fast enough that `large`
|
|
142
|
+
transcribes several times faster than real time.
|
|
96
143
|
## Usage
|
|
97
144
|
|
|
98
145
|
```python
|
|
@@ -165,6 +212,7 @@ class TranscriptionModel:
|
|
|
165
212
|
batch_size: int | None = None,
|
|
166
213
|
no_eos_is_ok: bool = True,
|
|
167
214
|
beam_size: int = 1,
|
|
215
|
+
prelude_forcing: bool = True,
|
|
168
216
|
) -> Generator[NoteStartEvent | NoteEndEvent | ProgressEvent, None, None]:
|
|
169
217
|
"""Transcribe audio into a stream of note events.
|
|
170
218
|
|
|
@@ -177,24 +225,39 @@ class TranscriptionModel:
|
|
|
177
225
|
temperature: Sampling temperature (only used when use_sampling=True).
|
|
178
226
|
cfg_coef: Classifier-free guidance coefficient. Keep to 1 for the released models (they are post-RL)
|
|
179
227
|
instruments: Optional list of instrument group names to
|
|
180
|
-
|
|
228
|
+
restrict decoding to (exact names, e.g.
|
|
181
229
|
["acoustic_piano", "drums"]). Run `muscriptor
|
|
182
230
|
list-instruments` (or GET /instruments on the server)
|
|
183
|
-
for the full list of valid names.
|
|
231
|
+
for the full list of valid names. When given, this is a
|
|
232
|
+
hard constraint: every program/drum token outside the
|
|
233
|
+
list is masked out during generation, so no unlisted
|
|
234
|
+
instrument can appear in the output. Leave unset to let
|
|
235
|
+
the model decode whatever instruments it detects.
|
|
184
236
|
batch_size: Number of 5-second chunks processed per forward
|
|
185
|
-
pass. `None` (default)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
events
|
|
192
|
-
|
|
237
|
+
pass. `None` (default) resolves to 1 while prelude forcing
|
|
238
|
+
is on; with `prelude_forcing=False` it picks a value based
|
|
239
|
+
on the device: 1 on CPU, 4 on GPU. Values > 1 trade
|
|
240
|
+
chunk-boundary quality for throughput and therefore require
|
|
241
|
+
`prelude_forcing=False` (ValueError otherwise). Batching
|
|
242
|
+
also delays streaming: several chunks generate together, so
|
|
243
|
+
events belonging to later chunks of a batch won't arrive
|
|
244
|
+
until the whole batch finishes. Within a batch, events are
|
|
245
|
+
always yielded in temporal order; all events from chunk N
|
|
246
|
+
are emitted before any event from chunk N+1.
|
|
193
247
|
no_eos_is_ok: If True, a chunk that doesn't emit EOS within
|
|
194
248
|
the generation budget produces a warning instead of raising.
|
|
195
249
|
beam_size: Beam search width. 1 (default) uses greedy decoding
|
|
196
250
|
(or sampling, with use_sampling=True); >= 2 enables beam
|
|
197
251
|
search, which is slower but can be more accurate.
|
|
252
|
+
prelude_forcing: If True (default), every chunk after the first
|
|
253
|
+
has its tie prologue — the tokens declaring which notes are
|
|
254
|
+
sustained from the previous chunk — teacher-forced from the
|
|
255
|
+
previous chunk's actually-unfinished notes, instead of
|
|
256
|
+
letting the model guess (which occasionally makes a chunk
|
|
257
|
+
restart with the wrong instruments). Requires chunks to be
|
|
258
|
+
generated strictly in order, so it only works with
|
|
259
|
+
batch_size=1 (the default); set prelude_forcing=False
|
|
260
|
+
explicitly to use larger batches.
|
|
198
261
|
|
|
199
262
|
Returns:
|
|
200
263
|
Generator of NoteStartEvent, NoteEndEvent and ProgressEvent
|
|
@@ -206,6 +269,12 @@ class TranscriptionModel:
|
|
|
206
269
|
plus a tiny duration. Note: this tokenizer does not preserve
|
|
207
270
|
velocity (loudness) — only onset/offset timing, pitch, and
|
|
208
271
|
instrument are recovered.
|
|
272
|
+
|
|
273
|
+
Event times may all carry the same small lag (up to ~25 ms).
|
|
274
|
+
Correcting it needs the beat grid and the whole transcription's
|
|
275
|
+
onsets, which are only available once the stream has finished, so
|
|
276
|
+
prefer `transcribe_to_midi` when precise note timing matters — it
|
|
277
|
+
measures the lag and takes it out.
|
|
209
278
|
"""
|
|
210
279
|
|
|
211
280
|
def transcribe_to_midi(
|
|
@@ -218,6 +287,7 @@ class TranscriptionModel:
|
|
|
218
287
|
batch_size: int | None = None,
|
|
219
288
|
no_eos_is_ok: bool = True,
|
|
220
289
|
beam_size: int = 1,
|
|
290
|
+
prelude_forcing: bool = True,
|
|
221
291
|
) -> bytes:
|
|
222
292
|
"""Same as `transcribe`, but returns a MIDI file as bytes instead
|
|
223
293
|
of a generator of events. Useful when you want to save the MIDI
|
|
@@ -237,10 +307,11 @@ muscriptor transcribe audio.wav -o out.mid
|
|
|
237
307
|
# a local safetensors path, or an hf:// / http(s):// URL
|
|
238
308
|
muscriptor transcribe audio.wav --model large
|
|
239
309
|
|
|
240
|
-
#
|
|
310
|
+
# Restrict decoding to only the instruments listed (comma-separated names;
|
|
241
311
|
# run `muscriptor list-instruments` for the full list). Case-insensitive,
|
|
242
312
|
# and unambiguous abbreviations work: 'timp,dist' = timpani + distorted
|
|
243
|
-
# electric guitar
|
|
313
|
+
# electric guitar. Every other instrument's tokens are masked out during
|
|
314
|
+
# generation, so nothing else can appear in the output.
|
|
244
315
|
muscriptor transcribe audio.wav --instruments acoustic_piano,drums
|
|
245
316
|
|
|
246
317
|
# Get the event stream instead of MIDI: json (single array) or
|
|
@@ -252,6 +323,13 @@ muscriptor transcribe audio.wav --format jsonl -o -
|
|
|
252
323
|
muscriptor transcribe audio.wav --sampling -t 0.8
|
|
253
324
|
muscriptor transcribe audio.wav --beam-size 4
|
|
254
325
|
|
|
326
|
+
# Notes sustained across a chunk boundary are teacher-forced into the next
|
|
327
|
+
# chunk's prologue by default, so a chunk can't restart them with the wrong
|
|
328
|
+
# instrument. This needs chunks generated in order, so the batch size
|
|
329
|
+
# defaults to 1; batching (faster on GPU, lower quality at chunk boundaries)
|
|
330
|
+
# must be opted into explicitly:
|
|
331
|
+
muscriptor transcribe audio.wav --no-prelude-forcing --batch-size 4
|
|
332
|
+
|
|
255
333
|
# Render a stereo check-mix of the result (left channel = original audio,
|
|
256
334
|
# right channel = synthesized MIDI; requires fluidsynth on PATH)
|
|
257
335
|
muscriptor transcribe audio.wav -o out.mid --auralize check.wav
|
|
@@ -16,6 +16,30 @@ MuScriptor is the first music transcription model that has been trained on a lar
|
|
|
16
16
|
</p>
|
|
17
17
|
-->
|
|
18
18
|
|
|
19
|
+
## HuggingFace login (required)
|
|
20
|
+
|
|
21
|
+
The model weights are hosted on [HuggingFace](https://huggingface.co/MuScriptor)
|
|
22
|
+
and gated behind their CC BY-NC 4.0 license, so downloading them — including
|
|
23
|
+
on the first `uvx muscriptor` run — requires a (free) HuggingFace account:
|
|
24
|
+
|
|
25
|
+
1. Accept the model license on the model page, e.g.
|
|
26
|
+
[muscriptor-medium](https://huggingface.co/MuScriptor/muscriptor-medium)
|
|
27
|
+
(access is granted automatically).
|
|
28
|
+
2. Authenticate on your machine:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uvx hf auth login
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
or set a token (create one at
|
|
35
|
+
[huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export HF_TOKEN=hf_...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The weights are then downloaded on first use and cached locally.
|
|
42
|
+
|
|
19
43
|
## Try it locally
|
|
20
44
|
|
|
21
45
|
You can try it locally with the web UI with:
|
|
@@ -24,12 +48,29 @@ You can try it locally with the web UI with:
|
|
|
24
48
|
uvx muscriptor serve
|
|
25
49
|
```
|
|
26
50
|
|
|
51
|
+
On windows, the default PyTorch backend is `cpu`, so to use the GPU on Windows, you'll need to run:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uvx --torch-backend=cu128 muscriptor serve
|
|
55
|
+
```
|
|
56
|
+
Use the `--torch-backend=cu128` flag every time you run a `uvx` command on Windows.
|
|
57
|
+
|
|
27
58
|
or with the CLI:
|
|
28
59
|
|
|
29
60
|
```bash
|
|
30
61
|
uvx muscriptor transcribe
|
|
31
62
|
```
|
|
32
63
|
|
|
64
|
+
> **Intel Macs:** PyTorch stopped shipping Intel-mac (x86_64) wheels after
|
|
65
|
+
> torch 2.2.2, which supports Python ≤ 3.12, so you must tell uvx to use
|
|
66
|
+
> Python 3.12:
|
|
67
|
+
>
|
|
68
|
+
> ```bash
|
|
69
|
+
> uvx --python 3.12 muscriptor serve
|
|
70
|
+
> ```
|
|
71
|
+
>
|
|
72
|
+
> If you install with pip/uv instead, use Python 3.10–3.12.
|
|
73
|
+
|
|
33
74
|
|
|
34
75
|
## Installation
|
|
35
76
|
|
|
@@ -58,7 +99,9 @@ weights are downloaded and cached automatically. The architecture is a transform
|
|
|
58
99
|
|
|
59
100
|
`small` is the practical choice on CPU-only machines, `medium` is the default
|
|
60
101
|
speed/accuracy trade-off, and `large` is the most accurate but really wants a
|
|
61
|
-
GPU.
|
|
102
|
+
GPU. On Apple Silicon the model runs on Metal (MPS) automatically, in float16
|
|
103
|
+
by default (`--dtype float32` to override) — fast enough that `large`
|
|
104
|
+
transcribes several times faster than real time.
|
|
62
105
|
## Usage
|
|
63
106
|
|
|
64
107
|
```python
|
|
@@ -131,6 +174,7 @@ class TranscriptionModel:
|
|
|
131
174
|
batch_size: int | None = None,
|
|
132
175
|
no_eos_is_ok: bool = True,
|
|
133
176
|
beam_size: int = 1,
|
|
177
|
+
prelude_forcing: bool = True,
|
|
134
178
|
) -> Generator[NoteStartEvent | NoteEndEvent | ProgressEvent, None, None]:
|
|
135
179
|
"""Transcribe audio into a stream of note events.
|
|
136
180
|
|
|
@@ -143,24 +187,39 @@ class TranscriptionModel:
|
|
|
143
187
|
temperature: Sampling temperature (only used when use_sampling=True).
|
|
144
188
|
cfg_coef: Classifier-free guidance coefficient. Keep to 1 for the released models (they are post-RL)
|
|
145
189
|
instruments: Optional list of instrument group names to
|
|
146
|
-
|
|
190
|
+
restrict decoding to (exact names, e.g.
|
|
147
191
|
["acoustic_piano", "drums"]). Run `muscriptor
|
|
148
192
|
list-instruments` (or GET /instruments on the server)
|
|
149
|
-
for the full list of valid names.
|
|
193
|
+
for the full list of valid names. When given, this is a
|
|
194
|
+
hard constraint: every program/drum token outside the
|
|
195
|
+
list is masked out during generation, so no unlisted
|
|
196
|
+
instrument can appear in the output. Leave unset to let
|
|
197
|
+
the model decode whatever instruments it detects.
|
|
150
198
|
batch_size: Number of 5-second chunks processed per forward
|
|
151
|
-
pass. `None` (default)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
events
|
|
158
|
-
|
|
199
|
+
pass. `None` (default) resolves to 1 while prelude forcing
|
|
200
|
+
is on; with `prelude_forcing=False` it picks a value based
|
|
201
|
+
on the device: 1 on CPU, 4 on GPU. Values > 1 trade
|
|
202
|
+
chunk-boundary quality for throughput and therefore require
|
|
203
|
+
`prelude_forcing=False` (ValueError otherwise). Batching
|
|
204
|
+
also delays streaming: several chunks generate together, so
|
|
205
|
+
events belonging to later chunks of a batch won't arrive
|
|
206
|
+
until the whole batch finishes. Within a batch, events are
|
|
207
|
+
always yielded in temporal order; all events from chunk N
|
|
208
|
+
are emitted before any event from chunk N+1.
|
|
159
209
|
no_eos_is_ok: If True, a chunk that doesn't emit EOS within
|
|
160
210
|
the generation budget produces a warning instead of raising.
|
|
161
211
|
beam_size: Beam search width. 1 (default) uses greedy decoding
|
|
162
212
|
(or sampling, with use_sampling=True); >= 2 enables beam
|
|
163
213
|
search, which is slower but can be more accurate.
|
|
214
|
+
prelude_forcing: If True (default), every chunk after the first
|
|
215
|
+
has its tie prologue — the tokens declaring which notes are
|
|
216
|
+
sustained from the previous chunk — teacher-forced from the
|
|
217
|
+
previous chunk's actually-unfinished notes, instead of
|
|
218
|
+
letting the model guess (which occasionally makes a chunk
|
|
219
|
+
restart with the wrong instruments). Requires chunks to be
|
|
220
|
+
generated strictly in order, so it only works with
|
|
221
|
+
batch_size=1 (the default); set prelude_forcing=False
|
|
222
|
+
explicitly to use larger batches.
|
|
164
223
|
|
|
165
224
|
Returns:
|
|
166
225
|
Generator of NoteStartEvent, NoteEndEvent and ProgressEvent
|
|
@@ -172,6 +231,12 @@ class TranscriptionModel:
|
|
|
172
231
|
plus a tiny duration. Note: this tokenizer does not preserve
|
|
173
232
|
velocity (loudness) — only onset/offset timing, pitch, and
|
|
174
233
|
instrument are recovered.
|
|
234
|
+
|
|
235
|
+
Event times may all carry the same small lag (up to ~25 ms).
|
|
236
|
+
Correcting it needs the beat grid and the whole transcription's
|
|
237
|
+
onsets, which are only available once the stream has finished, so
|
|
238
|
+
prefer `transcribe_to_midi` when precise note timing matters — it
|
|
239
|
+
measures the lag and takes it out.
|
|
175
240
|
"""
|
|
176
241
|
|
|
177
242
|
def transcribe_to_midi(
|
|
@@ -184,6 +249,7 @@ class TranscriptionModel:
|
|
|
184
249
|
batch_size: int | None = None,
|
|
185
250
|
no_eos_is_ok: bool = True,
|
|
186
251
|
beam_size: int = 1,
|
|
252
|
+
prelude_forcing: bool = True,
|
|
187
253
|
) -> bytes:
|
|
188
254
|
"""Same as `transcribe`, but returns a MIDI file as bytes instead
|
|
189
255
|
of a generator of events. Useful when you want to save the MIDI
|
|
@@ -203,10 +269,11 @@ muscriptor transcribe audio.wav -o out.mid
|
|
|
203
269
|
# a local safetensors path, or an hf:// / http(s):// URL
|
|
204
270
|
muscriptor transcribe audio.wav --model large
|
|
205
271
|
|
|
206
|
-
#
|
|
272
|
+
# Restrict decoding to only the instruments listed (comma-separated names;
|
|
207
273
|
# run `muscriptor list-instruments` for the full list). Case-insensitive,
|
|
208
274
|
# and unambiguous abbreviations work: 'timp,dist' = timpani + distorted
|
|
209
|
-
# electric guitar
|
|
275
|
+
# electric guitar. Every other instrument's tokens are masked out during
|
|
276
|
+
# generation, so nothing else can appear in the output.
|
|
210
277
|
muscriptor transcribe audio.wav --instruments acoustic_piano,drums
|
|
211
278
|
|
|
212
279
|
# Get the event stream instead of MIDI: json (single array) or
|
|
@@ -218,6 +285,13 @@ muscriptor transcribe audio.wav --format jsonl -o -
|
|
|
218
285
|
muscriptor transcribe audio.wav --sampling -t 0.8
|
|
219
286
|
muscriptor transcribe audio.wav --beam-size 4
|
|
220
287
|
|
|
288
|
+
# Notes sustained across a chunk boundary are teacher-forced into the next
|
|
289
|
+
# chunk's prologue by default, so a chunk can't restart them with the wrong
|
|
290
|
+
# instrument. This needs chunks generated in order, so the batch size
|
|
291
|
+
# defaults to 1; batching (faster on GPU, lower quality at chunk boundaries)
|
|
292
|
+
# must be opted into explicitly:
|
|
293
|
+
muscriptor transcribe audio.wav --no-prelude-forcing --batch-size 4
|
|
294
|
+
|
|
221
295
|
# Render a stereo check-mix of the result (left channel = original audio,
|
|
222
296
|
# right channel = synthesized MIDI; requires fluidsynth on PATH)
|
|
223
297
|
muscriptor transcribe audio.wav -o out.mid --auralize check.wav
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Device-agnostic accelerator helpers.
|
|
2
|
+
|
|
3
|
+
``torch.accelerator`` (``is_available``/``current_accelerator``/``synchronize``)
|
|
4
|
+
was only added in PyTorch 2.6; on older versions we fall back to checking CUDA
|
|
5
|
+
then MPS directly, in that order.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import platform
|
|
9
|
+
|
|
10
|
+
from packaging.version import Version
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
_HAS_TORCH_ACCELERATOR = Version(torch.__version__.split("+")[0]) >= Version("2.6")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _mps_available() -> bool:
|
|
18
|
+
"""Whether MPS is available and worth auto-selecting.
|
|
19
|
+
|
|
20
|
+
torch <= 2.2 also reports MPS as available on Intel Macs with AMD GPUs, a
|
|
21
|
+
backend that was never solid and has since been abandoned. Passing
|
|
22
|
+
``device="mps"`` explicitly still works there for those who want to try
|
|
23
|
+
it; this only affects auto-detection.
|
|
24
|
+
"""
|
|
25
|
+
return torch.backends.mps.is_available() and platform.machine() == "arm64"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_available() -> bool:
|
|
29
|
+
"""Whether an accelerator (GPU) is available."""
|
|
30
|
+
if _HAS_TORCH_ACCELERATOR:
|
|
31
|
+
return torch.accelerator.is_available()
|
|
32
|
+
return torch.cuda.is_available() or _mps_available()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def current_accelerator() -> torch.device:
|
|
36
|
+
"""The current accelerator device.
|
|
37
|
+
|
|
38
|
+
Raises ``RuntimeError`` if no accelerator is available; check
|
|
39
|
+
:func:`is_available` first.
|
|
40
|
+
"""
|
|
41
|
+
if _HAS_TORCH_ACCELERATOR:
|
|
42
|
+
return torch.accelerator.current_accelerator()
|
|
43
|
+
if torch.cuda.is_available():
|
|
44
|
+
return torch.device("cuda")
|
|
45
|
+
if _mps_available():
|
|
46
|
+
return torch.device("mps")
|
|
47
|
+
raise RuntimeError("No available accelerator detected.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def synchronize() -> None:
|
|
51
|
+
"""Wait for all kernels on the current accelerator to complete.
|
|
52
|
+
|
|
53
|
+
No-op if no accelerator is available.
|
|
54
|
+
"""
|
|
55
|
+
if _HAS_TORCH_ACCELERATOR:
|
|
56
|
+
# torch.accelerator.synchronize() still tries to init CUDA even on CPU-only systems
|
|
57
|
+
# Only call it if we actually have a non-CPU accelerator
|
|
58
|
+
try:
|
|
59
|
+
current_device = torch.accelerator.current_device_index()
|
|
60
|
+
if current_device >= 0:
|
|
61
|
+
torch.accelerator.synchronize()
|
|
62
|
+
except RuntimeError:
|
|
63
|
+
# No accelerator available (CUDA not found, etc.)
|
|
64
|
+
pass
|
|
65
|
+
return
|
|
66
|
+
if torch.cuda.is_available():
|
|
67
|
+
torch.cuda.synchronize()
|
|
68
|
+
elif _mps_available():
|
|
69
|
+
torch.mps.synchronize()
|