[Faust] Coder de l’audio en sifflotant

40
20
nov.
2017
Audiovisuel

FAUST (Functional Audio Stream) est un langage de haut niveau (c’est‐à‐dire loin du métal) qui permet d’écrire des applications audio complexes en manipulant des abstractions (relativement) simples. Ce langage fait partie de la distribution FAUST, initialement développée et portée par le GRAME (centre national de création musicale, à Lyon) bénéfice aujourd’hui d’une communauté mondiale, le projet éponyme réunit ce langage fonctionnel de haut niveau, son compilateur et de nombreux outils.

Une spécification de traitement du signal écrite en FAUST peut être compilée pour une grande variété de langages cibles (C++, C, Java, JavaScript, ASM JavaScript, LLVM IR, WebAssembly etc.). La même spécification écrite en FAUST peut être intégrée et utilisée dans un grand nombre de logiciels, greffons et bibliothèques (CSOUND, LADSPA, MAX/MSP, PureData, Q, SuperCollider, VST, AU, LV2, etc.).

Enfin, le compilateur FAUST ne demande que les bibliothèques standards C++ et aucune autre dépendance.

N. D. M. : Le compilateur est sous licence GPL v2.

Sommaire

Une appli audio…

Aujourd’hui des applications audio fonctionnent sur processeur et des appliances, matériels dédiés, y passent également. Mais nombreuses sont les applications spécialisées dans l’audio qui utilisent des processeurs dédiés, dont la programmation est toutefois notoirement complexe.

Comme un processeur central, un processeur de signal numérique (DSP) est mis en œuvre en lui associant de la mémoire (vive ou morte) et des périphériques. Un DSP typique se présente sous la forme d’un microcontrôleur intégrant généralement de la mémoire, des minuteurs (timers), des ports série synchrones rapides, des contrôleurs d’accès direct à la mémoire (DMA) et divers ports d’entrée‐sortie.

Dans le cas de l’audio, on peut résumer (en simplifiant) le rôle du DSP à la partie exclusivement numérique du traitement audio, celle qui se trouve entre les convertisseurs analogique‐numérique (en entrée) et numérique‐analogique (en sortie) de l’interface audio, aussi appelée « carte son ».

… c’est surtout des maths

Après avoir été numérisé, le signal se présente sous la forme d’une suite de valeurs numériques discrètes. Cette suite de valeurs (ou échantillons) est apte à être stockée et traitée par un système informatique. Par nature, le traitement numérique du signal revient à effectuer essentiellement des opérations arithmétiques de base du type A = (B × C) + D.

Un processeur classique va nécessiter plusieurs cycles d’horloge pour effectuer un tel calcul, par exemple un 68k a besoin d’une dizaine de cycles pour effectuer une addition, et 70 pour une multiplication.

Les DSP sont conçus pour optimiser tout ça et disposent de fonctions permettant de calculer A beaucoup plus rapidement, voire en garantissant un temps d’exécution, afin d’obtenir une latence (le temps écoulé entre l’entrée et la sortie du signal) mesurable et constante.

Faust

Le compilateur Faust est contenu tout entier dans un seul exécutable, invoqué par une unique commande : faust.

Opérateurs

Le signal audio est figuré par des opérateurs très intuitifs :

  1. A , B parallèle : A est à gauche, B à droite ;
  2. A : B séquence : A entre dans B ;
  3. A <:B,C séparation : A est séparé en deux signaux, B et C ;
  4. A,B :>C fusion : A et B sont réunis en un signal C ;
  5. A ~ B récursion : la sortie de B est séparée en deux signaux, le premier étant connecté à la première entrée de A, l’autre étant une sortie.

process = 0;

Ici, on a donc du silence (0) :
Silence

Que l’on va passer en stéréo :

process = 0 <: _ , _;

Stéréo
Ici, `n’est pas un opérateur mais une « primitive » (voir [manuel](http://faust.grame.fr/images/faust-quick-reference.pdf) : « égalité », en l’occurence, le signal passe) comme+ou*`._

Exemple : un instrument basique

sine.dsp

Le code suivant est un simple générateur d’onde sinusoïdale, sans entrée ni audio (pas de traitement du son, juste de la génération) ni [MIDI], on ne pourra donc en moduler la fréquence (la hauteur perçue de la note) qu’à l’aide du slider « osc » défini ci‐dessous :

