libpd / libpd

Pure Data embeddable audio synthesis library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CSharp on Linux: out buffer always zero

residuum opened this issue · comments

While trying to figure out the problem with #113 (comment) I think I have located the bug:

In both libpd_process_raw() and the macro PROCESS(_x, _y) the variable sys_soundout is constantly 0.

I have tested by adding a variable count to the functions and a line if (*p != 0) count++; in the copy loop (e.g. line 152), returning this variable and then outputting the value to the command line. These functions still return 0.

Subscribing to control rate receivers is no problem.

Here are my changes to the code for the test:

int libpd_process_raw(const float *inBuffer, float *outBuffer) {
  size_t n_in = sys_inchannels * DEFDACBLKSIZE;
  size_t n_out = sys_outchannels * DEFDACBLKSIZE;
  t_sample *p;
  size_t i;
  size_t count = 0;
  sys_microsleep(0);
  for (p = sys_soundin, i = 0; i < n_in; i++) {
    *p++ = *inBuffer++;
  }
  memset(sys_soundout, 0, n_out * sizeof(t_sample));
  SCHED_TICK(sys_time + sys_time_per_dsp_tick);
  for (p = sys_soundout, i = 0; i < n_out; i++) {
    if (*p1 != 0) count++; \
    *outBuffer++ = *p++;
  }
  return count;
}

and

#define PROCESS(_x, _y) \
  int i, j, k; \
  int count = 0;\
  t_sample *p0, *p1; \
  sys_microsleep(0); \
  for (i = 0; i < ticks; i++) { \
    for (j = 0, p0 = sys_soundin; j < DEFDACBLKSIZE; j++, p0++) { \
     for (k = 0, p1 = p0; k < sys_inchannels; k++, p1 += DEFDACBLKSIZE) {�\
        *p1 = *inBuffer++ _x; \
      } \
    } \
    memset(sys_soundout, 0, sys_outchannels*DEFDACBLKSIZE*sizeof(t_sample)); \
    SCHED_TICK(sys_time + sys_time_per_dsp_tick); \
    for (j = 0, p0 = sys_soundout; j < DEFDACBLKSIZE; j++, p0++) { \
      for (k = 0, p1 = p0; k < sys_outchannels; k++, p1 += DEFDACBLKSIZE) { \
        if (*p1 != 0) count++; \
        *outBuffer++ = *p1 _y; \
      } \
    } \
  } \
  return count;

Great. Pinging @nettoyeurny

After playing around with this issue once again: I think, the problem is with starting DSP from C#. Is there a way to test, if libpd is running DSP?

Not from the C layer I think, but you could add a boolean in the C# layer that gets set when the computeAudio function wrapper is called.

I have already subscribed to the pd receiver and get a message dsp and argument 1 back, so that receiver gets the message for starting.

Hah yeah, you could do it that way. I was thinking your question was "how can I query the state of DSP whenever I want from code."

I am not sure if the message does the right thing inside libpd, so a query for enabled DSP could help me with locating the error.

There is a unit test, and there the out buffer is not constantly zero. There must be some other problem with my sample code.

It seems that processing is not done correctly. When I use this patch, and add values to the in buffer, then this data is copied to the out buffer. Whenever I insert something in the signal way, e.g. [*~ 0.9], then out buffer is constantly zero. Adding [*~ 1] does not zero out the output.

If you share your sample code, I'll see whether I can reproduce this on my side.

This is really strange: A stripped down C version of the code is working correctly. I will compare my C# platform invoke code with the C version line by line.

Here is the code in question: https://github.com/residuum/libpd/tree/csharp-linux

My current situation:
I am still not able to find the problem. With a C version, everything is working correctly. In C#, this patch generates a correct output:

 [osc~ 440]
 |\
 | \
 [dac~]

While this patch generates constant 0:

 [osc~ 440]
 |
 [*~ 0.9]
 |\
 | \
 [dac~]

The C# test code:

[TestFixture]
class LibPdOutTests
{
    [Test]
    public static void TestGeneratedStereoMultiplied()
    {
        LibPD.OpenAudio(0, 2, 44100);
        LibPD.OpenPatch(@"../../test_csharp2.pd");
        LibPD.ComputeAudio(true);
        float[] outBuffer = new float[512];
        int err = LibPD.Process(2, null, outBuffer);
        Assert.AreEqual(0, err);

        int notEmpty = outBuffer.Count(f => Math.Abs(f) > 0.01);
        Assert.AreNotEqual(0, notEmpty);
        LibPD.Release();
    }

