Optimisation - synthèse sonore temps réel

Optimisation - synthèse sonore temps réel - C++ - Programmation

Marsh Posté le 09-11-2004 à 21:49:53    

En ce moment je m'éclate avec le SDK VST de Steinberg, ça permet de faire entre autres des synthés virtuels pour des softs de son genre cubase.
 
Je poste ici paske c'est en C++, même si l'utilisateur du SDK a surtout de l'algo à faire et qu'en fait, ça pourrait très bien être en C.
Dans le principe c'est très simple :
 
On a des entrées diverses en float (les paramètres du synthé) qui changent de manière exceptionelle, et quand une fonction process (float **inputs, float **outputs, long sampleFrames) est appelée, on doit remplir les outputs sur autant de sampleFrames qu'il faut.
 
 
J'y connais vraiment rien en asm, donc j'me pose des questions.
 
Sachant que ça peut tourner que sous mac ou pc (à ma conaissance...) est-ce que je dois travailler en double/float en interne ? à priori c'est comme ça que ça se fait je crois, mais comme il faut vraiment des performances optimales... j'me tâte.
En plus, je fais des LUT et si je travaillais directement en int, ce serait bien pratique.
 
J'ai entendu dire une fois que certains processeurs savent calculer les sin. Ça veut dire que si j'utilise le sin de cmath, à la compilation j'aurai un code asm qui fera un sin  ? Ça me parraît trop beau pour être vrai.
 
Il est d'usage de sortir un signal compris entre -1.0f et 1.0f. On peut faire plus mais je crois qu'il vaut mieux éviter : moi ça me fait donc mal au coeur de faire une LUT par exemple pour la fonction de distortion non linéaire de sortie (là j'en ai plusieurs dont une qui fait appel à des exponentielles) qui le limitera forcément ma résolution... en gros, même si j'enregistre 10 000 float par fonction, ça me fera que 10 000 valeurs possibles, 6.5536 fois moins que la dynamique 16 bits
 
Pour c++ y'a ptet des libs qui ont des fonctions optimisées pour ce genre de traitement ..?
 
 
Bon voilà, un peu fouilli; je prends bien volontiers des avis sur les LUT, des conseils :)

Reply

Marsh Posté le 09-11-2004 à 21:49:53   

Reply

Marsh Posté le 09-11-2004 à 21:59:10    

float **inputs, float **outputs
 
c'est alloué comment ça ? dimensions ?
 
est-ce que tu as bien auditer ton code pour être sur de ma faire des conversion allé-retour float->double->float ?
 
(si tu fais du C (à priori nom) est-ce que tu utilises les version pour float des fonctions trigonométriques ?)

Reply

Marsh Posté le 09-11-2004 à 22:10:40    

float **inputs et float **outputs c'est genre float[16][sampleFrames] à chaque fois.
 
Enfin, je dis ça.. je suis pas sûr mais le 16 c'est le nombre de pistes (je me sers que de la 0 et 1 pour gauche et droite) et sampleFrames, ça c'est le programe hôte du plug-in qui le définit en fonction du besoin.
 
Je crois qu'il essaye de le faire le plus grand possible en fonction des ressources mais ça doit être de l'ordre de 10 000
 

Citation :


est-ce que tu as bien auditer ton code pour être sur de ma faire des conversion allé-retour float->double->float ?


non, pas du tout
 
tout ce que je peux recalculer de manière exceptionelle, je le fais (concrètement, les changements de paramètres se font quand le mec tourne un bouton) et c'est là que je cast et que je garde en interne ma petite cuisine que je déduis des paramètres.
 
 
j'ai choisi la facilité en récuérant le template VC++ de steinberg, après un echec critique sur dev donc je pense pas être dans le cas que tu cites.


Message édité par raytaller le 09-11-2004 à 22:11:08
Reply

Marsh Posté le 09-11-2004 à 22:17:02    

float[16][sampleFrames]
je veux dire, est-ce alloué de manière contigüe ?  
 
pour les conversion, je veux dire qu'il faut faire vachement gaffe, parce que le moindre oublie de suffixe 'f' dans une constante, implique une conversion.
 
y a plein de bibliothèques de math template C++ c'est clair.
mais est-ce que ça pédale déjà ? tu as profilé ? t'as du code :) ?

Reply

Marsh Posté le 09-11-2004 à 22:39:31    

De manière contigue ça voudrait dire que c'est pas alloué dynamiquement ?
En fait je sais pas trop ce qui se passe derrière ça.
 
Ouais, là ça marche assez bien déjà, j'ai un petit synthé polyphonique qui marche :)
 
