DSP Labs
  • INTRODUCTION
  • BILL OF MATERIALS
  • 1. OVERVIEW AND INSTALLATION
    • 1.1 Hardware
    • 1.2 Software
      • CubeMX
      • SW4STM32
      • Eclipse tips
    • 1.3 First project!
  • 2. AUDIO PASSTHROUGH
    • 2.1 Audio I/O theory
      • Microphone
      • Stereo decoder
    • 2.2 Updating peripherals
    • 2.3 Wiring audio I/O
    • 2.4 Coding passthrough
  • 3. ALIEN VOICE EFFECT
    • 3.1 How it works
    • 3.2 Real-time DSP tips
    • 3.3 Real-time with Python
    • 3.4 C implementation
  • 4. DIGITAL FILTER DESIGN
    • 4.1 Design approaches
    • 4.2 Real-time implementation
  • 5. GRANULAR SYNTHESIS
    • 5.1 How it works
    • 5.2 Implementation
  • 6. LINEAR PREDICTION
    • 6.1 Theory behind LPC
    • 6.2 Implementation
  • 7. DFT PITCH SHIFTING
    • 7.1 How it works
    • 7.2 Python implementation
Powered by GitBook
On this page
  • Modifying the process function
  • 1. Compute the LPC coefficients for the input speech
  • 2. Inverse-filter the raw samples in order to estimate the excitation
  • 3. Apply pitch-shifting on the excitation signal
  • 4. Forward-filter the modified grain
  • Real-time implementation

Was this helpful?

  1. 6. LINEAR PREDICTION

6.2 Implementation

Previous6.1 Theory behind LPCNext7. DFT PITCH SHIFTING

Last updated 6 years ago

Was this helpful?

Below are the LPC utility functions provided in the .

def bac(x, p):
    # compute the biased autocorrelation for x up to lag p
    L = len(x)
    r = np.zeros(p+1)
    for m in range(0, p+1):
        for n in range(0, L-m):
            r[m] += x[n] * x[n+m]
        r[m] /= float(L)
    return r


def ld(r, p):
    # solve the toeplitz system using the Levinson-Durbin algorithm
    g = r[1] / r[0]
    a = np.array([g])
    v = (1. - g * g) * r[0];
    for i in range(1, p):
        g = (r[i+1] - np.dot(a, r[1:i+1])) / v
        a = np.r_[ g,  a - g * a[i-1::-1] ]
        v *= 1. - g*g
    # return the coefficients of the A(z) filter
    return np.r_[1, -a[::-1]]


def lpc(x, p):
    # compute p LPC coefficients for a speech segment
    return ld(bac(x, p), p)

The function bac is sufficient from a real-time microcontroller "point of view" as it performs operations sample-by-sample in order to compute the entries of the (biased) autocorrelation matrix RRR. For our microcontroller implementation in C, we may, however, wish to pre-allocate a global array for r, as its values will change for each grain.

The function ld (for performing the Levinson-Durbin recursion) is not suitable for a microcontroller C implementation, as we have array operations (np.dot) and memory is allocated on the fly (np.r_ concatenates values into a new row vector).

We will therefore re-implement the ld function so that porting it to C will be much more straightforward. Below we provide you an incomplete function ld_eff that is meant to implement Levinson-Durbin recursion in a "C-friendly" manner.

def ld_eff(r, order):
    # solve the toeplitz system using the Levinson-Durbin algorithm
    a = np.ones(order+1)
    a_prev = np.ones(order)
    a[1] = r[1]/r[0]
    for p in range(2, order+1):

        for j in range(1, p):
            a_prev[j] = a[j]

        # TODO: compute `k` from `r` and `a`
        k = 1

        # TODO: compute new `a` with `a_prev` and `k`
        # separate vector is needed so we don't overwrite!
        for j in range(1, p):
            a[j] = a_prev[j]
        a[p] = k

    # by convention, have negative of coefficients
    for p in range(1, order+1):
        a[p] *= -1

    return a

As for bac, for our microcontroller implementation of ld_eff in C, we may wish to pre-allocate global arrays for a and a_prev.

Modifying the process function

In fact, it is possible to use the same function for "vanilla" and LPC granular synthesis pitch shifting. We can do this by introducing a boolean variable use_LPC.