    [Test]
    public static void TestGeneratedStereo()
    {
        LibPD.OpenAudio(0, 2, 44100);
        LibPD.OpenPatch(@"../../test_csharp3.pd");
        LibPD.ComputeAudio(true);
        float[] outBuffer = new float[512];
        int err = LibPD.Process(2, null, outBuffer);
        Assert.AreEqual(0, err);

        int notEmpty = outBuffer.Count(f => Math.Abs(f) > 0.01);
        Assert.AreNotEqual(0, notEmpty);
        LibPD.Release();
    }
}

Have you tried instrumenting the [*~] object to see what's going on? I would add log statements to all parts of it, i.e., times_setup, times_new, times_dsp, times_perform, etc. in d_arithmetic.c, and compare the output from C and C#.

Also, in case you haven't tried this yet, make sure to hook up a print callback and see whether Pd logs any errors when loading or performing your patch.

I think you mean the scalartimes_* functions, but I will try that later.

OK, the problem is in times_new(): When I insert a post into the function and subscribe to the post, then x->x_g is 0. (but with a , as decimal separator, most likely because my locale is German, this comes from printf using the locale, so probably false alarm.)

Here is the change:

static void *times_new(t_symbol *s, int argc, t_atom *argv)
{
    if (argc > 1) post("*~: extra arguments ignored");
    if (argc)
    {
        t_scalartimes *x = (t_scalartimes *)pd_new(scalartimes_class);
        floatinlet_new(&x->x_obj, &x->x_g);
        x->x_g = atom_getfloatarg(0, argc, argv);
        outlet_new(&x->x_obj, &s_signal);
        post("setup_times: %f", x->x_g);
        x->x_f = 0;
        return (x);
    }
    else
    {
        t_times *x = (t_times *)pd_new(times_class);
        inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
        outlet_new(&x->x_obj, &s_signal);
        x->x_f = 0;
        return (x);
    }
}

And the output is then setup_times: 0,000000\n

I'm unfamiliar with the implications of changing the locale in C, but is it possible that in your setup, Pd is trying to parse German-style decimals, i.e., the value 0.9 might be invalid, and then Pd's attempt to parse it might fail silently?

I would try two things right now. First, use the default locale and see what happens. Second, with the German locale, see whether multipliers like 2, 3, and 0,9 are handled correctly.

Here's some evidence supporting my guess: http://stackoverflow.com/questions/13919817/sscanf-and-locales-how-does-one-really-parse-things-like-3-14

According to one of the answers, setlocale(LC_NUMERIC, "C"); might solve the problem with decimals while leaving the rest of your locale intact.

Setting the locale to Posix (with export LANG=) is making libpd load the file correctly. What is strange, is that changing the number to 0,9 does not load the patch correctly with "error: canvas: no method for 'float'\n, probably because the split for messages at comma is done before parsing the input.

So we've got a diagnosis, then: unfortunate interactions with non-default locales. Would you mind checking whether calling setlocale(LC_NUMERIC, "C"); at the beginning fixes the problem? If it does, then we may want to add that to libpd's init function.

Setting locale as libpd_init() does load the file correctly.

I do not think, that setting the locale at libpd init is the correct way to do it, as this sets the locale for the whole application, if you write a C application. People probably do not want that. It should only be done at loading and writing files, similar to my patch for petri-foo: https://github.com/jwm-art-net/Petri-Foo/pull/20/files

Good point. We may be stuck with two bad choices, though --- either override the locale or leave people with hard-to-debug parsing problems. Either way, somebody will be grumpy.

In any case, let's confirm that setlocale(LC_NUMERIC, "C"); solves the problem. If it does, we can figure out what to do about it.

Yes, setlocale(LC_NUMERIC, "C"); at libpd_init() does solve the problem.

On another note: How are lists sent to libpd? Are floats there also set as strings and then parsed later on, or are these real floats (no pun intended)?

It could be a build option so users have some control if they want it or not.

I prefer the route to setlocale(LC_NUMERIC, "C"); on every function that takes strings and convert those to numbers at entry and then add setlocale(LC_NUMERIC, ""); on leave. This is simple work, that I can do over the next week, only the functions need to be identified.

If we must patch Pd's source, then we should get consent from @millerpuckette as well.

Before you do anything, post the issue and possible solution to the pd-dev list. That's the best way to get the ball rolling and hear about possible approach, issues, etc. Include a link to this issue page too.

