import os
import logging
import openai
 
# Настройка логирования
logging.basicConfig(level=logging.INFO)
 
# Загрузка ключа API из переменных среды
openai.api_key = os.environ.get('OPENAI_API_KEY')
 
# Проверка, что ключ API загружен
if openai.api_key:
    try:
        # Пробный запрос к OpenAI API для проверки подключения
        response = openai.Completion.create(
            model="gpt-4",
            prompt="Hello, world!",
            max_tokens=5
        )
        logging.info("Подключение к OpenAI API успешно")
    except openai.error.AuthenticationError:
        logging.error("Ошибка аутентификации: Неверный ключ OpenAI API")
    except Exception as e:
        logging.error(f"Ошибка при подключении к OpenAI API: {e}")
else:
    logging.warning("Ключ OpenAI API не найден. API будет проигнорирован")

# ... (остальной код скрипта)

if __name__ == "__main__":
    file_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.mp3'
    output_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.musicxml'
    output_image_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.png'

    # Проверка существования директории для сохранения файла
    output_dir = os.path.dirname(output_path)
    if not os.path.exists(output_dir):
        logging.error(f"Output directory '{output_dir}' does not exist. MusicXML file will not be saved.")
        sys.exit(1)

    # Проверка существования файла
    if os.path.exists(output_path):
        logging.info(f"MusicXML file '{output_path}' will be overwritten.")
    else:
        logging.info(f"MusicXML file will be saved to '{output_path}'.")

    start_time = time.time()
    main(file_path, output_path, output_image_path)
    logging.info(f"Execution time: {time.time() - start_time} seconds")

import sys
import torch
from torch import nn, optim
from torch.utils.data import DataLoader, Dataset
import torchaudio
import librosa
import noisereduce as nr
import numpy as np
import crepe
import madmom
from music21 import stream, note, converter, instrument
from transformers import Wav2Vec2Tokenizer, Wav2Vec2ForCTC, HubertForCTC
from scipy import signal
import openunmix
from demucs import pretrained
from demucs.apply import apply_model
import openl3
import pydub
import parselmouth as pm
from spleeter.separator import Separator
import soundfile as sf
import time
import tonic
import asyncio
from diskcache import Cache
import tensorflow as tf
import importlib.util
import threading
import sounddevice as sd
from pydub import AudioSegment
from pydub.playback import play
 
# Проверка наличия GPU с помощью TensorFlow
if tf.test.gpu_device_name():
    print('GPU found')
else:
    print('No GPU found, using CPU instead')
 
# Добавление пути для импорта onsets_and_frames
sys.path.append(r'C:\Users\ARTYOM\onsets-and-frames\onsets_and_frames')
 
# Проверка наличия файла example.py
example_path = r'C:\Users\ARTYOM\onsets-and-frames\onsets_and_frames\example.py'
if not os.path.exists(example_path):
    logging.error("Файл example.py не найден в директории onsets_and_frames")
    sys.exit(1)
 
# Импорт необходимых модулей из onsets_and_frames с использованием importlib
try:
    spec = importlib.util.spec_from_file_location("example", example_path)
    example = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(example)
    NonBinaryClassification = example.NonBinaryClassification
    logging.info("Successfully imported NonBinaryClassification from onsets_and_frames.example")
except Exception as e:
    logging.error(f"Error importing NonBinaryClassification from onsets_and_frames.example: {e}")
    sys.exit(1)
 
# Загрузка ключа API из переменных среды
openai.api_key = os.environ.get('OPENAI_API_KEY')
 
# Настройка логирования
logging.basicConfig(level=logging.INFO)
 
# Настройка кэширования
cache = Cache('audio_cache')
 
# Конфигурационные параметры
config = {
    "output_dir": "output",
    "noise_reduction_level": 5,
    "pitch_shift": 2,
    "speed_change": 1.5,
    "batch_size": 16,
    "epochs": 10,
    "target_format": "wav"
}
 
# Определение функции custom_pad_center
def custom_pad_center(data, size):
    """
    Центрирует и заполняет массив до указанного размера.
    """
    if len(data) >= size:
        return data
    pad_size = (size - len(data)) // 2
    return np.pad(data, (pad_size, size - len(data) - pad_size), mode='constant')
 
# Определение функции melspectrogram
def melspectrogram(y, sr, n_mels=128, fmax=8000):
    S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels, fmax=fmax)
    return librosa.power_to_db(S, ref=np.max)
 
