import numpy as np
import soundfile as sf
import librosa
import os
from scipy.signal import butter, filtfilt, stft, istft

# Step 1: Load audio files
def load_audio(audio_path):
    audio, sr = librosa.load(audio_path, sr=48000)
    #audio, fs = sf.read(audio_path)
    return audio, sr

# Step 2: Detect effective signal bandwidth
def detect_bandwidth_org(signal, fs, energy_threshold=0.95):
    f, t, Zxx = stft(signal, fs=fs)
    psd = np.abs(Zxx)**2
    total_energy = np.sum(psd)
    cumulative_energy = np.cumsum(np.sum(psd, axis=1)) / total_energy
    f_low = f[np.argmax(cumulative_energy > (1 - energy_threshold))]
    f_high = f[np.argmax(cumulative_energy >= energy_threshold)]
    return f_low, f_high

def detect_bandwidth(signal, fs, energy_threshold=0.99):
    f, t, Zxx = stft(signal, fs=fs)
    psd = np.abs(Zxx)**2
    total_energy = np.sum(psd)
    cumulative_energy = np.cumsum(np.sum(psd, axis=1)) / total_energy
    
    # Exclude DC component (0 Hz)
    valid_indices = np.where(f > 0)[0]
    f_low = f[valid_indices][np.argmax(cumulative_energy[valid_indices] > (1 - energy_threshold))]
    f_high = f[np.argmax(cumulative_energy >= energy_threshold)]
    return f_low, f_high
    
# Step 3: Apply bandpass and lowpass filters
def bandpass_filter(signal, fs, f_low, f_high):
    nyquist = 0.5 * fs
    low = f_low / nyquist
    high = f_high / nyquist
    b, a = butter(N=4, Wn=[low, high], btype='band')
    return filtfilt(b, a, signal)

def lowpass_filter(signal, fs, cutoff):
    nyquist = 0.5 * fs
    cutoff_normalized = cutoff / nyquist
    b, a = butter(N=4, Wn=cutoff_normalized, btype='low')
    return filtfilt(b, a, signal)

def highpass_filter(signal, fs, cutoff):
    nyquist = 0.5 * fs
    cutoff_normalized = cutoff / nyquist
    b, a = butter(N=4, Wn=cutoff_normalized, btype='high')
    return filtfilt(b, a, signal)

# Step 4: Replace bandwidth
def replace_bandwidth(signal1, signal2, fs, f_low, f_high):
    # Extract effective band from signal1
    #effective_band = bandpass_filter(signal1, fs, f_low, f_high)
    effective_band = lowpass_filter(signal1, fs, f_high)
    # Extract lowpass band from signal2
    #signal2_lowpass = lowpass_filter(signal2, fs, f_high)
    signal2_highpass = highpass_filter(signal2, fs, f_high)
    
    # Match lengths of the two signals
    min_length = min(len(effective_band), len(signal2_highpass))
    effective_band = effective_band[:min_length]
    signal2_highpass = signal2_highpass[:min_length]
    
    # Combine the two signals
    return signal2_highpass + effective_band

# Step 5: Smooth transitions
def smooth_transition(signal1, signal2, fs, transition_band=100):
    fade = np.linspace(0, 1, int(transition_band * fs / 1000))
    crossfade = np.concatenate([fade, np.ones(len(signal1) - len(fade))])
    min_length = min(len(signal1), len(signal2))
    smoothed_signal = (1 - crossfade) * signal2[:min_length] + crossfade * signal1[:min_length]
    return smoothed_signal

# Step 6: Save audio
def save_audio(file_path, audio, fs):
    sf.write(file_path, audio, fs)


def bandwidth_sub(low_bandwidth_audio, high_bandwidth_audio, fs=48000):
    # Detect effective bandwidth of the first signal
    f_low, f_high = detect_bandwidth(low_bandwidth_audio, fs)
        
    # Replace the lower frequency of the second audio
    substituted_audio = replace_bandwidth(low_bandwidth_audio, high_bandwidth_audio, fs, f_low, f_high)

    # Optional: Smooth the transition
    smoothed_audio = smooth_transition(substituted_audio, low_bandwidth_audio, fs)
    return smoothed_audio
        
# Main process
if __name__ == "__main__":
    low_spectra_dir = 'LJSpeech_22k'
    upper_spectra_dir = 'LJSpeech_22k_hifi-sr_speech_g_03925000'
    output_dir = upper_spectra_dir+'_restored'
    if not os.path.exists(output_dir):
        os.mkdir(output_dir)
        
    filelist = [file for file in os.listdir(low_spectra_dir) if file.endswith('.wav')]
    for audio_name in filelist:
        audio1, fs1 = load_audio(low_spectra_dir + "/" + audio_name)  # Source for effective bandwidth
        audio2, fs2 = load_audio(upper_spectra_dir + "/" + audio_name.replace('.wav', '_generated.wav'))  # Target audio to replace lower frequencies

        if fs1 != 48000 or fs2 != 48000:
            raise ValueError("Both audio files must have a sampling rate of 48 kHz.")

        # Detect effective bandwidth of the first signal
        f_low, f_high = detect_bandwidth(audio1, fs1)
        print(f"Effective bandwidth: {f_low} Hz to {f_high} Hz")

        # Replace the lower frequency of the second audio
        replaced_audio = replace_bandwidth(audio1, audio2, fs2, f_low, f_high)

        # Optional: Smooth the transition
        smoothed_audio = smooth_transition(replaced_audio, audio1, fs1)

        # Save the result
        save_audio(output_dir+"/"+audio_name, smoothed_audio, fs2)
