<p><spanclass="sc"><abbrtitle="Trop long à lire">tlàl</abbr> : </span> Je me suis amusé à lire un fichier <code>wav</code>. Le <code>C</code> fut le langage le mieux adapté à ce traitement. Bien meilleur que Ruby par exemple.</p>
<p>edit: Je voulais que ce programme fonctionne sur une machine spécifique. En aucun cas je ne pensais publier ce code pour une utilisation autre que celle-ci.</p>
</div>
<p>J’ai eu besoin de calculer la somme des valeurs absolue des données d’un fichier <code>wav</code>.
Pour des raison d’efficacité (et aussi de fun), j’ai fait le programme en <code>C</code>.</p>
<p>Celà faisait longtemps que je n’avais pas programmé en <code>C</code>.
De mémoire il était peu aisé de manipuler des fichiers.
Mais je dois concéder que j’ai été étonné de la clarté du code que j’ai obtenu.</p>
<p>Tout d’abord, un fichier <code>wav</code> se compose d’un entête qui contient pas mal de meta données.
Cet entête a été optimisé pour prendre peu de place.
Donc on discute de l’entête avec des nombres d’octets :</p>
<ul>
<li>Les 4 premiers octets doivent contenir <code>RIFF</code> en ASCII ;</li>
<li>les 4 octects suivant correspondent à un entier codé sur 32 bits qui donne la taille du fichier moins 8 octets. etc..</li>
</ul>
<p>Etonnamment je pense que lire ce type de fichier avec un langage de haut niveau aurait été plus pénible qu’en C.
La preuve, il m’a suffit de chercher sur le net le format complet de l’entête et de l’écrire dans un struct.</p>
<preclass="twilight">
<spanclass="Storage">struct</span> wavfile
{
<spanclass="Storage">char</span> id[<spanclass="Constant">4</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should always contain "RIFF"</span>
<spanclass="Storage">int</span> totallength; <spanclass="Comment"><spanclass="Comment">//</span> total file length minus 8</span>
<spanclass="Storage">char</span> wavefmt[<spanclass="Constant">8</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should be "WAVEfmt "</span>
<spanclass="Storage">int</span> format; <spanclass="Comment"><spanclass="Comment">//</span> 16 for PCM format</span>
<spanclass="Storage">short</span> pcm; <spanclass="Comment"><spanclass="Comment">//</span> 1 for PCM format</span>
<spanclass="Storage">char</span> data[<spanclass="Constant">4</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should always contain "data"</span>
<spanclass="Storage">int</span> bytes_in_data;
};
</pre>
<p>Si j’avais eu à faire ça en Ruby, je pense qu’il m’aurait fallu pour chaque bloc de l’entête écrire un bout de code de lecture du bon nombre d’octets.
Alors qu’en <code>C</code> il m’a suffit d’écrire: </p>
<spanclass="Comment"><spanclass="Comment">//</span> do something with value</span>
}
</pre>
<p>Finallement je suis arrivé au code suivant, sachant que le format de wav était connu, avec notamment échantillonage sur 16 bits en 48000Hz :</p>
<spanclass="Storage">char</span> id[<spanclass="Constant">4</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should always contain "RIFF"</span>
<spanclass="Storage">int</span> totallength; <spanclass="Comment"><spanclass="Comment">//</span> total file length minus 8</span>
<spanclass="Storage">char</span> wavefmt[<spanclass="Constant">8</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should be "WAVEfmt "</span>
<spanclass="Storage">int</span> format; <spanclass="Comment"><spanclass="Comment">//</span> 16 for PCM format</span>
<spanclass="Storage">short</span> pcm; <spanclass="Comment"><spanclass="Comment">//</span> 1 for PCM format</span>
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span>ERROR: Not wav format<spanclass="StringConstant">\n</span><spanclass="String">"</span></span>);
<p>Bien entendu ce code n’est qu’un <em>hack</em>.
Mais on voit bien comment on peut facilement améliorer ce code, ajouter des cas possibles par exemple.
Comme je dis souvent : le bon outil pour la bonne tâche.
On voit en effet que pour cette tâche <code>C</code> est bien supérieur à Ruby par exemple.</p>
<p>_màj : pour des raisons de compatibilité (machines 64 bits) j’ai utilisé <code>int16_t</code> au lieu de <code>short</code> et <code>int</code> au lieu de <code>int</code>.</p>
<p>Je serai curieux de savoir s’il existe un manière plus propre en Ruby que je ne connais pas.
Certainement qu’en Python ça doit être la cas.</p>
<divclass="intro">
<p>Màj (2) : après toutes les remarques concernant la portabilité.
J’ai fait une nouvelle version qui devrait être plus portable.
Elle fait aussi plus de test pour vérifier le fichier.
Cependant j’utilise une assertion spécifique à <code>gcc</code> pour être certain que la structure de donnée n’ai pas de “trou” :</p>
<preclass="twilight">
__attribute__((__packed__))
</pre>
<p>Le nouveau code n’utilise pas mmap et devrait être plus compatible.<br/>
<spanclass="CCCPreprocessorLine">#<spanclass="CCCPreprocessorDirective">include</span><spanclass="String"><spanclass="String"><</span>string.h<spanclass="String">></span></span></span><spanclass="Comment"><spanclass="Comment">//</span> for memcmp</span>
<spanclass="CCCPreprocessorLine">#<spanclass="CCCPreprocessorDirective">include</span><spanclass="String"><spanclass="String"><</span>stdint.h<spanclass="String">></span></span></span><spanclass="Comment"><spanclass="Comment">//</span> for int16_t and int32_t</span>
<spanclass="Storage">struct</span> wavfile
{
<spanclass="Storage">char</span> id[<spanclass="Constant">4</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should always contain "RIFF"</span>
<spanclass="Support">int32_t</span> totallength; <spanclass="Comment"><spanclass="Comment">//</span> total file length minus 8</span>
<spanclass="Storage">char</span> wavefmt[<spanclass="Constant">8</span>]; <spanclass="Comment"><spanclass="Comment">//</span> should be "WAVEfmt "</span>
<spanclass="Support">int32_t</span> format; <spanclass="Comment"><spanclass="Comment">//</span> 16 for PCM format</span>
<spanclass="Support">int16_t</span> pcm; <spanclass="Comment"><spanclass="Comment">//</span> 1 for PCM format</span>
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span>ERROR: <spanclass="StringConstant">%s</span> is not a big endian wav file<spanclass="StringConstant">\n</span><spanclass="String">"</span></span>, filename);
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span>ERROR: <spanclass="StringConstant">%s</span> is not a little endian wav file<spanclass="StringConstant">\n</span><spanclass="String">"</span></span>, filename);
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span>ERROR: Not wav format<spanclass="StringConstant">\n</span><spanclass="String">"</span></span>);
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span><spanclass="StringConstant">\n</span>ERROR: not 16 bit wav format.<spanclass="String">"</span></span>);
<spanclass="SupportFunction">fprintf</span>(stderr,<spanclass="String"><spanclass="String">"</span>, not PCM (<spanclass="StringConstant">%d</span>)<spanclass="String">"</span></span>, header.format);
<spanclass="Keyword">print</span><spanclass="String"><spanclass="String">"</span>error: not wav file.<spanclass="String">"</span></span>
exit(<spanclass="Constant">1</span>)
<spanclass="Keyword">print</span><spanclass="SupportFunction">sum</span>(<spanclass="SupportFunction">abs</span>(word) <spanclass="Keyword">for</span> word <spanclass="Keyword">in</span> word_iter(f))
values <spanclass="Keyword">=</span> data.<spanclass="Entity">unpack</span><spanclass="String"><spanclass="String">'</span>Z4 i Z8 i s s i i s s Z4 i s*<spanclass="String">'</span></span>
sum <spanclass="Keyword">=</span> values.<spanclass="Entity">drop</span>(<spanclass="Constant">12</span>).<spanclass="Entity">map</span>(<spanclass="Keyword">&</span><spanclass="Constant"><spanclass="Constant">:</span>abs</span>).<spanclass="Entity">inject</span>(<spanclass="Constant"><spanclass="Constant">:</span>+</span>)
<ahref="/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/"><spanclass="nicer">«</span> Utilisation de git pour calculer les mtimes</a>
<ahref="/Scratch/fr/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/">Pourquoi je n'utiliserai pas CoffeeScript (malheureusement) <spanclass="nicer">»</span></a>