Du code... ouais j'en ai aussi mais, je sais pas si c'est très présentable.
 
 
Voilà ma fonction process, celle qui est le coeur du bordel et là où tout se joue (appelée en moyenne 44100 fois par seconde)
 

Code :
  1. void Poivre::process (float **inputs, float **outputs, long sampleFrames)
  2. {
  3. float *out1 = outputs[0];
  4. float *out2 = outputs[1];
  5. char i;
  6. double vibL;
  7. double vibR;
  8. double phzwrp;
  9. while (--sampleFrames >= 0){
  10.  _perfectL=0.0;
  11.  _perfectR=0.0;
  12.  // vibrato
  13.  _vibPhase+=_vibDelta;
  14.  limit(_vibPhase);
  15.  vibL=getFreqFactor(getWave(sineform,getLimit(_vibPhase-_vibSpread))*_vibAmp+.5);
  16.  vibR=getFreqFactor(getWave(sineform,getLimit(_vibPhase+_vibSpread))*_vibAmp+.5);
  17.  phzwrp=_phaseWarp*getFastRand();
  18.  for(i=0; i<MAX_POLY; ++i){
  19.   if(not _active[i])continue;
  20.   _phaseL[i]+=(_rfreq[i]*vibL)+phzwrp;
  21.   _phaseR[i]+=(_rfreq[i]*vibR)+phzwrp;
  22.   // phase
  23.   limit(_phaseR[i]);
  24.   limit(_phaseL[i]);
  25.   // enveloppe
  26.   if(_drvelo[i]<_rvelo[i]-_deltaStep[i])
  27.    _drvelo[i]+=_deltaStep[i];
  28.   else if(_drvelo[i]>_rvelo[i]){
  29.    _drvelo[i]-=_deltaStep[i];
  30.    // si l'ampli est plus petite que 1/1000 bah la
  31.    // voie est inactive et se libère
  32.    if(_drvelo[i]<0.001){
  33.     _active[i]=false;
  34.     _drvelo[i]=0.0;
  35.    }
  36.   }
  37.   _perfectL+=getWave(waveform,_phaseL[i])*_drvelo[i];
  38.   _perfectR+=getWave(waveform,_phaseR[i])*_drvelo[i];
  39.  }
  40.  // un filtre bidon d'ordre 2 qui marche mal, j'aurais cru que ça marcherait mais non
  41.  _dFiltredL+=(_perfectL-_filtredL)*_filterCut;
  42.  _dFiltredR+=(_perfectR-_filtredR)*_filterCut;
  43.  _dFiltredL*=_filterQ;
  44.  _dFiltredR*=_filterQ;
  45.  _filtredL+=_dFiltredL;
  46.  _filtredR+=_dFiltredR;
  47.  // limiter
  48.  if(params[__limiter]<1.0)
  49.   if(_filtredL>_limiter)
  50.    _filtredL=_limiter;
  51.   else if(_filtredL<-_limiter)
  52.    _filtredL=-_limiter;
  53.   if(_filtredR>_limiter)
  54.    _filtredR=_limiter;
  55.   else if(_filtredR<-_limiter)
  56.    _filtredR=-_limiter;
  57.  (*out1++)=_filtredL;
  58.  (*out2++)=_filtredR;
  59. }
  60. }


 
et j'ai inliné getWave qui va lire dans mes lut :
 

Code :
  1. inline double getWave(int wave, double phase){
  2. return waves[wave][static_cast<int>(phase*WAVE_SIZE)];
  3. }


 
pardon par avance...

Reply

Marsh Posté le 09-11-2004 à 22:44:18    

Pour profiler... en fait je sais vraiment pas comment m'y prendre là.
J'utilise fruityloops pour tester mon truc, et on m'a dit qu'on pouvait utiliser debugger avec, ce qui n'est pas le cas de cubase, mais bon.
 
Pour l'instant pour les perfs je regarde la barre de cpu de fruity, et pour le signal de sortie, je fais marcher mes oreilles avec le casque, et je visualise avec cool edit


Message édité par raytaller le 09-11-2004 à 22:45:15
Reply

Marsh Posté le 09-11-2004 à 22:59:06    

tu vois tes constantes sont des doubles et non des float.
 
du reste quand contigüe, ça veut dire contigüe. le contraire
 
de float*[16] ou tu as 16 zones mémoires allouées qui sont réparties n'importe comment. Je sais pas comment tu utilises  tes données, mais ça peut faire assez mal si les accès mémoire sont désordonnés parce que les données sont éparpillées.
 
 

