Romain Malinge

Projet de Traitement des données Audio-Visuelles

TP10 - Manipulation de signaux audionumériques

Dans cette partie, nous nous proposons de travailler sur le traitement de données audio ! En effet, ce domaine du traitement de données présente de nombreuses applications intéressantes, que je vais tenter de vous présenter ici.
Dans un premier temps, nous verrons comment travailler sur le son, puis comment le compresser et enfin comment lui appliquer des transformations.

TFCT et ITFCT

Pour travailler de manière efficace et pertinente sur le son, nous utiliserons le merveilleux outil qu’est le sonagramme ! Le sonagramme affiche l'évolution du spectre d’un signal au cours du temps, offrant ainsi une représentation en trois dimensions : le temps, la fréquence et l’amplitude.

Pour le construire, nous avons besoin de passer dans le domaine fréquentiel et ce à court terme, c’est-à-dire sur une plage définie. On peut également choisir la pondération entre les plages pour adoucir le rendu (Rectangulaire ou Hann). Voilà mon implémentation de la TFCT :


function Y = TFCT(y, N, D, fenetre)

    Y = buffer(y, N, N-D, 'nodelay');
    fenetre = strcmp(fenetre, 'hann') * hann(N) + strcmp(fenetre, 'rect') * ones(N, 1);
    
    % Calculer de la TFCT
    Y = fft(Y .* fenetre(:));
    Y = Y(1:floor(N/2) + 1, :);

end
        

Pour tester notre fonction, nous allons utiliser un extrait de la musique Polaire de Gen:

Voici le sonagramme obtenu avec la fonction TFCT :

Sonagramme de Polaire

Nous devons maintenant implémenter le chemin inverse pour reconstruire le signal d’origine. Voilà mon implémentation de la ITFCT (légère modification) :


function y = ITFCT(Y, N, D, fenetre)

    Y(end+1:N, :) = 0;
    w = strcmp(fenetre, 'hann') * hann(N) + strcmp(fenetre, 'rect') * ones(N, 1);
    n = (size(Y, 2) - 1) * D + N;
    y = zeros(n, 1);
    w_sum = zeros(n, 1);

    for t = 1:size(Y, 2)
        idx = (1:N) + (t - 1) * D;
        y(idx) = y(idx) + ifft(Y(:, t), 'symmetric') .* w;
        w_sum(idx) = w_sum(idx) + w.^2;
    end

    y(w_sum ~= 0) = y(w_sum ~= 0) ./ w_sum(w_sum ~= 0);
    y = y(D:end-D);

end
        

On peut vérifier que le signal reconstruit est bien le même que le signal d’origine :

Compression

La compression vise à conserver uniquement une faible proportion des coefficients de Fourier (les plus élevés). Une première implémentation naïve vise à garder uniquement les k coefficients les plus importants par colonne du sonagramme :


function [Y_modifie, taux_compression] = compression(Y, k)

    % Conserver les k valeurs les plus hautes
    Y_modifie = zeros(size(Y));
    for col = 1:size(Y, 2)
        [~, indices] = maxk(Y(:, col), k);
        Y_modifie(indices, col) = Y(indices, col);
    end

    % Calcul du taux de compression
    taux_compression = (1 - nnz(Y_modifie) / numel(Y)) * 100;

end
        

On peut alors tester notre fonction de compression sur le sonagramme de Polaire :

Sonagramme de Polaire compressé

Avec cette première implémentation (figure 31), on obtient tout de même 90% de compression. Pour améliorer ce résultat, on peut faire de la décimation, c’est-à-dire retirer des lignes et des colonnes dans le sonagramme avant de garder les coefficients les plus importants :


