nakas commited on
Commit
251b2e1
·
verified ·
1 Parent(s): ff0aee9

deploy: piano-roll notes output (runtime files only)

Browse files
Files changed (3) hide show
  1. app.py +12 -2
  2. stemflipper/export.py +27 -0
  3. stemflipper/pipeline.py +3 -0
app.py CHANGED
@@ -5,6 +5,7 @@ separation stage is GPU-relevant, so it alone is wrapped with @spaces.GPU
5
  (a no-op everywhere else).
6
  """
7
 
 
8
  import os
9
  import tempfile
10
  from pathlib import Path
@@ -104,7 +105,13 @@ def flip(audio_path, model, progress=gr.Progress()):
104
  else None
105
  for name in PREVIEW_STEMS
106
  ]
107
- return str(result["zip_path"]), summary, *previews
 
 
 
 
 
 
108
 
109
 
110
  with gr.Blocks(title="StemFlipper") as demo:
@@ -125,10 +132,13 @@ with gr.Blocks(title="StemFlipper") as demo:
125
  preview_outs = [
126
  gr.Audio(label=name, interactive=False) for name in PREVIEW_STEMS
127
  ]
 
 
 
128
  go_btn.click(
129
  flip,
130
  inputs=[audio_in, model_in],
131
- outputs=[zip_out, summary_out, *preview_outs],
132
  api_name="flip",
133
  )
134
 
 
5
  (a no-op everywhere else).
6
  """
7
 
8
+ import json
9
  import os
10
  import tempfile
11
  from pathlib import Path
 
105
  else None
106
  for name in PREVIEW_STEMS
107
  ]
108
+
109
+ # Per-stem detected notes for the client piano-roll (appended LAST so the preview
110
+ # output indices above stay stable for existing API callers).
111
+ notes_path = bundle / "notes.json"
112
+ notes = json.loads(notes_path.read_text()) if notes_path.exists() else {"stems": {}}
113
+
114
+ return str(result["zip_path"]), summary, *previews, notes
115
 
116
 
117
  with gr.Blocks(title="StemFlipper") as demo:
 
132
  preview_outs = [
133
  gr.Audio(label=name, interactive=False) for name in PREVIEW_STEMS
134
  ]
135
+ # Per-stem detected notes → the static web frontend draws piano-rolls from this.
136
+ # Hidden in the Gradio UI itself (visible=False) but present in the API output.
137
+ notes_out = gr.JSON(visible=False)
138
  go_btn.click(
139
  flip,
140
  inputs=[audio_in, model_in],
141
+ outputs=[zip_out, summary_out, *preview_outs, notes_out],
142
  api_name="flip",
143
  )
144
 
stemflipper/export.py CHANGED
@@ -135,6 +135,33 @@ def write_rpp(
135
  return path
136
 
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  def write_manifest(bundle_dir: str | Path, meta: dict) -> Path:
139
  path = Path(bundle_dir) / "manifest.json"
140
  path.write_text(json.dumps(meta, indent=2))
 
135
  return path
136
 
137
 
138
+ def write_notes(tracks: dict, duration: float, bundle_dir: str | Path) -> Path:
139
+ """Per-stem detected notes → notes.json, for UI piano-rolls (and the download).
140
+
141
+ tracks: {stem_name: {"notes": [{pitch,start,end,velocity}], "is_drum": bool}}.
142
+ Each stem's notes are stored as compact ``[pitch, start, end, velocity]`` rows
143
+ (ints/rounded floats) so the file stays small and a client can draw them without
144
+ parsing MIDI. ``duration`` bounds the time axis. Stems with no notes are omitted.
145
+ """
146
+ stems = {}
147
+ for name, data in tracks.items():
148
+ notes = data.get("notes") or []
149
+ if not notes:
150
+ continue
151
+ stems[name] = {
152
+ "is_drum": bool(data.get("is_drum")),
153
+ "notes": [
154
+ [int(n["pitch"]), round(float(n["start"]), 4),
155
+ round(float(n["end"]), 4), int(n.get("velocity", 100))]
156
+ for n in notes
157
+ ],
158
+ }
159
+ payload = {"duration": round(float(duration), 3), "stems": stems}
160
+ path = Path(bundle_dir) / "notes.json"
161
+ path.write_text(json.dumps(payload))
162
+ return path
163
+
164
+
165
  def write_manifest(bundle_dir: str | Path, meta: dict) -> Path:
166
  path = Path(bundle_dir) / "manifest.json"
167
  path.write_text(json.dumps(meta, indent=2))
stemflipper/pipeline.py CHANGED
@@ -142,6 +142,9 @@ def run_pipeline(
142
 
143
  report(0.9, "Writing MIDI, manifest, Reaper project")
144
  export.write_midi(tracks, analysis.tempo, bundle_dir / "midi")
 
 
 
145
  manifest = export.make_manifest_meta(input_path.name, analysis, model, stems_meta)
146
  export.write_manifest(bundle_dir, manifest)
147
  export.write_readme(bundle_dir, input_path.name, analysis)
 
142
 
143
  report(0.9, "Writing MIDI, manifest, Reaper project")
144
  export.write_midi(tracks, analysis.tempo, bundle_dir / "midi")
145
+ export.write_notes(tracks, analysis.duration, bundle_dir)
146
+ for name, meta in stems_meta.items():
147
+ meta["notes"] = "notes.json" if tracks.get(name, {}).get("notes") else None
148
  manifest = export.make_manifest_meta(input_path.name, analysis, model, stems_meta)
149
  export.write_manifest(bundle_dir, manifest)
150
  export.write_readme(bundle_dir, input_path.name, analysis)