Jeudi 15 Septembre 2011
Décorticage
Etude sympatique proposé par Zerosquare: que fait le code suivant : ?

#include <stdio.h>  
#include <math.h>  
#include <inttypes.h>  
  
int32_t mystere(uint32_t n)  
{  
    n &= 0xFF;  
    if (!n) return -21;  
    if ((n & 0xC0) == 0x40) n &= 0xBF;  
    if ((n & 0x1F) > 16) return mystere((n ^ 0x1F) + 1);  
    if (n & 0x1C) return mystere(n - 4) + 12;  
    if ((n & 0xC3) == 0xC0) return mystere(n & 0xBF) - 1;  
    if ((n & 3) == 1) return mystere(n - 1) + 2 + ((n >> 7) * (((n & 0x40) >> 6) + 1));  
    if ((n & 3) == 2) return mystere(n - 1) + 3 + ((((n & 0xC0) == 0x80) << 1) - ((!(n & 0xC0)) * (((n & 0x20) >> 5) + 1)));  
    if ((n & 3) == 3) return mystere(n - 1) + 4 - ((!(n & 0xE0)) + (((n & 0xC0) == 0x80) << 1));  
    return mystere(0) - 3 + (((n & 0xE0) == 0xA0) << 1);  
}  
  
int main(void)  
{  
    int16_t *buf = malloc(10584320);  
    int16_t *p = buf;  
    FILE *f = fopen("mystere.bin", "wb");  
    for (uint32_t i = 0; i < 320; i++)  
    {  
        for (uint32_t j = 0; j < 8269; j++)  
        {  
            double s[2], n = sin((double)(j) * 0.062689 * pow(2, (double)mystere(i) / 12.0)) * 16384.0;  
            n *= exp((double)j * -5E-5);  
            if (i & 0x100) n *= exp(-(double)(i & 0xFF) * 0.1);  
            if (j < 441) n *= (double)(j) * 0.002268;  
            if (j > 7828) n *= (double)(8269 - j) * 0.002268;  
            s[0] = s[1] = n;  
            if ((p - buf) > 1999) { s[0] += (double)p[-2000] * 0.3; s[1] += (double)p[-2000] * 0.3; }  
            if (i > 1) { s[0] += (double)p[-33076] * 0.125; s[1] += (double)p[-33076] * 0.375; }  
            if (i > 2) { s[0] += (double)p[-49614] * 0.1875; s[1] += (double)p[-49614] *  0.0625; }  
            *p++ = (int16_t)s[0]; *p++ = (int16_t)s[1];  
        }  
    }  
    fwrite(buf, 1, 10584320, f); return 0;  
}


Explication après la coupure (pas pub), ne lisez pas si vous voulez trouver vous meme !

Le code est en fait assez simple, on va faire dans l'ordre d'execution :

 int16_t *buf = malloc(10584320);  
    int16_t *p = buf;  
    FILE *f = fopen("mystere.bin", "wb"); 