# Асинхронная функция для конвейера обработки
async def preprocess_audio(file_path):
    """
    Конвейер обработки аудио
    """
    try:
        logging.info(f"Loading and preprocessing audio from {file_path}")
        y, sr = librosa.load(file_path, sr=None)
        
        if (y, sr) in cache:
            logging.info("Using cached preprocessed audio")
            return cache[(y, sr)]
        
        y = librosa.util.normalize(y)
        y = nr.reduce_noise(y=y, sr=sr)
        y = signal.wiener(y)
 
        cache[(y, sr)] = (y, sr)
        logging.info("Audio preprocessing completed")
        
        return y, sr
    except Exception as e:
        logging.error(f"Error preprocessing audio: {e}")
        return None, None
 
async def extract_features_task(y, sr):
    """
    Асинхронная задача для извлечения признаков
    """
    try:
        cqt = librosa.cqt(y, sr=sr)
        cqt_db = librosa.amplitude_to_db(np.abs(cqt), ref=np.max)
        
        # Pitch detection using CREPE
        _, frequency, confidence, _ = crepe.predict(y, sr)
        
        return cqt_db, frequency, confidence
    except Exception as e:
        logging.error(f"Error extracting features: {e}")
        return None, None, None
 
def transformer_features_wav2vec2(y, sr):
    """
    Асинхронная задача для извлечения признаков с использованием Wav2Vec2
    """
    try:
        tokenizer = Wav2Vec2Tokenizer.from_pretrained("facebook/wav2vec2-base-960h")
        model = Wav2Vec2ForCTC.from_pretrained("facebook/wav2vec2-base-960h")
        
        input_values = tokenizer(y.tolist(), return_tensors="pt", padding=True).input_values
        logits = model(input_values).logits
        predicted_ids = torch.argmax(logits, dim=-1)
        
        transcription = tokenizer.batch_decode(predicted_ids)[0]
        return transcription
    except Exception as e:
        logging.error(f"Error extracting transformer features with Wav2Vec2: {e}")
        return None
 
def transformer_features_hubert(y, sr):
    """
    Асинхронная задача для извлечения признаков с использованием HuBERT
    """
    try:
        tokenizer = Wav2Vec2Tokenizer.from_pretrained("facebook/wav2vec2-base-960h")
        model = HubertForCTC.from_pretrained("facebook/hubert-large-ls960-ft")
        
        input_values = tokenizer(y.tolist(), return_tensors="pt", padding=True).input_values
        logits = model(input_values).logits
        predicted_ids = torch.argmax(logits, dim=-1)
        
        transcription = tokenizer.batch_decode(predicted_ids)[0]
        return transcription
    except Exception as e:
        logging.error(f"Error extracting transformer features with Hubert: {e}")
        return None
 
def generate_text_descriptions_with_chatgpt(prompt):
    """
    Генерация текста с использованием GPT-4
    """
    try:
        response = openai.Completion.create(
            model="gpt-4",
            prompt=prompt,
            max_tokens=150  # Увеличиваем количество токенов для получения более детального ответа
        )
        return response.choices[0].text.strip()
    except Exception as e:
        logging.error(f"Error generating text descriptions with GPT-4: {e}")
        return None
 
