grame-cncm / faust

Functional programming language for signal processing and sound synthesis
http://faust.grame.fr
Other
2.51k stars 318 forks source link

faust2teensy strange behaviour #507

Open danidev opened 3 years ago

danidev commented 3 years ago

Hello,

I've compiled and installed faust to use the faust2teensy. I use the command:

faust2teensy -lib FaustVoice.dsp

The cpp and h file are different from the files generated by the faust IDE online. The teensy compiler then crashes with a lot of error (while it doesn't with the files generated by the online IDE)

Also the parameter -nvoices seems having no effect.

the reason why I'm using the compiled faust2teensy and not the exporter is that I need support for polyphony and the online exporter seems to not export polyphonic voices.

Any hint will be very useful. Thanks!!!

danidev commented 3 years ago

I did some other tests on exporting with faust2teensy and I have some other info to share.

first of all, I did a fresh installation of faust on a Ubuntu 18.04.5 LTS. Cloned the repo and then:

make
sudo make install

then I compiled the .dsp script with:

faust2teensy -lib script.dsp

I've replaced the .cpp and .h previously exported with the online faust ide and then compiled with the teensyduino. It compiles (not tested yet on the board).

There are some differences on the .cpp files, here is a sample (but there are more):

This is the parseDouble function exported by faust2teensy:

/**
 * @brief parseDouble : parse number [s]dddd[.dddd] or [s]d[.dddd][E|e][s][dddd] and store the result in x
 * @param p the string to parse, then the remaining string
 * @param x the float number found if any
 * @return true if a float number was found at the begin of p
 */
static bool parseDouble(const char*& p, double& x)
{
    double sign = 1.0;     // sign of the number
    double ipart = 0;      // integral part of the number
    double dpart = 0;      // decimal part of the number before division
    double dcoef = 1.0;    // division factor for the decimal part
    double expsign = 1.0;  // sign of the E|e part
    double expcoef = 0.0;  // multiplication factor of E|e part

    bool valid = false;    // true if the number contains at least one digit

    skipBlank(p);
    const char* saved = p;  // to restore position if we fail

    // Sign
    if (parseChar(p, '+')) {
        sign = 1.0;
    } else if (parseChar(p, '-')) {
        sign = -1.0;
    }

    // Integral part
    while (isdigit(*p)) {
        valid = true;
        ipart = ipart*10 + (*p - '0');
        p++;
    }

    // Possible decimal part
    if (parseChar(p, '.')) {
        while (isdigit(*p)) {
            valid = true;
            dpart = dpart*10 + (*p - '0');
            dcoef *= 10.0;
            p++;
        }
    }

    // Possible E|e part
    if (parseChar(p, 'E') || parseChar(p, 'e')) {
        if (parseChar(p, '+')) {
            expsign = 1.0;
        } else if (parseChar(p, '-')) {
            expsign = -1.0;
        }
        while (isdigit(*p)) {
            expcoef = expcoef*10 + (*p - '0');
            p++;
        }
    }

    if (valid)  {
        x = (sign*(ipart + dpart/dcoef)) * std::pow(10.0, expcoef*expsign);
    } else {
        p = saved;
    }
    return valid;
}

The same function generated by the web exporter:

/**
 * @brief parseDouble : parse number [s]dddd[.dddd] and store the result in x
 * @param p the string to parse, then the remaining string
 * @param x the float number found if any
 * @return true if a float number was found at the begin of p
 */
static bool parseDouble(const char*& p, double& x)
{
    std::stringstream reader(p);
    std::streambuf* pbuf = reader.rdbuf();

    // Keep position before parsing
    std::streamsize size1 = pbuf->in_avail();

    // Parse the number
    reader >> x;

    // Keep position after parsing
    std::streamsize size2 = pbuf->in_avail();

    // Move from the actual size
    p += (size1 - size2);

    // True if the number contains at least one digit
    return (size1 > size2);
}

Also, if I try to make the script polyphonic with:

faust2teensy -lib -nvoices 4 script.dsp

seems that the nvoices param is not recognised and if i use:

faust2teensy -lib script.dsp -nvoices 4

It exports 2 times on the .h file the dsp class, and I get a lot of errors by the teensyduino, here is the .h file:

#define NVOICES 4
/************************************************************************
 FAUST Architecture File
 Copyright (C) 2019-2020 GRAME, Centre National de Creation Musicale
 ---------------------------------------------------------------------
 This Architecture section is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 3 of
 the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; If not, see <http://www.gnu.org/licenses/>.

 EXCEPTION : As a special exception, you may create a larger work
 that contains this FAUST architecture section and distribute
 that work under terms of your choice, so long as this FAUST
 architecture section is not modified.

 ************************************************************************/

#ifndef faust_MicrocosmosDsp_h_
#define faust_MicrocosmosDsp_h_

#include <string>

#include "Arduino.h"
#include "AudioStream.h"
#include "Audio.h"

class dsp;
class MapUI;
class MidiUI;
#if MIDICTRL
class teensy_midi;
#endif

class MicrocosmosDsp : public AudioStream
{
    public:

        MicrocosmosDsp();
        ~MicrocosmosDsp();

        virtual void update(void);

        void setParamValue(const std::string& path, float value);
        float getParamValue(const std::string& path);

    private:

        template <int INPUTS, int OUTPUTS>
        void updateImp(void);

        float** fInChannel;
        float** fOutChannel;
        MapUI* fUI;
    #if MIDICTRL
        teensy_midi* fMIDIHandler;
        MidiUI* fMIDIInterface;
    #endif
        dsp* fDSP;
};

#endif
#define NVOICES 4
/************************************************************************
 FAUST Architecture File
 Copyright (C) 2019-2020 GRAME, Centre National de Creation Musicale
 ---------------------------------------------------------------------
 This Architecture section is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 3 of
 the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; If not, see <http://www.gnu.org/licenses/>.

 EXCEPTION : As a special exception, you may create a larger work
 that contains this FAUST architecture section and distribute
 that work under terms of your choice, so long as this FAUST
 architecture section is not modified.

 ************************************************************************/

#ifndef faust_MicrocosmosDsp_h_
#define faust_MicrocosmosDsp_h_

#include <string>

#include "Arduino.h"
#include "AudioStream.h"
#include "Audio.h"

class dsp;
class MapUI;
class MidiUI;
#if MIDICTRL
class teensy_midi;
#endif

class MicrocosmosDsp : public AudioStream
{
    public:

        MicrocosmosDsp();
        ~MicrocosmosDsp();

        virtual void update(void);

        void setParamValue(const std::string& path, float value);
        float getParamValue(const std::string& path);

    private:

        template <int INPUTS, int OUTPUTS>
        void updateImp(void);

        float** fInChannel;
        float** fOutChannel;
        MapUI* fUI;
    #if MIDICTRL
        teensy_midi* fMIDIHandler;
        MidiUI* fMIDIInterface;
    #endif
        dsp* fDSP;
};

#endif
sletz commented 3 years ago
danidev commented 3 years ago

thanks @sletz for the clarification.

I've used as the main source of information this paper: https://ccrma.stanford.edu/~rmichon/publications/doc/SMC-19-teensy.pdf

(Right now I've found out that you are one of the authors! So I'm very glad to talk with you!)

In this paper you used:

faust2api -teensy -nvoices 4 -effect auto MyFaustSynthPoly.dsp

in order to achieve polyphony: that was my second attempt to develop a polyphonic synth with faust/teensy.

The first attempt was spawning several instances of the faust generated class and then feeding a mixer object but I got a lot of issues (probably memory leaks, but I didn't find out the main reason - something related to MIDI, I guess).

Having more than one voice is a crucial feature, not necessarily a polyphonic mode, but using more than one instance/class could be a workaround and then developing voices management outside faust (indeed I did it in my first attempt, so I have a raw working polyphonic mode for the teensy).

Do other exporters (like the one for osx or linux) work the same way or it is possible having more than one instance or include several different classes on one project? This could point me on the right direction to eventually join the dev team to port it for the teensy :-)

rmichon commented 3 years ago

Hi Daniele,

Yes, the nvoices option was available for a while, but it was basically unusable because generated objects would use too much memory on the Teensy and almost never work. Hence, we decided to remove it. Even though the Teensy is quite powerful computationally speaking, it's small memory quickly becomes an issue for this type of applications.

Instantiating a Faust object multiple times (e.g., FaustSawtooth faustSawtooth[4];) and handling polyphony by hand is definitely an option. Our problem is that the Faust infrastructure around polyphony was never really designed to run on such small system and its heaviness make it more or less impossible to use on the Teensy.

All Faust architectures work in a different way in that regard. It works in the case of the Teensy because we generate an object for the Teensy audio library so you still have to do the wiring with the audio engine "by hand". In come cases, like on the ESP32, the Faust architecture manages the whole audio chain so one Faust object is directly linked to audio resources. In that case, its' not possible to declare multiple Faust objects in parallel or at least they would have to be allocated/de-allocated every time.

Romain

sletz commented 3 years ago

@danidev You may have a look at OWL Faust architecture C++ file here: https://github.com/pingdynasty/OwlProgram/blob/feature/faust-buttons/FaustCode/owl.cpp, it seems to do polyphony in a simpler way !?