Dylan Deshler commited on
Commit
fea940a
·
1 Parent(s): 021839a

UI refactor

Browse files
Files changed (1) hide show
  1. app.py +247 -194
app.py CHANGED
@@ -78,6 +78,36 @@ ALL_SIGNAL_LABELS = {
78
  }
79
  ALL_SIGNAL_CHOICES = [(ALL_SIGNAL_LABELS[s], s) for s in ALL_SIGNALS]
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  # The third tab is a lab bench rather than a feature: which model stage, the two
82
  # history knobs whose right value is still an open question, and the A/B between
83
  # the two guidance compositions. Off on a public Space, on with SHOW_TESTING=1.
@@ -988,9 +1018,10 @@ def _simple_status(msg: str) -> str:
988
  # edit report from controls Generate does not have; the latter is bookkeeping
989
  # about a track the listener can already hear, so it says nothing.
990
  return out("" if head.startswith(
991
- ("Continued", "Fast-forwarded", "Regenerated", "Rendered", "Applied",
992
- "Set ", "Morphed", "Coverage", "Varied", "Undo", "Seeded",
993
- "Extracted", "Unconditional")) else head)
 
994
 
995
 
996
  def _coverage_table(track: Track) -> str:
@@ -1018,9 +1049,9 @@ def _coverage_table(track: Track) -> str:
1018
  for s in sigs:
1019
  n_on = int(np.count_nonzero(np.asarray(c.active[s], dtype=bool)))
1020
  if n_on == T:
1021
- state = "🟩 **on** — following the curve"
1022
  elif n_on == 0:
1023
- state = "⬜ off — free"
1024
  else:
1025
  state = f"🟨 partial — on for {n_on} of {T}"
1026
  rows.append(f"| {ALL_SIGNAL_LABELS[s]} | {state} |")
@@ -1133,7 +1164,6 @@ def _preview_figure(track, signal, start, end, shape, mode, wave, *pvals):
1133
  def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1134
  backend = backend or MockBackend()
1135
  presets = backend.list_presets()
1136
- signal_choices = [(SIGNAL_LABELS[s], s) for s in EDITABLE_SIGNALS]
1137
 
1138
  with gr.Blocks(title="Conditioning Composer") as demo:
1139
  gr.HTML(_PLOT_CSS, elem_classes="style-inject")
@@ -1227,14 +1257,15 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1227
  # GENERATE
1228
  # =================================================================
1229
  with gr.Tab("Generate"):
 
 
 
 
1230
  gr.Markdown(
1231
- "Pick something to start from, press play, and let it keep "
1232
- "writing. When you have something you like, go to **Edit** "
1233
- "and take 24 measures of it apart — the track carries "
1234
- "across.")
1235
  with gr.Row():
1236
  with gr.Column(scale=1):
1237
- gr.Markdown("### 1 · Start from")
1238
  s_preset = gr.Dropdown(presets, value=presets[0],
1239
  label="A real song")
1240
  s_preset_btn = gr.Button("Start from this song",
@@ -1252,12 +1283,23 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1252
  "it.")
1253
  s_text_btn = gr.Button("Start from this description")
1254
 
 
 
 
 
 
 
 
 
 
 
 
1255
  # ---- tempo ------------------------------------------
1256
- # Directly under the description, because that is the
1257
- # pairing: a song and an upload arrive with a measured
1258
- # tempo, a description says nothing about tempo at all
1259
- # and takes whatever this block says. Adjacency is the
1260
- # whole explanation, which is why there is no longer a
1261
  # paragraph making it. The preview is live and works
1262
  # with no track loaded -- dial the curve in BEFORE
1263
  # generating, which is the whole reason it is up here.
@@ -1286,18 +1328,18 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1286
  "decode time, so it is never ignored, even with "
1287
  "every signal off.</sub>")
1288
 
1289
- # Last, because it applies to all three entry points
1290
- # above rather than to any one of them, and because it
1291
- # is the control you change least.
1292
- s_seed = gr.Number(
1293
- value=0, label="Seed", precision=0,
1294
- info="Where the music starts from: the same seed "
1295
- "and the same settings open the same way. "
1296
- "Change it for a different take of the same "
1297
- "idea.")
1298
-
1299
- with gr.Column(scale=2):
1300
- gr.Markdown("### 2 · Listen")
1301
  s_audio = gr.Audio(label="Current track",
1302
  type="filepath", interactive=False)
1303
  # The radio's player lives HERE, in the same slot as the
@@ -1322,13 +1364,6 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1322
  # hear.
1323
  s_summary = gr.Markdown(visible=False)
1324
 
1325
- # One section, not two, because the three buttons at the
1326
- # bottom of it are the same operation at three lengths --
1327
- # eight measures, a leg, or until you stop it -- and all
1328
- # of them read the controls above them. Splitting
1329
- # "continue" from "radio" would put those controls under
1330
- # one heading and silently apply them to the other.
1331
- gr.Markdown("### 3 · Keep it going")
1332
  # One control for every way the style can move, because
1333
  # in the backend they are one choice: the typed steer,
1334
  # the two walks and style-off are exclusive branches of
@@ -1455,7 +1490,8 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1455
  scale=1)
1456
  s_status = gr.Markdown()
1457
 
1458
- # Open by default: "how it moves" is the section above
 