class AudioPipeline:
    def __init__(self, config):
        self.config = config
 
    def play_audio(self, audio):
        try:
            data = np.array(audio.get_array_of_samples())
            sr = audio.frame_rate
            sd.play(data, sr)
            sd.wait()
        except Exception as e:
            logging.error(f"Error playing audio: {e}")
    
    async def run_pipeline(self, file_path):
        """
        Запуск конвейера обработки
        """
        # Предварительная обработка
        y, sr = await preprocess_audio(file_path)
        if y is None or sr is None:
            logging.error("Error in audio preprocessing step")
            return
        
        # Запуск задач извлечения признаков в фоновом режиме
        features_task = extract_features_task(y, sr)
        
        # Запуск Преобразующих Моделей
        transcription_wav2vec2_task = asyncio.to_thread(transformer_features_wav2vec2, y, sr)
        transcription_hubert_task = asyncio.to_thread(transformer_features_hubert, y, sr)
        
        # Ожидание завершения задач
        features_result = await features_task
        transcription_wav2vec2 = await transcription_wav2vec2_task
        transcription_hubert = await transcription_hubert_task
        
        cqt_db, frequency, confidence = features_result
        
        logging.info(f"Feature extraction complete: {cqt_db.shape}, {len(frequency)}, {transcription_wav2vec2}, {transcription_hubert}")
        
        # Запуск дополнительных задач
        additional_features = await asyncio.to_thread(self.additional_features_essentia, y, sr)
        
        # Сепарация источников
        sources_openunmix = await asyncio.to_thread(self.source_separation_openunmix, y, sr)
        sources_demucs_v3 = await asyncio.to_thread(self.source_separation_demucs_v3, y, sr)
        vocals_path, accompaniment_path = await asyncio.to_thread(self.source_separation_spleeter, file_path)
        
        logging.info(f"Source separation complete: OpenUnmix: {sources_openunmix}, Demucs V3: {sources_demucs_v3}, Spleeter: {vocals_path}, {accompaniment_path}")
        
        # Анализ высоты тона с использованием Parselmouth
        pitch_analysis = await asyncio.to_thread(self.analyze_pitch_with_parselmouth, file_path)
        logging.info(f"Pitch analysis (Parselmouth): {pitch_analysis}")
        
        # Оптимизация
        audio = AudioSegment.from_file(file_path)
        reduced_noise_audio = self.reduce_noise(audio, self.config["noise_reduction_level"])
        modified_audio = self.change_timbre_and_speed(reduced_noise_audio, self.config["pitch_shift"], self.config["speed_change"])
        normalized_audio = self.normalize_volume_level(modified_audio)
        
        # Использование OpenAI для предварительной обработки аудио
        optimized_audio = self.optimize_audio_with_openai(y, sr)
        
        return (cqt_db, frequency, confidence, transcription_wav2vec2, transcription_hubert, additional_features, 
                sources_openunmix, sources_demucs_v3, vocals_path, accompaniment_path, pitch_analysis, normalized_audio, optimized_audio)
 
def reduce_noise(self, audio, noise_reduction_level=5):
    try:
        reduced_noise_audio = audio - noise_reduction_level
        logging.info("Noise reduced")
        return reduced_noise_audio
    except Exception as e:
        logging.error(f"Error reducing noise: {e}")
        return None
 
def change_timbre_and_speed(self, audio, pitch_shift=2, speed_change=1.5):
    try:
        new_timbre_audio = audio._spawn(audio.raw_data, overrides={'frame_rate': int(audio.frame_rate * pitch_shift)})
        new_timbre_speed_audio = new_timbre_audio.speedup(playback_speed=speed_change)
        logging.info("Timbre and speed changed")
        return new_timbre_speed_audio
    except Exception as e:
        logging.error(f"Error changing timbre and speed: {e}")
        return None
 
def normalize_volume_level(self, audio):
    try:
        normalized_audio = audio.apply_gain(-audio.dBFS)
        logging.info("Volume level normalized")
        return normalized_audio
    except Exception as e:
        logging.error(f"Error normalizing volume level: {e}")
        return None
 
def analyze_pitch_with_parselmouth(self, file_path):
    try:
        snd = parselmouth.Sound(file_path)
        pitch = snd.to_pitch()
        pitch_values = pitch.selected_array['frequency']
        return pitch_values
    except Exception as e:
        logging.error(f"Error analyzing pitch with Parselmouth: {e}")
        return None
 
def additional_features_essentia(self, y, sr):
    try:
        from essentia.standard import MusicExtractor, MonoLoader
        loader = MonoLoader()
        y = loader(y)
        extractor = MusicExtractor(lowlevelStats=['mean', 'stdev'])
        features, _ = extractor(y)
        return features
    except Exception as e:
        logging.error(f"Error extracting additional features with Essentia: {e}")
        return None
 
def source_separation_openunmix(self, y, sr):
    try:
        separator = openunmix.separate(
            audio_signal=y.tolist(),
            sample_rate=sr,
            targets=["vocals", "drums", "bass", "other"]
        )
        return {
            "vocals": separator["vocals"],
            "drums": separator["drums"],
            "bass": separator["bass"],
            "other": separator["other"],
        }
    except Exception as e:
        logging.error(f"Error in source separation with OpenUnmix: {e}")
        return None
 
def source_separation_demucs_v3(self, y, sr):
    try:
        model = pretrained.get_model('htdemucs')
        model.cpu()
        wav = torch.tensor(y).unsqueeze(0)
        sources = apply_model(model, wav, device='cpu', split=True)
        return sources
    except Exception as e:
        logging.error(f"Error in source separation with Demucs v3: {e}")
        return None