import("stdfaust.lib");

declare name "LinuxFR SINE";
declare author "Faust Team";

phasor(f)   = f/ma.SR : (+,1.0:fmod) ~ _ ;
osc(f)      = phasor(f) * 6.28318530718 : sin;

process     = osc(hslider("freq", 440, 20, 20000, 1)) * hslider("level", 0, 0, 1, 0.01);

Dans cet exemple, quelques définitions de fonctions, du pattern‐matching, des fonctions FAUST standards comme ma.SR et des abstractions d’interface comme hslider("paramName",default,min,max,step).

L’implémentation d’une entrée MIDI standard pour le contrôle de la fréquence au clavier est typiquement assez complexe en C/C++, et si triviale en Faust, qu’elle est présentement laissée en exercice au lecteur.

Sauvé dans un fichier sine.dsp, son « chemin de signal » peut être visualisé à l’aide de la commande suivante :

faust -svg sine.dsp

Qui va produire ce diagramme :
Sine

Pour compiler ce programme vers un environnement (architecture, toolkit) donné, utiliser le script correspondant, ici, par exemple, Linux x86_64 / lv2 :

faust2lv2 -gui sine.dsp

Ce qui produira un dossier sine.lv2 :

./sine.dsp        # Fichier source
./sine.cpp        # Fichier C++ généré
./sine.lv2        # Dossier du plugin
    manifest.ttl  # Manifeste LV2
    sineui.so     # Binaire exécutable de l'UI (optionnelle)
    sine.ttl      # Fichier de définition de l'UI
    sine.so       # Binaire exécutable du plugin
./sine-svg        # Dossier des diagrammes
    process.svg

Il suffit de le copier dans le chemin de découverte (en général et au choix, /usr/lib/lv2/ ou ~/.lv2/) pour être immédiatement utilisable dans une des pistes MIDI d’un hôte/DAW.

LinuxFR Sine   Le greffon, compilé pour Linux 64 / Jack / Qt.

Le code C++ généré, ainsi que les fichiers spécifiques à l’architecture cible, sont en annexe.

Multiplates‐formes

En utilisant le script idoine, notre greffon deviendra une application autonome.

Le « paquet » Faust contient les scripts cibles de la plupart (sinon toutes : VST/Windows, AudioUnit/macOS, etc.) des plates‐formes actuelles.

La liste est longue de tout ce que propose FAUST comme sorties.

Il devient donc possible, avec Faust, de développer rapidement des applications audio extrêmement puissantes et/ou complexes, en s’assurant de produire du code valide.

Faust est à l’origine de nombreux gros projets, notamment dans le monde Linux Audio, comme le montre cette liste partielle.

Annexes

Longue liste des cibles atteignables

Cible Script Faust
Android app faust2android
WebAudio code asmjs faust2asmjs
WebAudio Web app faust2webaudioasm
iOS app faust2ios
iOS app avec Qt faust2caqtios
CoreAudio app avec Qt faust2caqt
Raspberry Pi ALSA cli app faust2rpialsaconsole
Raspberry Pi JACK cli faust2rpinetjackconsole
ROS (Robot OS) app faust2ros
ROS app avec GTK faust2rosgtk
API générateur faust2api
CLI pour le debug DSP faust2plot
ALSA cli faust2alsaconsole
ALSA avec Qt faust2alqt
ALSA avec GTK faust2alsa
JACK cli faust2jackconsole
JACK app avec Qt faust2jaqt
JACK app avec GTK faust2jack
NetJack cli faust2netjackconsole
NetJack app avec Qt faust2netjackqt
LADSPA greffon faust2ladspa
SuperCollider faust2supercollider
VST greffon faust2faustvst
LV2 greffon faust2lv2
PureData faust2puredata
MaxMSP 5 patch et greffon faust2msp
MaxMSP 6 et plus faust2max6
Audio Unit plugin faust2au
BELA programme faust2bela
CSOUND Opcode faust2csound
OWL App faust2owl
PDF diagramme faust2pdf
PDF documentation mathématiques faust2mathdoc
PNG diagramme faust2png
SVG diagramme faust2svg
SVG graph faust2graph
SVG signal faust2sig
Octave script faust2octave

Code C++

sine.cpp

//----------------------------------------------------------
// name: "LinuxFR SINE"
// author: "Faust Team"
//
// Code generated with Faust 0.10.2 (http://faust.grame.fr)
//----------------------------------------------------------