1459
  # it, and this is the only place that answers whether
1460
  # the mode you picked is doing anything. Updated on
1461
  # every press and at every leg boundary of a live
@@ -1633,28 +1669,19 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1633
  # Turning a signal off over PART of a song is still
1634
  # reachable, and correctly: seat the window there and
1635
  # edit it as its own window.
1636
- gr.Markdown(
1637
- "### Conditioning coverage\n"
1638
- f"On or off for **all {N_CHUNKS} measures the model "
1639
- "is reading**. Where a signal is off the model "
1640
- "invents it instead of following the curve; "
1641
- "everything off is unconditional generation.\n\n"
1642
- "<sub>Whole window only: this checkpoint never saw "
1643
- "a signal that was on for some measures and off for "
1644
- "others. To free one over part of a song, move the "
1645
- "selection there and do it a window at a "
1646
- "time.</sub>")
1647
  coverage_tbl = gr.Markdown()
1648
- cov_signal = gr.Dropdown(ALL_SIGNAL_CHOICES,
1649
- value=ALL_SIGNALS[0],
1650
- label="signal")
 
 
 
 
 
1651
  with gr.Row():
1652
- cov_on = gr.Button("Turn ON (condition)",
1653
- variant="primary")
1654
- cov_off = gr.Button("Turn OFF (free)")
1655
- with gr.Row():
1656
- cov_all_off = gr.Button("All OFF (unconditional)")
1657
- cov_all_on = gr.Button("All ON")
1658
 
1659
  # =================================================================
1660
  # EDIT
@@ -1703,90 +1730,95 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1703
  "training's *_cfg*). Higher = stronger "
1704
  "adherence to the conditioning, but can "
1705
  "distort.")
1706
- vary_seed = gr.Number(value=2, label="Variation seed",
1707
- precision=0)
1708
- vary_btn = gr.Button("↻ Vary (new noise)")
1709
- gr.Markdown(
1710
- "<sub>*Vary* rewrites the whole window from the "
1711
- "same conditioning with different noise — a second "
1712
- "take, not an edit.</sub>")
1713
 
1714
- # ---- editors ------------------------------------------------
1715
- with gr.Row():
1716
- with gr.Column():
1717
- gr.Markdown("### Shape a signal over a region")
1718
- signal_dd = gr.Dropdown(
1719
- signal_choices, value=EDITABLE_SIGNALS[0],
1720
- label="conditioning signal")
1721
- with gr.Row():
1722
- ed_start = gr.Slider(0, N_CHUNKS - 1, value=0,
1723
- step=1,
1724
- label="from measure (in window)")
1725
- ed_end = gr.Slider(1, N_CHUNKS, value=N_CHUNKS,
1726
- step=1,
1727
- label="to measure (in window)")
1728
- shape_dd = gr.Dropdown(SHAPES, value=SHAPES[0],
1729
- label="shape")
1730
- shape_help = gr.Markdown(f"*{SHAPE_HELP[SHAPES[0]]}*")
1731
- # generic parameter slots (relabelled per shape)
1732
- pslots = [gr.Number(value=0.0, label=f"p{i}",
1733
- visible=False)
1734
- for i in range(MAX_PARAMS)]
1735
- wave_dd = gr.Dropdown(["sine", "triangle", "saw"],
1736
- value="sine", label="wave",
1737
- visible=False)
1738
- mode_rb = gr.Radio(["replace", "add"], value="replace",
1739
- label="mode")
1740
- preview_plot = gr.Plot(
1741
- label="Preview (live — not yet applied)",
1742
- elem_classes="plot-card")
1743
- apply_btn = gr.Button("Apply shape", variant="primary")
1744
-
1745
- with gr.Column():
1746
- gr.Markdown("### Key / style")
1747
- with gr.Row():
1748
- root_dd = gr.Dropdown(
1749
- [("C", 0), ("C#", 1), ("D", 2), ("D#", 3),
1750
- ("E", 4), ("F", 5), ("F#", 6), ("G", 7),
1751
- ("G#", 8), ("A", 9), ("A#", 10), ("B", 11)],
1752
- value=0, label="root")
1753
- minor_cb = gr.Checkbox(value=False, label="minor")
1754
- key_btn = gr.Button("Set key (whole window)")
1755
- with gr.Row():
1756
- style_dd = gr.Dropdown(presets, value=presets[0],
1757
- label="morph style toward")
1758
- style_amt = gr.Slider(0.0, 1.0, value=0.5,
1759
- step=0.05, label="amount")
1760
- style_btn = gr.Button("Morph style (whole window)")
1761
- style_text = gr.Textbox(
1762
- label="…or set style from text",
1763
- placeholder="e.g. smoky late-night piano trio")
1764
- # Whole window, like the two above it and like coverage.
1765
- # It used to take a range, which was the same untrained
1766
- # configuration the coverage panel now refuses to make:
1767
- # this writes `active["style"] = True` over the range it
1768
- # is given, so a range on a track whose style was off
1769
- # produced a window with style on for part of it.
1770
- style_text_btn = gr.Button(
1771
- "Set style from text (whole window)")
1772
- gr.Markdown(
1773
- "<sub>*Morph* slerps each measure's style vector "
1774
- "part-way toward a preset's, keeping the shape of "
1775
- "what is there. *From text* replaces it outright "
1776
- "with one CLAP embedding, the same vector a text "
1777
- "prompt on **Generate** starts from.</sub>")
 
 
 
 
 
 
 
 
1778
 
