Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.20.0
title: progress-music-generation
emoji: πΌ
colorFrom: indigo
colorTo: blue
sdk: gradio
sdk_version: 6.15.2
app_file: app.py
python_version: '3.10'
pinned: false
ProGress UI
Interactive demo for the ProGress music generation system, combining:
- SchenkerDiff β discrete graph-diffusion model for Schenkerian voice-leading
- ProGress Supplement β rejection-sampling + phrase stitching into full compositions
This folder is self-contained: everything the app needs at runtime β the
phrase_stitching code, a trimmed copy of the SchenkerDiff model code, the
phrase library, the conditioning tensors, and the last-v1.ckpt checkpoint β is
vendored under vendor/. No sibling research directories are
required, so it can be deployed as a single package.
Quickstart
cd ProGress_UI
pip install -r requirements.txt # torch 2.8 + PyG + spaces, see notes
python app.py # serves http://localhost:7860
Python 3.10 is the validated runtime. music21 is used only for MIDI export
(MuseScore is not required). The MIDI player loads from a CDN, so audio
playback needs internet access.
Running from inside the original research tree still works: if
vendor/is absent,backend.pyfalls back to the sibling../ProGress_Supplementand../SchenkerDifffolders. ThePROGRESS_SUPPLEMENT_DIR/PROGRESS_SCHENKER_DIRenv vars override either root.
Deploy (Hugging Face ZeroGPU)
The Space runs on ZeroGPU: a GPU is attached on demand for each generation
via the @spaces.GPU decorator, and the app falls back to CPU automatically
if the GPU can't be acquired or errors mid-run.
From this folder, with the HF CLI authenticated (hf auth login):
pip install gradio
gradio deploy
Then set the Space hardware to ZeroGPU (Settings β Hardware; requires a PRO
account). gradio deploy uploads the directory and runs app.py against
requirements.txt.
Notes:
- torch is pinned to 2.8.0 β ZeroGPU requires one of 2.8.0 / 2.9.1 / 2.10.0 /
2.11.0; PyG 2.6.1 and Lightning 2.6.5 pair with it.
torch-scatter/-sparseandtorchvisionare not needed. - The checkpoint is a slim, inference-only re-save of
last-v1.ckpt(state_dict+ cfg, ~3 MB). The original embedded Lightning-2.0 Trainer state, which won't unpickle under Lightning 2.6. To regenerate it from a full checkpoint, seelast-v1.ckpt.orighandling in the commit history. - A generation run must finish inside ZeroGPU's per-call window (the decorator requests up to 120 s), so keep the target phrase count modest.
Local / CPU / standard-GPU hosts. spaces is optional β without it the
@spaces.GPU decorator is a no-op, so the app runs in-process and uses CUDA when
present, else CPU. A Dockerfile is included for self-hosting
(docker run --gpus all -p 7860:7860 progress-ui; drop --gpus all for CPU).
Workflow (three tabs)
Tab 1 Β· Browse & Select Melodies
- Click Load Phrase Library β loads all pre-generated phrases from
ProGress_Supplement/phrase_stitching/diffusion_output/and applies rejection sampling (illegal harmonics, bad mode mixture, bad counterpoint). Results are cached in.phrase_cache.jsonfor fast re-loads. - Filter by mode (major / minor / mixed) and start/end harmony.
- Enter a phrase ID, click Preview to hear it in the embedded MIDI player.
- Click Mark as Favourite to tag phrases you like β the stitcher will preferentially draw from your selection.
Tab 2 Β· Generate New Phrases (requires SchenkerDiff checkpoint)
- Place
last-v1.ckptinSchenkerDiff/to enable this tab. - Adjust batch size and click Generate β the SchenkerDiff diffusion model runs 100 denoising steps and realises each output graph as a 2-voice score.
- Phrases that pass the same rejection filters are offered for addition to the pool.
Tab 3 Β· Stitch & Export
- Choose a harmonic structure (e.g., I β V β I, i β III β iv β i β¦).
- Click Stitch! β four phrase sections are sampled (preferring favourites), transposed to match the progression, inner voices are filled, and the sections are concatenated into a full piece.
- Listen to each section and the full composition in the embedded MIDI player.
- Click Download MIDI to save the result.
- Resample to try a different combination with the same structure.
Package layout
Everything below ships inside this folder β no external directories needed.
ProGress_UI/ β deployable package (this folder)
βββ app.py β Gradio entrypoint (HF Space app_file)
βββ backend.py β API layer; resolves paths to ./vendor
βββ requirements.txt β pinned CPU runtime
βββ Dockerfile / .dockerignore β portable container build
βββ .gitattributes β Git LFS for *.ckpt, *.pt
βββ vendor/ β vendored runtime dependencies
βββ phrase_stitching/ β from ProGress_Supplement
β βββ *.py β stitching / rejection-sampling logic
β βββ diffusion_output/ β phrase library (output_graphs_{1-13}/*.xml)
βββ SchenkerDiff/ β trimmed model code (no training data/checkpoints)
βββ last-v1.ckpt β diffusion checkpoint (LFS) β enables Tab 2
βββ inference.py, src/, output_vis/, configs/
βββ data/schenker/β¦/processed/*.pt β conditioning tensors (LFS)
The vendored copy excludes everything not used at inference time (training
checkpoints, saved_models/, the schenkerian_clusters/ dataset, wandb/,
visualization HTML, notebooks), bringing the SchenkerDiff dependency from
~858 MB down to ~78 MB.
Notes
- The MIDI player uses html-midi-player (loaded from CDN) with Magenta sound fonts. Internet access is required for audio playback.
- First-time phrase loading takes ~20 s on this machine for the full 1 200-file library (about 90 phrases typically pass the rejection filters). Subsequent loads use the JSON cache and take ~2 s.
Smoke-test status
End-to-end smoke tests inside the digress conda env (/home/peter/miniconda3/envs/digress):
| Path | Status | Notes |
|---|---|---|
| Phrase load + rejection sampling | β works | 90/1200 valid, 20 s |
| Cache reload | β works | 1.5 s |
| MIDI byte conversion | β works | valid MThd header |
| All 5 stitch structures | β works | each produces a 4-part / 8-measure MIDI |
| Favourite-preference stitching | β works | favourites picked when compatible |
| Gradio app build | β works | 64 blocks, no errors |
SchenkerDiff generate_new_phrases() |
β works | batch=2 β ~60 s, batch=4 β ~50 s on CPU; ~50 % rejection-pass rate |
SchenkerDiff generation (Tab 2)
backend.generate_new_phrases() runs the full diffusion model end-to-end:
load checkpoint β sample conditioning E/r from a processed .pt file β 100
DDIM steps β realization.py β rejection-sampling filters.
Several upstream issues in the SchenkerDiff repo had to be worked around inside
backend.generate_new_phrases():
graph_toolimport insrc/analysis/spectre_utils.pyfails on this env with alibgompsymbol mismatch. Stubbed insys.modules(the only consumer of that module is the training-time sampling metric, which we never call).PlanarSamplingMetricsis constructed insideinitialize_model()and is pickled as part of the checkpoint's module tree, so the stub must subclasstorch.nn.Modulefornamed_modules()to work during deserialisation.- Checkpoint was saved on CUDA. PyTorch Lightning explicitly passes
map_location=None, sotorch.loadis monkey-patched to default to CPU when no GPU is available. inference.sample_r_E()hardcodesE_sampleto shape(m, m, 10)but the model'sEdim_outputis 30 and the regenerated.ptfiles have edge_attr width 30. Reimplemented inbackend.pyso it reads the dimension frommodel.limit_dist.E.
Regenerating processed data
If SchenkerDiff/data/schenker/processed/heterdatacleaned/processed/ is empty
(or out of date with the dataset code), run:
python ProGress_UI/regenerate_processed.py
This invokes SchenkerGraphDataModule(cfg), which triggers PyG's
Dataset._process() β process_file() for every XML in train-names.txt,
writing 0_processed.pt β¦ N_processed.pt into the processed dir. Takes
~4 min for the full 1 780-file run on this machine. No training, no GPU,
no checkpoint writes.