Code :
  1. #
  2. #         if(params[__limiter]<1.0)
  3. #             if(_filtredL>_limiter)
  4. #                 _filtredL=_limiter;
  5. #             else if(_filtredL<-_limiter)
  6. #                 _filtredL=-_limiter;
  7. #             if(_filtredR>_limiter)
  8. #                 _filtredR=_limiter;
  9. #             else if(_filtredR<-_limiter)
  10. #                 _filtredR=-_limiter;


 
superbe obfuscation

Reply

Marsh Posté le 09-11-2004 à 23:19:47    

ouais, les constantes sont des doubles
 
pour les données éparpillées... eux dans un exemple, ils y accèdent comme ça, avec le même while.
ça doit être ce qu'il y a de mieux à faire, là dans le contexte.
 
 
mais alors, travailler avec des entiers de -32768 à 32767, ça se ferait ? j'l'avais fait une fois avec Snack en python et ça avait roulé.

Reply

Marsh Posté le 09-11-2004 à 23:53:05    

mais qu'est-ce que tu raconte, c'est quoi ces histoires d'entier 16bits ?!

Reply

Marsh Posté le 10-11-2004 à 00:12:12    

nan mais je dis entiers... je veux dire valeurs entières.
Après int ou pas int je sais pas, mais ça irait pas plus vite ?

Reply

Marsh Posté le 10-11-2004 à 00:12:12   

Reply

Marsh Posté le 10-11-2004 à 00:51:14    

de quoi ? de travailler avec des int au lieu de float ? bien sur que si !

Reply

Marsh Posté le 10-11-2004 à 01:00:07    

donc, j'me demande si ça vaut pas le coup que je reprenne tout en int.

Reply

Marsh Posté le 10-11-2004 à 02:32:57    

t'as toujours pas répondu : est-ce que c'est lent ?

Reply

Marsh Posté le 10-11-2004 à 19:22:14    

pour l'instant non mais j'aimerais bien aller un peu plus loin.
disons que là, d'autres synthés qui font mieux prennent la moitié en cpu.
 
et bon, ma carte son est faite pour du 24 bits / 96 kHz, donc là en 16/44 ça marche bien, mais c'est sûrement pas le cas chez tout le monde.
 
 
en fait là c'est bon, mais j'me dis qu'à le faire comme je le fais, c'est impossible d'arriver aux performances que d'autres synthés qui sont fait avec le même SDK, donc j'me dis qu'il doit y avoir une astuce quelque part (en fait j'en ai trouvé quelques unes, par exemple ici : http://musicdsp.org/archive.php?classid=1#9 )
 
Parce que ce qui pompe le plus là, j'imagine que c'est les multiplications de double, mais les multiplications, il faut bien les faire à un moment, c'est pourquoi je me demande si eux ne les font pas en int.
 
Si on est fort en asm, y'a sûrement moyen de regarder ça de plus près en désassemblant mais moi j'y connais vraiment rien.


Message édité par raytaller le 10-11-2004 à 19:40:20
Reply

Marsh Posté le 10-11-2004 à 19:57:52    

par exemple, c'est assez rigolo ça :
 
http://musicdsp.org/archive.php?classid=5#48

Reply

Marsh Posté le 11-11-2004 à 03:00:51    

Citation :

Après int ou pas int je sais pas, mais ça irait pas plus vite ?


Franchement je m'avancerais pas autant que Taz. De nos jours, une multiplication prend 3 cycles sur un p3 ou p4 en float (fpu) contre 9 sur l'alu entier (cause: 2 registres de résultat si ton entier est 32b). Petit gain aussi sur la division, qu'il faut de toutes façons absolument éviter (c'est du style 39 en float), mieux vaut multiplier par l'inverse en cas de constantes, ou essayer de transformer le calcul pour minimiser le nombre de divisions.
J'ai même vu sur un forum un gars qui se plaignait que faire une table optimisée sse pour sqrt (version sur le site de nvidia) était 3x plus lent que de bêtement utiliser fqsrt (qui a un 70 de latence mais une exécution de 3 cycles, dans le style, donc si le fsqrt est prévisible....... salut la table inutile qui nique le cache).
Donc perso je pense que si ton code bosse bcp avec les float avant de transformer quoique ce soit en entier pour résultat... tu vas plutôt PERDRE en le transformant en calcul d'entiers 32b.


Message édité par g012 le 11-11-2004 à 03:01:36
Reply

Marsh Posté le 11-11-2004 à 06:44:43    