1779
  # ---- regenerate ---------------------------------------------
1780
- # At the bottom, after the editors, because that is the order
1781
- # the work happens in: nothing above this point makes a sound.
1782
- gr.Markdown(
1783
- "<sub>Everything above changes what the model is **asked** "
1784
- "for. This is where it answers.</sub>")
 
 
 
 
 
 
 
 
1785
  with gr.Row():
1786
- # Follows the track (see refresh_window): pressing
1787
- # Regenerate without touching anything has to be the same
1788
- # seed the window was written with, or "unchanged" is a
1789
- # setting the UI makes unreachable.
1790
  rg_seed = gr.Number(value=1, label="Seed",
1791
  precision=0, scale=1)
1792
  regen_btn = gr.Button("♫ Regenerate",
@@ -2075,8 +2107,7 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2075
  return _edit_outputs(tr, f"Switched to stage {int(stage)} model.")
2076
 
2077
  def _stage_controls(stage, cur_sig, cur_cov):
2078
- """Restrict the two signal pickers — and the chroma-only key button —
2079
- to the signals the selected model reads.
2080
 
2081
  The plots gate themselves off `track.stage`, but these are inputs,
2082
  not views: with no track loaded there is nothing to gate against,
@@ -2085,10 +2116,19 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2085
  the track because stage_rb is what the entry points read for the
2086
  NEXT generation — and on_swap pushes the restored track's stage back
2087
  into it, so swapping is covered for free. The current selection is
2088
- kept when it survives the switch."""
 
 
 
 
 
2089
  sigs = signals_for_stage(int(stage))
2090
  ed = [(SIGNAL_LABELS[s], s) for s in EDITABLE_SIGNALS if s in sigs]
2091
- cov = [(ALL_SIGNAL_LABELS[s], s) for s in ALL_SIGNALS if s in sigs]
 
 
 
 
2092
 
2093
  def keep(v, opts):
2094
  return v if any(v == o[1] for o in opts) else opts[0][1]
@@ -2096,7 +2136,6 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2096
  return (
2097
  gr.update(choices=ed, value=keep(cur_sig, ed)),
2098
  gr.update(choices=cov, value=keep(cur_cov, cov)),
2099
- gr.update(interactive=("chroma" in sigs)),
2100
  )
2101
 
2102
  def _diag_note(track):
@@ -2849,6 +2888,35 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2849
  tr, f"{mode.title()}d {shape} on **{lbl}** over "
2850
  f"measures [{int(start)},{int(end)}).")
2851
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2852
  # ---- moving the editable window --------------------------------------
2853
  # Not a control of its own any more: the selection says which measures
2854
  # to rewrite, `_sel_window` says which N_CHUNKS have to be loaded to do
@@ -2942,25 +3010,22 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2942
  f"the window was held frozen.{clamped}" + _splice_note(tr)
2943
  + _diag_note(tr))
2944
 
2945
- def on_vary(track, seed, is_draft, guidance):
2946
- if track is None:
2947
- return (gr.update(),) * 5 + ("Start a track first.",)
2948
- tr = backend.set_guidance(track, float(guidance))
2949
- tr = backend.vary(tr, int(seed), n_steps=_steps(is_draft))
2950
- a, c, cov, s = _render_outputs(tr)
2951
- return (tr, a, c, cov, s,
2952
- f"Variation with seed {int(seed)}." + _splice_note(tr))
2953
-
2954
  def _apply_coverage(track, signal, on):
2955
- """Toggle a signal on/off across the WHOLE window (edit only — no
2956
- re-render). Not a range: see the comment on the coverage panel."""
 
 
2957
  if track is None:
2958
  return (gr.update(),) * 5 + ("Start a track first.",)
2959
- tr = backend.set_active(track, signal, 0, track.cond.T, bool(on))
2960
- lbl = ALL_SIGNAL_LABELS.get(signal, signal)
 
 
 
 
2961
  first = int(track.prefix_measures)
2962
  return _edit_outputs(
2963
- tr, f"Turned **{lbl}** {'on' if on else 'off'} for measures "
2964
  f"{first}–{first + track.cond.T}.")
2965
 
2966
  def on_cov_on(track, signal):
@@ -2969,22 +3034,6 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
2969
  def on_cov_off(track, signal):
2970
  return _apply_coverage(track, signal, False)
2971
 
2972
- def _apply_all(track, on):
2973
- if track is None:
2974
- return (gr.update(),) * 5 + ("Start a track first.",)
2975
- tr = track
2976
- for sig in ALL_SIGNALS:
2977
- tr = backend.set_active(tr, sig, 0, tr.cond.T, on)
2978
- msg = ("All signals off — unconditional." if not on
2979
- else "All signals on — fully conditioned.")
2980
- return _edit_outputs(tr, msg)
2981
-
2982
- def on_cov_all_off(track):
2983
- return _apply_all(track, False)
2984
-
2985
- def on_cov_all_on(track):
2986
- return _apply_all(track, True)
2987
-
2988
  # ---- A/B slots ------------------------------------------------------
2989
  # A = `cur` (being edited), B = `prev` (parked snapshot). Both hold a