def source_separation_spleeter(self, file_path):
    try:
        separator = Separator('spleeter:2stems')
        separator.separate_to_file(file_path, output_path='output')
        return 'output/vocals.wav', 'output/accompaniment.wav'
    except Exception as e:
        logging.error(f"Error in source separation with Spleeter: {e}")
        return None, None

def recognize_instruments(self, y, sr):
    try:
        import yamnet as yamnet_model
        params = yamnet_model.Params()
        yamnet = yamnet_model.yamnet(params)
        yamnet.load_weights('yamnet.h5')

        waveform = np.reshape(y, [len(y), 1])

        class_scores, embeddings, spectrogram = yamnet(waveform)

        classes = yamnet_model.class_names(params)
        top_classes = np.argsort(class_scores, axis=-1)[0, -5:][::-1]
        recognized_classes = [classes[i] for i in top_classes]

        return recognized_classes
    except Exception as e:
        logging.error(f"Error recognizing instruments: {e}")
        return None

def recognize_genre(self, y, sr):
    try:
        features = self.additional_features_essentia(y, sr)
        genre_description = generate_text_descriptions_with_chatgpt(f"Recognize the genre based on these features: {features}")
        return genre_description
    except Exception as e:
        logging.error(f"Error recognizing genre: {e}")
        return None

# Функция для создания MusicXML
import lxml.etree as ET  # Для ручной обработки XML

def create_musicxml(frequencies, beats, frames, onsets, offsets, text_descriptions, instruments, use_music21=True, use_lxml=True):
    score = None

    if use_music21:
        try:
            score = stream.Score()
            part = stream.Part()

            for freq, beat, frame, onset, offset, description, inst in zip(frequencies, beats, frames, onsets, offsets, text_descriptions, instruments):
                if onset >= 0:
                    n = note.Note()
                    n.pitch.frequency = freq
                    n.quarterLength = beat
                    n.offset = offset
                    n.addLyric(description)

                    # Добавление инструмента к ноте
                    if hasattr(instrument, inst):
                        instrument_class = getattr(instrument, inst)
                        part.append(instrument_class())
                    else:
                        n.addLyric("Unknown Instrument")

                    part.append(n)

            score.append(part)
        except Exception as e:
            logging.error(f"Error creating MusicXML with music21: {e}")

    if use_lxml:
        try:
            # Создание MusicXML с использованием lxml. Это упрощенная версия и требует вашего расширения.
            score = ET.Element("score-partwise", version="3.1")
            part = ET.SubElement(score, "part", id="P1")
            measure = ET.SubElement(part, "measure", number="1")

            for freq, beat, frame, onset, offset, description, inst in zip(frequencies, beats, frames, onsets, offsets, text_descriptions, instruments):
                if onset >= 0:
                    note_element = ET.SubElement(measure, "note")
                    pitch_element = ET.SubElement(note_element, "pitch")
                    ET.SubElement(pitch_element, "step").text = "C"  # Пример, вам нужно будет преобразовать частоту в ноту
                    ET.SubElement(pitch_element, "octave").text = "4"
                    ET.SubElement(note_element, "duration").text = str(int(beat * 4))  # Просто пример
                    ET.SubElement(note_element, "type").text = "quarter"  # Вам нужно правильно определить тип нот
                    lyric_element = ET.SubElement(note_element, "lyric")
                    ET.SubElement(lyric_element, "text").text = description
        except Exception as e:
            logging.error(f"Error creating MusicXML with lxml: {e}")

    return score

# Функция для сохранения MusicXML
def save_musicxml(score, output_path, use_music21=True):
    try:
        output_dir = os.path.dirname(output_path)
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)

        if use_music21:
            score.write('musicxml', fp=output_path)
        else:
            tree = ET.ElementTree(score)
            tree.write(output_path, encoding='UTF-8', xml_declaration=True, pretty_print=True)

        logging.info(f'MusicXML saved to {output_path}')

        # Проверка, что файл был успешно сохранен
        if os.path.exists(output_path):
            logging.info(f"MusicXML file saved successfully: {output_path}")
        else:
            logging.error(f"Failed to save MusicXML file: {output_path}")
    except Exception as e:
        logging.error(f"Error writing MusicXML: {e}")

