Spaces:
Running on Zero
Running on Zero
| import gradio as gr | |
| import torch | |
| import torch.nn as nn | |
| import numpy as np | |
| import spaces | |
| import random | |
| import os | |
| import requests | |
| from PIL import Image, ImageDraw | |
| import io | |
| import tempfile | |
| import base64 | |
| import pretty_midi | |
| from huggingface_hub import hf_hub_download | |
| import subprocess | |
| import wave | |
| import struct | |
| import time | |
| # ============================================================ | |
| # 0. ЗАГРУЗКА ФАЙЛОВ ИЗ БАКЕТА | |
| # ============================================================ | |
| def download_file(url, local_path): | |
| if os.path.exists(local_path): | |
| print(f"[INFO] Файл уже есть: {local_path}") | |
| return local_path | |
| print(f"[INFO] Скачиваю {url}") | |
| response = requests.get(url, stream=True) | |
| response.raise_for_status() | |
| with open(local_path, 'wb') as f: | |
| for chunk in response.iter_content(chunk_size=8192): | |
| if chunk: | |
| f.write(chunk) | |
| print(f"[INFO] Скачано: {local_path}") | |
| return local_path | |
| VAE_URL = "https://huggingface.co/buckets/root39058/shhh/resolve/andrey_dreams.pt?download=true" | |
| VIDEO_URL = "https://huggingface.co/buckets/root39058/shhh/resolve/andrey_videos.pt?download=true" | |
| MUSIC_URL = "https://huggingface.co/buckets/root39058/shhh/resolve/andrey_music.pt?download=true" | |
| VAE_PATH = download_file(VAE_URL, "andrey_dreams.pt") | |
| VIDEO_PATH = download_file(VIDEO_URL, "andrey_videos.pt") | |
| MUSIC_PATH = download_file(MUSIC_URL, "andrey_music.pt") | |
| CHAT_MODEL = hf_hub_download( | |
| repo_id="root39058/AndreyBot", | |
| filename="pytorch_model.bin", | |
| local_dir="./andrey_model" | |
| ) | |
| WORLD_MODEL = hf_hub_download(repo_id="root39058/AndreyWorld", filename="andrey_world_model.pt") | |
| print("[INFO] Все модели загружены") | |
| # ============================================================ | |
| # 1. УСТРОЙСТВО | |
| # ============================================================ | |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') | |
| print(f"[INFO] Устройство: {device}") | |
| # ============================================================ | |
| # 2. ТЕКСТОВАЯ МОДЕЛЬ ДЛЯ ОПИСАНИЙ | |
| # ============================================================ | |
| class TextModel(nn.Module): | |
| def __init__(self, vocab_size=50, embed_dim=64, hidden_dim=128): | |
| super().__init__() | |
| self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) | |
| self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True) | |
| self.fc = nn.Linear(hidden_dim, vocab_size) | |
| def forward(self, x): | |
| x = self.embedding(x) | |
| x, _ = self.lstm(x) | |
| x = self.fc(x) | |
| return x | |
| def load_text_model(): | |
| checkpoint = torch.load(VAE_PATH, map_location='cpu') | |
| vocab = checkpoint['vocab'] | |
| word_to_idx = checkpoint['word_to_idx'] | |
| idx_to_word = checkpoint['idx_to_word'] | |
| model = TextModel(vocab_size=len(vocab), embed_dim=64, hidden_dim=128) | |
| model.load_state_dict(checkpoint['text_state']) | |
| model.eval() | |
| return model, vocab, word_to_idx, idx_to_word | |
| text_model, text_vocab, text_w2i, text_i2w = load_text_model() | |
| text_model = text_model.to(device) | |
| print("[INFO] Текстовая модель загружена") | |
| def generate_description(prompt, max_len=15): | |
| text_model.eval() | |
| words = prompt.lower().split() | |
| ids = [text_w2i.get(w, 1) for w in words] | |
| if len(ids) < 5: | |
| ids = ids + [0] * (5 - len(ids)) | |
| inp = torch.tensor([ids[:5]], dtype=torch.long).to(device) | |
| generated = [] | |
| current = inp | |
| with torch.no_grad(): | |
| for _ in range(max_len): | |
| logits = text_model(current) | |
| probs = torch.softmax(logits[0, -1, :] / 0.8, dim=-1) | |
| probs[0] = 0 | |
| probs[1] = 0 | |
| probs = probs / probs.sum() | |
| idx = torch.multinomial(probs, 1).item() | |
| if idx == text_w2i.get('', 2): | |
| break | |
| generated.append(idx) | |
| current = torch.cat([current, torch.tensor([[idx]]).to(device)], dim=1) | |
| if len(generated) >= 12: | |
| break | |
| words = [text_i2w.get(i, '?') for i in generated] | |
| return ' '.join(words) | |
| # ============================================================ | |
| # 3. ЧАТ-МОДЕЛЬ | |
| # ============================================================ | |
| class AndreyBot(nn.Module): | |
| def __init__(self, vocab_size=155, hidden_size=512, num_layers=3, embedding_dim=128): | |
| super().__init__() | |
| self.vocab_size = vocab_size | |
| self.hidden_size = hidden_size | |
| self.num_layers = num_layers | |
| self.embedding_dim = embedding_dim | |
| self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) | |
| self.lstm = nn.LSTM(embedding_dim, hidden_size, batch_first=True, num_layers=num_layers, dropout=0.2) | |
| self.fc = nn.Linear(hidden_size, vocab_size) | |
| self.dropout = nn.Dropout(0.2) | |
| def forward(self, x): | |
| if x.dim() == 1: | |
| x = x.unsqueeze(0) | |
| emb = self.embedding(x) | |
| out, _ = self.lstm(emb) | |
| out = self.dropout(out) | |
| out = out[:, -1, :] | |
| return self.fc(out) | |
| def load_chat_model(): | |
| checkpoint = torch.load(CHAT_MODEL, map_location='cpu') | |
| vocab_size = checkpoint.get('vocab_size', 155) | |
| hidden_size = checkpoint.get('hidden_size', 512) | |
| num_layers = checkpoint.get('num_layers', 3) | |
| embedding_dim = checkpoint.get('embedding_dim', 128) | |
| model = AndreyBot( | |
| vocab_size=vocab_size, | |
| hidden_size=hidden_size, | |
| num_layers=num_layers, | |
| embedding_dim=embedding_dim | |
| ) | |
| if 'model_state' in checkpoint: | |
| model.load_state_dict(checkpoint['model_state']) | |
| elif 'model_state_dict' in checkpoint: | |
| model.load_state_dict(checkpoint['model_state_dict']) | |
| else: | |
| model.load_state_dict(checkpoint) | |
| model.eval() | |
| word_to_idx = checkpoint.get('word_to_idx', {}) | |
| idx_to_word = {int(k): v for k, v in checkpoint.get('idx_to_word', {}).items()} | |
| return model, word_to_idx, idx_to_word | |
| chat_model, word_to_idx, idx_to_word = load_chat_model() | |
| chat_model = chat_model.to(device) | |
| print(f"[INFO] Чат-модель загружена: {len(word_to_idx)} слов, 512 нейронов") | |
| PAD = 0 | |
| UNK = 1 | |
| def tokenize(text): | |
| words = text.lower().split() | |
| return [word_to_idx.get(w, UNK) for w in words if w in word_to_idx] | |
| def detokenize(tokens): | |
| words = [] | |
| for t in tokens: | |
| if t not in [PAD, UNK]: | |
| words.append(idx_to_word.get(t, '?')) | |
| return ' '.join(words) | |
| def chat_response(message, history): | |
| """Генератор: выдаёт ответ по одному токену с задержкой""" | |
| chat_model.eval() | |
| tokens = tokenize(message) | |
| if not tokens: | |
| yield "..." | |
| return | |
| current = tokens[0] | |
| response_tokens = [] | |
| partial_text = "" | |
| with torch.no_grad(): | |
| for step in range(15): | |
| logits = chat_model(torch.tensor([[current]]).to(device)) | |
| probs = torch.softmax(logits[0] / 0.85, dim=-1) | |
| top_k = min(8, len(word_to_idx)) | |
| top_probs, top_idx = torch.topk(probs, top_k) | |
| probs = top_probs / top_probs.sum() | |
| current = top_idx[torch.multinomial(probs, 1)].item() | |
| if current in [PAD, UNK]: | |
| continue | |
| response_tokens.append(current) | |
| word = idx_to_word.get(current, '') | |
| if word: | |
| partial_text += word + " " | |
| time.sleep(0.08) | |
| yield partial_text.strip() | |
| if len(response_tokens) >= 10: | |
| break | |
| if not partial_text.strip(): | |
| yield "..." | |
| # ============================================================ | |
| # 4. МОДЕЛЬ МИРОВ | |
| # ============================================================ | |
| class AndreyWorldMLP(nn.Module): | |
| def __init__(self, vocab_size=38, hidden_size=128): | |
| super().__init__() | |
| self.vocab_size = vocab_size | |
| self.fc1 = nn.Linear(vocab_size, hidden_size) | |
| self.fc2 = nn.Linear(hidden_size, hidden_size) | |
| self.fc3 = nn.Linear(hidden_size, hidden_size // 2) | |
| self.fc4 = nn.Linear(hidden_size // 2, hidden_size // 4) | |
| self.fc5 = nn.Linear(hidden_size // 4, vocab_size) | |
| self.dropout = nn.Dropout(0.2) | |
| self.relu = nn.ReLU() | |
| def forward(self, x): | |
| if x.dim() == 0: | |
| x = x.unsqueeze(0) | |
| if x.dim() == 1: | |
| x = x.unsqueeze(0) | |
| x = torch.nn.functional.one_hot(x, num_classes=self.vocab_size).float() | |
| x = self.relu(self.fc1(x)) | |
| x = self.dropout(x) | |
| x = self.relu(self.fc2(x)) | |
| x = self.dropout(x) | |
| x = self.relu(self.fc3(x)) | |
| x = self.dropout(x) | |
| x = self.relu(self.fc4(x)) | |
| x = self.fc5(x) | |
| return x | |
| def load_world_model(): | |
| checkpoint = torch.load(WORLD_MODEL, map_location='cpu') | |
| vocab_size = checkpoint['vocab_size'] | |
| word_to_idx = checkpoint['word_to_idx'] | |
| idx_to_word = {int(k): v for k, v in checkpoint['idx_to_word'].items()} | |
| model_config = checkpoint.get('model_config', {}) | |
| hidden_size = model_config.get('hidden_size', 128) | |
| model = AndreyWorldMLP(vocab_size=vocab_size, hidden_size=hidden_size) | |
| model.load_state_dict(checkpoint['model_state']) | |
| model.eval() | |
| return model, word_to_idx, idx_to_word | |
| world_model, world_w2i, world_i2w = load_world_model() | |
| world_model = world_model.to(device) | |
| print("[INFO] Модель миров загружена") | |
| def tokenize_world(text): | |
| words = text.lower().split() | |
| return [world_w2i.get(w, 1) for w in words if w in world_w2i] | |
| def detokenize_world(tokens): | |
| words = [] | |
| for t in tokens: | |
| if t not in [0, 1]: | |
| words.append(world_i2w.get(t, '?')) | |
| return ' '.join(words) | |
| def generate_world(prompt, temperature=0.85): | |
| world_model.eval() | |
| tokens = tokenize_world(prompt) | |
| if not tokens: | |
| return "..." | |
| current = tokens[0] | |
| response_tokens = [] | |
| with torch.no_grad(): | |
| for _ in range(20): | |
| logits = world_model(torch.tensor([[current]]).to(device)) | |
| probs = torch.softmax(logits[0] / temperature, dim=-1) | |
| top_k = min(8, len(world_w2i)) | |
| top_probs, top_idx = torch.topk(probs, top_k) | |
| probs = top_probs / top_probs.sum() | |
| current = top_idx[torch.multinomial(probs, 1)].item() | |
| if current in [0, 1]: | |
| continue | |
| response_tokens.append(current) | |
| if len(response_tokens) >= 15: | |
| break | |
| if not response_tokens: | |
| return "..." | |
| return detokenize_world(response_tokens) | |
| def draw_world_image(text): | |
| img = Image.new('RGB', (512, 256), color=(10, 10, 30)) | |
| draw = ImageDraw.Draw(img) | |
| draw.rectangle([(0, 180), (512, 256)], fill=(40, 60, 30)) | |
| for y in range(180): | |
| r = 10 + y // 5 | |
| g = 10 + y // 8 | |
| b = 30 + y // 4 | |
| draw.line([(0, y), (512, y)], fill=(r, g, b)) | |
| for i in range(4): | |
| x = i * 130 + 20 | |
| h = random.randint(60, 120) | |
| draw.polygon([(x, 180), (x + 60, 180 - h), (x + 120, 180)], fill=(80, 70, 60)) | |
| draw.ellipse([(400, 30), (470, 100)], fill=(255, 200, 80)) | |
| for i in range(8): | |
| x = random.randint(20, 490) | |
| draw.rectangle([(x, 180 - random.randint(20, 40)), (x + 6, 180)], fill=(50, 30, 20)) | |
| draw.ellipse([(x - 12, 180 - random.randint(30, 50)), (x + 18, 180 - random.randint(10, 30))], fill=(30, 120, 30)) | |
| draw.text((20, 20), text[:50], fill=(255, 255, 255)) | |
| return img | |
| # ============================================================ | |
| # 5. ПОЛНАЯ VAE | |
| # ============================================================ | |
| class FullVAE(nn.Module): | |
| def __init__(self, latent_dim=64, img_size=128): | |
| super().__init__() | |
| self.latent_dim = latent_dim | |
| self.img_size = img_size | |
| self.encoder = nn.Sequential( | |
| nn.Conv2d(3, 32, 4, 2, 1), | |
| nn.BatchNorm2d(32), | |
| nn.LeakyReLU(0.2), | |
| nn.Conv2d(32, 64, 4, 2, 1), | |
| nn.BatchNorm2d(64), | |
| nn.LeakyReLU(0.2), | |
| nn.Conv2d(64, 128, 4, 2, 1), | |
| nn.BatchNorm2d(128), | |
| nn.LeakyReLU(0.2), | |
| nn.Conv2d(128, 256, 4, 2, 1), | |
| nn.BatchNorm2d(256), | |
| nn.LeakyReLU(0.2), | |
| nn.Flatten(), | |
| ) | |
| self.fc_mu = nn.Linear(256 * 8 * 8, latent_dim) | |
| self.fc_logvar = nn.Linear(256 * 8 * 8, latent_dim) | |
| self.decoder_input = nn.Linear(latent_dim, 256 * 8 * 8) | |
| self.decoder = nn.Sequential( | |
| nn.ConvTranspose2d(256, 128, 4, 2, 1), | |
| nn.BatchNorm2d(128), | |
| nn.ReLU(), | |
| nn.ConvTranspose2d(128, 64, 4, 2, 1), | |
| nn.BatchNorm2d(64), | |
| nn.ReLU(), | |
| nn.ConvTranspose2d(64, 32, 4, 2, 1), | |
| nn.BatchNorm2d(32), | |
| nn.ReLU(), | |
| nn.ConvTranspose2d(32, 3, 4, 2, 1), | |
| nn.Sigmoid(), | |
| ) | |
| def decode(self, z): | |
| h = self.decoder_input(z) | |
| h = h.view(-1, 256, 8, 8) | |
| return self.decoder(h) | |
| def load_vae(): | |
| checkpoint = torch.load(VAE_PATH, map_location='cpu') | |
| model = FullVAE(latent_dim=64, img_size=128) | |
| model.load_state_dict(checkpoint['vae_state']) | |
| model.eval() | |
| return model | |
| vae_model = load_vae().to(device) | |
| print("[INFO] VAE загружена") | |
| def generate_dream(): | |
| with torch.no_grad(): | |
| z = torch.randn(1, 64).to(device) | |
| img = vae_model.decode(z) | |
| img = img.squeeze().cpu().numpy() | |
| img = np.transpose(img, (1, 2, 0)) | |
| img = np.clip(img, 0, 1) * 255 | |
| pil_img = Image.fromarray(img.astype(np.uint8)) | |
| pil_img = pil_img.resize((512, 512), Image.Resampling.LANCZOS) | |
| dream_types = ['космос', 'лес', 'океан', 'город', 'подводный мир', 'пустыня', 'горы', 'ночь'] | |
| dream_type = random.choice(dream_types) | |
| description = generate_description(f"сон про {dream_type}") | |
| return pil_img, description | |
| # ============================================================ | |
| # 6. ВИДЕО (GIF) | |
| # ============================================================ | |
| def generate_video_frame(z): | |
| with torch.no_grad(): | |
| img = vae_model.decode(z) | |
| img = img.squeeze().cpu().numpy() | |
| img = np.transpose(img, (1, 2, 0)) | |
| img = np.clip(img, 0, 1) * 255 | |
| pil_img = Image.fromarray(img.astype(np.uint8)) | |
| return pil_img.resize((512, 512), Image.Resampling.LANCZOS) | |
| def slerp(z1, z2, t): | |
| return (1 - t) * z1 + t * z2 | |
| def generate_gif(duration=5, fps=10): | |
| total_frames = duration * fps | |
| z1 = torch.randn(1, 64).to(device) | |
| z2 = torch.randn(1, 64).to(device) | |
| frames = [] | |
| frames_per_segment = 20 | |
| for i in range(total_frames): | |
| t = (i % frames_per_segment) / frames_per_segment | |
| if i % frames_per_segment == 0: | |
| z1 = z2 | |
| z2 = torch.randn(1, 64).to(device) | |
| z = slerp(z1, z2, t) | |
| frames.append(generate_video_frame(z)) | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.gif') | |
| temp_path = temp_file.name | |
| temp_file.close() | |
| frames[0].save(temp_path, save_all=True, append_images=frames[1:], | |
| duration=1000//fps, loop=0, optimize=True) | |
| dream_types = ['космос', 'лес', 'океан', 'город', 'подводный мир', 'пустыня', 'горы', 'ночь'] | |
| dream_type = random.choice(dream_types) | |
| video_description = generate_description(f"видео сон про {dream_type}") | |
| return temp_path, video_description | |
| # ============================================================ | |
| # 7. УЛУЧШЕННАЯ МУЗЫКАЛЬНАЯ МОДЕЛЬ С НОВЫМИ ФУНКЦИЯМИ | |
| # ============================================================ | |
| class MusicGenerator(nn.Module): | |
| def __init__(self, vocab_size=128, embed_dim=64, hidden_dim=128, num_layers=2): | |
| super().__init__() | |
| self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) | |
| self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True, dropout=0.2) | |
| self.fc = nn.Linear(hidden_dim, vocab_size) | |
| def forward(self, x, hidden=None): | |
| x = self.embedding(x) | |
| x, hidden = self.lstm(x, hidden) | |
| x = self.fc(x) | |
| return x, hidden | |
| class MusicTokenizer: | |
| def __init__(self): | |
| self.notes = list(range(36, 85)) | |
| self.durations = [0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0] | |
| self.vocab = ['', '', '', ''] | |
| for note in self.notes: | |
| for dur in self.durations: | |
| self.vocab.append(f'{note}_{dur}') | |
| self.word_to_idx = {w: i for i, w in enumerate(self.vocab)} | |
| self.idx_to_word = {i: w for w, i in self.word_to_idx.items()} | |
| self.vocab_size = len(self.vocab) | |
| self.max_len = 50 | |
| def tokenize(self, notes, durations): | |
| tokens = [] | |
| for n, d in zip(notes, durations): | |
| if n in self.notes and d in self.durations: | |
| tokens.append(f'{n}_{d}') | |
| return [self.word_to_idx.get(t, 1) for t in tokens] | |
| def detokenize(self, ids): | |
| notes = [] | |
| durations = [] | |
| for i in ids: | |
| if i in [0, 1, 2, 3]: | |
| continue | |
| token = self.idx_to_word.get(i, '') | |
| if '_' in token: | |
| try: | |
| n, d = token.split('_') | |
| notes.append(int(n)) | |
| durations.append(float(d)) | |
| except: | |
| continue | |
| return notes, durations | |
| def load_music_model(): | |
| checkpoint = torch.load(MUSIC_PATH, map_location='cpu') | |
| tokenizer = MusicTokenizer() | |
| model = MusicGenerator( | |
| vocab_size=tokenizer.vocab_size, | |
| embed_dim=64, | |
| hidden_dim=128, | |
| num_layers=2 | |
| ) | |
| if 'model_state' in checkpoint: | |
| try: | |
| model.load_state_dict(checkpoint['model_state']) | |
| except: | |
| print("[WARNING] Не удалось загрузить веса модели, используем случайные") | |
| elif 'model_state_dict' in checkpoint: | |
| try: | |
| model.load_state_dict(checkpoint['model_state_dict']) | |
| except: | |
| print("[WARNING] Не удалось загрузить веса модели, используем случайные") | |
| model.eval() | |
| return model, tokenizer | |
| music_model, music_tokenizer = load_music_model() | |
| music_model = music_model.to(device) | |
| print("[INFO] Музыкальная модель загружена") | |
| # Словарь стилей музыки | |
| MUSIC_STYLES = { | |
| 'Классика': {'tempo': 100, 'program': 0, 'note_range': (48, 72)}, | |
| 'Джаз': {'tempo': 130, 'program': 6, 'note_range': (40, 80)}, | |
| 'Электроника': {'tempo': 140, 'program': 88, 'note_range': (36, 84)}, | |
| 'Рок': {'tempo': 160, 'program': 29, 'note_range': (40, 76)}, | |
| 'Акустика': {'tempo': 110, 'program': 25, 'note_range': (45, 75)}, | |
| 'Фортепиано': {'tempo': 120, 'program': 0, 'note_range': (36, 84)}, | |
| } | |
| def generate_melody_with_style(length=30, temperature=0.9, style='Классика'): | |
| """Генерация мелодии в определённом стиле""" | |
| music_model.eval() | |
| style_config = MUSIC_STYLES.get(style, MUSIC_STYLES['Классика']) | |
| note_min, note_max = style_config['note_range'] | |
| start_token = music_tokenizer.word_to_idx[''] | |
| inp = torch.tensor([[start_token]], dtype=torch.long).to(device) | |
| generated = [] | |
| hidden = None | |
| last_notes = [] | |
| with torch.no_grad(): | |
| for step in range(length): | |
| logits, hidden = music_model(inp, hidden) | |
| probs = torch.softmax(logits[0, -1, :] / temperature, dim=-1) | |
| probs[0] = 0 # PAD | |
| probs[1] = 0 # UNK | |
| probs[2] = 0 # EOS | |
| probs[3] = 0 # START | |
| # Ограничиваем диапазон нот для стиля | |
| for i in range(len(probs)): | |
| token = music_tokenizer.idx_to_word.get(i, '') | |
| if '_' in token: | |
| try: | |
| note = int(token.split('_')[0]) | |
| if note < note_min or note > note_max: | |
| probs[i] = 0 | |
| except: | |
| pass | |
| if probs.sum() == 0: | |
| break | |
| probs = probs / probs.sum() | |
| top_k = min(20, len(music_tokenizer.vocab)) | |
| top_probs, top_idx = torch.topk(probs, top_k) | |
| noise = torch.randn_like(top_probs) * 0.05 | |
| top_probs = top_probs + noise | |
| top_probs = torch.clamp(top_probs, min=0) | |
| top_probs = top_probs / top_probs.sum() | |
| idx = top_idx[torch.multinomial(top_probs, 1)].item() | |
| token = music_tokenizer.idx_to_word.get(idx, '') | |
| if '_' in token: | |
| note = token.split('_')[0] | |
| if len(last_notes) >= 3 and all(n == note for n in last_notes[-3:]): | |
| temp_idx = top_idx[torch.multinomial(top_probs / 1.5, 1)].item() | |
| if temp_idx != idx: | |
| idx = temp_idx | |
| token = music_tokenizer.idx_to_word.get(idx, '') | |
| if '_' in token: | |
| note = token.split('_')[0] | |
| last_notes.append(note) | |
| if len(last_notes) > 5: | |
| last_notes.pop(0) | |
| generated.append(idx) | |
| inp = torch.cat([inp, torch.tensor([[idx]]).to(device)], dim=1) | |
| if len(generated) >= length: | |
| break | |
| if not generated: | |
| return generate_random_melody(length, note_min, note_max) | |
| notes, durations = music_tokenizer.detokenize(generated) | |
| if len(notes) < 5: | |
| extra_notes, extra_durs = generate_random_melody(length - len(notes), note_min, note_max) | |
| notes.extend(extra_notes) | |
| durations.extend(extra_durs) | |
| return notes, durations | |
| def generate_random_melody(length=20, note_min=48, note_max=72): | |
| """Генерация случайной мелодии""" | |
| notes = [] | |
| durations = [] | |
| for _ in range(length): | |
| note = random.randint(note_min, note_max) | |
| dur = random.choice([0.25, 0.5, 0.75, 1.0, 1.5, 2.0]) | |
| notes.append(note) | |
| durations.append(dur) | |
| return notes, durations | |
| def create_chord_progression(notes, durations, chord_style='simple'): | |
| """Добавляет аккорды к мелодии""" | |
| if len(notes) < 4: | |
| return notes, durations | |
| chord_notes = [] | |
| chord_durations = [] | |
| chords = { | |
| 'C': [0, 4, 7], | |
| 'D': [2, 6, 9], | |
| 'E': [4, 8, 11], | |
| 'F': [5, 9, 0], | |
| 'G': [7, 11, 2], | |
| 'A': [9, 1, 4], | |
| 'B': [11, 3, 6] | |
| } | |
| progression = ['C', 'G', 'Am', 'F'] | |
| chord_index = 0 | |
| for i, (note, dur) in enumerate(zip(notes, durations)): | |
| chord_notes.append(note) | |
| chord_durations.append(dur) | |
| if i % 4 == 0 and i > 0: | |
| chord_index = (chord_index + 1) % len(progression) | |
| chord_name = progression[chord_index] | |
| if chord_name in chords: | |
| base_note = chords[chord_name][0] + 48 # C4 | |
| for j, interval in enumerate(chords[chord_name]): | |
| chord_note = base_note + interval | |
| if 36 <= chord_note <= 84: | |
| chord_notes.append(chord_note) | |
| chord_durations.append(dur * 0.5) | |
| return chord_notes, chord_durations | |
| def notes_to_midi_bytes(notes, durations, tempo=120, program=0, add_chords=True): | |
| """Создание MIDI с возможностью добавления аккордов""" | |
| midi = pretty_midi.PrettyMIDI(initial_tempo=tempo) | |
| instrument = pretty_midi.Instrument(program=program) | |
| time = 0.0 | |
| for note, dur in zip(notes, durations): | |
| if note < 21 or note > 108: | |
| continue | |
| note_obj = pretty_midi.Note( | |
| velocity=random.randint(70, 100), | |
| pitch=int(note), | |
| start=time, | |
| end=time + dur | |
| ) | |
| instrument.notes.append(note_obj) | |
| time += dur | |
| midi.instruments.append(instrument) | |
| if add_chords and len(notes) > 8: | |
| chord_instrument = pretty_midi.Instrument(program=program + 1) | |
| chord_notes, chord_durations = create_chord_progression(notes, durations) | |
| time = 0.0 | |
| for i, (note, dur) in enumerate(zip(chord_notes, chord_durations)): | |
| if i % 2 == 0: | |
| continue | |
| if note < 21 or note > 108: | |
| continue | |
| note_obj = pretty_midi.Note( | |
| velocity=random.randint(50, 70), | |
| pitch=int(note), | |
| start=time, | |
| end=time + dur * 0.7 | |
| ) | |
| chord_instrument.notes.append(note_obj) | |
| time += dur | |
| midi.instruments.append(chord_instrument) | |
| buffer = io.BytesIO() | |
| midi.write(buffer) | |
| buffer.seek(0) | |
| return buffer.getvalue() | |
| # ============================================================ | |
| # 8. GRADIO ИНТЕРФЕЙС | |
| # ============================================================ | |
| with gr.Blocks(title="🌌 Андрей — Целостный ИИ") as demo: | |
| gr.Markdown(""" | |
| # 🌌 Андрей — Целостный ИИ | |
| **Чат • Миры • Сны • Видео • Музыка — всё в одном месте** | |
| 🤖 **Новый Андрей:** 22 МБ, 512 нейронов, 3 слоя, 155 слов | |
| 🎵 **Музыка:** 6 стилей + аккорды | |
| """) | |
| with gr.Tabs(): | |
| # ===== ВКЛАДКА 1: ЧАТ ===== | |
| with gr.TabItem("💬 Чат"): | |
| gr.Markdown("### Поговори с Андреем (512 нейронов)") | |
| chatbot = gr.ChatInterface( | |
| fn=chat_response, | |
| title="Андрей", | |
| description="Тихий друг с 512 нейронами — текст появляется по токенам" | |
| ) | |
| # ===== ВКЛАДКА 2: МИРЫ ===== | |
| with gr.TabItem("🌍 Миры"): | |
| gr.Markdown("### Андрей создаёт миры") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| world_img = gr.Image(label="Мир", type="pil") | |
| world_desc = gr.Textbox(label="📖 Описание мира", lines=3) | |
| with gr.Column(scale=1): | |
| world_prompt = gr.Textbox(label="Что за мир?", placeholder="Например: лесной мир") | |
| world_btn = gr.Button("🌀 Создать мир", variant="primary") | |
| def create_world(prompt): | |
| if not prompt.strip(): | |
| prompt = "тихий лесной мир" | |
| text = generate_world(prompt) | |
| img = draw_world_image(text) | |
| return img, text | |
| world_btn.click( | |
| fn=create_world, | |
| inputs=[world_prompt], | |
| outputs=[world_img, world_desc] | |
| ) | |
| # ===== ВКЛАДКА 3: СНЫ ===== | |
| with gr.TabItem("🌙 Сны"): | |
| gr.Markdown("### Андрей видит сны") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| dream_img = gr.Image(label="Сон", type="pil") | |
| dream_desc = gr.Textbox(label="📖 Описание сна", lines=3) | |
| with gr.Column(scale=1): | |
| dream_btn = gr.Button("🌀 Новый сон + описание", variant="primary") | |
| dream_btn.click( | |
| fn=generate_dream, | |
| outputs=[dream_img, dream_desc] | |
| ) | |
| # ===== ВКЛАДКА 4: ВИДЕО ===== | |
| with gr.TabItem("🎬 Видео"): | |
| gr.Markdown("### GIF-видео сна (5 секунд)") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| video_output = gr.Image(label="GIF-сон", type="filepath") | |
| video_desc = gr.Textbox(label="📖 Описание видео", lines=3) | |
| with gr.Column(scale=1): | |
| video_btn = gr.Button("🌀 Сгенерировать GIF", variant="primary") | |
| video_btn.click( | |
| fn=generate_gif, | |
| outputs=[video_output, video_desc] | |
| ) | |
| # ===== ВКЛАДКА 5: МУЗЫКА (ОБНОВЛЁННАЯ) ===== | |
| with gr.TabItem("🎵 Музыка"): | |
| gr.Markdown("### Андрей сочиняет мелодии с аккордами и стилями") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| music_output = gr.File(label="🎵 MIDI-файл", file_types=[".mid"]) | |
| music_info = gr.Textbox(label="📖 Описание", lines=3) | |
| music_style_display = gr.Textbox(label="🎼 Стиль", lines=1) | |
| with gr.Column(scale=1): | |
| music_style = gr.Dropdown( | |
| choices=list(MUSIC_STYLES.keys()), | |
| value='Классика', | |
| label="🎹 Стиль музыки" | |
| ) | |
| music_length = gr.Slider( | |
| minimum=10, | |
| maximum=80, | |
| value=40, | |
| step=1, | |
| label="Длина мелодии (нот)" | |
| ) | |
| temperature_slider = gr.Slider( | |
| minimum=0.5, | |
| maximum=1.5, | |
| value=0.9, | |
| step=0.05, | |
| label="🌡️ Температура (разнообразие)" | |
| ) | |
| add_chords = gr.Checkbox( | |
| label="🎶 Добавить аккорды", | |
| value=True | |
| ) | |
| music_btn = gr.Button("🌀 Создать мелодию", variant="primary", size="lg") | |
| def generate_music_with_style(length, temperature, style, add_chords_flag): | |
| notes, durations = generate_melody_with_style( | |
| length=length, | |
| temperature=temperature, | |
| style=style | |
| ) | |
| if not notes: | |
| return None, "❌ Не удалось сгенерировать мелодию", "" | |
| style_config = MUSIC_STYLES.get(style, MUSIC_STYLES['Классика']) | |
| midi_bytes = notes_to_midi_bytes( | |
| notes, | |
| durations, | |
| tempo=style_config['tempo'], | |
| program=style_config['program'], | |
| add_chords=add_chords_flag | |
| ) | |
| temp = tempfile.NamedTemporaryFile(delete=False, suffix='.mid') | |
| temp.write(midi_bytes) | |
| temp.close() | |
| desc = generate_description(f"{style} мелодия из {len(notes)} нот") | |
| chord_info = " + аккорды" if add_chords_flag and len(notes) > 8 else "" | |
| return temp.name, f"🎵 {len(notes)} нот • {desc}{chord_info}", f"{style} • {style_config['tempo']} BPM" | |
| music_btn.click( | |
| fn=generate_music_with_style, | |
| inputs=[music_length, temperature_slider, music_style, add_chords], | |
| outputs=[music_output, music_info, music_style_display] | |
| ) | |
| gr.Markdown(""" | |
| ### 🎼 Примеры стилей: | |
| \- **Классика** — медленные, плавные мелодии | |
| \- **Джаз** — свинговые, с характерными гармониями | |
| \- **Электроника** — быстрые, повторяющиеся паттерны | |
| \- **Рок** — энергичные, с сильным ритмом | |
| \- **Акустика** — тёплые, натуральные звуки | |
| \- **Фортепиано** — классическое звучание | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch(share=True, theme=gr.themes.Soft()) |