2990
  # whole Track, so a swap carries the conditioning arrays, per-measure
@@ -3127,17 +3176,19 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
3127
  # `_raw_continue` / `_raw_fast_forward`, which were bound above this
3128
  # block precisely so the Generate wrappers get mirrored once rather
3129
  # than twice.
 
 
 
 
 
3130
  (on_new_preset, on_new_text, on_new_upload, on_set_bpm,
3131
- on_style_from_text, on_set_stage, on_simple_continue,
3132
- on_simple_ff, on_apply, on_set_key, on_morph_style, on_regen, on_vary,
3133
- on_cov_on, on_cov_off, on_cov_all_off, on_cov_all_on, on_swap,
3134
- on_seat) = [
3135
  _mirror(f) for f in (
3136
  on_new_preset, on_new_text, on_new_upload,
3137
- on_set_bpm, on_style_from_text, on_set_stage,
3138
- on_simple_continue, on_simple_ff, on_apply, on_set_key,
3139
- on_morph_style, on_regen, on_vary, on_cov_on, on_cov_off,
3140
- on_cov_all_off, on_cov_all_on, on_swap, on_seat)]
3141
  on_undo = _mirror(on_undo, off=1)
3142
 
3143
  # ---- wiring ---------------------------------------------------------
@@ -3155,6 +3206,13 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
3155
 
3156
  def refresh_preview(track, signal, start, end, shape, mode, wave,
3157
  *pvals):
 
 
 
 
 
 
 
3158
  return _preview_figure(track, signal, start, end, shape, mode, wave,
3159
  *pvals)
3160
 
@@ -3465,31 +3523,26 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
3465
  .then(on_regen,
3466
  [cur, sel_from, sel_to, rg_seed, draft, guidance_sl],
3467
  out_all))
3468
- tail(vary_btn.click(_snapshot, [cur, hist], hist).then(
3469
- on_vary, [cur, vary_seed, draft, guidance_sl], out_all))
3470
 
3471
- # --- the editors ------------------------------------------------------
 
 
 
 
3472
  tail(apply_btn.click(_snapshot, [cur, hist], hist).then(
3473
- on_apply,
3474
  [cur, signal_dd, ed_start, ed_end, shape_dd, mode_rb, wave_dd,
 
3475
  draft, *pslots],
3476
  out_all))
3477
- tail(key_btn.click(_snapshot, [cur, hist], hist).then(
3478
- on_set_key, [cur, root_dd, minor_cb, draft], out_all))
3479
- tail(style_btn.click(_snapshot, [cur, hist], hist).then(
3480
- on_morph_style, [cur, style_dd, style_amt, draft], out_all))
3481
- tail(style_text_btn.click(_snapshot, [cur, hist], hist).then(
3482
- on_style_from_text, [cur, style_text], out_all))
3483
 
3484
  # --- coverage ---------------------------------------------------------
3485
  tail(cov_on.click(_snapshot, [cur, hist], hist).then(
3486
  on_cov_on, [cur, cov_signal], out_all))
3487
  tail(cov_off.click(_snapshot, [cur, hist], hist).then(
3488
  on_cov_off, [cur, cov_signal], out_all))
3489
- tail(cov_all_off.click(_snapshot, [cur, hist], hist).then(
3490
- on_cov_all_off, [cur], out_all))
3491
- tail(cov_all_on.click(_snapshot, [cur, hist], hist).then(
3492
- on_cov_all_on, [cur], out_all))
3493
 
3494
  # --- undo / A / B -----------------------------------------------------
3495
  # Undo restores a whole Track, so it has to rebuild every A-facing view
@@ -3517,7 +3570,7 @@ def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
3517
  # views, this one rebuilds the controls. demo.load runs it once so the
3518
  # initial radio position is honoured before anything is generated.
3519
  stage_ctl_in = [stage_rb, signal_dd, cov_signal]
3520
- stage_ctl_out = [signal_dd, cov_signal, key_btn]
3521
  stage_rb.change(_stage_controls, stage_ctl_in, stage_ctl_out)
3522
  demo.load(_stage_controls, stage_ctl_in, stage_ctl_out)
3523
 
 
78
  }
79
  ALL_SIGNAL_CHOICES = [(ALL_SIGNAL_LABELS[s], s) for s in ALL_SIGNALS]
80
 
81
+ # A value for the coverage picker that means "every signal", so that turning the
82
+ # lot on or off is the same two buttons as turning one on or off. Sentinel-
83
+ # prefixed because it shares a dropdown with real signal names and is passed to
84
+ # the same handler; nothing in ALL_SIGNALS can ever start with "~".
85
+ COV_ALL = "~all"
86
+ COV_ALL_LABEL = "All signals"
87
+
88
+ # Edit's one picker: the shapeable curves, then the three edits that are not
89
+ # curves. All four write conditioning for the same window and none of them makes
90
+ # a sound — what differs is the controls each needs, so they share a selector
91
+ # that reveals the right ones (see the editor block in build_app). Sentinel-
92
+ # prefixed for the same reason COV_ALL is: they travel through handlers that
93
+ # also take real signal names.
94
+ EDIT_KEY, EDIT_MORPH, EDIT_TEXT = "~key", "~morph", "~text"
95
+ EDIT_SPECIALS = (EDIT_KEY, EDIT_MORPH, EDIT_TEXT)
96
+ EDIT_SPECIAL_CHOICES = [("key", EDIT_KEY),
97
+ ("style — morph toward a preset", EDIT_MORPH),
98
+ ("style — from text", EDIT_TEXT)]
99
+ EDIT_TARGETS = ([(SIGNAL_LABELS[s], s) for s in EDITABLE_SIGNALS]
100
+ + EDIT_SPECIAL_CHOICES)
101
+ _EDIT_ACTIONS = {EDIT_KEY: "Set key",
102
+ EDIT_MORPH: "Morph style",
103
+ EDIT_TEXT: "Set style from text"}
104
+
105
+
106
+ def _edit_action(target: str) -> str:
107
+ """The Apply button's label for a target. Every curve shares one label
108
+ because for a curve the shape dropdown is the verb, not the signal."""
109
+ return _EDIT_ACTIONS.get(target, "Apply shape")
110
+
111
  # The third tab is a lab bench rather than a feature: which model stage, the two