def optimize_audio_with_openai(self, y, sr):
    """
    Использование OpenAI для оптимизации аудио
    """
    try:
        # Анализ аудио с помощью OpenAI
        audio_analysis = generate_text_descriptions_with_chatgpt(f"Analyze the audio and provide recommendations for optimal preprocessing, such as noise reduction level, pitch shift, and speed change.")
        
        # Извлечение рекомендаций из анализа
        noise_reduction_level = int(audio_analysis.split(",")[0].split(":")[1].strip())
        pitch_shift = float(audio_analysis.split(",")[1].split(":")[1].strip())
        speed_change = float(audio_analysis.split(",")[2].split(":")[1].strip())
        
        # Применение рекомендаций
        reduced_noise_audio = self.reduce_noise(y, noise_reduction_level)
        modified_audio = self.change_timbre_and_speed(reduced_noise_audio, pitch_shift, speed_change)
        normalized_audio = self.normalize_volume_level(modified_audio)
        
        return normalized_audio
    except Exception as e:
        logging.error(f"Error optimizing audio with OpenAI: {e}")
        return None

def visualize_score(score, output_image_path):
    """
    Визуализация партитуры и сохранение в виде изображения
    """
    try:
        s = converter.parse(score)
        s.show('text') # Показать партитуру в текстовом виде (можно удалить, если не нужно)
        
        fp = s.write('musicxml.png', fp=output_image_path)
        logging.info(f'Партитура сохранена в {fp}')
    except Exception as e:
        logging.error(f"Error visualizing score: {e}")

class MusicDataset(Dataset):
    def __init__(self, features, labels):
        self.features = features
        self.labels = labels
    
    def __len__(self):
        return len(self.features)
    
    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]

def train_cnn_model(train_loader, in_channels, out_channels, epochs=10):
    model = AudioCNN(in_channels, out_channels)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    for epoch in range(epochs):
        for inputs, labels in train_loader:
            # Возможно, добавить использование GPU
            if torch.cuda.is_available():
                inputs, labels = inputs.cuda(), labels.cuda()
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            
        logging.info(f"Epoch {epoch+1}/{epochs}, Loss: {loss.item()}")
    
    return model

def generate_text_descriptions(features):
    try:
        response = openai.Completion.create(
            model="gpt-4",
            prompt=f"Generate a coherent description based on the following features: {features}",
            max_tokens=100
        )
        return response.choices[0].text.strip()
    except Exception as e:
        logging.error(f"Error generating text descriptions: {e}")
        return None

def modify_notes_with_gpt4(score, action="add", instrument="piano"):
    notes_data = [(n.pitch.frequency, n.quarterLength, n.offset) for n in score.flat.notes]
    prompt = f"Партитура содержит следующие данные нот:\n{notes_data}\n Пожалуйста, предоставьте необходимые изменения, чтобы {('добавить' if action == 'add' else 'удалить')} ноты для инструмента '{instrument}', и обеспечьте соблюдение правильных музыкальных правил. Также проанализируйте сложность композиции."

    try:
        response = openai.Completion.create(
            model="gpt-4",
            prompt=prompt,
            max_tokens=150  # Увеличиваем количество токенов для получения более детального ответа
        )

        modified_notes = response.choices[0].text.strip().split('\n')

        for note_data in modified_notes:
            try:
                note_info = note_data.split(',')
                if len(note_info) == 3:
                    freq, length, offset = map(float, note_info)
                    if action == "add":
                        new_note = note.Note()
                        new_note.pitch.frequency = freq
                        new_note.quarterLength = length
                        new_note.offset = offset
                        score.append(new_note)
                    elif action == "remove":
                        for n in score.flat.notes:
                            if (n.pitch.frequency == freq and
                                n.quarterLength == length and
                                n.offset == offset):
                                score.remove(n)
                                break
            except ValueError:
                logging.warning(f"Skipping invalid note data: {note_data}")
    except Exception as e:
        logging.error(f"Error generating modifications with GPT-4: {e}")

    return score

def plot_spectrogram(y, sr, output_image_path):
    try:
        import matplotlib.pyplot as plt
        import librosa.display
        
        D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
        plt.figure(figsize=(12, 8))
        librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
        plt.colorbar(format='%+2.0f dB')
        plt.title('Spectrogram')
        plt.savefig(output_image_path)
        logging.info(f'Spectrogram saved to {output_image_path}')
        plt.close()
    except Exception as e:
        logging.error(f"Error plotting spectrogram: {e}")