/* link with  */
#include <math.h>
#ifndef FAUSTFLOAT
#define FAUSTFLOAT float
#endif  


#ifndef FAUSTCLASS 
#define FAUSTCLASS mydsp
#endif

class mydsp : public dsp {
  public:
    FAUSTFLOAT  fslider0;
    float   fConst0;
    float   fRec0[2];
    FAUSTFLOAT  fslider1;
    int fSamplingFreq;

  public:
    virtual void metadata(Meta* m) { 
        m->declare("name", "LinuxFR SINE");
        m->declare("author", "Faust Team");
        m->declare("maths.lib/name", "Faust Math Library");
        m->declare("maths.lib/version", "2.0");
        m->declare("maths.lib/author", "GRAME");
        m->declare("maths.lib/copyright", "GRAME");
        m->declare("maths.lib/license", "LGPL with exception");
    }

    virtual int getNumInputs() { return 0; }
    virtual int getNumOutputs() { return 1; }
    static void classInit(int samplingFreq) {
    }
    virtual void instanceConstants(int samplingFreq) {
        fSamplingFreq = samplingFreq;
        fConst0 = (1.0f / min(1.92e+05f, max(1e+03f, (float)fSamplingFreq)));
    }
    virtual void instanceResetUserInterface() {
        fslider0 = 4.4e+02f;
        fslider1 = 0.0f;
    }
    virtual void instanceClear() {
        for (int i=0; i<2; i++) fRec0[i] = 0;
    }
    virtual void init(int samplingFreq) {
        classInit(samplingFreq);
        instanceInit(samplingFreq);
    }
    virtual void instanceInit(int samplingFreq) {
        instanceConstants(samplingFreq);
        instanceResetUserInterface();
        instanceClear();
    }
    virtual mydsp* clone() {
        return new mydsp();
    }
    virtual int getSampleRate() {
        return fSamplingFreq;
    }
    virtual void buildUserInterface(UI* ui_interface) {
        ui_interface->openVerticalBox("LinuxFR SINE");
        ui_interface->addHorizontalSlider("freq", &fslider0, 4.4e+02f, 2e+01f, 2e+04f, 1.0f);
        ui_interface->addHorizontalSlider("level", &fslider1, 0.0f, 0.0f, 1.0f, 0.01f);
        ui_interface->closeBox();
    }
    virtual void compute (int count, FAUSTFLOAT** input, FAUSTFLOAT** output) {
        float   fSlow0 = (fConst0 * float(fslider0));
        float   fSlow1 = float(fslider1);
        FAUSTFLOAT* output0 = output[0];
        for (int i=0; i<count; i++) {
            fRec0[0] = fmodf((fSlow0 + fRec0[1]),1.0f);
            output0[i] = (FAUSTFLOAT)(fSlow1 * sinf((6.2831855f * fRec0[0])));
            // post processing
            fRec0[1] = fRec0[0];
        }
    }
};


#ifdef FAUST_UIMACROS
    #define FAUST_INPUTS 0
    #define FAUST_OUTPUTS 1
    #define FAUST_ACTIVES 2
    #define FAUST_PASSIVES 0
    FAUST_ADDHORIZONTALSLIDER("freq", fslider0, 4.4e+02f, 2e+01f, 2e+04f, 1.0f);
    FAUST_ADDHORIZONTALSLIDER("level", fslider1, 0.0f, 0.0f, 1.0f, 0.01f);
#endif

Manifeste LV2

manifest.ttl

########## https://faustlv2.bitbucket.io/sine ##########

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://faustlv2.bitbucket.io/sine>
    a lv2:Plugin ;
    lv2:binary <sine.so> ;
    rdfs:seeAlso <sine.ttl> .

<https://faustlv2.bitbucket.io/sineui>
    a ui:Qt5UI ;
    ui:binary <sineui.so> .

Fichier de définition de l’interface graphique utilisateur

sine.ttl