Another puzzling question is: Why did it not surface before, e.g. on Android phones with different locale? What is special about C#?

@danomatika writing mail right now.

Note that in mingw, you might have to call putenv("LC_NUMERIC=C") instead of setlocale(LC_NUMERIC, "C"). Don't remember the details. Had the same problem in libpds.

Maybe I am wrong, but after reading the wrapper files, only libpd_openfile() seems to be affected, as all other functions are already typed, and no string conversion is done.

On the other hand: What platforms are really bitten by this bug? So far, only C# on Linux seems to be affected.

Don't remember the details.

What platforms does this affect?

Think this had something to do with Qt, not libpd (when a qt program was hosting libpd/libpds). Qt has some of the same problems as scanf/etc when converting between floats and strings, but in Qt the only workaround I found was setting then environment variable "LC_NUMERIC" to "C", i.e. it was not enough to call setlocale:

#if !defined(FOR_WINDOWS)
  setenv("LC_NUMERIC", "C", 1);
#endif

  // for mingw
  putenv(V_strdup("LC_NUMERIC=C"));

So it seems the only reason for using putenv here is that setenv is/was not available under mingw.

I also had the same problem in libpd, but there it was apparently enough just to call setlocale:
kmatheussen@9d7d52c

Thank you @kmatheussen .

As I wrote yesterday, only libpd_openfile() seems to be affected, and every platform supports locale.h and setlocale(). Just to be sure, I have wrapped the calls:

residuum@b73267b

Changing libpd_openfile is a partial solution at best; there are other calls that read values from files, and this functionality can also be invoked by messages within Pd. I don't think it's desirable or even possible to find every potential string conversion and wrap locale changes around it.

Moreover, as @kmatheussen reports, there are things like Qt that seem to set the locale for their purposes, and my understanding is that the locale API is problematic in a concurrent setting.

So, if we're willing to deal with non-default locales at all, let's just override LC_NUMERIC on init, using setlocale if we can or setenv if we must.

@kmatheussen, is it possible that we might avoid the setenv call if we initialize things in the right order, e.g., Qt first, then libpd?

I would prefer libpd not to change LC_NUMERIC for the whole application.

All work that the code of Pd is doing is wrapped by a function call in a function in libpd_wrapper, am I right?

Setting LC_NUMERIC to C and back could be done at the entry and leave of every function. Both functions are macros, that can be changed according to some variable in the Makefile.

PRO: That is the cleanest solution, because it also includes the principle of least surprise: Why would an audio library change the number format? Why does it break things, when I change the locale in the program?

CONTRA: Setting and unsetting LC_NUMERIC in every call means setting and unsetting it on every processing of a small buffer. That can be an overhead in complex patches on limited platforms (think of a game on Android that is maxing out old devices).

@kmatheussen, is it possible that we might avoid the setenv call if we initialize things in the right order, e.g., Qt first, then libpd?

I don't remember the details about this, just that I had trouble forcing qt to do what I want. Maybe I didn't have to call setenv at all, maybe it was enough to call setlocale, but it doesn't look so from my code.

There are two things which are important here:

  1. The general concept of LC_LOCALE is probably creating more trouble than it its trying to solve. I think the purpose of this variable is how numbers are displayed to the user, but unfortunately it is also used when converting between floats and strings internally in a computer program.
  2. setenv() only affects the current process.

So I think the only bad consequence of calling setenv("LC_NUMERIC", "C", 1) during initializing of a program, would be that, for instance, 1000.0 could be displayed as "1000.0" instead of "1000,0" for french users. Another situtaion would be if a french person reads in floats from a file which had previously been stored in the "1000,0" format, and that would now fail since LC_NUMERIC suddenly has changed. The good thing is that new files now will be compatible across different languages.

This program demonstrates how bad LC_NUMERIC is:

#include <string.h>
#include <stdio.h>
#include <locale.h>
#include <stdlib.h>

int main(){

  float original = 50.2;
  float read_back;

  {
    setlocale(LC_NUMERIC, "fr_FR");
    FILE *f = fopen("/tmp/test.txt","w");  
    fprintf(f, "%f\n",original);
    fclose(f);
  }

  {
    setlocale(LC_NUMERIC, "C");
    FILE *f = fopen("/tmp/test.txt","r");

    char temp[100] = {0};
    fgets(temp, 99, f);

    read_back = atof(temp);

    fclose(f);
  }

  printf("Original: %f, read_back: %f\n", original, read_back);

  return 0;
}
[kjetil@ttlush test]$ gcc -Wall numeric.c && ./a.out 
Original: 50.200001, read_back: 50.000000