def plot_chromagram(y, sr, output_image_path):
    try:
        import matplotlib.pyplot as plt
        import librosa.display
        
        chromagram = librosa.feature.chroma_stft(y=y, sr=sr)
        plt.figure(figsize=(12, 8))
        librosa.display.specshow(chromagram, sr=sr, x_axis='time', y_axis='chroma')
        plt.colorbar(format='%+2.0f')
        plt.title('Chromagram')
        plt.savefig(output_image_path)
        logging.info(f'Chromagram saved to {output_image_path}')
        plt.close()
    except Exception as e:
        logging.error(f"Error plotting chromagram: {e}")

def compute_spectral_centroid(y, sr):
    try:
        spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
        return spectral_centroids
    except Exception as e:
        logging.error(f"Error computing spectral centroid: {e}")
        return None

def compute_tonality_and_stability(y, sr):
    try:
        chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
        key = librosa.estimate_tuning(y=y, sr=sr)
        return key, chroma_stft
    except Exception as e:
        logging.error(f"Error computing tonality and stability: {e}")
        return None, None

def harmonic_rhythm_analysis(y, sr):
    try:
        tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
        chords = librosa.effects.harmonic(y)
        
        # Анализ ритма с использованием madmom
        proc = madmom.features.beats.RNNBeatProcessor()(y)
        beat_times = madmom.features.beats.DBNBeatTrackingProcessor(beats_per_bar=[3, 4])(proc)
        
        return tempo, beat_frames, chords, beat_times
    except Exception as e:
        logging.error(f"Error in harmonic rhythm analysis: {e}")
        return None, None, None, None

# Реализация потоковой обработки и воспроизведения аудио в реальном времени
async def process_audio_chunk(chunk, sr):
    try:
        y = np.array(chunk.get_array_of_samples())
        y = librosa.util.normalize(y)
        y = nr.reduce_noise(y=y, sr=sr)
        y = signal.wiener(y)
        return y
    except Exception as e:
        logging.error(f"Error processing audio chunk: {e}")
        return None

async def play_and_process_audio(file_path):
    try:
        audio = AudioSegment.from_file(file_path)
        sr = audio.frame_rate
        chunk_size = 1000  # Размер буфера в миллисекундах

        for i in range(0, len(audio), chunk_size):
            chunk = audio[i:i + chunk_size]
            y = await process_audio_chunk(chunk, sr)
            if y is not None:
                sd.play(y, sr)
                sd.wait()
            else:
                logging.error("Error processing audio chunk, skipping playback")
    except Exception as e:
        logging.error(f"Error in play_and_process_audio: {e}")