@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix ui:    <http://lv2plug.in/ns/extensions/ui#> .
@prefix epp:   <http://lv2plug.in/ns/ext/port-props#> .
@prefix atom:  <http://lv2plug.in/ns/ext/atom#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
<https://faustlv2.bitbucket.io/sine>
       a lv2:Plugin ;
       doap:name "LinuxFR SINE" ;
       lv2:binary <sine.so> ;
       lv2:optionalFeature epp:supportsStrictBounds ;
       lv2:optionalFeature lv2:hardRtCapable ;
       doap:maintainer [ foaf:name "Faust Team" ] ;
       ui:ui <https://faustlv2.bitbucket.io/sineui> ;
    lv2:port [
    a lv2:InputPort ;
    a lv2:ControlPort ;
    lv2:index 0 ;
    lv2:symbol "freq_0" ;
    lv2:name "freq" ;
        lv2:portProperty epp:hasStrictBounds ;
        epp:rangeSteps 19980 ;
    lv2:default 440 ;
    lv2:minimum 20 ;
    lv2:maximum 20000 ;
    ] , [
    a lv2:InputPort ;
    a lv2:ControlPort ;
    lv2:index 1 ;
    lv2:symbol "level_1" ;
    lv2:name "level" ;
        lv2:portProperty epp:hasStrictBounds ;
        epp:rangeSteps 100 ;
    lv2:default 0 ;
    lv2:minimum 0 ;
    lv2:maximum 1 ;
    ] , [
    a lv2:OutputPort ;
    a lv2:AudioPort ;
    lv2:index 2 ;
    lv2:symbol "out0" ;
    lv2:name "out0" ;
    ]
.
  • # Hello world!

    Posté par (page perso) . Évalué à 9 (+8/-0).

    Un langage où on ne fait pas un "hello world!" pour le présenter, voilà qui est novateur!

    • [^] # Re: Hello world!

      Posté par . Évalué à 7 (+5/-0).

      Un générateur d'onde sinusoïdale, dans le domaine audio, c'est proche d'un "Hello World!". ;-)
      Après on peut jouer sur la fréquence ("Hello @hpiedcoq!") ou même la forme du signal ("@hpiedcoq, les sinusoïdes c'est la base du traitement du signal ;-)"). :-)

      Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

  • # Bare Metal

    Posté par . Évalué à 8 (+6/-0).

    un langage de haut niveau (c’est‐à‐dire loin du métal) qui permet d’écrire des applications audio complexes

    Encore une discrimination à l'encontre des amateurs du genre Metal :-p

    --> []

  • # La documentation automatique

    Posté par . Évalué à 10 (+9/-0).

    Le petit truc qui m'a soufflé dans Faust, c'est le module de documentation automatique.

    En matière d'installation artistique, la pérennité est une vraie problématique. Une installation faite à un moment donné sera très très compliquée à remettre en place 100 ans plus tard. Pour ça, Faust propose un module qui va générer un pdf qui va décrire la sémantique du programme. Et il sera ainsi possible de le réimplémenter dans le langage qui sera standard dans 100 ans.

    • [^] # Re: La documentation automatique

      Posté par . Évalué à 6 (+6/-0).

      Est-ce que tu aurais des exemples d'installations artistiques montées avec Faust ? Je suis curieux des raisons qui pousseraient à utiliser Faust plutôt qu'un environnement de plus haut niveau comme Pure Data. Merci !

      • [^] # Re: La documentation automatique

        Posté par . Évalué à 9 (+9/-0).

        Est-ce que tu aurais des exemples d'installations artistiques montées avec Faust ?

        En général (en tout cas dans mon expérience) Faust est à un niveau un peu plus bas que Pd: par exemple, on va écrire un module d'effet spécifique en Faust, comme une disto ou un synthé, qui va être inclus dans le contexte un peu plus large d'un show qui lui sera souvent écrit avec Max / Pd / Supercollider / Ableton Live / etc. Les fichiers d'architecture de Faust sont très pratiques pour ça: à partir du code Faust on exporte un objet Pd puis ça roule.

        Dans les proceedings des ICMC et autre (http://www.icmc2017.com/en/download.html) il y a souvent des retours d'expérience et installs qui sont décrites. Au SCRIME des gens s'en sont servis notamment pour faire de la spatialisation sur dôme de HP.

  • # En passant

    Posté par (page perso) . Évalué à 10 (+8/-0).

    Pour faire du FAUST avec Emacs, j'ai développé Faustine, qui permet de générer les diagrammes, le code, le binaire, la doc, etc. à la volée. Au départ il faisait la coloration syntaxique et l'indentation, mais j'ai finalement mergé le code en question avec faust-mode, que j'ai ajouté en dépendance.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.