k-l-lambda commited on
Commit
2fa4e1a
·
1 Parent(s): 55b687e

refined input controls.

Browse files
Files changed (2) hide show
  1. app.py +116 -30
  2. assets/styles.json +23 -0
app.py CHANGED
@@ -4,7 +4,7 @@ Left column:
4
  (1) generation parameter panel (2) streaming run log
5
  (3) .lyl file list (session outputs + built-in examples) (4) editable lyl editor
6
  Right column:
7
- sheet-music panel (placeholder for now; later a lyl->MEI->Verovio SVG renderer
8
  reusing the lilylet-live-editor pipeline).
9
 
10
  Generation streams patch-by-patch: raw decoded text (with `[r:x/y]` stream
@@ -14,7 +14,9 @@ fills the editor. The backend is the int8 + two-level KV-cache ONNX generator
14
  """
15
 
16
  import os
 
17
  import time
 
18
 
19
  import gradio as gr
20
 
@@ -31,6 +33,14 @@ OUTPUT_DIR = os.path.join(HERE, 'outputs')
31
  EXAMPLE_PREFIX = '\U0001F4C4 ' # 📄 examples
32
  OUTPUT_PREFIX = '✨ ' # ✨ session outputs
33
 
 
 
 
 
 
 
 
 
34
  _GEN = None
35
 
36
 
@@ -53,11 +63,53 @@ def load_examples ():
53
  return store
54
 
55
 
56
- def run_generation (prompt, measures, temperature, top_k, top_p, max_patches, seed, store):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  '''Streaming generate callback. Yields updates for (log, editor, file_list, store).
58
 
59
  store: {label: lyl_text} dict held in gr.State; the produced document is added
60
  to it under a timestamped label once generation finishes.
 
 
61
  '''
62
  gen = get_generator()
63
  meas = int(measures) if measures and int(measures) > 0 else None
@@ -92,15 +144,31 @@ SHEET_PLACEHOLDER = '''
92
  <div style="font-size:42px;margin-bottom:8px;">&#127932;</div>
93
  <div>Sheet-music preview</div>
94
  <div style="font-size:12px;margin-top:4px;">
95
- (coming soon: Lilylet &rarr; MEI &rarr; Verovio)
96
  </div>
97
  </div>
98
  </div>
99
  '''
100
 
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  def build_ui ():
103
- examples = load_examples()
104
 
105
  with gr.Blocks(title='LilyScript') as demo:
106
  gr.Markdown('## 🎼 LilyScript — symbolic music generation with Lilylet')
@@ -109,43 +177,61 @@ def build_ui ():
109
  with gr.Row(equal_height=True):
110
  # ---------------- LEFT ----------------
111
  with gr.Column(scale=5):
112
- # top row: (1) params | (2) run log
113
- with gr.Row(equal_height=True):
 
114
  with gr.Group():
115
- gr.Markdown('**① Parameters**')
116
- prompt = gr.Textbox(label='Metadata prompt', lines=3, value='',
117
- placeholder='[composer "..."]\n[genre "..."]\n(optional)')
118
- measures = gr.Number(label='Measures (0 = let model decide)', value=8, precision=0)
119
- with gr.Row():
120
- temperature = gr.Slider(0.0, 2.0, value=1.0, step=0.05, label='temperature')
121
- top_p = gr.Slider(0.0, 1.0, value=0.9, step=0.01, label='top_p')
122
- with gr.Row():
123
- top_k = gr.Number(label='top_k (0 = off)', value=0, precision=0)
124
- max_patches = gr.Number(label='max patches', value=256, precision=0)
125
- seed = gr.Number(label='seed', value=0, precision=0)
126
  with gr.Row():
127
- gen_btn = gr.Button('Generate', variant='primary')
128
- stop_btn = gr.Button('Stop', variant='stop')
129
-
130
- log = gr.Textbox(label='② Run log', lines=16, max_lines=16,
131
- autoscroll=True, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  # bottom row: (3) file list | (4) editor
134
  with gr.Row(equal_height=True):
135
- file_list = gr.Radio(label='③ Files', choices=list(examples.keys()),
136
- value=None, interactive=True)
137
- editor = gr.Code(label=' Lilylet editor', language=None, lines=18,
138
- interactive=True)
 
 
 
 
 
 
 
139
 
140
  # ---------------- RIGHT ----------------
141
  with gr.Column(scale=6):
142
- gr.Markdown('**Sheet music**')
143
- gr.HTML(SHEET_PLACEHOLDER)
 
144
 
145
  # ---- wiring ----
 
 
 
 
146
  gen_event = gen_btn.click(
147
  run_generation,
148
- inputs=[prompt, measures, temperature, top_k, top_p, max_patches, seed, store],
149
  outputs=[log, editor, file_list, store],
150
  )
151
  stop_btn.click(None, None, None, cancels=[gen_event])
@@ -155,4 +241,4 @@ def build_ui ():
155
 
156
 
157
  if __name__ == '__main__':
158
- build_ui().queue().launch(theme=gr.themes.Soft())
 
4
  (1) generation parameter panel (2) streaming run log
5
  (3) .lyl file list (session outputs + built-in examples) (4) editable lyl editor
6
  Right column:
7
+ sheet-music panel (placeholder for now; later a Lilylet music score renderer
8
  reusing the lilylet-live-editor pipeline).
9
 
10
  Generation streams patch-by-patch: raw decoded text (with `[r:x/y]` stream
 
14
  """
15
 
16
  import os
17
+ import re
18
  import time
19
+ import json
20
 
21
  import gradio as gr
22
 
 
33
  EXAMPLE_PREFIX = '\U0001F4C4 ' # 📄 examples
34
  OUTPUT_PREFIX = '✨ ' # ✨ session outputs
35
 
36
+ # Suggested metadata values (editable — the dropdowns allow custom input), loaded
37
+ # from assets/styles.json. Drawn from the NotaGenX period/instrumentation
38
+ # vocabulary + values seen in examples.
39
+ _STYLES = json.load(open(os.path.join(ASSET_DIR, 'styles.json'), encoding='utf-8'))
40
+ COMPOSERS = _STYLES['composers']
41
+ GENRES = _STYLES['genres']
42
+ INSTRUMENTS = _STYLES['instruments']
43
+
44
  _GEN = None
45
 
46
 
 
63
  return store
64
 
65
 
66
+ def load_outputs ():
67
+ '''Read previously-generated .lyl files from the outputs dir into a
68
+ {label: text} dict, so past session outputs survive a server restart.'''
69
+ store = {}
70
+ if os.path.isdir(OUTPUT_DIR):
71
+ for name in sorted(os.listdir(OUTPUT_DIR)):
72
+ if name.endswith('.lyl'):
73
+ with open(os.path.join(OUTPUT_DIR, name), encoding='utf-8') as f:
74
+ store[OUTPUT_PREFIX + name[:-4]] = f.read()
75
+ return store
76
+
77
+
78
+ def load_library ():
79
+ '''Initial file list: built-in examples + any persisted session outputs.'''
80
+ return {**load_examples(), **load_outputs()}
81
+
82
+
83
+ _STYLE_LINE_RE = re.compile(r'^\[(composer|genre|instrument)\s+".*"\]\s*$')
84
+
85
+
86
+ def sync_prompt (composer, genre, instrument, current):
87
+ '''Rewrite the metadata-prompt text from the three style dropdowns.
88
+
89
+ The `[composer/genre/instrument "..."]` lines are regenerated from the
90
+ dropdowns and placed at the top; any other lines the user typed (e.g.
91
+ `[key "..."]`) are preserved below in their original order.
92
+ '''
93
+ lines = []
94
+ for field, value in (('composer', composer), ('genre', genre), ('instrument', instrument)):
95
+ value = (value or '').strip()
96
+ if value:
97
+ lines.append(f'[{field} "{value}"]')
98
+ # keep every line that isn't one of the three managed style lines
99
+ for ln in (current or '').splitlines():
100
+ if not _STYLE_LINE_RE.match(ln.strip()):
101
+ if ln.strip():
102
+ lines.append(ln)
103
+ return '\n'.join(lines)
104
+
105
+
106
+ def run_generation (prompt, measures, temperature, max_patches, seed, store, top_k=0, top_p=0.9):
107
  '''Streaming generate callback. Yields updates for (log, editor, file_list, store).
108
 
109
  store: {label: lyl_text} dict held in gr.State; the produced document is added
110
  to it under a timestamped label once generation finishes.
111
+
112
+ top_k / top_p have fixed defaults (no UI controls); pass them explicitly to override.
113
  '''
114
  gen = get_generator()
115
  meas = int(measures) if measures and int(measures) > 0 else None
 
144
  <div style="font-size:42px;margin-bottom:8px;">&#127932;</div>
145
  <div>Sheet-music preview</div>
146
  <div style="font-size:12px;margin-top:4px;">
147
+ (coming soon)
148
  </div>
149
  </div>
150
  </div>
151
  '''
152
 
153
 
154
+ CUSTOM_CSS = '''
155
+ /* Score List: truncate long file names to a single line with an ellipsis. */
156
+ .score-list label {
157
+ max-width: 100%;
158
+ }
159
+ .score-list label > span {
160
+ display: inline-block;
161
+ max-width: 100%;
162
+ overflow: hidden;
163
+ text-overflow: ellipsis;
164
+ white-space: nowrap;
165
+ vertical-align: middle;
166
+ }
167
+ '''
168
+
169
+
170
  def build_ui ():
171
+ examples = load_library()
172
 
173
  with gr.Blocks(title='LilyScript') as demo:
174
  gr.Markdown('## 🎼 LilyScript — symbolic music generation with Lilylet')
 
177
  with gr.Row(equal_height=True):
178
  # ---------------- LEFT ----------------
179
  with gr.Column(scale=5):
180
+ # (1) compose params, with (2) the collapsible run log stacked below
181
+ with gr.Group():
182
+ gr.Markdown('## Compose')
183
  with gr.Group():
184
+ gr.Markdown('- Style Options')
 
 
 
 
 
 
 
 
 
 
185
  with gr.Row():
186
+ composer = gr.Dropdown(label='composer', choices=COMPOSERS, value='',
187
+ allow_custom_value=True)
188
+ genre = gr.Dropdown(label='genre', choices=GENRES, value='',
189
+ allow_custom_value=True)
190
+ instrument = gr.Dropdown(label='instrument', choices=INSTRUMENTS, value='',
191
+ allow_custom_value=True)
192
+ prompt = gr.Textbox(label='Metadata prompt', lines=3, value='',
193
+ placeholder='extra metadata lines, e.g.\n[key "C major"]\n(optional)')
194
+ measures = gr.Number(label='Measures (0 = let model decide)', value=0, precision=0)
195
+ with gr.Row():
196
+ temperature = gr.Slider(0.0, 2.0, value=1.0, step=0.05, label='temperature')
197
+ max_patches = gr.Number(label='max patches', value=1024, precision=0)
198
+ seed = gr.Slider(0, 2147483647, value=42, step=1, label='seed')
199
+ with gr.Row():
200
+ gen_btn = gr.Button('Generate', variant='primary')
201
+ stop_btn = gr.Button('Stop', variant='stop')
202
+
203
+ with gr.Accordion('Logs', open=True):
204
+ log = gr.Textbox(show_label=False, lines=10, max_lines=10,
205
+ autoscroll=True, interactive=False, container=False)
206
 
207
  # bottom row: (3) file list | (4) editor
208
  with gr.Row(equal_height=True):
209
+ with gr.Column(scale=2, min_width=160):
210
+ with gr.Group():
211
+ gr.Markdown('## Score List')
212
+ file_list = gr.Radio(show_label=False, choices=list(examples.keys()),
213
+ value=None, interactive=True, container=False,
214
+ elem_classes=['score-list'])
215
+ with gr.Column(scale=5):
216
+ with gr.Group():
217
+ gr.Markdown('## Lilylet editor')
218
+ editor = gr.Code(show_label=False, language=None, lines=18,
219
+ max_lines=18, interactive=True)
220
 
221
  # ---------------- RIGHT ----------------
222
  with gr.Column(scale=6):
223
+ with gr.Group():
224
+ gr.Markdown('## Sheet music')
225
+ gr.HTML(SHEET_PLACEHOLDER)
226
 
227
  # ---- wiring ----
228
+ # style dropdowns -> keep the metadata-prompt text box in sync
229
+ for field in (composer, genre, instrument):
230
+ field.change(sync_prompt, inputs=[composer, genre, instrument, prompt], outputs=[prompt])
231
+
232
  gen_event = gen_btn.click(
233
  run_generation,
234
+ inputs=[prompt, measures, temperature, max_patches, seed, store],
235
  outputs=[log, editor, file_list, store],
236
  )
237
  stop_btn.click(None, None, None, cancels=[gen_event])
 
241
 
242
 
243
  if __name__ == '__main__':
244
+ build_ui().queue().launch(theme=gr.themes.Soft(), css=CUSTOM_CSS)
assets/styles.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "composers": [
3
+ "Bach, Johann Sebastian",
4
+ "Handel, George Frideric",
5
+ "Haydn, Joseph",
6
+ "Mozart, Wolfgang Amadeus",
7
+ "Beethoven, Ludwig van",
8
+ "Schubert, Franz",
9
+ "Chopin, Frederic",
10
+ "Schumann, Robert",
11
+ "Liszt, Franz",
12
+ "Brahms, Johannes",
13
+ "Tchaikovsky, Pyotr",
14
+ "Dvorak, Antonin",
15
+ "Smetana, Bedrich",
16
+ "Berlioz, Hector",
17
+ "Debussy, Claude",
18
+ "Ravel, Maurice",
19
+ "Shostakovich, Dmitry"
20
+ ],
21
+ "genres": ["Baroque", "Classical", "Romantic", "Modern"],
22
+ "instruments": ["Keyboard", "Choral", "Chamber"]
23
+ }