LPC Playback Formant-Shifting


#63

clock_phase should wrap at (and by) 65536.


#64

Ah, thank you!


#65

It still clicks a little bit, unfortunately.


#66

Well it’s hard for me to troubleshoot code I cannot fully see and cannot run.


#67

Absolutely, sorry. That was more of a note-to-self, really. I really appreciate the advice you’ve given me. I’m going to add getting rid of the clicks to the to-do list, and tie up some other loose ends before coming back to it later, I think.

Thanks again.


#68

Got rid of the clicks :slight_smile:

It sounds like a low-pass filter. Definitely rounds off the sharp edges, on lower Formant settings, but takes a bit too much off the top, at medium to high Formant.

I think make it only interpolate at lower formant settings, and maybe add a crossfade, between interpolated and non-interpolated output so it’s filtered more with lower settings.

Thanks again for all your help, @pichenettes!


#69

Incidentally, I was researching LPC encoding earlier, and came across a forum message of yours from 2012 on the subject. Have you worked with LPC before?


#70

Going back to something you said earlier about the equations involved in AR models and their application to speech-processing being “not pretty-looking”, did you mean that they’re difficult mathematically? Or computationally?

Do you think the equations required to shift LPC formants up and down would be difficult? You imply that to do that with the lattice filter ‘K’ coefficients would be very difficult (potentially not possible?).

If it’s relatively easy to shift formants using the LPC coefficient values, and then converting them back to reflection coefficients/PARCORs, I guess that suggests two things:

  1. Is it feasible on a Cortex M4 or equivalent to convert from K to LPC values, manipulate the values, then convert back to lattice filter coefficients in realtime at a reasonable frame-rate, and
  2. is it worth investigating re-encoding word data using the LPC coefficient data, rather than the reflection coefficients, in order to eliminate one of the conversion steps?

These may of course be stupid questions. Please pardon my ignorance, if so.


#71

Have you worked with LPC before?

Yes. You can’t stay long in an audio signal processing career without encountering it.

Just not obvious and pretty. But nothing impossible computationally.

The conversion between lattice filter coefficients and other flavours of coefficients is an easy step.


#72

Cool, so it would be feasible to convert both ways at runtime, I guess.


#73

Do these look like the right conversion functions?

They’re from this file, but the naming is confusing.

vec poly2rc(const vec &a)
 {
   // a is [1 xx xx xx], a.size()=order+1
   int   m, i;
   int    order = a.size() - 1;
   vec k(order);
   vec any(order + 1), aold(a);
 
   for (m = order - 1;m > 0;m--) {
     k[m] = aold[m+1] ;
     if (fabs(k[m]) > 1) k[m] = 1.0 / k[m];
     for (i = 0;i < m;i++) {
       any[i+1] = (aold[i+1] - aold[m-i] * k[m]) / (1 - k[m] * k[m]);
     }
     aold = any;
   }
   k[0] = any[1];
   if (fabs(k[0]) > 1) k[0] = 1.0 / k[0];
   return k;
 }
 
 vec rc2poly(const vec &k)
 {
   int  m, i;
   vec a(k.length() + 1), any(k.length() + 1);
 
   a[0] = 1;
   any[0] = 1;
   a[1] = k[0];
   for (m = 1;m < k.size();m++) {
     any[m+1] = k[m];
     for (i = 0;i < m;i++) {
       any[i+1] = a[i+1] + a[m-i] * k[m];
     }
     a = any;
   }
   return a;
 }

#74

Ah, the name isn’t very confusing… that will be ‘polynomial to reflections coefficients’, and vice-versa, I guess.

These must be the ones. I was expecting it to be more complicated…


#75

I’ve converted the functions from the library above to work with standard C arrays, instead of vectors.

// Convert reflection coefficient array to linear prediction coefficient polynomial array
void rc2poly(const float *in_rc, float *out_poly) {
    int  m, i, j;
    float any[11];
    out_poly[0] = 1;
    any[0] = 1;
    out_poly[1] = in_rc[0];
    for(m = 1; m < 10; m++) {
        any[m + 1] = in_rc[m];
        for (i = 0; i < m; i++) {
            any[i + 1] = out_poly[i + 1] + out_poly[m - i] * in_rc[m];
        };
        for(j = 0; j < 11; j++)
            out_poly[j] = any[j];
    };
};

// Convert linear prediction coefficient polynomial array to reflection coefficient array
void poly2rc(const float *in_poly, float *out_rc) {
    int j, m, i, k;
    float any[11] = {0};
    float aold[11] = {0};
    for(j = 0; j < 11; j++)
        aold[j] = in_poly[j];
    for(m = 10; m > 0; m--) {
        out_rc[m] = aold[m + 1];
        if(fabs(out_rc[m]) > 1) out_rc[m] = 1.0 / out_rc[m];
        for (i = 0; i < m; i++) {
            any[i + 1] = (aold[i + 1] - aold[m - i] * out_rc[m]) / (1 - out_rc[m] * out_rc[m]);
        };
        for(k = 0; k < 10; k++)
            aold[k] = any[k];
    };
    out_rc[0] = any[1];
    if(fabs(out_rc[0]) > 1) out_rc[0] = 1.0 / out_rc[0];
};

I’ve cast the fixed-point (10-bit signed) reflection coefficients to a floating-point array, created a float linear-prediction polynomial array from them (0 - 1 range), then cast back to integer for output to the filter.

Now, for the fun bit… (or not so much)…

What do I do to the LP coefficients to shift the filter peaks up and down in a controllable manner?

I’m afraid at this point, I’m a bit lost…


#76

I guess the task breaks down into:

  1. Convert reflection coefficients to linear prediction polynomials
  2. Calculate 5x filter pole centre-frequencies and widths from linear prediction polynomial pairs
  3. Shift filter pole center frequencies, preserving the proper relationship between them
  4. Reverse step 2 to convert shifted polynomials back to reflection coefficients

I have steps 1 and 4 in the bag, I think. Unless the conversion functions I was using are not the correct ones for the all-pole lattice filter I’m using.

I’d love to know if I’m even on the right track, here.


#77

Yes that’s the way to do it!


#78

Great!

And can I ask, does the rc to poly function above look correct for the kind of all-pole 10th-order lattice filter used in the original TI toys?


#79

Should be good! Maths is maths :slight_smile:


#80

Good to know!


#81

I was looking at a paper than describes the use of LSFs as a way of manipulating formant parameters.

Would converting the LP polynomials I have to an LSF representation make formant manipulation easier, do you think?

The conversion to LSF seems quite complex (and maybe computationally expensive).


#82

True. But I would expect to see different numbers for different target filter types. Maybe that’s not the case, though.