While the audio(tts) is playing, do not hear the voice of user
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

import numpy as np
import threading
import queue
import time
import sounddevice as sd
from faster_whisper import WhisperModel
from gen_response import generate
from voice import text_to_speech, AudioPlayer
import ast
from datetime import datetime

def main():
    # Initialize Whisper model
    model_size = "base.en"  # Use the smaller model for faster performance
    compute_type = "float16"
    model = WhisperModel(model_size, device="cuda", compute_type=compute_type)

    # Audio parameters
    sample_rate = 16000  # Whisper expects 16kHz audio
    block_size = 4000    # Approximately 0.25 seconds of audio
    channels = 1         # Mono audio

    # Silence detection parameters
    silence_threshold = 0.01  # Adjust based on ambient noise
    silence_duration = 1    # Duration of silence to consider as a pause

    # Queue to communicate between the audio thread and the transcription thread
    audio_queue = queue.Queue()

    # Buffer to hold audio data
    audio_buffer = np.empty((0,), dtype=np.float32)

    # Variables to track silence
    last_speech_time = time.time()
    is_speaking = False

    # Initialize conversation
    now = datetime.now()
    name = 'Justin'
    system_prompt = {
        'role': 'system',
        'content': (
            f'You are Julia, a cute and humorous girl. You are a girlfriend of the user and '
            f'flirt with the user to make him feel better. User\'s name is {name}. Current time is '
            f'{now.year}-{now.month}-{now.day} {now.hour}:{now.minute}. But remember to answer '
            f'shortly in a sentence.'
        )
    }
    with open('conversations.txt', 'a+') as f:
        f.write(str(system_prompt) + '\n')

    messages = [system_prompt]
    with open('conversations.txt', 'r') as f:
        for x in f.readlines():
            print(x)
            messages.append(ast.literal_eval(x))

    # Instantiate the audio player
    audio_player = AudioPlayer()

    def audio_callback(indata, frames, time_info, status):
        nonlocal audio_buffer, last_speech_time, is_speaking

        if status:
            print(f"Audio callback status: {status}", flush=True)

        # Flatten the array and append to buffer
        audio_data = indata[:, 0]
        audio_buffer = np.concatenate((audio_buffer, audio_data))

        # Calculate the root mean square (RMS) energy to detect speech presence
        rms = np.sqrt(np.mean(audio_data ** 2))

        current_time = time.time()

        # Check if the audio block contains speech
        if rms > silence_threshold:
            last_speech_time = current_time
            if not is_speaking:
                is_speaking = True
                print("Speech detected.", flush=True)
        else:
            if is_speaking and (current_time - last_speech_time) >= silence_duration:
                is_speaking = False
                print("Silence detected.", flush=True)
                # Send the current audio buffer for transcription
                if len(audio_buffer) > 0:
                    # Send the buffered audio to the transcription thread
                    audio_queue.put(audio_buffer.copy())
                    # Reset the buffer
                    audio_buffer = np.empty((0,), dtype=np.float32)

    def transcription_worker():
        while True:
            audio_data = audio_queue.get()
            if audio_data is None:
                print("Transcription worker received stop signal.", flush=True)
                break

            # Skip empty audio data
            if len(audio_data) == 0:
                continue

            # Run the transcription with optimized parameters
            segments, _ = model.transcribe(
                audio_data,
                language='en',
                beam_size=1  # Use beam_size=1 for faster transcription (greedy decoding)
            )

            # Get the full transcription text
            full_text = ''.join([segment.text for segment in segments]).strip()
            if not full_text:
                continue  # Skip if the transcription is empty

            print(f"User: {full_text}", flush=True)

            # Add to messages and write to file
            messages.append({'role': 'user', 'content': full_text})
            with open('conversations.txt', 'a+') as f:
                f.write(str({'role': 'user', 'content': full_text}) + '\n')

            # Generate response
            response = generate(messages, model='llama3.2')
            print(f"Assistant: {response}", flush=True)

            messages.append({'role': 'assistant', 'content': response})
            with open('conversations.txt', 'a+') as f:
                f.write(str({'role': 'assistant', 'content': response}) + '\n')

            # Convert response to speech and play
            try:
                audio_file = text_to_speech(response, 'en-US-AvaNeural', 0, 0)
                audio_player.play_audio(audio_file)
            except Exception as e:
                print(f"Error in text to speech: {e}")

        print("Transcription worker stopped.", flush=True)

    # Start the transcription thread
    transcription_thread = threading.Thread(target=transcription_worker)
    transcription_thread.start()

    try:
        # Open the audio stream
        with sd.InputStream(callback=audio_callback,
                            channels=channels,
                            samplerate=sample_rate,
                            blocksize=block_size,
                            dtype='float32'):
            print("Recording audio. Press Ctrl+C to stop.", flush=True)
            while True:
                time.sleep(0.1)
    except KeyboardInterrupt:
        print("Stopping audio recording.", flush=True)
    except Exception as e:
        print(f"An error occurred: {e}", flush=True)
    finally:
        # Send any remaining audio buffer for transcription
        if len(audio_buffer) > 0:
            audio_queue.put(audio_buffer.copy())
            audio_buffer[:] = []

        # Signal the transcription thread to stop
        audio_queue.put(None)
        transcription_thread.join()

if __name__ == "__main__":
    main()