112
  # history knobs whose right value is still an open question, and the A/B between
113
  # the two guidance compositions. Off on a public Space, on with SHOW_TESTING=1.
 
1018
  # edit report from controls Generate does not have; the latter is bookkeeping
1019
  # about a track the listener can already hear, so it says nothing.
1020
  return out("" if head.startswith(
1021
+ ("Continued", "Fast-forwarded", "Regenerated", "Rewrote", "Rendered",
1022
+ "Applied", "Replaced", "Added", "Set ", "Style set", "Morphed",
1023
+ "Turned", "Coverage", "Undo", "Seeded", "Extracted",
1024
+ "Unconditional")) else head)
1025
 
1026
 
1027
  def _coverage_table(track: Track) -> str:
 
1049
  for s in sigs:
1050
  n_on = int(np.count_nonzero(np.asarray(c.active[s], dtype=bool)))
1051
  if n_on == T:
1052
+ state = "🟩 **on**"
1053
  elif n_on == 0:
1054
+ state = "⬜ off"
1055
  else:
1056
  state = f"🟨 partial — on for {n_on} of {T}"
1057
  rows.append(f"| {ALL_SIGNAL_LABELS[s]} | {state} |")
 
1164
  def build_app(backend: ModelBackend | None = None) -> gr.Blocks:
1165
  backend = backend or MockBackend()
1166
  presets = backend.list_presets()
 
1167
 
1168
  with gr.Blocks(title="Conditioning Composer") as demo:
1169
  gr.HTML(_PLOT_CSS, elem_classes="style-inject")
 
1257
  # GENERATE
1258
  # =================================================================
1259
  with gr.Tab("Generate"):
1260
+ # Starting a track and extending one were two halves of one tab
1261
+ # and are two different sittings: you start once, then spend the
1262
+ # session on **Continue**. Nothing here makes a sound on this
1263
+ # tab -- the player is on Continue, which is where you go next.
1264
  gr.Markdown(
1265
+ "Pick something to start from, then hear it and keep it "
1266
+ "going on **Continue**.")
 
 
1267
  with gr.Row():
1268
  with gr.Column(scale=1):
 
1269
  s_preset = gr.Dropdown(presets, value=presets[0],
1270
  label="A real song")
1271
  s_preset_btn = gr.Button("Start from this song",
 
1283
  "it.")
1284
  s_text_btn = gr.Button("Start from this description")
1285
 
1286
+ # Last, because it applies to all three entry points
1287
+ # above rather than to any one of them, and because it
1288
+ # is the control you change least.
1289
+ s_seed = gr.Number(
1290
+ value=0, label="Seed", precision=0,
1291
+ info="Where the music starts from: the same seed "
1292
+ "and the same settings open the same way. "
1293
+ "Change it for a different take of the same "
1294
+ "idea.")
1295
+
1296
+ with gr.Column(scale=1):
1297
  # ---- tempo ------------------------------------------
1298
+ # Beside the entry points, because that is the pairing:
1299
+ # a song and an upload arrive with a measured tempo, a
1300
+ # description says nothing about tempo at all and takes
1301
+ # whatever this block says. Adjacency is the whole
1302
+ # explanation, which is why there is no longer a
1303
  # paragraph making it. The preview is live and works
1304
  # with no track loaded -- dial the curve in BEFORE
1305
  # generating, which is the whole reason it is up here.
 
1328
  "decode time, so it is never ignored, even with "
1329
  "every signal off.</sub>")
1330
 
1331
+ # =================================================================
1332
+ # CONTINUE
1333
+ # =================================================================
1334
+ # Extending a track, and hearing it. One section, not two, because
1335
+ # the three buttons at the bottom are the same operation at three
1336
+ # lengths -- eight measures, a leg, or until you stop it -- and all
1337
+ # of them read the controls above them. Splitting "continue" from
1338
+ # "radio" would put those controls under one heading and silently
1339
+ # apply them to the other.
1340
+ with gr.Tab("Continue"):
1341
+ with gr.Row():
1342
+ with gr.Column(scale=1):
1343
  s_audio = gr.Audio(label="Current track",
1344
  type="filepath", interactive=False)
1345
  # The radio's player lives HERE, in the same slot as the
 
1364
  # hear.
1365
  s_summary = gr.Markdown(visible=False)
1366
 
 
 
 
 
 
 
 
1367
  # One control for every way the style can move, because