function [Y_modifie, taux_compression] = compression_decimation(Y, m, df)

    % Décimer le sonagramme Y
    Y_decimated = Y(1:df:end, 1:df:end);
    Y_modifie_decimated = zeros(size(Y_decimated));
    for col = 1:size(Y_decimated, 2)
        [~, indices] = maxk(Y_decimated(:, col), m);
        Y_modifie_decimated(indices, col) = Y_decimated(indices, col);
    end

    % Reconstituer le sonagramme avec des zéros aux positions supprimées
    Y_modifie = zeros(size(Y));
    Y_modifie(1:df:end, 1:df:end) = Y_modifie_decimated * df;

    % Calculer le taux de compression
    taux_compression = (1 - nnz(Y_modifie) / numel(Y)) * 100;
    
end
        

Le facteur df (décimation factor) correspond à l’intervalle entre chaque ligne ou colonne que l’on garde. Ici on a df = 2, donc il reste 1 ligne sur 2 et une colonne sur 2 :

Sonagramme de Polaire compressé

On obtient 95% de compression ! Pour df = 3, le signal devient trop détérioré et la voix robotique :

Sonagramme de Polaire compressé

Transformations

Enfin, on peut appliquer des transformations sur le sonagramme pour obtenir des effets intéressants. Voila une liste de transformations que j’ai implémentées.

Filtres passe bas

Sonagramme de Polaire compressé

Filtres passe haut

Sonagramme de Polaire compressé

Filtres passe bande

Sonagramme de Polaire compressé

Echo simple

Sonagramme de Polaire compressé

Echo multiple

Sonagramme de Polaire compressé

Voilà le code de la fonction de réverbération (params est une matrice de deux colonnes, la première contenant les délais et la deuxième les taux de réverbération) :


function [Y_modifie, valeurs_t_modifie] = reverbe(Y, valeurs_t, params)

    % Calculer le nombre de colonnes du décalement maximum
    delay_max = sum(valeurs_t < max(params(:, 1)));
    nb_freq = size(Y, 1);
    nb_temps = size(Y, 2);
    Y_modifie = [Y, zeros(nb_freq, delay_max)];

    % Appliquer chaque réverbération
    for idx = 1:size(params, 1)
        delay = sum(valeurs_t < params(idx, 1));
        taux = params(idx, 2);
        Y_modifie(:, delay + 1:nb_temps + delay) = Y_modifie(:, delay + 1:nb_temps + delay) + taux * Y;
    end

    % Calculer les valeurs de temps en plus
    valeurs_t_modifie = [valeurs_t, valeurs_t(end) + (1:delay_max) * valeurs_t(2)];

end
        

Modifier la vitesse de lecture

Afin d’étirer temporellement un signal audio, une première idée consiste à changer la vitesse de lecture, ou, de façon équivalente, à faire comme si le signal avait été échantillonné à une fréquence plus élevée que sa véritable fréquence d’échantillonnage :

Sonagramme de Polaire compressé

Cependant, cette méthode altère également le contenu fréquentiel du signal. Pour résoudre ce problème, un algorithme d’étirement temporel traite le module du sonagramme du signal comme une image, que l’on peut facilement étirer horizontalement par simple interpolation linéaire. Le problème réside plutôt dans la manipulation de la phase.
Cet algorithme propose d’utiliser, comme différence de phase entre deux colonnes du sonagramme interpolé, la différence de phase entre les deux colonnes du sonagramme d’origine ayant servi à l’interpolation :


function y_modifie = etirement_temporel(y, taux)

    N = 2048;
    D = 512;
    Y = TFCT(y, N, D, 'hann');
    C = 1:taux:size(Y,2);
    y_modifie = zeros(size(Y,1),length(C));
    ang = angle(Y(:,1));

    % Reconstruction du signal
    for i = 1:length(C)-1
        c = floor(C(i));
        alpha = C(i) - c;
        rho = (1-alpha)*abs(Y(:,c)) + alpha*abs(Y(:,c+1));
        y_modifie(:,i) = rho .* exp(1j * ang);
        ang = ang + angle(Y(:,c+1)) - angle(Y(:,c));
    end
    
    y_modifie = ITFCT(y_modifie, N, D, 'hann');

end
        

De cette manière, la modification de vitesse ne change pas la tonalité de la voix :

Crédit

Crédit
Polaire - Gen (Album: Fidel)