On alloue un buffer (~10Mo!) et on ouvre le fichier de destination. Rien de bien complexe.

 for (uint32_t i = 0; i < 320; i++)  
    {  
        for (uint32_t j = 0; j < 8269; j++)  
        { 

La on lance deux boucles. 320 * 8269 = 2646080... 10584320 / 2646080 = 4. Tiens tiens...

oubliont pour l'instant le contenu de la fonction mystere:

double s[2], n = sin((double)(j) * 0.062689 * pow(2, (double)mystere(i) / 12.0)) * 16384.0;


On fait appel a la fonction mystere pour alimenter le calcul d'un sinus.. vous devriez commencer a comprendre de quoi on pourrais parler...

            n *= exp((double)j * -5E-5); 

On triture un peu ce chiffre donné par le sinux et ce en lien avec la boucle J...

            if (i & 0x100) n *= exp(-(double)(i & 0xFF) * 0.1); 

Tiens, quand on arrive a la fin de la boucle I on fait progressivement tendre les min/max sinus vers 0..

            if (j < 441) n *= (double)(j) * 0.002268; 

Sur la boucle J cette fois, on fait partir cette sinus de 0 vers la valeur normale

            if (j > 7828) n *= (double)(8269 - j) * 0.002268; 

Et la a la fin de la boucle J on fait retendre les min/max de cette sinus vers zero...

Ca ressemble fortement a du traitement audio, et pour etre précis pour les 3 derniere ligne au calcul de l'enveloppe du signal (ou du volume si vous préferez).

            s[0] = s[1] = n; 


On remplis le tableau S avec la valeur de N

            if ((p - buf) > 1999) { s[0] += (double)p[-2000] * 0.3; s[1] += (double)p[-2000] * 0.3; } 


On reinjecte la premiere moitié de la boucle J courante dans la seconde moitié..

 
            if (i > 1) { s[0] += (double)p[-33076] * 0.125; s[1] += (double)p[-33076] * 0.375; }  
            if (i > 2) { s[0] += (double)p[-49614] * 0.1875; s[1] += (double)p[-49614] *  0.0625; } 


Tiens tiens.. 33076/4 = 8269, 49614/4 = 12403.5 / 2 = 6201.75 . Hummm et on reinjecte des valeurs précédentes. Interessant

            *p++ = (int16_t)s[0]; *p++ = (int16_t)s[1]; 

La on remplis le buffer avec les valeurs du tableau S..


Ca ressemble fortement, tres fortement à un générateur de son tout ça ! Et forcement, puisque c'est bien ce dont il s'agit !

Explications:
On boucle sur I avec I le numero de la note courante. La boucle J permet de configurer la longueur d'une note.

Quand on fait le calcul 320*8269, on se rends compte que la valeur obtenue est la taille du buffer divisé par 4. Pourquoi ? Simplement parce que le sample audio généré est en 16bit (2 octets par informations) et sur deux canaux. Pourquoi 16bit et 2 canaux ? c'est simple, le buffer est de type int16_t (donc sur un pc du little endian sur 16, soit du LE16) et a chaque boucle on écrit le contenu du tableau S qui possede deux cases: soit de la stéréo.

Si on imagine qu'un est a une fréquence d'échantillonage de 44100Hz, chaque note dure 8269 points, avec 1/44100Hz = 0.000023 s soit 0.022ms, chaque note doit durer 8269 * 0.022 = ~187ms. En suposant que chaque note est une noire, on peu calculer le BPM (battement par minute) : 1s = 1000 ms, 1000/187 = 5.3, 5.3 * 60 = ~320. Donc on est a peux pres a 320 BPM. (Sachant que 320 est plutot rapide, les notes doivent plutot etre des croches que des noires, donc un avec BPM à 160)

Viens la suite: la ligne 33 ajoute une enveloppe globale a la note qui est généré, et celle-ci est lié a sa durée. Le volume va baisse progressivement avec le temps, mais pas completement.

Le ligne 34 sert a faire diminuer le volume vers la fin de la partition. (idem, l'exponentielle va progressivement diminuer pour que la valeur de N soit de plus en plus proche de 0)

La ligne 35 fait aussi plus ou moins la meme chose, mais sans l'exponentielle, la on a une droite bien classique, en gros, on l'utilis pour regler l'attaque de la note.
La ligne 36 est linverse de la ligne 35, on y regle le relachement de la note.

La ou ça deviens interessant, c'est la ligne 38 a 40. On ajoute en fait un écho au signal d'origine. il y a trois écho en fait la note courante elle meme, et la note précédente, et quasiment la note n-2. Bien sur chacune a un niveau d'injection différent, sinon on saturerais vite, et ça ne serait pas très joli ;)

En gros, la ligne 38 va injecter en echo la premiere partie de la note (avec un facteur 0.3, autant dire que c'est pas trop présent.)

La ligne 39 et 40 elle font la meme chose, mais a une échelle plus grande, on va aller chercher la valeur d'une a deux note dans le "passé" et les réinjecter pour faire un echo. On remarquera que les deux canaux n'ont pas le meme echo, ce qui permet généralement d'avoir un son plus agréable. (et donne un effet un peu plus surround ;) )

Le reste du code est assez classique.

On peux donc découper la fonction main en trois blocs:

ligne 31: on recupere la note et génération du niveau actuel du signal
lignes 33 à 38: on défini comment l'instrument est généré (on a un synthétiseur sous les yeux sisi!) A l'exception de la ligne 34 qui est un peu partiticuliere (ce n'est pas la note, mais la partition dans sa globalitée qui est prise en compte la)
ligne 39 et 40: on ajoute un double effet d'écho

Je vous laisse vous amuser a décortiquer la fonction "mystere", mais ce n'est rien de plus qu'un générateur de nombre suivant une fonction qui définie la mélodie qu'on va jouer. Sa tombe bien le morceau joué par ce programme est plutot simple (des rampes de notes qui monte et qui descende..) J'en passe les détails, si vous voulez vous amuser dessus, n'hésitez pas ;)




Je vous propose une petite modification :

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
#include <inttypes.h> 
double mystere2(double c) { 
   if (c > 6.28319) return mystere2(c - 6.28319); 
   if (c <= 1.57808) return c/1.57808; else c -= 1.57808; 
   if (c <= 3.14159) return -(c*0.628319)+1; else c -= 3.14159; 
   return mystere2(c) - 1; }    
int32_t mystere(uint32_t n) { 
    n &= 0xFF; 
    if (!n) return -21; 
    if ((n & 0xC0) == 0x40) n &= 0xBF; 
    if ((n & 0x1F) > 16) return mystere((n ^ 0x1F) + 1); 
    if (n & 0x1C) return mystere(n - 4) + 12; 
    if ((n & 0xC3) == 0xC0) return mystere(n & 0xBF) - 1; 
    if ((n & 3) == 1) return mystere(n - 1) + 2 + ((n >> 7) * (((n & 0x40) >> 6) + 1)); 
    if ((n & 3) == 2) return mystere(n - 1) + 3 + ((((n & 0xC0) == 0x80) << 1) - ((!(n & 0xC0)) * (((n & 0x20) >> 5) + 1))); 
    if ((n & 3) == 3) return mystere(n - 1) + 4 - ((!(n & 0xE0)) + (((n & 0xC0) == 0x80) << 1)); 
    return mystere(0) - 3 + (((n & 0xE0) == 0xA0) << 1); } 
int main(void) { 
    uint32_t i, j; 
    int16_t *buf = malloc(10584320*2); 
    int16_t *p = buf; 
    FILE *f = fopen("mystere3.bin", "wb"); 
    for (i = 0; i < 320; i++) 
        for (j = 0; j < 8269; j++) 
        { 
            double n = mystere3((double)(j) * 0.062689 * pow(2, (double)mystere(i) / 12.0)) * 16384.0; 
            double s[2]; 
            n *= exp((double)j * -5E-5); 
            if (i & 0x100) n *= exp(-(double)(i & 0xFF) * 0.1); 
 
            if (j < 441) n *= (double)(j) * 0.002268; 
            if (j > 7828) n *= (double)(8269 - j) * 0.002268; 
 
            s[0] = s[1] = n;          
            if ((p - buf) > 1999) { s[0] += (double)p[-2000] * 0.3; s[1] += (double)p[-2000] * 0.3; } 
            if (i > 1) { s[0] += (double)p[-33076] * 0.125; s[1] += (double)p[-33076] * 0.375; } 
            if (i > 2) { s[0] += (double)p[-49614] * 0.1875; s[1] += (double)p[-49614] *  0.0625; } 
            *p++ = (int16_t)s[0]; *p++ = (int16_t)s[1]; 
        } 
    fwrite(buf, 1, 10584320*2, f); 
    return 0; }

C'est globalement la meme chose, toujours en 42 lignes ;) mais au lieu d'utiliser sin, le signal est généré par la fonction mytere2, je vous laisse la décortiquer si vous voulez, c'est fort simple ;)

Vous pouvez télécharger ce fichier la: http://www.mirari.fr/jbnb

Autre modif possible, remplacer le contenu de mystere2 par :

if (c > 6.28319) return mystere2(c - 6.28319); 
if (c <= 1.57808) return 1; return -1;


L'effet est sympa aussi :)




Posté à
16:44
 par Godzil - | Autres

1. Zerosquare à 16:59 15/09/2011 -
Félicitations pour ce décorticage en règle #top#
(et merci, y'a plusieurs paragraphes que je n'aurai pas à écrire pour le prochain billet, du coup #hehe# )

Pseudo :

Adresse mail : (optionnel)

Site web : (optionnel)

Veuillez entrer la somme de quatre et deux :
Message :


[ img
img
 RSS  - ©yNBlogs 2004

Login : - Mot de passe :