1368
  # in the backend they are one choice: the typed steer,
1369
  # the two walks and style-off are exclusive branches of
 
1490
  scale=1)
1491
  s_status = gr.Markdown()
1492
 
1493
+ with gr.Column(scale=1):
1494
+ # Open by default: "how it moves" is the column beside
1495
  # it, and this is the only place that answers whether
1496
  # the mode you picked is doing anything. Updated on
1497
  # every press and at every leg boundary of a live
 
1669
  # Turning a signal off over PART of a song is still
1670
  # reachable, and correctly: seat the window there and
1671
  # edit it as its own window.
1672
+ gr.Markdown("### Conditioning coverage")
 
 
 
 
 
 
 
 
 
 
1673
  coverage_tbl = gr.Markdown()
1674
+ # "All signals" is an entry in the picker rather than a
1675
+ # second pair of buttons. Four buttons where two would
1676
+ # do, and the two extra ones were the same verb again
1677
+ # with a different scope -- scope is what the dropdown
1678
+ # above them is for.
1679
+ cov_signal = gr.Dropdown(
1680
+ [(COV_ALL_LABEL, COV_ALL)] + ALL_SIGNAL_CHOICES,
1681
+ value=COV_ALL, label="signal")
1682
  with gr.Row():
1683
+ cov_on = gr.Button("On", variant="primary")
1684
+ cov_off = gr.Button("Off")
 
 
 
 
1685
 
1686
  # =================================================================
1687
  # EDIT
 
1730
  "training's *_cfg*). Higher = stronger "
1731
  "adherence to the conditioning, but can "
1732
  "distort.")
 
 
 
 
 
 
 
1733
 
1734
+ # ---- the editor ---------------------------------------------
1735
+ # ONE picker for everything that can be changed, and one button
1736
+ # that does it. Key and style used to be a second column with
1737
+ # two more buttons in it, which read as a different kind of
1738
+ # operation; they are not -- all four write conditioning for the
1739
+ # same window and none of them makes a sound. What differs is
1740
+ # the controls each one needs, so the picker reveals those and
1741
+ # hides the rest. EDIT_TARGETS is the whole mapping.
1742
+ gr.Markdown("### Edit the conditioning")
1743
+ signal_dd = gr.Dropdown(EDIT_TARGETS, value=EDITABLE_SIGNALS[0],
1744
+ label="what to edit")
1745
+
1746
+ # Groups, not individual components, because `_param_updates`
1747
+ # already owns the visibility of `pslots` and `wave_dd` (per
1748
+ # shape) -- two handlers writing the same `visible` flag from
1749
+ # different triggers is a race with no winner. A container's
1750
+ # visibility is a flag nothing else touches.
1751
+ with gr.Column(visible=True) as curve_grp:
1752
+ with gr.Row():
1753
+ ed_start = gr.Slider(0, N_CHUNKS - 1, value=0, step=1,
1754
+ label="from measure (in window)")
1755
+ ed_end = gr.Slider(1, N_CHUNKS, value=N_CHUNKS, step=1,
1756
+ label="to measure (in window)")
1757
+ with gr.Row():
1758
+ with gr.Column():
1759
+ shape_dd = gr.Dropdown(SHAPES, value=SHAPES[0],
1760
+ label="shape")
1761
+ shape_help = gr.Markdown(
1762
+ f"*{SHAPE_HELP[SHAPES[0]]}*")
1763
+ # generic parameter slots (relabelled per shape)
1764
+ pslots = [gr.Number(value=0.0, label=f"p{i}",
1765
+ visible=False)
1766
+ for i in range(MAX_PARAMS)]
1767
+ wave_dd = gr.Dropdown(["sine", "triangle", "saw"],
1768
+ value="sine", label="wave",
1769
+ visible=False)
1770
+ mode_rb = gr.Radio(["replace", "add"],
1771
+ value="replace", label="mode")
1772
+ with gr.Column():
1773
+ preview_plot = gr.Plot(
1774
+ label="Preview (live — not yet applied)",
1775
+ elem_classes="plot-card")
1776
+
1777
+ with gr.Column(visible=False) as key_grp:
1778
+ with gr.Row():
1779
+ root_dd = gr.Dropdown(
1780
+ [("C", 0), ("C#", 1), ("D", 2), ("D#", 3),
1781
+ ("E", 4), ("F", 5), ("F#", 6), ("G", 7),
1782
+ ("G#", 8), ("A", 9), ("A#", 10), ("B", 11)],
1783
+ value=0, label="root")
1784
+ minor_cb = gr.Checkbox(value=False, label="minor")
1785
+
1786
+ with gr.Column(visible=False) as morph_grp:
1787
+ with gr.Row():
1788
+ style_dd = gr.Dropdown(presets, value=presets[0],
1789
+ label="morph style toward")
1790
+ style_amt = gr.Slider(0.0, 1.0, value=0.5, step=0.05,
1791
+ label="amount")
1792
+
1793
+ with gr.Column(visible=False) as text_grp:
1794
+ # Whole window, like the two above it and like coverage. It
1795
+ # used to take a range, which was the same untrained
1796
+ # configuration the coverage panel now refuses to make:
1797
+ # this writes `active["style"] = True` over the range it is
1798
+ # given, so a range on a track whose style was off produced
1799
+ # a window with style on for part of it.
1800
+ style_text = gr.Textbox(
1801
+ label="style prompt",
1802
+ placeholder="e.g. smoky late-night piano trio")
1803
+
1804
+ apply_btn = gr.Button(_edit_action(EDITABLE_SIGNALS[0]),
1805
+ variant="primary")
1806
 
