# -*- coding: utf-8 -*-
"""
Created on Tue Apr  8 21:22:12 2025

@author: AKourgli
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import lfilter

# Paramètres de simulation
Nb_symb = 10000               # nombre de symboles simulés
bits_par_symb = 2             # 4-ASK: 2 bits par symbole
M = 2**bits_par_symb          # nombre de symboles (4)
# Choix des amplitudes (pour un signal positif, facilitant la détection d'enveloppe)
A_levels = np.array([1, 3, 5, 7])  

# Paramètres temporels et porteuse
T_symb = 1e-3                 # durée d'un symbole (1 ms)
fs = 100e3                    # fréquence d'échantillonnage (100 kHz)
Ns = int(T_symb * fs)         # nombre d'échantillons par symbole
t_symb = np.arange(Ns)/fs     # vecteur temps pour un symbole
fc = 20e3                     # fréquence porteuse (20 kHz)

# Génération aléatoire des symboles (indices)
symb_idx = np.random.randint(0, M, Nb_symb)
A_tx = A_levels[symb_idx]     # amplitudes associées aux symboles

# Génération du signal modulé
# Chaque symbole est modulé en s(t)=A*cos(2*pi*fc*t) sur [0, T_symb]
signal = np.zeros(Nb_symb * Ns)
for i in range(Nb_symb):
    t0 = i * Ns
    signal[t0:t0+Ns] = A_tx[i] * np.cos(2*np.pi*fc*t_symb)

# Fonction de détection par filtrage (filtre moyenneur sur un symbole)
def symbol_integrator(x, Ns):
    # Intégration sur la durée du symbole (équivalent à une moyenne multipliée par Ns)
    return np.array([np.sum(x[i*Ns:(i+1)*Ns]) for i in range(len(x)//Ns)])

# Démodulation cohérente (synchrone)
def demod_coherent(r, fc, fs, Ns):
    t = np.arange(Ns)/fs
    # Générer la porteuse locale parfaitement synchronisée
    local_carrier = np.cos(2*np.pi*fc*t)
    # Initialiser le vecteur de démodulation
    r_demod = np.zeros(len(r)//Ns)
    for i in range(len(r_demod)):
        r_seg = r[i*Ns:(i+1)*Ns]
        # Multiplication par la porteuse locale puis intégration
        r_demod[i] = (2/Ns) * np.sum(r_seg * local_carrier)
    return r_demod

# Démodulation par détection d'enveloppe
def demod_enveloppe(r, Ns):
    r_rect = np.abs(r)
    r_demod = np.array([np.mean(r_rect[i*Ns:(i+1)*Ns]) for i in range(len(r)//Ns)])
    correction_factor = np.pi / 2  # pour compenser mean(|cos|) = 2/pi
    return r_demod * correction_factor



# Décision sur la valeur du symbole (comparaison avec des seuils optimaux)
def decision_demod(x, A_levels):
    # Pour 4-ASK à amplitudes croissantes, les seuils optimaux sont aux milieux
    thresholds = (A_levels[:-1] + A_levels[1:]) / 2
    decisions = np.zeros(len(x), dtype=int)
    for i, val in enumerate(x):
        if val < thresholds[0]:
            decisions[i] = 0
        elif val < thresholds[1]:
            decisions[i] = 1
        elif val < thresholds[2]:
            decisions[i] = 2
        else:
            decisions[i] = 3
    return decisions

# Simulation sur une gamme de SNR (en dB)
SNRdB_range = np.arange(1, 30, 2)
SER_coherent = []
SER_enveloppe = []

# Puissance du signal de base (sur la porteuse modulée)
P_signal = np.mean(signal**2)

for SNRdB in SNRdB_range:
    # Calcul de la puissance du bruit à ajouter
    SNR_linear = 10**(SNRdB/10)
    P_noise = P_signal / SNR_linear
    noise_std = np.sqrt(P_noise)
    
    # Ajout du bruit AWGN
    bruit = noise_std * np.random.randn(len(signal))
    r = signal + bruit
    
    # Démodulation cohérente
    r_coherent = demod_coherent(r, fc, fs, Ns)
    decisions_coherent = decision_demod(r_coherent, A_levels)
    erreurs_coherent = np.sum(decisions_coherent != symb_idx)
    SER_coherent.append(erreurs_coherent / Nb_symb)
    
    # Démodulation par enveloppe
    r_enveloppe = demod_enveloppe(r, Ns)
    decisions_enveloppe = decision_demod(r_enveloppe, A_levels)
    erreurs_enveloppe = np.sum(decisions_enveloppe != symb_idx)
    SER_enveloppe.append(erreurs_enveloppe / Nb_symb)
    
    print(f"SNR = {SNRdB} dB: SER cohérente = {SER_coherent[-1]:.4f}, SER enveloppe = {SER_enveloppe[-1]:.4f}")

# Tracé des résultats
plt.figure(figsize=(12,8))
plt.semilogy(SNRdB_range, SER_coherent, 'o-', label="Démodulation cohérente")
plt.semilogy(SNRdB_range, SER_enveloppe, 's-', label="Démodulation par enveloppe")
plt.xlabel("SNR (dB)")
plt.ylabel("Taux d'erreur symbole (SER)")
plt.title("Comparaison des performances en 4-ASK\n(rupture en présence de bruit AWGN)")
plt.grid(True, which='both')
plt.legend()

# ========== Affichage des signaux pour visualisation ==========

# Choix d’un SNR fixe pour visualisation (par ex. 10 dB)
SNRdB_visu = 10
SNR_linear = 10**(SNRdB_visu/10)
P_noise = P_signal / SNR_linear
noise_std = np.sqrt(P_noise)
bruit = noise_std * np.random.randn(len(signal))
r = signal + bruit

# Redémodulation pour affichage
r_coherent = demod_coherent(r, fc, fs, Ns)
r_enveloppe = demod_enveloppe(r, Ns)

decisions_coherent = decision_demod(r_coherent, A_levels)
decisions_enveloppe = decision_demod(r_enveloppe, A_levels)

# Tracer sur les 10 premiers symboles
N_visu = 40
t_visu = np.arange(N_visu * Ns) / fs

plt.figure(figsize=(14, 8))

# Signal modulé (avec bruit)
plt.subplot(3,1,1)
plt.plot(t_visu*1e3, signal[:N_visu*Ns], label='Signal modulé (sans bruit)')
plt.plot(t_visu*1e3, r[:N_visu*Ns], label='Signal reçu (bruité)', alpha=0.6)
plt.title("Signal 4-ASK modulé sur les 10 premiers symboles")
plt.ylabel("Amplitude")
plt.xlabel("Temps (ms)")
plt.grid(True)
plt.legend()

# Symboles émis et détectés (cohérente)
plt.subplot(3,1,2)
plt.stem(np.arange(N_visu), A_tx[:N_visu], linefmt='k-', markerfmt='ro', basefmt='k-', label='Symboles émis')
plt.stem(np.arange(N_visu), A_levels[decisions_coherent[:N_visu]], linefmt='g-', markerfmt='go', basefmt='g-', label='Décodé cohérent', use_line_collection=True)
plt.title("Symboles émis vs détectés (démodulation cohérente)")
plt.xlabel("Indice symbole")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()

# Symboles détectés (enveloppe)
plt.subplot(3,1,3)
plt.stem(np.arange(N_visu), A_tx[:N_visu], linefmt='k-', markerfmt='ro', basefmt='k-', label='Symboles émis')
plt.stem(np.arange(N_visu), A_levels[decisions_enveloppe[:N_visu]], linefmt='b-', markerfmt='bo', basefmt='b-', label='Décodé enveloppe', use_line_collection=True)
plt.title("Symboles émis vs détectés (détection enveloppe)")
plt.xlabel("Indice symbole")
plt.ylabel("Amplitude")
plt.grid(True)
plt.legend()

plt.figure(figsize=(12, 5))

# --- Constellation cohérente ---
plt.subplot(1, 2, 1)
plt.plot(r_coherent, np.zeros_like(r_coherent), 'go', alpha=0.5, label='Symb. reçus')
plt.plot(A_levels, np.zeros_like(A_levels), 'rx', markersize=10, label='Niveaux idéaux')
plt.title("Constellation - Démodulation cohérente")
plt.xlabel("Amplitude (I)")
plt.ylabel("Q (ici nul)")
plt.grid(True)
plt.legend()
plt.axis('equal')

# --- Constellation détection d'enveloppe ---
plt.subplot(1, 2, 2)
plt.plot(r_enveloppe, np.zeros_like(r_enveloppe), 'bo', alpha=0.5, label='Symb. détectés (enveloppe)')
plt.plot(A_levels, np.zeros_like(A_levels), 'rx', markersize=10, label='Niveaux idéaux')
plt.title("Constellation - Détection d'enveloppe")
plt.xlabel("Amplitude détectée")
plt.ylabel("Q (non utilisé)")
plt.grid(True)
plt.legend()
plt.axis('equal')

plt.tight_layout()
plt.show()
plt.tight_layout()
plt.show()