def process(input_buffer, output_buffer, buffer_len):

    # TODO: need to specify those global variables changing in this function (state variables and intermediate values)
    # copy from granular synthesis
    global ...

    if USE_LPC:
        global lpc_coef, lpc_prev_in, lpc_prev_out

    # TODO: append samples from previous buffer
    # copy from granular synthesis
    for n in range(GRAIN_LEN_SAMP):
        ...

    # TODO: obtain the LPC coefficients and inverse filter the grain to esimtate excitation
    if use_LPC:
        # compute LPC coefficients, cast input to `np.float32`
        lpc_coef

        # estimate excitation
        lpc_prev_in

    # TODO: resample grain
    # copy from granular synthesis
    for n in range(GRAIN_LEN_SAMP):
        ...

    # TODO: forward filter the resampled grain
    if use_LPC:
       lpc_prev_out

    # TODO: apply window
    # copy from granular synthesis
    for n in range(GRAIN_LEN_SAMP):
        ...

    # TODO: write to output and update state variables
    # copy from granular synthesis
    for n in range(GRAIN_LEN_SAMP):
        # overlapping part
        if n < OVERLAP_LEN:
            ...
        # non-overlapping part
        elif n < STRIDE:
            ...
        # update state variables
        else:
            ...

Run the file and make sure the output is the same as before!

We can now begin adding the code for LPC! Let's remind ourselves of the steps we mentioned in the previous section:

1. Compute the LPC coefficients for the input speech

TASK 3: Complete the code after the comment # compute LPC coefficients, namely compute the LPC coefficients for the input raw samples.

Hint: use the function lpc_eff and cast the input raw samples to np.float32.

2. Inverse-filter the raw samples in order to estimate the excitation

TASK 4: Complete the code after the comment # estimate excitation, namely filter the raw input samples with the "recently" obtained LPC coefficients.

Hints:

  • You can rewrite into the concatenated raw samples vector, NOT input_buffer!

  • Don't forget to apply GAIN!

3. Apply pitch-shifting on the excitation signal

This is already done with your code from the granular synthesis effect!

4. Forward-filter the modified grain

TASK 5: Complete the code after the comment # forward filter the resampled grain, namely filter the resampled grain with the LPC coefficients.

Hints:

  • We are applying an IIR filter in this case.

  • You can rewrite into the resampled grain vector.

  • Use lpc_prev_out for the previous output samples.

If you notice some strange output, make sure you are casting (when appropriate) to int; this is a common point for mistakes.

Real-time implementation

Congrats on incorporating this LPC component to your granular synthesis pitch shifter! Given the Python implementation, the porting to C should be more straightforward. As noted earlier, it may be useful to pre-allocate memory for the LPC coefficients and their computation.

TASK 1: Complete the above function ld_eff in the file so that it correctly implements Levinson-Durbin recursion.

Hint: we refer you to (p. 5) in order to determine the correct expression for k and a[j].

You can test your implementation by running the script . The script should print CORRECT! if you have successfully implemented the function; otherwise it will print Something's wrong... or error out if you have a bug in your implementation.

Below, we provide the incomplete process function, which you can find in .

TASK 2: As a sanity check, you can first copy your code from your granular synthesis implementation into the above process function and the init function in the script . (Copy the appropriate lines under the comments # copy from granular synthesis.)

We are applying an FIR filter in this case; recall your implementation from the Digital Filter Design chapter, notably the code from . In this case input_buffer should be the concatenated raw samples vector, x should be lpc_prev_in, and there is no equivalent to y since this is an FIR filter.

And that's all the extra code needed for this LPC feature! Try out your completed script with the fixed WAV file (make sure use_LPC=True) and listen to the output to see if it sounds correct.

TASK 6: When your implementaton works with the fixed WAV file, you can complete the in order to run the effect in real-time with your laptop's soundcard.

In the , we implement a DFT-based pitch shifter. With this effect, we will shift our speech up in order to create a chipmunk-like voice; no need to inhale helium!

IPython notebook
utils_lpc.py
this document
test_lpc_utils.py
this script
granular_synthesis_LPC_incomplete.py
this script
granular_synthesis_LPC_incomplete.py
sounddevice template
next chapter