def main(file_path, output_path, output_image_path):
    # Конфигурирование кэширования данных
    dataset_config = {
        "features": "path_to_features_dataset",
        "labels": "path_to_labels_dataset"
    }

    # Инициализация конвейера и запуск обработки
    audio_pipeline = AudioPipeline(config)
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(audio_pipeline.run_pipeline(file_path))
    
    if results:
        # Обработка результатов
        (cqt_db, frequency, confidence, transcription_wav2vec2, transcription_hubert, additional_features, 
         sources_openunmix, sources_demucs_v3, vocals_path, accompaniment_path, pitch_analysis, normalized_audio, optimized_audio) = results
        
        logging.info(f"Processing results: {cqt_db.shape}, {len(frequency)}, {transcription_wav2vec2}, {transcription_hubert}")

        # Гармонический анализ и ритм
        y, sr = librosa.load(file_path, sr=None)
        tempo, beat_frames, chords, beat_times = harmonic_rhythm_analysis(y, sr)
        if tempo is None or beat_frames is None or chords is None or beat_times is None:
            logging.error("Error in harmonic rhythm analysis")
            return

        # Генерация текстовых описаний музыкальных фрагментов с использованием ChatGPT (GPT-4)
        text_descriptions = generate_text_descriptions(additional_features)
        if text_descriptions is None:
            logging.error("Error generating text descriptions")
            return

        # Распознавание музыкальных инструментов
        instruments = audio_pipeline.recognize_instruments(y, sr)
        if instruments is None:
            logging.error("Error recognizing instruments")
            return

        # Распознавание жанра музыки
        genre_description = audio_pipeline.recognize_genre(y, sr)
        if genre_description is None:
            logging.error("Error recognizing genre")
            return

        # Спектральный центроид
        spectral_centroid = compute_spectral_centroid(y, sr)
        if spectral_centroid is None:
            logging.error("Error computing spectral centroid")
            return

        # Тональность и тональная стабильность
        key, chroma_stft = compute_tonality_and_stability(y, sr)
        if key is None or chroma_stft is None:
            logging.error("Error computing tonality and stability")
            return

        # Создание MusicXML из результатов
        # Используем обе библиотеки для преобразования
        score_music21 = create_musicxml(frequency, beat_times, range(len(beat_times)), range(len(beat_times)), 
                                        range(len(beat_times)), text_descriptions, instruments, use_music21=True, use_lxml=False)
        score_lxml = create_musicxml(frequency, beat_times, range(len(beat_times)), range(len(beat_times)), 
                                     range(len(beat_times)), text_descriptions, instruments, use_music21=False, use_lxml=True)
        
        if score_music21 is None and score_lxml is None:
            logging.error("Error creating MusicXML")
            return

        # Логика добавления или удаления нот с использованием GPT-4 (ChatGPT)
        score_music21 = modify_notes_with_gpt4(score_music21, action="add", instrument="piano")

        # Сохранение и визуализация партитуры в формате MusicXML и изображения
        output_path_music21 = output_path.replace('.xml', '_music21.xml')
        output_path_lxml = output_path.replace('.xml', '_lxml.xml')

        save_musicxml(score_music21, output_path_music21, use_music21=True)
        save_musicxml(score_lxml, output_path_lxml, use_music21=False)

        visualize_score(score_music21, output_image_path)

        # Визуализация спектрограммы аудиофайла
        spectrogram_image_path = output_image_path.replace(".png", "_spectrogram.png")
        y, sr = preprocess_audio(file_path)
        plot_spectrogram(y, sr, spectrogram_image_path)

        # Визуализация хромограммы аудиофайла
        chromagram_image_path = output_image_path.replace(".png", "_chromagram.png")
        plot_chromagram(y, sr, chromagram_image_path)

        # Демонстрация воспроизведения аудио
        audio_pipeline.play_audio(normalized_audio)
        audio_pipeline.play_audio(optimized_audio)

        # Окончательное описание всех источников
        full_description = {
            "Transcription (Wav2Vec2)": transcription_wav2vec2,
            "Transcription (HuBERT)": transcription_hubert,
            "Genre Description (GPT-4)": genre_description,
            "Instrument Recognition (YAMNet)": instruments,
            "Additional Features (Essentia)": additional_features,
            "Pitch Analysis (Parselmouth)": pitch_analysis,
            "Spectral Centroid": spectral_centroid,
            "Key (Tuning)": key,
            "Chroma (Tonality Stability)": chroma_stft
        }
        

logging.info("Full Description:")
for key, value in full_description.items():
    logging.info(f"{key}: {value}")

# Сохранение оптимизированного аудио
optimized_audio_path = os.path.join(config["output_dir"], "optimized_audio.wav")
sf.write(optimized_audio_path, optimized_audio, sr)
logging.info(f"Optimized audio saved to: {optimized_audio_path}")

# Сохранение разделенных источников
if vocals_path and accompaniment_path:
    logging.info(f"Vocals saved to: {vocals_path}")
    logging.info(f"Accompaniment saved to: {accompaniment_path}")

# Сохранение дополнительных визуализаций
plot_spectral_features(y, sr, output_image_path.replace(".png", "_spectral_features.png"))
plot_harmonic_rhythm(tempo, beat_times, chords, output_image_path.replace(".png", "_harmonic_rhythm.png"))

# Сохранение дополнительных данных
save_additional_data(
    file_path,
    cqt_db, frequency, confidence,
    transcription_wav2vec2, transcription_hubert,
    additional_features, instruments, genre_description,
    pitch_analysis, spectral_centroid, key, chroma_stft,
    output_dir=config["output_dir"]
)

