muscriptor 0.1.0__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.
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: muscriptor
3
+ Version: 0.1.0
4
+ Summary: Audio-to-MIDI transcription using a transformer language model
5
+ Project-URL: Homepage, https://github.com/muscriptor/muscriptor
6
+ Project-URL: Repository, https://github.com/muscriptor/muscriptor
7
+ Project-URL: Issues, https://github.com/muscriptor/muscriptor/issues
8
+ Author: Kyutai x Mirelo
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: audio,midi,music,transcription,transformer
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: einops>=0.4
22
+ Requires-Dist: fastapi>=0.136.3
23
+ Requires-Dist: httpx>=0.28.1
24
+ Requires-Dist: huggingface-hub>=0.13
25
+ Requires-Dist: mido>=1.3
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: python-multipart>=0.0.29
28
+ Requires-Dist: safetensors>=0.4
29
+ Requires-Dist: soundfile>=0.14.0
30
+ Requires-Dist: torch<2.7,>=2.0
31
+ Requires-Dist: typer>=0.10
32
+ Requires-Dist: uvicorn[standard]>=0.48.0
33
+ Description-Content-Type: text/markdown
34
+
35
+ <p align="center">
36
+ <img src="web/logo_muscriptor_final.png" alt="MuScriptor logo" width="300">
37
+ </p>
38
+
39
+ # MuScriptor
40
+
41
+ MuScriptor is a multi-instrument music transcription model developed by [Kyutai](https://kyutai.org) and [Mirelo](https://www.mirelo.ai).
42
+ MuScriptor is the first music transcription model that has been trained on a large scale dataset of 170k songs from classical music to heavy metal.
43
+
44
+ [Online Demo](https://muscriptor.kyutai.org) | [Paper] (coming soon) | [HuggingFace](https://huggingface.co/MuScriptor)
45
+
46
+ <!-- TODO: record the demo GIF (web UI piano roll), save it as assets/demo.gif,
47
+ then uncomment:
48
+ <p align="center">
49
+ <img src="assets/demo.gif" alt="MuScriptor web UI: live piano roll while transcribing" width="700">
50
+ </p>
51
+ -->
52
+
53
+ ## Try it locally
54
+
55
+ You can try it locally with the web UI with:
56
+
57
+ ```bash
58
+ uvx muscriptor serve
59
+ ```
60
+
61
+ or with the CLI:
62
+
63
+ ```bash
64
+ uvx muscriptor transcribe
65
+ ```
66
+
67
+
68
+ ## Installation
69
+
70
+ with uv (recommended):
71
+
72
+ ```bash
73
+ uv add muscriptor
74
+ ```
75
+
76
+ ```bash
77
+ pip install muscriptor
78
+ ```
79
+
80
+ ## Models
81
+
82
+ Three variants are published under the [MuScriptor](https://huggingface.co/MuScriptor)
83
+ HuggingFace organization. Everywhere a model is selected (`load_model()`, the
84
+ CLI's `--model`, `serve --model`) you can pass the bare size keyword and the
85
+ weights are downloaded and cached automatically. The architecture is a transformer decoder only. Here are the detailed model sizes:
86
+
87
+ | Variant | Parameters | Layers | Dim | HuggingFace repo |
88
+ |---|---|---|---|---|
89
+ | `small` | 103M | 14 | 768 | [muscriptor-small](https://huggingface.co/MuScriptor/muscriptor-small) |
90
+ | `medium` (default) | 307M | 24 | 1024 | [muscriptor-medium](https://huggingface.co/MuScriptor/muscriptor-medium) |
91
+ | `large` | 1.4B | 48 | 1536 | [muscriptor-large](https://huggingface.co/MuScriptor/muscriptor-large) |
92
+
93
+ `small` is the practical choice on CPU-only machines, `medium` is the default
94
+ speed/accuracy trade-off, and `large` is the most accurate but really wants a
95
+ GPU.
96
+ ## Usage
97
+
98
+ ```python
99
+ from pathlib import Path
100
+ from muscriptor import TranscriptionModel
101
+
102
+ # Downloads the default "medium" variant from HuggingFace (cached under
103
+ # ~/.cache/muscriptor/). Also accepts "small"/"large", a local safetensors
104
+ # path, or an hf:// / http(s):// URL.
105
+ model = TranscriptionModel.load_model()
106
+
107
+ # Stream events as they're transcribed. Optionally tell the model which
108
+ # instruments to expect — run `muscriptor list-instruments` for the names.
109
+ for event in model.transcribe("audio.wav", instruments=["acoustic_piano", "drums"]):
110
+ print(event)
111
+
112
+ # Or get a MIDI file directly
113
+ midi_bytes = model.transcribe_to_midi("audio.wav")
114
+ Path("out.mid").write_bytes(midi_bytes)
115
+ ```
116
+
117
+ ```python
118
+ from dataclasses import dataclass
119
+ from typing import Generator
120
+
121
+
122
+ @dataclass
123
+ class NoteStartEvent:
124
+ pitch: int
125
+ # Start of the note in seconds, from the beginning of the audio.
126
+ start_time: float
127
+ # A unique index for this note, used to match the corresponding
128
+ # NoteEndEvent.
129
+ index: int
130
+ instrument: str
131
+
132
+
133
+ @dataclass
134
+ class NoteEndEvent:
135
+ end_time: float
136
+ # The NoteStartEvent this end matches. Convenient for consumers —
137
+ # when serializing (e.g. to JSON) drop this field and rely on
138
+ # `start_event_index` to refer back to the start by id.
139
+ start_event: NoteStartEvent
140
+
141
+ @property
142
+ def start_event_index(self) -> int:
143
+ return self.start_event.index
144
+
145
+
146
+ @dataclass
147
+ class ProgressEvent:
148
+ # A coarse progress anchor woven into the event stream: `completed` of
149
+ # `total` 5-second chunks have been transcribed. One is emitted up front
150
+ # with completed == 0 (so consumers learn `total`), then one per finished
151
+ # chunk. Advisory only — consumers that just build notes can ignore them.
152
+ completed: int
153
+ total: int
154
+
155
+
156
+ class TranscriptionModel:
157
+ ...
158
+ def transcribe(
159
+ self,
160
+ audio: str | Path | tuple[torch.Tensor, int],
161
+ use_sampling: bool = False,
162
+ temperature: float = 1.0,
163
+ cfg_coef: float = 1.0,
164
+ instruments: list[str] | None = None,
165
+ batch_size: int | None = None,
166
+ no_eos_is_ok: bool = True,
167
+ beam_size: int = 1,
168
+ ) -> Generator[NoteStartEvent | NoteEndEvent | ProgressEvent, None, None]:
169
+ """Transcribe audio into a stream of note events.
170
+
171
+ Args:
172
+ audio: Path to an audio file, or a tuple `(tensor, sample_rate)`
173
+ with a float tensor of shape [T] or [1, T] at `sample_rate`
174
+ Hz. The tuple form is useful when the audio is already
175
+ loaded in memory.
176
+ use_sampling: Use temperature sampling instead of greedy decoding.
177
+ temperature: Sampling temperature (only used when use_sampling=True).
178
+ cfg_coef: Classifier-free guidance coefficient. Keep to 1 for the released models (they are post-RL)
179
+ instruments: Optional list of instrument group names to
180
+ condition the model on (exact names, e.g.
181
+ ["acoustic_piano", "drums"]). Run `muscriptor
182
+ list-instruments` (or GET /instruments on the server)
183
+ for the full list of valid names.
184
+ batch_size: Number of 5-second chunks processed per forward
185
+ pass. `None` (default) picks a value based on the device:
186
+ 1 on CPU, 4 on GPU. Use `batch_size=1` for the lowest
187
+ streaming latency — larger batches process several chunks
188
+ together, so events belonging to later chunks of a batch
189
+ won't arrive until the whole batch finishes. Within a
190
+ batch, events are always yielded in temporal order; all
191
+ events from chunk N are emitted before any event from
192
+ chunk N+1.
193
+ no_eos_is_ok: If True, a chunk that doesn't emit EOS within
194
+ the generation budget produces a warning instead of raising.
195
+ beam_size: Beam search width. 1 (default) uses greedy decoding
196
+ (or sampling, with use_sampling=True); >= 2 enables beam
197
+ search, which is slower but can be more accurate.
198
+
199
+ Returns:
200
+ Generator of NoteStartEvent, NoteEndEvent and ProgressEvent
201
+ objects. Every
202
+ NoteStartEvent is guaranteed to be followed by exactly one
203
+ matching NoteEndEvent later in the stream (with the same
204
+ `index`). Drum hits appear as a NoteStartEvent immediately
205
+ followed by its matching NoteEndEvent at the same start time
206
+ plus a tiny duration. Note: this tokenizer does not preserve
207
+ velocity (loudness) — only onset/offset timing, pitch, and
208
+ instrument are recovered.
209
+ """
210
+
211
+ def transcribe_to_midi(
212
+ self,
213
+ audio: str | Path | tuple[torch.Tensor, int],
214
+ use_sampling: bool = False,
215
+ temperature: float = 1.0,
216
+ cfg_coef: float = 1.0,
217
+ instruments: list[str] | None = None,
218
+ batch_size: int | None = None,
219
+ no_eos_is_ok: bool = True,
220
+ beam_size: int = 1,
221
+ ) -> bytes:
222
+ """Same as `transcribe`, but returns a MIDI file as bytes instead
223
+ of a generator of events. Useful when you want to save the MIDI
224
+ to disk or send it over a network without going through the
225
+ event stream.
226
+ """
227
+ ```
228
+
229
+
230
+ ## CLI
231
+
232
+ ```bash
233
+ # Transcribe to MIDI (defaults to <audio_file>.mid next to the input)
234
+ muscriptor transcribe audio.wav -o out.mid
235
+
236
+ # Pick a model variant: small / medium / large (default: medium),
237
+ # a local safetensors path, or an hf:// / http(s):// URL
238
+ muscriptor transcribe audio.wav --model large
239
+
240
+ # Tell the model which instruments to expect (comma-separated names;
241
+ # run `muscriptor list-instruments` for the full list). Case-insensitive,
242
+ # and unambiguous abbreviations work: 'timp,dist' = timpani + distorted
243
+ # electric guitar
244
+ muscriptor transcribe audio.wav --instruments acoustic_piano,drums
245
+
246
+ # Get the event stream instead of MIDI: json (single array) or
247
+ # jsonl (one event per line, streamed while transcribing); -o - = stdout
248
+ muscriptor transcribe audio.wav --format jsonl -o -
249
+
250
+ # Decoding options: temperature sampling, or beam search (slower, can be
251
+ # more accurate)
252
+ muscriptor transcribe audio.wav --sampling -t 0.8
253
+ muscriptor transcribe audio.wav --beam-size 4
254
+
255
+ # Render a stereo check-mix of the result (left channel = original audio,
256
+ # right channel = synthesized MIDI; requires fluidsynth on PATH)
257
+ muscriptor transcribe audio.wav -o out.mid --auralize check.wav
258
+ ```
259
+
260
+ See `muscriptor transcribe --help` for the full list of options.
261
+
262
+ ## Web UI
263
+
264
+ A browser client is included under `web/`. The FastAPI server serves both
265
+ the UI and a POST `/transcribe` endpoint that streams `NoteStart`/`NoteEnd`
266
+ events back as Server-Sent Events. The UI accepts an audio file (WAV, or any
267
+ format soundfile/libsndfile can read — mp3, flac, ogg, m4a, …) via drag-and-drop,
268
+ renders a live piano roll while events arrive, auto-plays once enough notes
269
+ are available, and crossfades between the original WAV and the synthesized
270
+ MIDI playback.
271
+
272
+ ### One-time setup
273
+
274
+ ```bash
275
+ uv sync
276
+ cd web && pnpm install && pnpm run build && cd ..
277
+ ```
278
+
279
+ `pnpm run build` is required once — the FastAPI server auto-mounts
280
+ `web/dist/` if it exists. The first build downloads two soundfonts:
281
+ `MuseScore_General.sf2` (215 MB, to the repo root, used by `/auralize`) and
282
+ `MuseScore_General.sf3` (38 MB, the compressed build the UI plays); later
283
+ builds skip files already on disk.
284
+
285
+ ### Run
286
+
287
+ ```bash
288
+ uv run muscriptor serve \
289
+ --model medium \
290
+ --device cuda \
291
+ --host 0.0.0.0 \
292
+ --port 8222
293
+ ```
294
+
295
+ `--model` accepts a size keyword (`small`, `medium`, `large`) that downloads
296
+ the matching variant from HuggingFace (cached under `~/.cache/muscriptor/`),
297
+ a local safetensors path, or an `hf://` / `http(s)://` URL. It defaults to
298
+ `medium` when omitted.
299
+
300
+ Then open <http://127.0.0.1:8222/> (or the LAN address of the host) and drop
301
+ a WAV onto the page.
302
+
303
+ - Drop `--device cuda` if running CPU-only.
304
+ - `--host 0.0.0.0` makes it reachable on the LAN; the default `127.0.0.1`
305
+ is local-only.
306
+ - Playback runs a full SoundFont synthesizer ([SpessaSynth](https://github.com/spessasus/spessasynth_lib))
307
+ in the browser, fed with `MuseScore_General.sf3` — the same soundfont the
308
+ `/auralize` endpoint uses, served by the app itself from `/soundfonts/`,
309
+ no third-party CDN.
310
+
311
+ ## License
312
+
313
+ The code in this repository is released under the [MIT license](LICENSE).
314
+
315
+ The model weights, published on
316
+ [HuggingFace](https://huggingface.co/MuScriptor), are released under the
317
+ [CC BY-NC 4.0 license](https://creativecommons.org/licenses/by-nc/4.0/)
318
+ (non-commercial use).
319
+
320
+ The MuseScore General SoundFont downloaded for playback / auralization is
321
+ distributed under its own (MIT) license.
322
+
323
+ ## Citation
324
+
325
+ <!-- TODO: fill in the BibTeX entry when the paper is out:
326
+ ```bibtex
327
+ @techreport{muscriptor2026,
328
+ title = {MuScriptor: ...},
329
+ author = {...},
330
+ year = {2026},
331
+ url = {...},
332
+ }
333
+ ```
334
+ -->
335
+
336
+ The paper is coming soon — a citation entry will be added here with it.
@@ -0,0 +1,28 @@
1
+ muscriptor/__init__.py,sha256=seJGp-40aQUe5ezeUTMeiChi1EXAwFUzTM0cscrOb7Q,292
2
+ muscriptor/__main__.py,sha256=HX2B06oycjHcSo-OTJAUhfT8OEvkrkGZY1qYqLpnzYY,41
3
+ muscriptor/events.py,sha256=LCS9iOW2G9ig8hMCuEF8au9jaLstINgOAaaZ3YbISWU,7682
4
+ muscriptor/main.py,sha256=PgsU8RRejiNNvqYaSoaRcFK72r4C_jOETMYCg6vfKmk,10301
5
+ muscriptor/server.py,sha256=JcxI_48kgy-1Octawza1y8vX8E4k5dlm1lEmcsbCRR8,10802
6
+ muscriptor/transcription_model.py,sha256=S7DE0tcFiPg2BVC6Q0BKbbsQxin2_7Z9ZzoJFHIRhlc,23459
7
+ muscriptor/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ muscriptor/models/lm.py,sha256=K8d0DgFAYR4hR74eItBP_y4GHuvLZnvWRND5ZILiwZU,19328
9
+ muscriptor/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ muscriptor/modules/conditioners.py,sha256=jlRRKdm3MTuNI_o0kje___6ZeiQgdrM81gZgkeRoGzI,10723
11
+ muscriptor/modules/mel_spectrogram.py,sha256=1Fb2OHWPDpgNwhQD2yNC7D8mZH6b9Nf0QRDavYuGdHM,3299
12
+ muscriptor/modules/streaming.py,sha256=0DoVh-hUarDBqiIhe43f1368uscKNHscFJS7ehsI5oo,2548
13
+ muscriptor/modules/transformer.py,sha256=iJKJYvUSHJ_xNMtYz93nMFeOuKd6XdjkpKSB2ra3epI,6900
14
+ muscriptor/tokenizer/__init__.py,sha256=LUiljAqOAGF218DM8RpoTcXnUFuv9GQXukdrscWGiIM,102
15
+ muscriptor/tokenizer/mt3.py,sha256=IAawG5kV1e_Zxfyu8H-BGUEfIqmugJcNmeZeSz9DrvI,7423
16
+ muscriptor/tokenizer/notes.py,sha256=l8PpyAKLSF0PvV6H8j3Dn0HBDSyjD8uHrba-K4u2cMQ,11533
17
+ muscriptor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ muscriptor/utils/audio.py,sha256=ghzurZ9jUOMODmAZ8vokdaXHVzEY37rHIXeKdrlw2Fg,3818
19
+ muscriptor/utils/auralization.py,sha256=NFhivpV6drHBEKZ45NNFEKVTjyhXzergLOhCHCsjDsg,5155
20
+ muscriptor/utils/download.py,sha256=4PE5tvlirZHu1SvoTdo5e1-8F2xsLHCkLFzA-bpZ-CA,2762
21
+ muscriptor/utils/midi.py,sha256=j2Zx8xTkuCcNY1R82EaxL64ar8h6oY8qO3DQzePRxIs,744
22
+ muscriptor/utils/resample.py,sha256=KypbyuFzNGYwB_mBlYN7CzXU5V4NRLgBnkvyEBQCVIc,8039
23
+ muscriptor/utils/sampling.py,sha256=sNuUUFtxPwBKqr1hMEfmbP2ejPwSCtaenZarwSNuNrI,3380
24
+ muscriptor-0.1.0.dist-info/METADATA,sha256=CfWYkYQ_H-xcal2iL3J2UqAw8iV0ia_ggQfdXsfTbIs,12413
25
+ muscriptor-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
26
+ muscriptor-0.1.0.dist-info/entry_points.txt,sha256=FuoO_u9NK2uJASbxjUxubhHSeIIcxeIJODf9T81sbhQ,52
27
+ muscriptor-0.1.0.dist-info/licenses/LICENSE,sha256=MX2df89NK_JBNrZRAzbLdv71LIc-cjOfspWWk0OHJ4s,1072
28
+ muscriptor-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ muscriptor = muscriptor.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyutai x Mirelo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.