Une convolution audio bruyante...

HerveHerve Membre
avril 2016 modifié dans API UIKit #1

Bonjour,


 


Je me remets à  la programmation et m'attèle à  un vocoder fonctionnant avec la fft et la convolution.


 


Apparemment, mon algo de fft (et ifft ) fonctionnent : des tests avec l'écoute et la visualisation d'un signal audio (synthé ou micro) font entendre le même signal en entrée et en sortie une fois passé par la "moulinette" fft/ifft.


 


Je fais bien une multiplication de nombres complexes avec la "sortie fft" de mon signal micro et synthé.


 


J'ai beau chercher, je ne trouve pas depuis deux jours : un bruit de click se fait perpétuellement entendre, comme si à  chaque appel de la méthode de convolution, quelque chose ne se passait pas bien. J'ai essayé de multiples réglages de niveau en entrée et sortie, rien n'y fait (sinon que l'on entend plus ou moins bien le signal vocodé).


 


J'utilise des tableaux de 1024 "double" dans des structures pour stabiliser la mémoire, avec une certaine indépendance entre la méthode "render audio" et la méthode "fft + convolution" grâce à  des tableaux de copie de valeurs.


 


Voici la méthode fft+convolution :



- (void) doFFTVocoding{

//////////////////////////////////
//Traitement entrée micro et synthétiseur
////////////////////////////////////////////

int n, n2, i, k, kn2, l, p;
double ang, s, c, trM, tiM, trS, tiS, trV, tiV;

n = (1 << bits);
n2 = n / 2;

for (i = 0; i < n; i++){
/*les tableaux de copie sont écrits dans le render*/
realMicIn->val[i] = copieAudioMic->val[i];
imagMicIn->val[i] = 0.0;
realSynthIn->val[i] = copieSynthIn->val[i];
imagSynthIn->val[i] = 0.0;
}

for (l = 0; l < bits; l++) {
/*bits = 10; Core audio fonctionne avec des tableaux de 2^10 valeurs*/
/*méthode testée avec succès dans un programme test : on affiche bien à  l'écran le spectre fréquentiel de l'entrée audio*/
for (k = 0; k < n; k += n2) {
for (i = 0; i < n2; i++, k++) {
p = bitreverse[k / n2];
ang = twopi * p / n;
c = cos(ang);
s = sin(ang);
kn2 = k + n2;

if (invFlag) s = -s;

trM = realMicIn->val[kn2] * c + imagMicIn->val[kn2] * s;
tiM = imagMicIn->val[kn2] * c - realMicIn->val[kn2] * s;

realMicIn->val[kn2] = realMicIn->val[k] - trM;
imagMicIn->val[kn2] = imagMicIn->val[k] - tiM;
realMicIn->val[k] += trM;
imagMicIn->val[k] += tiM;

trS = realSynthIn->val[kn2] * c + imagSynthIn->val[kn2] * s;
tiS = imagSynthIn->val[kn2] * c - realSynthIn->val[kn2] * s;

realSynthIn->val[kn2] = realSynthIn->val[k] - trS;
imagSynthIn->val[kn2] = imagSynthIn->val[k] - tiS;
realSynthIn->val[k] += trS;
imagSynthIn->val[k] += tiS;
}
}
n2 /= 2;
}

for (k = 0; k < n; k++) {
if ((i = bitreverse[k]) <= k){
}
else{
trM = realMicIn->val[k];
tiM = imagMicIn->val[k];
realMicIn->val[k] = realMicIn->val[i];
imagMicIn->val[k] = imagMicIn->val[i];
realMicIn->val[i] = trM;
imagMicIn->val[i] = tiM;

trS = realSynthIn->val[k];
tiS = imagSynthIn->val[k];
realSynthIn->val[k] = realSynthIn->val[i];
imagSynthIn->val[k] = imagSynthIn->val[i];
realSynthIn->val[i] = trS;
imagSynthIn->val[i] = tiS;
}
}


n = (1 << bits);
n2 = n / 2;

//////////
//Convolution
/////////////////

for (i = 0; i < n ; i++){
/*Je pense que c'est ici qu'est produit le bruit*/
realSynthIn->val[i] *= 0.005;/*différentes valeurs ont été testés, 1/n devient inaudible.*/
imagSynthIn->val[i] *= 0.005;
realMicIn->val[i] *= 0.005;
imagMicIn->val[i] *= 0.005;
/*la multiplication complexe, inversion de la partie imaginaire en prévision de ifft*/
realVocOut->val[i] = (realMicIn->val[i] * realSynthIn->val[i]) - (imagMicIn->val[i] * imagSynthIn->val[i]);
imagVocOut->val[i] = -1.0 * ((realMicIn->val[i] * imagSynthIn->val[i]) + (imagMicIn->val[i] * realSynthIn->val[i]));

/*test de contrôle*/
if ((fabs(realVocOut->val[i]) > 1.0) || (fabs(imagVocOut->val[i]) > 1.0))NSLog(@i = %d, realVoc = %f, imagVoc = %f,i,realVocOut->val[i],imagVocOut->val[i]);

}

/*différents tests permettant de contrôler fft et ifft : le signal sortant est identique à  l'entrant*/
//test synthIn
/*for (i = 0; i < n ; i++){ //test synthIn
realVocOut->val[i] = realSynthIn->val[i] * 0.0025;
imagVocOut->val[i] = imagSynthIn->val[i] * -0.0025;
}*/

//test MicIn
/*for (i = 0; i < n ; i++){
realVocOut->val[i] = realMicIn->val[i] * 0.005;
imagVocOut->val[i] = imagMicIn->val[i] * -0.005;
}*/


////////////////
//inverse FFT
/////////////////////
/*méthode identique à  la fft, simple inversion du signe de la partie imaginaire en entrée*/
for (l = 0; l < bits; l++) {
for (k = 0; k < n; k += n2) {
for (i = 0; i < n2; i++, k++) {
p = bitreverse[k / n2];
ang = twopi * p / n;

c = cos(ang);
s = sin(ang);
kn2 = k + n2;

if (invFlag) s = -s;

trV = realVocOut->val[kn2] * c + imagVocOut->val[kn2] * s;
tiV = imagVocOut->val[kn2] * c - realVocOut->val[kn2] * s;

realVocOut->val[kn2] = realVocOut->val[k] - trV;
imagVocOut->val[kn2] = imagVocOut->val[k] - tiV;
realVocOut->val[k] += trV;
imagVocOut->val[k] += tiV;
}
}
n2 /= 2;
}

for (k = 0; k < n; k++) {
if ((i = bitreverse[k]) <= k){
}
else{
trV = realVocOut->val[k];
tiV = imagVocOut->val[k];
realVocOut->val[k] = realVocOut->val[i];
imagVocOut->val[k] = imagVocOut->val[i];
realVocOut->val[i] = trV;
imagVocOut->val[i] = tiV;
}
}

for (i = 0; i < n ; i++) {
/*copie de la partie réelle pour la sortie audio*/
audioVocOut->val[i] = realVocOut->val[i]; // * 0.2;
if (fabs(audioVocOut->val[i]) > 1.0)NSLog(@out %d = %f, i,audioVocOut->val[i]);
}
}