That was exactly the situation, that I have addressed with this pull
request to Petri-Foo: petri-foo/Petri-Foo#20

I have even made it compatible with loading files with locale with comma
as decimal separators.

I think it's better to set it globally to be sure LC_NUMERIC never has a different value than "C". And in pd, you can't go and fix manually like this because of externals.

What's the resource cost for setting the locale? Maybe it would very slow to set and unset all over the place anyway. I would check...

Also, should this be handled inside libpd or should it be something noted in headers / wiki and be left up to the programmer, at least at the C level?

enohp ym morf tnes

Dan Wilcox
danomatika.com
robotcowboy.com

On Apr 30, 2016, at 6:03 AM, Kjetil Matheussen notifications@github.com wrote:

I think it's better to set it globally to be sure LC_NUMERIC never has a different value than "C". And in pd, you can't go and fix manually like this because of externals.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub

Setting and resetting the locale on every libpd call is neither clean nor a solution. Keep in mind that we're in a concurrent setting here --- in most cases, we'll have at least an audio thread and a GUI thread.

Best case, we'll see the GUI render numbers in different ways, depending on whether the audio thread is currently in a libpd call or not.

Worst case, we might see hard-to-reproduce crashes if the GUI is using the locale when the audio thread changes it; my understanding is that the locale API is not thread safe.

In any case, I think @danomatika's suggestion is right. Locale is not an audio issue, and so libpd should stay away from it. As a courtesy, we might choose a sane locale in libpd_init and document that applications need to leave LC_NUMERIC alone. Alternatively, we could just document that developers need to reset LC_NUMERIC when working in a setting that's affected by locales.

I wonder if this has come up before for regular pd. I did a string search in the source but there are no calls to locale anywhere. I also don't remember running into this issue as a user, although that's probably because I'm almost alway using en_US.

Maybe it hasn't come up before is that most users use either Miller's binary or the extended binary, both of which we're probably built in an English locale. That being said, what's important here? User locale and/or build locale?

enohp ym morf tnes

Dan Wilcox
danomatika.com
robotcowboy.com

On May 1, 2016, at 7:19 AM, Peter Brinkmann notifications@github.com wrote:

Setting and resetting the locale on every libpd call is neither clean nor a solution. Keep in mind that we're in a concurrent setting here --- in most cases, we'll have at least an audio thread and a GUI thread.

Best case, we'll see the GUI render numbers in different ways, depending on whether the audio thread is currently in a libpd call or not.

Worst case, we might see hard-to-reproduce crashes if the GUI uses the locale when the audio thread changes it; my understanding is that the locale API is not thread safe.

In any case, I think @danomatika's suggestion is right. Locale is not an audio issue, and so libpd should stay away from it. As a courtesy, we might choose a sane locale in libpd_init and document that applications need to leave LC_NUMERIC alone. Alternatively, we could just document that developers need to reset LC_NUMERIC when working in a setting that's affected by locales.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub

Oh, I just discovered something. I guess this is already known for many of you, and I"ve just been confused, but anyway: LC_NUMERIC is not that dangerous. Even if your LC_NUMERIC environment variable is set to "fr_FR", programs should behave as if setlocale(LC_NUMERIC, "C"); was called in the start of the program.

However, if you run a Qt program, this is not the case since Qt seems to call setlocale(LC_NUMERIC, getenv("LC_LOCALE")) during initialization.

So for Qt programs, calling setlocale(LC_NUMERIC, "C"); will only work if it's done after Qt is initialized, i.e after the call to either QCoreApplication app (argc, argv); or QApplication app (argc, argv);.

This doesn't work:

  setlocale(LC_NUMERIC, "C");
  QCoreApplication app (argc, argv);

This partly works:

  QCoreApplication app (argc, argv);
  setlocale(LC_NUMERIC, "C");

...as long as you don't use any of the Qt functions to convert between floats and strings.

This always works:

  setenv("LC_NUMERIC", "C");
  QCoreApplication app (argc, argv);

Fix is in form PR by @residuum.

Setting the default "C" numeric locale in libpd_init() is now a makefile option & define -DLIBPD_SETLOCALE:

make SETLOCALE=true

It is off by default.

Note: Based on #180, I decided to change the default to set LC_NUMERIC. There is info in the readme about this and the compiler define is now "LIBPD_NO_LOCALE" as it is now used to negate this default.