Spaces:
Sleeping
Sleeping
update
Browse files
main.py
CHANGED
|
@@ -74,105 +74,148 @@ model.eval() # Chuyển sang chế độ suy luận
|
|
| 74 |
with open('notes_corpus.pkl', 'rb') as f:
|
| 75 |
corpus = pickle.load(f)
|
| 76 |
|
| 77 |
-
|
| 78 |
-
#
|
| 79 |
-
#
|
| 80 |
-
|
| 81 |
-
idx = np.argsort(probs)[-k:]
|
| 82 |
-
probs = probs[idx]
|
| 83 |
-
probs = probs / probs.sum()
|
| 84 |
-
return np.random.choice(idx, p=probs)
|
| 85 |
-
|
| 86 |
-
def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 60):
|
| 87 |
-
num_notes = int(120 + bpm * 1.5)
|
| 88 |
-
# --- PHẦN THÊM MỚI: ĐỊNH NGHĨA THANG NGŨ CUNG (C Pentatonic) ---
|
| 89 |
-
# Các nốt: C, D, E, G, A ở nhiều quãng tám (từ trầm đến trung)
|
| 90 |
-
# Chỉ dùng các nốt từ C2 (36) đến C4 (60) - Tuyệt đối không để vượt quá 60
|
| 91 |
-
# PENTATONIC_MS = [36, 38, 40, 43, 45, 48, 50, 52, 55, 57, 60]
|
| 92 |
-
|
| 93 |
-
# def get_closest_pentatonic(pitch_input):
|
| 94 |
-
# """Hàm ép một nốt bất kỳ về nốt Ngũ cung gần nhất"""
|
| 95 |
-
# return min(PENTATONIC_MS, key=lambda x: abs(x - pitch_input))
|
| 96 |
-
# -----------------------------------------------------------
|
| 97 |
-
# Logic phản hồi sinh học
|
| 98 |
temperature = 0.6 + (bpm - 60) / 100
|
| 99 |
temperature = max(0.5, min(1.2, temperature))
|
| 100 |
-
|
| 101 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
start_idx = random.randint(0, len(corpus) - 100)
|
| 103 |
seed_sequence = corpus[start_idx:start_idx + 100]
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
| 106 |
generated_notes = []
|
| 107 |
-
|
| 108 |
-
print(f"
|
| 109 |
-
|
| 110 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
for i in range(num_notes):
|
| 112 |
sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device)
|
| 113 |
-
|
| 114 |
with torch.no_grad():
|
| 115 |
prediction = model(sequence_tensor)
|
| 116 |
-
|
| 117 |
-
# ÁP DỤNG TEMPERATURE
|
| 118 |
prediction = prediction / temperature
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
index = sample_with_top_k(probabilities, k=8)
|
| 123 |
-
|
| 124 |
result_note = int_to_note[index]
|
|
|
|
| 125 |
generated_notes.append(result_note)
|
| 126 |
-
|
| 127 |
-
# Cập nhật cửa sổ trượt (thêm nốt mới, xóa nốt cũ nhất)
|
| 128 |
pattern.append(index)
|
| 129 |
pattern = pattern[1:]
|
| 130 |
-
|
|
|
|
|
|
|
| 131 |
# ==========================================
|
| 132 |
-
#
|
| 133 |
# ==========================================
|
| 134 |
offset = 0
|
| 135 |
output_stream = stream.Stream()
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
try:
|
| 140 |
parts = pattern_note.split("_")
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
# REST
|
| 143 |
if parts[0] == "REST":
|
| 144 |
-
dur =
|
| 145 |
offset += dur
|
| 146 |
continue
|
| 147 |
|
| 148 |
# NOTE
|
| 149 |
elif parts[0] == "NOTE":
|
| 150 |
pitch = parts[1]
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
new_obj = note.Note(pitch)
|
| 154 |
new_obj.quarterLength = dur
|
| 155 |
|
| 156 |
-
# CHORD
|
| 157 |
elif parts[0] == "CHORD":
|
| 158 |
pitches = parts[1].split(".")
|
| 159 |
-
dur =
|
| 160 |
|
| 161 |
chord_notes = [note.Note(p) for p in pitches]
|
| 162 |
new_obj = chord.Chord(chord_notes)
|
| 163 |
new_obj.quarterLength = dur
|
| 164 |
|
| 165 |
-
#
|
| 166 |
-
new_obj.offset = offset
|
|
|
|
|
|
|
| 167 |
output_stream.append(new_obj)
|
| 168 |
|
| 169 |
offset += dur
|
| 170 |
|
| 171 |
except Exception as e:
|
| 172 |
print("Skip:", pattern_note, e)
|
| 173 |
-
continue
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
|
|
|
| 176 |
# ==========================================
|
| 177 |
# 4. API ENDPOINT
|
| 178 |
# ==========================================
|
|
|
|
| 74 |
with open('notes_corpus.pkl', 'rb') as f:
|
| 75 |
corpus = pickle.load(f)
|
| 76 |
|
| 77 |
+
def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 200):
|
| 78 |
+
# ==========================================
|
| 79 |
+
# 1. CONFIG CHILL MUSIC
|
| 80 |
+
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
temperature = 0.6 + (bpm - 60) / 100
|
| 82 |
temperature = max(0.5, min(1.2, temperature))
|
| 83 |
+
|
| 84 |
+
# 🎼 C Major scale (safe, chill)
|
| 85 |
+
SCALE = ["C", "D", "E", "F", "G", "A", "B"]
|
| 86 |
+
|
| 87 |
+
# 🎹 Chill chord progression (lofi classic)
|
| 88 |
+
CHORD_PROGRESSION = [
|
| 89 |
+
["C4", "E4", "G4"],
|
| 90 |
+
["A3", "C4", "E4"],
|
| 91 |
+
["D4", "F4", "A4"],
|
| 92 |
+
["G3", "B3", "D4"]
|
| 93 |
+
]
|
| 94 |
+
|
| 95 |
+
# 🥁 Rhythm pattern (groove)
|
| 96 |
+
RHYTHM_PATTERN = [0.5, 0.5, 1.0, 0.5, 0.5, 1.5]
|
| 97 |
+
|
| 98 |
+
# ==========================================
|
| 99 |
+
# 2. SEED (random mạnh hơn)
|
| 100 |
+
# ==========================================
|
| 101 |
start_idx = random.randint(0, len(corpus) - 100)
|
| 102 |
seed_sequence = corpus[start_idx:start_idx + 100]
|
| 103 |
+
random.shuffle(seed_sequence)
|
| 104 |
+
|
| 105 |
+
pattern = [note_to_int[n] for n in seed_sequence]
|
| 106 |
+
|
| 107 |
generated_notes = []
|
| 108 |
+
|
| 109 |
+
print(f"🎧 Generating chill music | Temp={temperature:.2f}")
|
| 110 |
+
|
| 111 |
+
# ==========================================
|
| 112 |
+
# 3. TOP-K SAMPLING
|
| 113 |
+
# ==========================================
|
| 114 |
+
def sample_with_top_k(probs, k=8):
|
| 115 |
+
idx = np.argsort(probs)[-k:]
|
| 116 |
+
probs = probs[idx]
|
| 117 |
+
probs = probs / probs.sum()
|
| 118 |
+
return np.random.choice(idx, p=probs)
|
| 119 |
+
|
| 120 |
+
# ==========================================
|
| 121 |
+
# 4. GENERATE NOTES
|
| 122 |
+
# ==========================================
|
| 123 |
for i in range(num_notes):
|
| 124 |
sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device)
|
| 125 |
+
|
| 126 |
with torch.no_grad():
|
| 127 |
prediction = model(sequence_tensor)
|
| 128 |
+
|
|
|
|
| 129 |
prediction = prediction / temperature
|
| 130 |
+
probs = torch.softmax(prediction, dim=1).cpu().numpy()[0]
|
| 131 |
+
|
| 132 |
+
index = sample_with_top_k(probs)
|
|
|
|
|
|
|
| 133 |
result_note = int_to_note[index]
|
| 134 |
+
|
| 135 |
generated_notes.append(result_note)
|
| 136 |
+
|
|
|
|
| 137 |
pattern.append(index)
|
| 138 |
pattern = pattern[1:]
|
| 139 |
+
|
| 140 |
+
print("Sample notes:", generated_notes[:10])
|
| 141 |
+
|
| 142 |
# ==========================================
|
| 143 |
+
# 5. BUILD MIDI STREAM
|
| 144 |
# ==========================================
|
| 145 |
offset = 0
|
| 146 |
output_stream = stream.Stream()
|
| 147 |
+
|
| 148 |
+
inst = instrument.Piano()
|
| 149 |
+
inst.midiProgram = 0
|
| 150 |
+
output_stream.insert(0, inst)
|
| 151 |
+
|
| 152 |
+
chord_idx = 0
|
| 153 |
+
|
| 154 |
+
for i, pattern_note in enumerate(generated_notes):
|
| 155 |
try:
|
| 156 |
parts = pattern_note.split("_")
|
| 157 |
|
| 158 |
+
# 🎹 ADD CHORD (every 8 notes)
|
| 159 |
+
if i % 8 == 0:
|
| 160 |
+
chord_notes = [note.Note(p) for p in CHORD_PROGRESSION[chord_idx]]
|
| 161 |
+
ch = chord.Chord(chord_notes)
|
| 162 |
+
ch.quarterLength = 4
|
| 163 |
+
ch.offset = offset
|
| 164 |
+
ch.volume.velocity = random.randint(50, 80)
|
| 165 |
+
output_stream.append(ch)
|
| 166 |
+
|
| 167 |
+
chord_idx = (chord_idx + 1) % len(CHORD_PROGRESSION)
|
| 168 |
+
|
| 169 |
# REST
|
| 170 |
if parts[0] == "REST":
|
| 171 |
+
dur = RHYTHM_PATTERN[i % len(RHYTHM_PATTERN)]
|
| 172 |
offset += dur
|
| 173 |
continue
|
| 174 |
|
| 175 |
# NOTE
|
| 176 |
elif parts[0] == "NOTE":
|
| 177 |
pitch = parts[1]
|
| 178 |
+
|
| 179 |
+
# 🎼 Scale filter
|
| 180 |
+
if pitch[0] not in SCALE:
|
| 181 |
+
continue
|
| 182 |
+
|
| 183 |
+
dur = RHYTHM_PATTERN[i % len(RHYTHM_PATTERN)]
|
| 184 |
|
| 185 |
new_obj = note.Note(pitch)
|
| 186 |
new_obj.quarterLength = dur
|
| 187 |
|
| 188 |
+
# CHORD (AI generated)
|
| 189 |
elif parts[0] == "CHORD":
|
| 190 |
pitches = parts[1].split(".")
|
| 191 |
+
dur = RHYTHM_PATTERN[i % len(RHYTHM_PATTERN)]
|
| 192 |
|
| 193 |
chord_notes = [note.Note(p) for p in pitches]
|
| 194 |
new_obj = chord.Chord(chord_notes)
|
| 195 |
new_obj.quarterLength = dur
|
| 196 |
|
| 197 |
+
# 🎧 HUMANIZATION
|
| 198 |
+
new_obj.offset = offset + random.uniform(-0.02, 0.02)
|
| 199 |
+
new_obj.volume.velocity = random.randint(60, 90)
|
| 200 |
+
|
| 201 |
output_stream.append(new_obj)
|
| 202 |
|
| 203 |
offset += dur
|
| 204 |
|
| 205 |
except Exception as e:
|
| 206 |
print("Skip:", pattern_note, e)
|
| 207 |
+
continue
|
| 208 |
+
final_stream = stream.Stream()
|
| 209 |
+
|
| 210 |
+
for i in range(3): # loop 3 lần
|
| 211 |
+
for el in output_stream:
|
| 212 |
+
new_el = el
|
| 213 |
+
new_el.offset += i * offset
|
| 214 |
+
final_stream.append(new_el)
|
| 215 |
+
|
| 216 |
+
print("Total notes:", len(final_stream))
|
| 217 |
|
| 218 |
+
final_stream.write('midi', fp=output_file)
|
| 219 |
# ==========================================
|
| 220 |
# 4. API ENDPOINT
|
| 221 |
# ==========================================
|