Hezu06 commited on
Commit
f5c3ac2
·
1 Parent(s): bd26bbb

refactor music generation by batching note sending and improving timing dynamics

Browse files
Files changed (2) hide show
  1. index.html +9 -7
  2. main.py +127 -102
index.html CHANGED
@@ -562,22 +562,24 @@
562
  ws.onmessage = (event) => {
563
  const data = JSON.parse(event.data);
564
 
 
 
 
 
565
  if (data.notes) {
566
- data.notes.forEach(n => {
567
 
 
568
  const noteName = Tone.Frequency(n.note, "midi").toNote();
569
 
570
- // 🎛 velocity humanize
571
- let velocity = n.velocity / 127;
572
- velocity = Math.max(0.25, velocity);
573
 
574
- // 🎲 timing humanize
575
- const humanize = (Math.random() - 0.5) * 0.02;
576
 
577
  piano.triggerAttackRelease(
578
  noteName,
579
  n.duration,
580
- Tone.now() + humanize,
581
  velocity
582
  );
583
  });
 
562
  ws.onmessage = (event) => {
563
  const data = JSON.parse(event.data);
564
 
565
+ if (data.bpm) {
566
+ Tone.Transport.bpm.value = data.bpm;
567
+ }
568
+
569
  if (data.notes) {
570
+ const now = Tone.now();
571
 
572
+ data.notes.forEach(n => {
573
  const noteName = Tone.Frequency(n.note, "midi").toNote();
574
 
575
+ const velocity = Math.max(0.3, n.velocity / 127);
 
 
576
 
577
+ const humanize = (Math.random() - 0.5) * 0.01;
 
578
 
579
  piano.triggerAttackRelease(
580
  noteName,
581
  n.duration,
582
+ now + n.time + humanize,
583
  velocity
584
  );
585
  });
main.py CHANGED
@@ -116,116 +116,141 @@ async def websocket_vibe(websocket: WebSocket):
116
  safe_notes = chord + [c + 12 for c in chord]
117
  return min(safe_notes, key=lambda p: abs(p - midi_val))
118
 
 
 
119
  while is_playing:
120
  beat = 60.0 / current_bpm
121
 
122
- # 🎧 SWING
123
- swing = 0.12
124
- if note_count % 2 == 0:
125
- sleep_time = beat * (1 + swing)
126
- else:
127
- sleep_time = beat * (1 - swing)
128
-
129
- # 🎼 phrase (16 step)
130
- phrase_pos = note_count % 16
131
-
132
- # 🎵 rest (nghỉ)
133
- if random.random() < 0.12:
134
- await asyncio.sleep(sleep_time)
135
- note_count += 1
136
- continue
137
-
138
- current_chord = progression[(note_count // 4) % len(progression)]
139
-
140
- inp_tensor[0] = torch.tensor(pattern, dtype=torch.long)
141
-
142
- temp = max(0.5, min(1.2, 0.7 + (current_bpm - 60) / 120))
143
-
144
- with torch.inference_mode():
145
- pred = model(inp_tensor)
146
- probs = torch.softmax(pred / temp, dim=1).numpy()[0]
147
-
148
- top_idx = np.argsort(probs)[-10:]
149
- probs_top = probs[top_idx] / probs[top_idx].sum()
150
- idx = np.random.choice(top_idx, p=probs_top)
151
-
152
- tok = int_to_note[idx]
153
- pattern = pattern[1:] + [idx]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- midi_val = None
156
- try:
157
- if tok.startswith("NOTE"):
158
- midi_val = note.Note(tok.split("_")[1]).pitch.midi
159
- except:
160
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- notes_to_send = []
 
163
 
164
- # 🎹 CHORD (mỗi 4 beat)
165
- if note_count % 4 == 0:
166
- for c in current_chord:
167
- notes_to_send.append({
168
- "note": c,
169
- "velocity": random.randint(35, 55),
170
- "duration": beat * 4
171
- })
172
 
173
- # 🎸 MELODY
174
- if midi_val:
175
- # clamp range (lofi range)
176
- while midi_val > 76: midi_val -= 12
177
- while midi_val < 58: midi_val += 12
178
-
179
- # snap có xác suất (giữ tự nhiên)
180
- if random.random() < 0.7:
181
- melody = snap_to_chord(midi_val, current_chord)
182
- else:
183
- melody = midi_val
184
-
185
- # 🎼 phrasing dynamics
186
- if phrase_pos < 8:
187
- vel = 60
188
- elif phrase_pos < 12:
189
- vel = 70
190
- else:
191
- vel = 80
192
-
193
- vel += int(8 * math.sin(note_count))
194
-
195
- notes_to_send.append({
196
- "note": melody,
197
- "velocity": vel,
198
- "duration": sleep_time * 1.4
199
- })
200
-
201
- # 🎶 harmony (30%)
202
- if random.random() < 0.3:
203
- harmony = snap_to_chord(melody - random.choice([3,4,7]), current_chord)
204
- notes_to_send.append({
205
- "note": harmony,
206
- "velocity": vel - 20,
207
- "duration": sleep_time * 1.2
208
- })
209
 
210
- # 🎧 DRUM giả lập bằng velocity pulse (nếu frontend hỗ trợ thì tách channel)
211
- if phrase_pos % 4 == 0:
212
- notes_to_send.append({
213
- "note": 36, # kick
214
- "velocity": 90,
215
- "duration": 0.1
216
- })
217
- elif phrase_pos % 4 == 2:
218
- notes_to_send.append({
219
- "note": 38, # snare
220
- "velocity": 70,
221
- "duration": 0.1
222
- })
223
-
224
- if notes_to_send:
225
- await websocket.send_json({"notes": notes_to_send})
226
-
227
- note_count += 1
228
- await asyncio.sleep(sleep_time)
229
 
230
  except Exception as e:
231
  print("Connection closed:", e)
 
116
  safe_notes = chord + [c + 12 for c in chord]
117
  return min(safe_notes, key=lambda p: abs(p - midi_val))
118
 
119
+ BATCH_SIZE = 12 # số step mỗi lần gửi (~1–2s nhạc)
120
+
121
  while is_playing:
122
  beat = 60.0 / current_bpm
123
 
124
+ notes_batch = []
125
+ current_time = 0
126
+
127
+ for i in range(BATCH_SIZE):
128
+ global_step = note_count + i
129
+ phrase_pos = global_step % 16
130
+ chord = progression[(global_step // 4) % len(progression)]
131
+
132
+ inp_tensor[0] = torch.tensor(pattern, dtype=torch.long)
133
+
134
+ # 🎛 temperature ổn định
135
+ temp = max(0.6, min(1.0, 0.75 + (current_bpm - 70) / 140))
136
+
137
+ with torch.inference_mode():
138
+ pred = model(inp_tensor)
139
+ probs = torch.softmax(pred / temp, dim=1).numpy()[0]
140
+
141
+ # 🔥 giảm random → đỡ nhảy loạn
142
+ top_idx = np.argsort(probs)[-5:]
143
+ probs_top = probs[top_idx] / probs[top_idx].sum()
144
+ idx = np.random.choice(top_idx, p=probs_top)
145
+
146
+ tok = int_to_note[idx]
147
+ pattern = pattern[1:] + [idx]
148
+
149
+ midi_val = None
150
+ try:
151
+ if tok.startswith("NOTE"):
152
+ midi_val = note.Note(tok.split("_")[1]).pitch.midi
153
+ except:
154
+ pass
155
+
156
+ # ===============================
157
+ # 🎼 TIME GRID (QUAN TRỌNG NHẤT)
158
+ # ===============================
159
+ step_time = beat * 0.5 # 8th note grid
160
+ note_time = current_time
161
+
162
+ # ===============================
163
+ # 🎹 CHORD
164
+ # ===============================
165
+ if global_step % 4 == 0:
166
+ for c in chord:
167
+ notes_batch.append({
168
+ "note": c,
169
+ "velocity": random.randint(40, 55),
170
+ "duration": beat * 3.5,
171
+ "time": note_time
172
+ })
173
+
174
+ # ===============================
175
+ # 🎸 MELODY
176
+ # ===============================
177
+ if midi_val:
178
+ while midi_val > 76: midi_val -= 12
179
+ while midi_val < 60: midi_val += 12
180
+
181
+ if random.random() < 0.8:
182
+ melody = snap_to_chord(midi_val, chord)
183
+ else:
184
+ melody = midi_val
185
+
186
+ # 🎯 giảm nhảy xa
187
+ if hasattr(websocket, "last_note"):
188
+ prev = websocket.last_note
189
+ if abs(melody - prev) > 7:
190
+ melody = prev + random.choice([-2, -1, 1, 2])
191
+
192
+ websocket.last_note = melody
193
+
194
+ # 🎼 velocity human
195
+ if phrase_pos in [0,4,8,12]:
196
+ vel = 85
197
+ elif phrase_pos in [2,6,10,14]:
198
+ vel = 65
199
+ else:
200
+ vel = 50
201
+
202
+ # 🎵 duration theo groove
203
+ dur = beat * (0.9 if phrase_pos % 2 == 0 else 0.5)
204
+
205
+ notes_batch.append({
206
+ "note": melody,
207
+ "velocity": vel,
208
+ "duration": dur,
209
+ "time": note_time
210
+ })
211
 
212
+ # harmony nhẹ
213
+ if random.random() < 0.25:
214
+ notes_batch.append({
215
+ "note": melody - 3,
216
+ "velocity": vel - 25,
217
+ "duration": dur,
218
+ "time": note_time
219
+ })
220
+
221
+ # ===============================
222
+ # 🥁 RHYTHM
223
+ # ===============================
224
+ if phrase_pos == 0:
225
+ notes_batch.append({
226
+ "note": 36,
227
+ "velocity": 70,
228
+ "duration": 0.1,
229
+ "time": note_time
230
+ })
231
+ elif phrase_pos == 8:
232
+ notes_batch.append({
233
+ "note": 38,
234
+ "velocity": 60,
235
+ "duration": 0.1,
236
+ "time": note_time
237
+ })
238
 
239
+ # tăng timeline
240
+ current_time += step_time
241
 
242
+ # ===============================
243
+ # 🚀 SEND BATCH
244
+ # ===============================
245
+ await websocket.send_json({
246
+ "notes": notes_batch,
247
+ "bpm": current_bpm
248
+ })
 
249
 
250
+ note_count += BATCH_SIZE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ # sleep NGẮN hơn để luôn buffer trước
253
+ await asyncio.sleep(beat * BATCH_SIZE * 0.4)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  except Exception as e:
256
  print("Connection closed:", e)