Si Cubase fonctionne en float en interne c'est carrément plus simple de rester tout le temps en float, et pour optimiser de tirer parti des capacitées vectorielles du cpu sous-jacent, 3DNow / SSE coté X86, Altivec coté PPC.
 
d'autant plus que les généralement le mix flottant/entier en x86 ça peut se révéler pénalisant.

Reply

Marsh Posté le 11-11-2004 à 07:35:43    


 
Excellent ce site, il est bourré de routines optimisées. :love:

Reply

Marsh Posté le 11-11-2004 à 08:02:36    

Es-tu sûr que tu ne peux pas sortir le if(_drvelo[i]<0.001) hors de la boucle ? Trop de branchements à l'intérieur de la boucle empêche la prédiction et tue les cycles.  
La libération du canal, y'a pas besoin de la faire à 44 KHz, non ? (et moins il y en a, mieux ça vaut pour le passage à SSE/3DNow)


Message édité par el muchacho le 11-11-2004 à 08:04:35
Reply

Marsh Posté le 11-11-2004 à 12:10:55    

g012 a écrit :

Citation :

Après int ou pas int je sais pas, mais ça irait pas plus vite ?


Franchement je m'avancerais pas autant que Taz. De nos jours, une multiplication prend 3 cycles sur un p3 ou p4 en float (fpu) contre 9 sur l'alu entier (cause: 2 registres de résultat si ton entier est 32b). Petit gain aussi sur la division, qu'il faut de toutes façons absolument éviter (c'est du style 39 en float), mieux vaut multiplier par l'inverse en cas de constantes, ou essayer de transformer le calcul pour minimiser le nombre de divisions.
J'ai même vu sur un forum un gars qui se plaignait que faire une table optimisée sse pour sqrt (version sur le site de nvidia) était 3x plus lent que de bêtement utiliser fqsrt (qui a un 70 de latence mais une exécution de 3 cycles, dans le style, donc si le fsqrt est prévisible....... salut la table inutile qui nique le cache).
Donc perso je pense que si ton code bosse bcp avec les float avant de transformer quoique ce soit en entier pour résultat... tu vas plutôt PERDRE en le transformant en calcul d'entiers 32b.


 
Tout à fait :).
Il faudrait donc passer tout le code en float, c'est à dire les constantes suffixées avec f, et virer l'usage des doubles partout (ex : getWave). Il faut également vérifier le bon usage des version float des fonctions de math.h, elles sont généralement préfixées ou suffixées par f.
 
Pour augmenter les perfs, tu peux tester de faire plusieurs calculs par iteration de boucle :
 

Code :
  1. for( i = 0; i < MAX_POLY; i += 4)
  2. {
  3.     calcul(i);
  4.     calcul(i+1);
  5.     calcul(i+2);
  6.     calcul(i+3);
  7. }


 
Je garantie pas que ça améliore les perfs :o, faut tester (le 4 n'est pas innocent, c'est un premier petit pas vers la vectorisation des calculs).  
Tu peux également essayer d'insérer un prefetch dans la boucle pour précharger les données. Il faut bien entendu que ton cpu le supporte.
 
Pour aller plus loin en perfs, il faudrait passer en mmx (calcul en entier 16 bits) ou SSE. Il ne faut surtout pas mixer calcul en mmx et flotant car les registres sont partagés et le changement de contexte du CPU coûte la peau des fesses.


Message édité par Panini le 11-11-2004 à 12:12:59
Reply

Marsh Posté le 11-11-2004 à 13:21:44    

bjone a écrit :

Si Cubase fonctionne en float en interne c'est carrément plus simple de rester tout le temps en float, et pour optimiser de tirer parti des capacitées vectorielles du cpu sous-jacent, 3DNow / SSE coté X86, Altivec coté PPC.
 
d'autant plus que les généralement le mix flottant/entier en x86 ça peut se révéler pénalisant.


 
Peut-être en float alors...
Mais j'étais passé en double dès le début en pensant qu'à force de transformer, transformer... je finirais par perdre de la qualité.
Mais en fait, au lieu de supputer, je ferais mieux d'essayer.
 
 
 

Panini a écrit :

Tout à fait :).
Il faudrait donc passer tout le code en float, c'est à dire les constantes suffixées avec f, et virer l'usage des doubles partout (ex : getWave). Il faut également vérifier le bon usage des version float des fonctions de math.h, elles sont généralement préfixées ou suffixées par f.
 
Pour augmenter les perfs, tu peux tester de faire plusieurs calculs par iteration de boucle :
 

