import numpy as np
import pyaudio
import soundfile as sf
from scipy.fft import fft
from scipy.signal import windows

# Constants
RATE = 44100  # Sample rate
DURATION = 60  # Duration in seconds
CHANNELS = 1  # Mono audio
FORMAT = pyaudio.paInt16  # Audio format
CHUNK = 1024  # Frames per buffer

# Function to capture audio from the microphone
def record_audio(duration):
    p = pyaudio.PyAudio()
    try:
        stream = p.open(format=FORMAT,
                        channels=CHANNELS,
                        rate=RATE,
                        input=True,
                        frames_per_buffer=CHUNK)

        print("Recording...")
        frames = []
        for _ in range(0, int(RATE / CHUNK * duration)):
            data = stream.read(CHUNK, exception_on_overflow=False)
            frames.append(data)

        print("Recording finished.")
    except Exception as e:
        print(f"An error occurred during recording: {e}")
        return None
    finally:
        stream.stop_stream()
        stream.close()
        p.terminate()

    return b''.join(frames)

# Function to perform frequency analysis
def analyze_frequency(audio_data):
    if audio_data is None:
        return None, None

    # Convert byte data to numpy array
    audio_array = np.frombuffer(audio_data, dtype=np.int16)

    # Apply Hamming window
    window = windows.hamming(len(audio_array))
    windowed_signal = audio_array * window

    # Perform FFT
    N = len(windowed_signal)
    yf = fft(windowed_signal)
    xf = np.fft.fftfreq(N, 1 / RATE)

    # Get the magnitude spectrum
    magnitude_spectrum = np.abs(yf[:N // 2])
    frequencies = xf[:N // 2]

    return frequencies, magnitude_spectrum

# Function to identify missing frequencies
def identify_missing_frequencies(frequencies, magnitudes):
    if frequencies is None or magnitudes is None:
        return None

    # Focus on human audible range
    idx = np.where((frequencies >= 20) & (frequencies <= 20000))
    frequencies = frequencies[idx]
    magnitudes = magnitudes[idx]

    # Calculate dynamic threshold based on mean magnitude
    threshold = np.mean(magnitudes) * 0.5
    missing_freqs = frequencies[magnitudes < threshold]

    # Limit the number of frequencies to prevent overloading
    max_freqs = 50  # Maximum number of frequencies to generate
    if len(missing_freqs) > max_freqs:
        missing_freqs = missing_freqs[:max_freqs]

    return missing_freqs

# Function to generate a sound wave with missing frequencies
def generate_missing_frequencies(missing_freqs):
    if missing_freqs is None or len(missing_freqs) == 0:
        print("No missing frequencies identified.")
        return None

    t = np.linspace(0, DURATION, int(RATE * DURATION), endpoint=False)

    # Create an empty signal
    signal = np.zeros_like(t)

    # Adjust amplitude to prevent clipping
    amplitude = 1.0 / len(missing_freqs)

    for freq in missing_freqs:
        signal += amplitude * np.sin(2 * np.pi * freq * t)

    # Normalize the signal
    signal /= np.max(np.abs(signal))

    return signal

# Main execution flow
if __name__ == "__main__":
    # Step 1: Record audio
    audio_data = record_audio(DURATION)

    # Step 2: Analyze frequency content
    frequencies, magnitudes = analyze_frequency(audio_data)

    # Step 3: Identify missing frequencies
    missing_freqs = identify_missing_frequencies(frequencies, magnitudes)

    # Step 4: Generate sound with missing frequencies
    generated_signal = generate_missing_frequencies(missing_freqs)

    # Step 5: Save the generated signal as FLAC file
    if generated_signal is not None:
        sf.write('missing_frequencies.flac', generated_signal, RATE)
        print("Generated audio saved as 'missing_frequencies.flac'.")
    else:
        print("No audio generated due to lack of missing frequencies.")
создай удобный графический интерфейс  добавь индексацию записи  с микрофона и возможность прослушать её перед конвертацией