def plot_spectral_features(y, sr, output_image_path):
    try:
        import matplotlib.pyplot as plt
        import librosa.display

        # Спектрограмма
        D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
        plt.figure(figsize=(12, 8))
        librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
        plt.colorbar(format='%+2.0f dB')
        plt.title('Spectrogram')
        plt.savefig(output_image_path)
        logging.info(f'Spectrogram saved to {output_image_path}')
        plt.close()

        # Хромаграмма
        chromagram = librosa.feature.chroma_stft(y=y, sr=sr)
        plt.figure(figsize=(12, 8))
        librosa.display.specshow(chromagram, sr=sr, x_axis='time', y_axis='chroma')
        plt.colorbar(format='%+2.0f')
        plt.title('Chromagram')
        plt.savefig(output_image_path.replace('_spectral_features.png', '_chromagram.png'))
        logging.info(f'Chromagram saved to {output_image_path.replace("_spectral_features.png", "_chromagram.png")}')
        plt.close()

        # Спектральный центроид
        spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
        plt.figure(figsize=(12, 8))
        plt.plot(spectral_centroids)
        plt.xlabel('Time')
        plt.ylabel('Spectral Centroid (Hz)')
        plt.title('Spectral Centroid')
        plt.savefig(output_image_path.replace('_spectral_features.png', '_spectral_centroid.png'))
        logging.info(f'Spectral Centroid saved to {output_image_path.replace("_spectral_features.png", "_spectral_centroid.png")}')
        plt.close()
    except Exception as e:
        logging.error(f"Error plotting spectral features: {e}")

def plot_harmonic_rhythm(tempo, beat_times, chords, output_image_path):
    try:
        import matplotlib.pyplot as plt
        import librosa.display

        # Темп
        plt.figure(figsize=(12, 8))
        plt.plot(beat_times, np.ones_like(beat_times), marker='.', linestyle='None')
        plt.xlabel('Time')
        plt.ylabel('Beat')
        plt.title(f'Tempo: {tempo:.2f} BPM')
        plt.savefig(output_image_path.replace('_harmonic_rhythm.png', '_tempo.png'))
        logging.info(f'Tempo plot saved to {output_image_path.replace("_harmonic_rhythm.png", "_tempo.png")}')
        plt.close()

        # Гармонический анализ
        plt.figure(figsize=(12, 8))
        librosa.display.specshow(chords, sr=1, x_axis='time', y_axis='chroma')
        plt.colorbar()
        plt.title('Harmonic Analysis')
        plt.savefig(output_image_path.replace('_harmonic_rhythm.png', '_harmonic_analysis.png'))
        logging.info(f'Harmonic Analysis saved to {output_image_path.replace("_harmonic_rhythm.png", "_harmonic_analysis.png")}')
        plt.close()
    except Exception as e:
        logging.error(f"Error plotting harmonic rhythm: {e}")

def save_additional_data(file_path, cqt_db, frequency, confidence, transcription_wav2vec2, transcription_hubert, additional_features, instruments, genre_description, pitch_analysis, spectral_centroid, key, chroma_stft, output_dir):
    try:
        # Сохранение данных в файлы
        np.save(os.path.join(output_dir, 'cqt_db.npy'), cqt_db)
        np.save(os.path.join(output_dir, 'frequency.npy'), frequency)
        np.save(os.path.join(output_dir, 'confidence.npy'), confidence)
        with open(os.path.join(output_dir, 'transcription_wav2vec2.txt'), 'w') as f:
            f.write(transcription_wav2vec2)
        with open(os.path.join(output_dir, 'transcription_hubert.txt'), 'w') as f:
            f.write(transcription_hubert)
        np.save(os.path.join(output_dir, 'additional_features.npy'), additional_features)
        with open(os.path.join(output_dir, 'instruments.txt'), 'w') as f:
            f.write(', '.join(instruments))
        with open(os.path.join(output_dir, 'genre_description.txt'), 'w') as f:
            f.write(genre_description)
        np.save(os.path.join(output_dir, 'pitch_analysis.npy'), pitch_analysis)
        np.save(os.path.join(output_dir, 'spectral_centroid.npy'), spectral_centroid)
        with open(os.path.join(output_dir, 'key.txt'), 'w') as f:
            f.write(str(key))
        np.save(os.path.join(output_dir, 'chroma_stft.npy'), chroma_stft)

        # Сохранение исходного аудиофайла
        shutil.copy(file_path, os.path.join(output_dir, os.path.basename(file_path)))

        logging.info("Additional data saved successfully")
    except Exception as e:
        logging.error(f"Error saving additional data: {e}")

# Запуск скрипта
if __name__ == "__main__":
    file_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.mp3'
    output_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.musicxml'
    output_image_path = r'C:\Users\ARTYOM\Documents\Python\Bring_it_up.png'
    start_time = time.time()
    main(file_path, output_path, output_image_path)
    logging.info(f"Execution time: {time.time() - start_time} seconds")