1807
  # ---- regenerate ---------------------------------------------
1808
+ # At the bottom, after the editor, because that is the order the
1809
+ # work happens in: nothing above this point makes a sound.
1810
+ #
1811
+ # THE seed for this tab, and the only one. There used to be a
1812
+ # second (a "variation seed" beside a Vary button), which was
1813
+ # the same operation -- rewrite the window from the same
1814
+ # conditioning with different noise -- reached by a different
1815
+ # control and remembered in a different box, so the two could
1816
+ # disagree about which take you were listening to. Regenerate
1817
+ # with this changed IS Vary; Regenerate with it untouched is the
1818
+ # take you already have, because it follows the track (see
1819
+ # refresh_window) and is always the seed the window was written
1820
+ # with.
1821
  with gr.Row():
 
 
 
 
1822
  rg_seed = gr.Number(value=1, label="Seed",
1823
  precision=0, scale=1)
1824
  regen_btn = gr.Button("♫ Regenerate",
 
2107
  return _edit_outputs(tr, f"Switched to stage {int(stage)} model.")
2108
 
2109
  def _stage_controls(stage, cur_sig, cur_cov):
2110
+ """Restrict the two pickers to the signals the selected model reads.
 
2111
 
2112
  The plots gate themselves off `track.stage`, but these are inputs,
2113
  not views: with no track loaded there is nothing to gate against,
 
2116
  the track because stage_rb is what the entry points read for the
2117
  NEXT generation — and on_swap pushes the restored track's stage back
2118
  into it, so swapping is covered for free. The current selection is
2119
+ kept when it survives the switch.
2120
+
2121
+ Key and style are filtered by the channel they actually write --
2122
+ key is chroma, both style edits are style -- which is how the old
2123
+ greyed-out key button's job gets done now that there is no button to
2124
+ grey: an edit the stage cannot hear is not in the list."""
2125
  sigs = signals_for_stage(int(stage))
2126
  ed = [(SIGNAL_LABELS[s], s) for s in EDITABLE_SIGNALS if s in sigs]
2127
+ ed += [(lbl, t) for lbl, t in EDIT_SPECIAL_CHOICES
2128
+ if ("chroma" if t == EDIT_KEY else "style") in sigs]
2129
+ cov = ([(COV_ALL_LABEL, COV_ALL)]
2130
+ + [(ALL_SIGNAL_LABELS[s], s) for s in ALL_SIGNALS
2131
+ if s in sigs])
2132
 
2133
  def keep(v, opts):
2134
  return v if any(v == o[1] for o in opts) else opts[0][1]
 
2136
  return (
2137
  gr.update(choices=ed, value=keep(cur_sig, ed)),
2138
  gr.update(choices=cov, value=keep(cur_cov, cov)),
 
2139
  )
2140
 
2141
  def _diag_note(track):
 
2888
  tr, f"{mode.title()}d {shape} on **{lbl}** over "
2889
  f"measures [{int(start)},{int(end)}).")
2890
 
2891
+ def _target_controls(target):
2892
+ """Show the controls the picked target needs, hide the rest, and
2893
+ say on the button what pressing it will do.
2894
+
2895
+ Containers, not components: `_param_updates` owns `pslots` and
2896
+ `wave_dd` per shape, and two handlers writing one `visible` flag
2897
+ from different triggers is a race."""
2898
+ return (gr.update(visible=target not in EDIT_SPECIALS),
2899
+ gr.update(visible=target == EDIT_KEY),
2900
+ gr.update(visible=target == EDIT_MORPH),
2901
+ gr.update(visible=target == EDIT_TEXT),
2902
+ gr.update(value=_edit_action(target)))
2903
+
2904
+ def on_edit_apply(track, target, start, end, shape, mode, wave,
2905
+ root, minor, style, amount, prompt, is_draft,
2906
+ *pvals):
2907
+ """The one Apply button. Four edits, one press, chosen by the
2908
+ picker -- the handlers below are unchanged, this only routes to
2909
+ them. They all return `_edit_outputs`, so the wiring downstream
2910
+ cannot tell which one ran."""
2911
+ if target == EDIT_KEY:
2912
+ return on_set_key(track, root, minor, is_draft)
2913
+ if target == EDIT_MORPH:
2914
+ return on_morph_style(track, style, amount, is_draft)
2915
+ if target == EDIT_TEXT:
2916
+ return on_style_from_text(track, prompt)
2917
+ return on_apply(track, target, start, end, shape, mode, wave,
2918
+ is_draft, *pvals)
2919
+
2920
  # ---- moving the editable window --------------------------------------
2921
  # Not a control of its own any more: the selection says which measures
2922
  # to rewrite, `_sel_window` says which N_CHUNKS have to be loaded to do
 
3010
  f"the window was held frozen.{clamped}" + _splice_note(tr)
3011
  + _diag_note(tr))
3012
 
 
 
 
 
 
 
 
 
 
3013
  def _apply_coverage(track, signal, on):