Code :
  1. for( i = 0; i < MAX_POLY; i += 4)
  2. {
  3.     calcul(i);
  4.     calcul(i+1);
  5.     calcul(i+2);
  6.     calcul(i+3);
  7. }


 
Je garantie pas que ça améliore les perfs :o, faut tester (le 4 n'est pas innocent, c'est un premier petit pas vers la vectorisation des calculs).  
Tu peux également essayer d'insérer un prefetch dans la boucle pour précharger les données. Il faut bien entendu que ton cpu le supporte.
 
Pour aller plus loin en perfs, il faudrait passer en mmx (calcul en entier 16 bits) ou SSE. Il ne faut surtout pas mixer calcul en mmx et flotant car les registres sont partagés et le changement de contexte du CPU coûte la peau des fesses.


 
Bon je vais rajouter des f partout alors ;)
Et en fait, si je me fixais une poly maximum maintenant, je pourrais carrément faire les 6 ou 8 lignes à la main, mais je pensais que si MAX_POLY était une contante, le compilateur optimiserait tout seul et déroulerait la boucle.
 
 
 

el muchacho a écrit :

Es-tu sûr que tu ne peux pas sortir le if(_drvelo[i]<0.001) hors de la boucle ? Trop de branchements à l'intérieur de la boucle empêche la prédiction et tue les cycles.  
La libération du canal, y'a pas besoin de la faire à 44 KHz, non ? (et moins il y en a, mieux ça vaut pour le passage à SSE/3DNow)


 
:D j'ai honte.....
merci de la remarque, je vira ça tout de suite ;)
 
 
 
Sinon, SSE / 3Dnow, c'est quoi ?
Vous pensez que je peux récupérer des optimisations qui servent pour la 3D ?

Reply

Marsh Posté le 11-11-2004 à 13:30:34    

SSE est un extension vectoriel qui permets de faire des cacluls en perrallele. c'est aussi une pietre imtiation de AltiVec :p

Reply

Marsh Posté le 11-11-2004 à 16:09:14    

3D Now a déjà l'air mieux foutu pour ce que j'en ai vu, enfin, on fait avec les moyens du bord.
MAX_POLY étant une constante, il y a effectivement des chances que le compilo déroule le tout. C'est facilement visible en assembleur même en y connaissant pas grand chose.

Reply

Marsh Posté le 11-11-2004 à 18:54:19    

Ah oui.. calcul vectoriel, j'avais entendu parler de ça.
 
Mais, au final le processeur fera quand même pas 2 calculs en parallèle, si ?
 
le pc arrive à s'y retrouver ?

Reply

Marsh Posté le 11-11-2004 à 19:21:32    

Typiquement il en fait 4.
Toutes ces extensions travaillent sur des vecteurs de 4 floats et il y a tout un jeu d'instructions associées comme dot, add, mad, etc.
 
par exemple :
 
add r1, r2, r3
 
va se traduire par :
 
r1.x = r2.x + r3.x;
r1.y = r2.y + r3.y;
r1.z = r2.z + r3.z;
r1.w = r2.w + r3.w;
 
Pour exploiter cela, il faut donc organiser tes données de manière à profiter de ce mode opératoire (en paquant les données 4 par 4 quoi).

Reply

Marsh Posté le 11-11-2004 à 20:10:46    

raytaller a écrit :


Mais, au final le processeur fera quand même pas 2 calculs en parallèle, si ?


 
si,
Typiquement AltiVec en fait 4,8 ou 16 simultanément

Reply

Marsh Posté le 11-11-2004 à 20:25:02    

ah en effet, c'est violent.
 
faut que je m'y mette à l'asm !

Reply

Marsh Posté le 11-11-2004 à 20:28:17    

il dit qu'il voit pas le rapport. personne ne te dit de faire de l'assembleur.
 
moi je vois pas mal de branlette dans ce topic : commence par faire un truc qui marche, et si ça tourne lentement -> règle ton compilateur, profile et trouve le point chaud. c'est pas la peine de se préoccuper de ça à ce niveau
 
 
#     calcul(i);
#     calcul(i+1);
#     calcul(i+2);
#     calcul(i+3);
 
si calcul est pas inline, t'encule bien les mouches avec ça

Reply

Marsh Posté le 16-05-2006 à 23:03:13    

cherche programmeur pour projet sérieux. bon niveau, mais surtout motivé et bosseur.  
 
projet : création plugin vst.  
 
(rémunération au pourcentage des ventes).  
 
 
contact :  
 
sacha1452@yahoo.com
 

Reply

Marsh Posté le 17-05-2006 à 00:50:14    

Et j'ai lu en entier un fil de 2004 pour ça :o

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed