music.py

import librosa
import soundfile as sf
import numpy as np
import pyloudnorm as pyln
from scipy import signal
import matplotlib.pyplot as plt
import pyrubberband as pyrb
from pydub import AudioSegment
from pydub.effects import compress_dynamic_range, normalize
import noisereduce as nr
from sklearn.cluster import KMeans
import warnings
warnings.filterwarnings("ignore")

class UltraProMixMaster:
   def __init__(self):
       self.sr = 44100
       self.n_fft = 2048
       self.hop_length = 512

   def load_and_analyze(self, file_path):
       print(f"Yükleniyor ve detaylı analiz ediliyor: {file_path}")
       y, sr = librosa.load(file_path, sr=self.sr)
       
       # Temel özellikler
       duration = librosa.get_duration(y=y, sr=sr)
       tempo, beats = librosa.beat.beat_track(y=y, sr=sr)
       
       # Spektral özellikler
       spectral_centroid = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
       spectral_bandwidth = librosa.feature.spectral_bandwidth(y=y, sr=sr)[0]
       spectral_contrast = librosa.feature.spectral_contrast(y=y, sr=sr)
       
       # Harmonik özellikler
       harmonic, percussive = librosa.effects.hpss(y)
       pitches, magnitudes = librosa.piptrack(y=y, sr=sr)
       
       # MFCC
       mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
       
       # Chroma özelliği
       chroma = librosa.feature.chroma_stft(y=y, sr=sr)
       
       # Flow analizi
       onset_env = librosa.onset.onset_strength(y=y, sr=sr)
       pulse = librosa.beat.plp(onset_envelope=onset_env, sr=sr)
       
       # Ses patlamaları tespiti
       rms = librosa.feature.rms(y=y)[0]
       peaks = librosa.util.peak_pick(rms, pre_max=3, post_max=3, pre_avg=3, post_avg=5, delta=0.5, wait=10)
       
       print(f"Süre: {duration:.2f} saniye, Tempo: {tempo:.2f} BPM")
       print(f"Ortalama spektral merkez: {np.mean(spectral_centroid):.2f} Hz")
       
       return y, {
           'duration': duration,
           'tempo': tempo,
           'beats': beats,
           'spectral_centroid': spectral_centroid,
           'spectral_bandwidth': spectral_bandwidth,
           'spectral_contrast': spectral_contrast,
           'harmonic': harmonic,
           'percussive': percussive,
           'pitches': pitches,
           'magnitudes': magnitudes,
           'mfcc': mfcc,
           'chroma': chroma,
           'pulse': pulse,
           'peaks': peaks
       }

   def adaptive_eq(self, y, analysis):
       print("Gelişmiş adaptif EQ uygulanıyor...")
       
       avg_centroid = np.mean(analysis['spectral_centroid'])
       avg_bandwidth = np.mean(analysis['spectral_bandwidth'])
       
       eq_bands = [
           (20, 60), (60, 200), (200, 600), (600, 2000),
           (2000, 6000), (6000, 20000)
       ]
       
       for low, high in eq_bands:
           band_energy = np.mean(analysis['spectral_contrast'][(low < analysis['spectral_centroid']) & (analysis['spectral_centroid'] < high)])
           if band_energy < -10:
               y = self.boost_frequencies(y, low, high, 2)
           elif band_energy > 10:
               y = self.cut_frequencies(y, low, high, -1)
       
       return y

   def boost_frequencies(self, y, low_freq, high_freq, gain):
       sos = signal.butter(10, [low_freq, high_freq], btype='bandpass', fs=self.sr, output='sos')
       filtered = signal.sosfilt(sos, y)
       return y + gain * filtered

   def cut_frequencies(self, y, low_freq, high_freq, gain):
       sos = signal.butter(10, [low_freq, high_freq], btype='bandstop', fs=self.sr, output='sos')
       filtered = signal.sosfilt(sos, y)
       return y + gain * filtered

   def advanced_compression(self, y, analysis):
       print("Gelişmiş çok bantlı kompresyon uygulanıyor...")
       
       rms = librosa.feature.rms(y=y)[0]
       dynamic_range = np.max(rms) / np.mean(rms)
       
       bands = [
           (20, 150), (150, 600), (600, 2000), (2000, 6000), (6000, 20000)
       ]
       
       compressed_bands = []
       for i, (low, high) in enumerate(bands):
           band = self.filter_band(y, low, high)
           
           band_rms = librosa.feature.rms(y=band)[0]
           band_dynamic_range = np.max(band_rms) / np.mean(band_rms)
           
           threshold = -30 + i * 3  # Lower threshold for lower frequencies
           ratio = 4 - i * 0.5  # Higher ratio for lower frequencies
           
           if band_dynamic_range > dynamic_range:
               ratio += 1  # Increase ratio for bands with high dynamic range
           
           compressed = self.compress(band, threshold, ratio)
           compressed_bands.append(compressed)
       
       return sum(compressed_bands)

   def filter_band(self, y, low_freq, high_freq):
       sos = signal.butter(10, [low_freq, high_freq], btype='bandpass', fs=self.sr, output='sos')
       return signal.sosfilt(sos, y)

   def compress(self, y, threshold, ratio):
       compressed = np.copy(y)
       mask = y > threshold
       compressed[mask] = threshold + (y[mask] - threshold) / ratio
       return compressed

   def apply_reverb(self, y, analysis, mix=0.3):
       print("Akıllı reverb uygulanıyor...")
       tempo = analysis['tempo']
       room_size = 1.0 - (tempo / 180)  # Tempo arttıkça room size azalır
       room_size = max(0.4, min(room_size, 0.9))  # 0.4 ile 0.9 arasında sınırla
       
       impulse_response = np.exp(-np.linspace(0, 5, int(self.sr * room_size)))
       reverb = signal.convolve(y, impulse_response, mode='full')[:len(y)]
       return (1 - mix) * y + mix * reverb

   def apply_delay(self, y, analysis, feedback=0.4):
       print("Akıllı delay uygulanıyor...")
       tempo = analysis['tempo']
       delay_time = 60 / tempo / 4  # Temponun çeyreği kadar delay
       
       delay_samples = int(delay_time * self.sr)
       delayed = np.zeros_like(y)
       delayed[delay_samples:] = y[:-delay_samples]
       output = y + feedback * delayed
       return output / np.max(np.abs(output))

   def pitch_correction(self, y, analysis):
       print("Gelişmiş pitch düzeltme uygulanıyor...")
       pitches = analysis['pitches']
       magnitudes = analysis['magnitudes']
       
       # En belirgin pitch'leri bul
       pitch_means = []
       for i in range(0, len(pitches), self.hop_length):
           segment = pitches[i:i+self.hop_length]
           segment_magnitudes = magnitudes[i:i+self.hop_length]
           if len(segment) > 0:
               pitch_mean = np.sum(segment * segment_magnitudes) / np.sum(segment_magnitudes)
               pitch_means.append(pitch_mean)
       
       # K-means ile pitch gruplarını bul
       kmeans = KMeans(n_clusters=3, random_state=0).fit(np.array(pitch_means).reshape(-1, 1))
       dominant_pitches = kmeans.cluster_centers_.flatten()
       
       # Her grup için en yakın nota frekansını bul ve düzelt
       corrected_y = np.zeros_like(y)
       for i, pitch in enumerate(dominant_pitches):
           note_freqs = librosa.midi_to_hz(np.arange(128))
           target_freq = note_freqs[np.argmin(np.abs(note_freqs - pitch))]
           
           # Pitch shift uygula
           n_steps = 12 * np.log2(target_freq / pitch)
           corrected_segment = librosa.effects.pitch_shift(y, sr=self.sr, n_steps=n_steps)
           
           # Düzeltilmiş segmenti ana sinyale ekle
           mask = (kmeans.labels_ == i)
           corrected_y[mask] += corrected_segment[mask]
       
       return corrected_y

   def stereo_enhancement(self, y):
       print("Gelişmiş stereo genişletme uygulanıyor...")
       if y.ndim == 1:
           y = np.column_stack((y, y))  # Mono to stereo
       
       mid = (y[:, 0] + y[:, 1]) / 2
       side = (y[:, 0] - y[:, 1]) / 2
       
       # Side sinyali frekans bantlarına ayır ve genişlet
       side_bands = [
           (0, 200), (200, 800), (800, 3000), (3000, 10000), (10000, self.sr//2)
       ]
       enhanced_side = np.zeros_like(side)
       for low, high in side_bands:
           band = self.filter_band(side, low, high)
           enhanced_side += band * (1 + (high - low) / 10000)  # Yüksek frekansları daha çok genişlet
       
       left = mid + enhanced_side
       right = mid - enhanced_side
       
       return np.column_stack((left, right))

   def dynamic_eq(self, y, analysis):
       print("Gelişmiş dinamik EQ uygulanıyor...")
       spectral_contrast = analysis['spectral_contrast']
       
       for i, band in enumerate(spectral_contrast):
           center_freq = librosa.band_to_hz(i, sr=self.sr)
           if np.mean(band) < -20:
               y = self.boost_frequencies(y, center_freq * 0.8, center_freq * 1.2, 2)
           elif np.mean(band) > 10:
               y = self.cut_frequencies(y, center_freq * 0.8, center_freq * 1.2, -1)
       
       return y

   def match_loudness(self, y, target_loudness=-14.0):
       print("Hassas ses yüksekliği eşitleme uygulanıyor...")
       meter = pyln.Meter(self.sr)
       loudness = meter.integrated_loudness(y)
       return pyln.normalize.loudness(y, loudness, target_loudness)

   def mix_tracks(self, vocal, beat, vocal_analysis, beat_analysis):
       print("Gelişmiş akıllı mix uygulanıyor...")
       
       # Tempo eşleştirme
       if abs(vocal_analysis['tempo'] - beat_analysis['tempo']) > 5:
           beat = pyrb.time_stretch(beat, self.sr, vocal_analysis['tempo'] / beat_analysis['tempo'])
       
       # Uzunlukları eşitle
       max_len = max(len(vocal), len(beat))
       vocal = librosa.util.fix_length(vocal, max_len)
       beat = librosa.util.fix_length(beat, max_len)
       
       # Dinamik mix oranları
       vocal_rms = librosa.feature.rms(y=vocal)[0]
       beat_rms = librosa.feature.rms(y=beat)[0]
       
       # Flow'a göre mix oranlarını ayarla
       vocal_pulse = vocal_analysis['pulse']
       beat_pulse = beat_analysis['pulse']
       
       mix_ratio = np.zeros(len(vocal))
       for i in range(len(mix_ratio)):
           if vocal_pulse[i] > beat_pulse[i]:
               mix_ratio[i] = 0.7  # Vokal öne çıksın
           else:
               mix_ratio[i] = 0.5  # Beat öne çıksın
       
       mixed = mix_ratio * vocal + (1 - mix_ratio) * beat
       return mixed

   def de_ess(self, y, threshold=-20, reduction_factor=0.5):
       print("De-essing uygulanıyor...")
       # 5000-8000 Hz bandını izole et
       sos = signal.butter(10, [5000, 8000], btype='bandpass', fs=self.sr, output='sos')
       high_freq = signal.sosfilt(sos, y)
       
       # Yüksek frekanslardaki aşırı sibilansı tespit et ve azalt
       envelope = np.abs(hilbert(high_freq))
       mask = envelope > 10**(threshold/20)
       high_freq[mask] *= reduction_factor
       
       # Düzeltilmiş yüksek frekansları orijinal sinyale ekle
       return y - signal.sosfilt(sos, y) + high_freq

   def remove_plosives(self, y, threshold=0.95):
       print("Plosive sesleri kaldırılıyor...")
       # Düşük frekans bandını izole et
       sos = signal.butter(10, 180, btype='lowpass', fs=self.sr, output='sos')
       low_freq = signal.sosfilt(sos, y)
       
       # Ani yükselişleri tespit et ve yumuşat
       envelope = np.abs(hilbert(low_freq))
       smooth_env = signal.savgol_filter(envelope, 101, 2)
       mask = envelope > threshold * smooth_env
       y[mask] = smooth_env[mask]
       
       return y
       def final_processing(self, y):
       print("Son işlemler uygulanıyor...")
       
       # Multiband limiter
       bands = [
           (0, 150), (150, 600), (600, 2000), (2000, 6000), (6000, self.sr//2)
       ]
       limited_bands = []
       for low, high in bands:
           band = self.filter_band(y, low, high)
           limited_band = np.clip(band, -0.98, 0.98)
           limited_bands.append(limited_band)
       
       y = sum(limited_bands)
       
       # Soft clipper
       y = np.tanh(y)
       
       # Dither uygula
       y += np.random.normal(0, 1/32768, y.shape)
       
       # Final normalization
       y = librosa.util.normalize(y, norm=np.inf, threshold=0.99)
       
       return y

   def visualize_spectrogram(self, y, title):
       plt.figure(figsize=(12, 8))
       D = librosa.stft(y)
       S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
       librosa.display.specshow(S_db, sr=self.sr, x_axis='time', y_axis='hz')
       plt.colorbar(format='%+2.0f dB')
       plt.title(title)
       plt.tight_layout()
       plt.show()

   def process(self, vocal_path, beat_path, output_path):
       vocal, vocal_analysis = self.load_and_analyze(vocal_path)
       beat, beat_analysis = self.load_and_analyze(beat_path)

       # Vokal işleme
       vocal = self.remove_plosives(vocal)
       vocal = self.de_ess(vocal)
       vocal = self.adaptive_eq(vocal, vocal_analysis)
       vocal = self.advanced_compression(vocal, vocal_analysis)
       vocal = self.pitch_correction(vocal, vocal_analysis)
       vocal = self.apply_reverb(vocal, vocal_analysis, mix=0.2)
       vocal = self.apply_delay(vocal, vocal_analysis, feedback=0.2)
       
       # Beat işleme
       beat = self.adaptive_eq(beat, beat_analysis)
       beat = self.advanced_compression(beat, beat_analysis)
       beat = self.stereo_enhancement(beat)
       
       # Mixleme
       mixed = self.mix_tracks(vocal, beat, vocal_analysis, beat_analysis)
       
       # Son işlemler
       mixed = self.dynamic_eq(mixed, vocal_analysis)  # Vokal analizini kullanıyoruz
       mixed = self.stereo_enhancement(mixed)
       mixed = self.final_processing(mixed)
       mixed = self.match_loudness(mixed)
       
       # Sonucu kaydet
       sf.write(output_path, mixed, self.sr)
       print(f"Mix ve mastering tamamlandı. Sonuç {output_path} konumuna kaydedildi.")
       
       # Spektrogramları görselleştir
       self.visualize_spectrogram(vocal, "Vokal Spektrogramı")
       self.visualize_spectrogram(beat, "Beat Spektrogramı")
       self.visualize_spectrogram(mixed, "Final Mix Spektrogramı")

   def analyze_flow(self, y, sr):
       print("Flow analizi yapılıyor...")
       onset_env = librosa.onset.onset_strength(y=y, sr=sr)
       tempo, beats = librosa.beat.beat_track(onset_envelope=onset_env, sr=sr)
       
       # Flow değişimlerini tespit et
       beat_strengths = onset_env[beats]
       flow_changes = np.diff(beat_strengths)
       
       significant_changes = np.where(np.abs(flow_changes) > np.std(flow_changes))[0]
       
       flow_segments = []
       last_change = 0
       for change in significant_changes:
           flow_segments.append((last_change, change))
           last_change = change
       flow_segments.append((last_change, len(beats)-1))
       
       return flow_segments, beats

   def flow_based_processing(self, y, sr):
       print("Flow tabanlı işleme uygulanıyor...")
       flow_segments, beats = self.analyze_flow(y, sr)
       
       processed_y = np.zeros_like(y)
       for start, end in flow_segments:
           segment = y[beats[start]:beats[end]]
           
           # Her flow segmenti için özel işlemler
           segment = self.adaptive_eq(segment, {'spectral_centroid': librosa.feature.spectral_centroid(y=segment, sr=sr)[0]})
           segment = self.advanced_compression(segment, {'rms': librosa.feature.rms(y=segment)[0]})
           
           # Flow değişimine göre efekt miktarını ayarla
           flow_intensity = np.mean(librosa.feature.rms(y=segment)[0])
           reverb_mix = 0.1 + 0.2 * flow_intensity
           segment = self.apply_reverb(segment, {'tempo': librosa.beat.tempo(y=segment, sr=sr)[0]}, mix=reverb_mix)
           
           processed_y[beats[start]:beats[end]] = segment
       
       return processed_y

# Kullanım örneği
mixer = UltraProMixMaster()
vocal_file = input("Vokal dosyasının yolunu girin: ")
beat_file = input("Beat dosyasının yolunu girin: ")
output_file = input("Çıktı dosyasının yolunu ve adını girin: ")

mixer.process(vocal_file, beat_file, output_file)