3014
+ """Toggle a signal — or all of them — on/off across the WHOLE
3015
+ window (edit only, no re-render). Not a range: see the comment on
3016
+ the coverage panel. COV_ALL is handled here rather than by its own
3017
+ pair of buttons, because "which signals" is the dropdown's job."""
3018
  if track is None:
3019
  return (gr.update(),) * 5 + ("Start a track first.",)
3020
+ sigs = ALL_SIGNALS if signal == COV_ALL else [signal]
3021
+ tr = track
3022
+ for s in sigs:
3023
+ tr = backend.set_active(tr, s, 0, tr.cond.T, bool(on))
3024
+ lbl = (COV_ALL_LABEL.lower() if signal == COV_ALL
3025
+ else f"**{ALL_SIGNAL_LABELS.get(signal, signal)}**")
3026
  first = int(track.prefix_measures)
3027
  return _edit_outputs(
3028
+ tr, f"Turned {lbl} {'on' if on else 'off'} for measures "
3029
  f"{first}–{first + track.cond.T}.")
3030
 
3031
  def on_cov_on(track, signal):
 
3034
  def on_cov_off(track, signal):
3035
  return _apply_coverage(track, signal, False)
3036
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3037
  # ---- A/B slots ------------------------------------------------------
3038
  # A = `cur` (being edited), B = `prev` (parked snapshot). Both hold a
3039
  # whole Track, so a swap carries the conditioning arrays, per-measure
 
3176
  # `_raw_continue` / `_raw_fast_forward`, which were bound above this
3177
  # block precisely so the Generate wrappers get mirrored once rather
3178
  # than twice.
3179
+ #
3180
+ # `on_apply`, `on_set_key`, `on_morph_style` and `on_style_from_text`
3181
+ # are absent for the same reason: `on_edit_apply` is the only thing
3182
+ # wired to them, and mirroring both it and them would append the four
3183
+ # mirror outputs twice.
3184
  (on_new_preset, on_new_text, on_new_upload, on_set_bpm,
3185
+ on_set_stage, on_simple_continue, on_simple_ff, on_edit_apply,
3186
+ on_regen, on_cov_on, on_cov_off, on_swap, on_seat) = [
 
 
3187
  _mirror(f) for f in (
3188
  on_new_preset, on_new_text, on_new_upload,
3189
+ on_set_bpm, on_set_stage,
3190
+ on_simple_continue, on_simple_ff, on_edit_apply,
3191
+ on_regen, on_cov_on, on_cov_off, on_swap, on_seat)]
 
3192
  on_undo = _mirror(on_undo, off=1)
3193
 
3194
  # ---- wiring ---------------------------------------------------------
 
3206
 
3207
  def refresh_preview(track, signal, start, end, shape, mode, wave,
3208
  *pvals):
3209
+ """The live shape preview, or nothing when the picker is not on a
3210
+ curve. `_preview_figure` reads `cond.<signal>`, and the three
3211
+ special targets are not channels -- the plot is hidden with the
3212
+ rest of the curve controls in that case, so there is nothing to
3213
+ draw either."""
3214
+ if signal in EDIT_SPECIALS:
3215
+ return gr.update()
3216
  return _preview_figure(track, signal, start, end, shape, mode, wave,
3217
  *pvals)
3218
 
 
3523
  .then(on_regen,
3524
  [cur, sel_from, sel_to, rg_seed, draft, guidance_sl],
3525
  out_all))
 
 
3526
 
3527
+ # --- the editor -------------------------------------------------------
3528
+ # One button, every edit. The inputs are the union of what the four
3529
+ # branches need, because gradio binds an event's inputs once and the
3530
+ # picker chooses the branch at press time -- the controls the branch
3531
+ # does not read are hidden, not absent, and their values are ignored.
3532
  tail(apply_btn.click(_snapshot, [cur, hist], hist).then(
3533
+ on_edit_apply,
3534
  [cur, signal_dd, ed_start, ed_end, shape_dd, mode_rb, wave_dd,
3535
+ root_dd, minor_cb, style_dd, style_amt, style_text,
3536
  draft, *pslots],
3537
  out_all))
3538
+ signal_dd.change(_target_controls, [signal_dd],
3539
+ [curve_grp, key_grp, morph_grp, text_grp, apply_btn])
 
 
 
 
3540
 
3541
  # --- coverage ---------------------------------------------------------
3542
  tail(cov_on.click(_snapshot, [cur, hist], hist).then(
3543
  on_cov_on, [cur, cov_signal], out_all))
3544
  tail(cov_off.click(_snapshot, [cur, hist], hist).then(
3545
  on_cov_off, [cur, cov_signal], out_all))
 
 
 
 
3546
 
3547
  # --- undo / A / B -----------------------------------------------------
3548
  # Undo restores a whole Track, so it has to rebuild every A-facing view
 
3570
  # views, this one rebuilds the controls. demo.load runs it once so the
3571
  # initial radio position is honoured before anything is generated.
3572
  stage_ctl_in = [stage_rb, signal_dd, cov_signal]
3573
+ stage_ctl_out = [signal_dd, cov_signal]
3574
  stage_rb.change(_stage_controls, stage_ctl_in, stage_ctl_out)
3575
  demo.load(_stage_controls, stage_ctl_in, stage_ctl_out)
3576