## LPC Formant-Shifting

Started by 1 year ago3 replieslatest reply 1 year ago112 views

Hi,

I'm looking for the least computationally-intensive (preferably integer-only) way to shift formants up/down in an LPC playback system.

I'm reading data from a bitstream, compressed to (something like) the scheme used in the original Texas Instruments Speak & X toys, so, after looking up the encoded indices in the relevant lookup tables, I have 10 signed 10-bit reflection coefficients for use with 10-bit integer lattice filter.

I've been told that it's probably not worth trying to manipulate the reflection coefficients directly, so I've added a function to convert reflection coefficients to floating-point Linear Prediction filter coefficients.

Can anyone offer any advice on what my next move might be?

Ideas I've been kicking around:

- Find root of LP polynomials, shift and re-create polynomials

- Convert LP coefficients to LSFs, manipulate those, and convert back to LP > RC

I don't have enough of a grasp of the maths involved in either approach to know if they might be possible on a relatively low-power device.

A slight complication is that the platform it will be running on is based around an ARM Cortex M4, so I don't have cycles to burn, and though the processor does have an FPU, an integer-only approach could be preferable.

Oh, and I'll be writing in C/C++.

[ - ]
Reply by April 19, 2018

You are correct in what has to be done:

1. Root the LPC denominator polynomial (using regula falsi or Bairstow or Halley).

2. Modify the pole angles and recompute the complex poles (with the original abs).

3. Find the coefficients of the new denominator polynomial.

4. I'm assuming that you are doing some kind of overlap and add, but am worried that arbitrary modification on each LPC frame might create noticeable energy jumps. You may have to smooth the energy.

Using LSF might reduce the rooting complexity a bit, but if you are working with 4 or 5 formants the rooting operation is not going to be a problem.

The rooting should be done with the FPU. All the rest can be done using integer arithmetic.

Y(J)S

[ - ]
Reply by April 19, 2018

Hi,

thank you very much for getting back to me so quickly, and for the pointers.

I will digest what you said :)

It's good to know I'm on more-ore-less the right track, anyway.

Thanks again.

[ - ]
Reply by April 20, 2018

Managed to find some root-finding C code here:
http://www.crbond.com/roots.htm

It seems to generate some numbers…

Here are the 10-bit reflection coefficients I started with (they’re not dumped from an actual frame, so they may not make much sense, but they’re all valid numbers randomly-picked out of the LPC coefficients lookup table):

<code>
-378,
-14,
35,
64,
-25,
71,
19,
-14,
56,
151
</code>

Using the rc2poly() function from http://itpp.sourceforge.net/4.3.0/lpcfunc_8cpp_sou..., I get the following floating-point LP coefficients:

<code>
1.0,
-0.690895081,
-0.0964249223,
0.0166289061,
0.15995276,
-0.174300209,
0.149925292,
0.0496155433,
-0.125319779,
-0.103898391,
0.294921875
</code>

Then, running those through the root-finder, I get (real numbers on the left, imaginary on the right:

<code>
0.271052,  0.906338
0.271052, -0.906338
-0.756533,  0.567256
-0.756533, -0.567256
0.778857,  0.561891
0.778857, -0.561891
-0.395675,  0.890396
-0.395675, -0.890396
-1.039200,  0.000000
0.948876,  0.000000
</code>

Unfortunately, I have no real idea if these are likely correct.

The comments in the root-finder file say that the coefficients should be entered in high to low order, so I reversed the array of LP coefficients, but I wasn’t sure whether I should include the initial 1.0 at the end of the reversed array.

I get quite different results if I remove it:

<code>
-0.628930,  0.848958
-0.628930, -0.848958
0.901539,  0.700048
0.901539, -0.700048
0.208433,  1.063890
0.208433, -1.063890
-1.046016,  0.444123
-1.046016, -0.444123
1.104033,  0.000000
0.378207,  0.000000
</code>

If I may ask, do they look OK to you?