Le problème est complexe (dans tous les sens du termes), si une personne pouvait me guider vers la solution, ce serait fantastique! 


Réponses

  • la convolution c'est pareil dans tous les traitements de signaux ?


    Si oui alors tout logiquement la convolution c'est le fait de pour tout i de prendre n voisins, d'effectuer un calcul et modifier i. J'ai bon ?


     


    Si jusque laÌ€ je me suis pas planté je sais que les bords sont des cas limites. Sur une image c'est dégueulasse si on fait pas un truc mais je sais plus lequel... Donc ici ça doit être pareil tu vas avoir un pic tout au début parce que ton calcul est biaisé. Regarde peut être la tronche de la courbe en sortie ça devrait valider la théorie.


     


    Oui parce qu'il faut pas oublier que ça n'est qu'une théorie. J'avoue ne pas avoir lu ton code non plus.


    Ouais on peut dire que c'est de l'aide de qualité 


  • Merci Pyroh pour ta réponse. En fait, je travaille sur de la convolution dans le domaine audio. Ce que tu décris concerne l'image.


     


    Le vocodeur fonctionne, il semble même être très intelligible (avec 512 bandes de fréquences ceci dit, c'est normal...) Mais il est bruyant, il fait "click, click, click, click..." tout le temps. je pense que le problème vient des niveaux de sortie, je vais encore faire des tests. Mais si vous avez des idées, ou l'expérience de ce genre de problème, je suis preneur! 


     


    Le code source vient du livre de Craig A. Lindley : "Digital audio with Java". On le trouve encore d'occasion sur Amazon. Excellent livre pour ceux que la MAO intéresse. (L'auteur est guitariste, il montre comment programmer des effets, des filtres, compresseurs, oscillateurs, delays, chorus, etc.)

  • HerveHerve Membre
    avril 2016 modifié #4

    Après analyse du signal de sortie avec Logic Audio, il apparaà®t que, à  chaque appel de la méthode, environ 110 samples sautent. Du coup, il y a une rupture régulière de la continuité de la courbe dans le signal, d'où le bruit. Avec un display "de ma fabrication" (dérivé de UIView), la méthode "doFFTVocoding" produit bien un signal en courbe continue. C'est lors du report dans le "renderAudio" que des samples sautent (une centaine encore une fois à  chaque report).


     


    Auriez-vous des idées? (je sais qu'il est rare de tomber dans ce forum sur des personnes travaillant sur le DSP audio, mais sait-on jamais?)


     


    Merci par avance...


     


    PS : en pièce jointe, la capture d'écran de Logic. Fléché en jaune, les points de rupture source du bruit.


  • Salut je bosse dans le sound design. La solution ne serait-elle pas de créer un offset (décalage) de 100 / 150 samples. La sortie ne serait plus direct par rapport à  l'entrée. Ca te permet de créer en même temps un buffet audio que tu enregistres en fichier à  la fin de l'enregistrement.


     


    A voir le confort à  l'utilisation pour l'utilisateur final.


  • Merci iLandes pour ton idée. Autant j'ai laissé tomber celle d'un "cross fade" entre deux séries de samples (ce serait encore plus bruyant en fait), autant l'idée de décaler une partie du calcul entre deux buffers est peut-être une piste : en deux moitiés en quelques sortes.... J'essaie cela et je vous tiens au courant.


    C'est étonnant tout de même! La méthode :



    for (i = 0; i < inNumberFrames; i++){
    //calcul des effets microphone et voix de synthétiseur
    def->copieAudioMic->val[i] = (double) filtrageMicIn;
    def->copieSynthIn->val[i] = (double) inputVocCorr;

    outGlobal = (Float32) def->audioVocOut->val[i];
    //calcul des effets de sortie

    devrait assurer une bonne synchronicité des données. Celle-ci est effective lorsque l'on ne fait pas la convolution, mais la dft directement après la fft! Etonnant!


     


    (J'ai appelé la méthode FFT en amont de ces calculs, en aval, ou au milieu, le décalage, ou la perte de données se fait toujours! Ces tableaux sont des structures C pour plus de stabilité mémoire. Je ne pense pas qu'il y ait de fuite de ce côté.)


     


    Ceci dit, étant donné que mon "inNumberFrames" est égal à  1024, je ne pourrai pas faire des tableaux plus grands en direction de la méthode.


     


    Allez, j'y retourne!


  • HerveHerve Membre
    avril 2016 modifié #7

    J'ai fait le test de mettre les méthodes fft/convolution/dft dans le render audio, faisant l'économie des copies de sureté : le problème est identique. 


    Je pense que le problème vient de la multiplication complexe :



    for (ir = 0; ir < n ; ir++){
    def->realSynthIn->val[ir] /= 1024.0;
    def->imagSynthIn->val[ir] /= 1024.0;
    def->realMicIn->val[ir] /= 1024.0;
    def->imagMicIn->val[ir] /= 1024.0;
    def->realVocOut->val[ir] = (def->realMicIn->val[ir] * def->realSynthIn->val[ir])
    - (def->imagMicIn->val[ir] * def->imagSynthIn->val[ir]);
    def->imagVocOut->val[ir] = -1.0 * ((def->realMicIn->val[ir] * def->imagSynthIn->val[ir])
    + (def->imagMicIn->val[ir] * def->realSynthIn->val[ir]));
    }

    Ce qui se passe alors est que l'on travaille avec deux analyses de fréquences (fft) faites à  un intervalle de 1024/44100 secondes. C'est là  que se produit la rupture de continuité : on travaille avec des analyses faites à  des moments trop distants. On n'entend pas cette rupture avec un seul signal, mais elle est très audible avec la convolution. (donc bruit...)


     


    A partir de là , que feriez-vous? Une moyenne de différents tableaux de parties réelles et imaginaires? Multipliser l'appel à  la fft/convolution/dft?


    Qu'en pensez-vous?


  • CéroceCéroce Membre, Modérateur

    Que se passe-t-il quand tu reprends le résultat de la FFT et que tu reconstitues les sinusoà¯dales ?


  • La discussion continue ailleurs, mais répondons au test de la sinusoà¯de ici.


    En ce qui concerne les sinusoà¯des, l'analyse spectrale est parfaite et le retours invFFT aussi (testé sur une application test avant de me lancer dans le vocodeur : des display dérivés de UIView permettent de visualiser le résultat de la FFT - partie réelle et imaginaire -comme les formes d'ondes entrantes et sortantes : celles-ci sont identiques; pas de click audio non plus, parfait!). 


  • CéroceCéroce Membre, Modérateur
    Tu me dis que la visualisation est correcte, je veux bien, mais ça indique seulement que ça fonctionne pour la partie visualisée. Par exemple, si tu loupes un buffer de 1024 échantillons, alors la visualisation des fréquences sera sûrement correcte.

    C'est pour ça que je te conseille de reconstituer le signal audio. S'il y a le moindre trou, tu l'entendras.
  • Oui oui, le signal audio est correctement restitué, que ce soit avec une sinusoà¯de, de la synthèse additive ou une entrée micro. Le problème vient de la convolution ( voir l'autre discussion : "Lisser des tableaux de valeurs"). 


     


    Merci beaucoup Céroce en tous les cas! (Toujours prêt à  donner un coup de main, tu es super!  :)  )


  • AliGatorAliGator Membre, Modérateur
    Oui mais la FFT d'une sinusoà¯de parfaite est un dirac, donc la reconstitution inverse est parfaite, elle tient dans un seul bucket ^^ Mais c'est clair qu'un signal audio n'est jamais une sinusoà¯de parfaite.


  • Oui mais la FFT d'une sinusoà¯de parfaite est un dirac, donc la reconstitution inverse est parfaite, elle tient dans un seul bucket ^^




     


    Pas tout à  fait. Cela dépend de la fréquence de la sinusoà¯de, de la fréquence d'échantillonnage et de la durée de l'échantillon sur lequel on applique la FFT.

Connectez-vous ou Inscrivez-vous pour répondre.