LPC Playback Formant-Shifting


#1

I’m intrigued to hear that LPC playback is available in the new Plaits module. Can’t wait to play with that, when mine arrives :smile:

In the meantime, I’ve started tieing up some loose ends with my LPC object set for Axoloti, with a view to eventually releasing them quite soon.

One of the things I never properly worked out how to do, but that seems to be a prominent part of other musical LPC implementations is formant-shifting, specifically formant-shifting without also shifting the pitch of the voiced source/carrier wave.

I’ve found it’s possible to change the pitch of the whole synthesis system by altering the sample-rate the 10-pole filter runs at, but since it runs natively at 8kHz, I only have 6 multiples of the fixed 48k sample-rate of the Axoloti going up, and of course, sub-dividing the already very low 8kHz, things quickly turns to mush.

Any advice from someone who knows more about DSP than I do would be greatly appreciated.


Mutable Instruments Plaits
#2

Formant shifting in Plaits works exactly like you describe: alter the sample rate and compensate by shifting the f0 up or down.

So your question is not about LPC but about running a synthesis algorithms at a different (fractional) sample rate than the native rate. You’re lucky here because you don’t care about nice interpolation: after all, a circuit-bent speak-and-spell won’t have a reconstruction filter, so the waveform at 0.2x the original rate is obtained by rendering a sample and repeating it 5 times, yielding a square-ish, S&H-ish waveform. For rates in-between this is similar to rendering band-limited clocked noise. You need “soft edges” whenever samples on the slower clock do not line up with samples of the native clock, and these soft edges can be synthesized using polybleps, just like when synthesizing a square waveform!

The inner loop of my LPC code looks like this:

  while (size--) {
    float this_sample;
    this_sample = next_sample_;
    next_sample_ = 0.0f;
    clock_phase_ += rate;
    if (clock_phase_ >= 1.0f) {
      clock_phase_ -= 1.0f;
      float reset_time = clock_phase_ / rate;
      float new_sample = synth_.RenderSample();
      float discontinuity = new_sample - sample_,
      this_sample += discontinuity * ThisBlepSample(reset_time);
      next_sample_ += discontinuity * NextBlepSample(reset_time);
      sample_ = new_sample;
    }
    next_sample_ += sample_;
    *output++ = this_sample * gain;
  }

rate is the ratio between the playback rate and the sample rate. For example, if 48kHz is my output rate, I need to set ratio to 0.16667f to invoke the LPC code at 8kHz.


#3

This is great, thanks @pichenettes!

Is the code of the ThisBlepSample() function in any of publicly-available source code in your GitHub repo?

can this technique be applied to any arbitrary synthesis algorithm?


#4

https://github.com/pichenettes/modules/blob/master/warps/dsp/oscillator.h#L80

Yes. As long as you don’t mind the harmonic-rich, sort-of-bitcrushed sound that is caused by using a zero-order hold instead of a proper resampling filter.


#5

Ah, this bit:

  static inline float ThisBlepSample(float t) {
    return 0.5f * t * t;
  }
  static inline float NextBlepSample(float t) {
    t = 1.0f - t;
    return -0.5f * t * t;
  }

I’m trying to pick my way through your LPC loop.

The variable ‘sample_’ is defined outside the loop, and is persistent, presumably.

‘size’ is the buffer size, I guess. I’ve never seen this abbreviated form of a while loop before.


#6

If the buffer is only 16 samples long (as it is in the Axoloti’s case), would that have an impact on the precision or range of possible formant-shifting?


#7

Why would the buffer length has any effect?

Buffer length on Plaits is 12 samples.

Really, think of it as a square oscillator, except that the height of each segment of the square wave is a sample read from the LPC synth.


#8

Still haven’t got around to implementing this, but it’s on my list :slight_smile:

I think I may have asked this before, so apologies if I’m repeating my self @pichenettes. What do you think are the chances of making a realtime TI-style LPC10-encoder work on a CortexM4?


#9

Easy. You won’t even use half the CPU power at 72MHz.


#10

Almost half… well, that’s not that easy… hehe.
Still, my LPC playback objects are quite lean, so should be OK.

Encouraging, thank you!


#11

Axoloti runs a STM32F4 MCU, at 168Mhz, apparently, so should be some cycles left over for other stuff.

I realise your estimate is predicated on it being a competent programmer who is writing the code to to the encoding, of course (not the scenario here, sadly)… :wink:


#12

Incidentally, and just out of interest, did you record the Plaits LPC words yourself, and if so, whose was the voice, and what did you use to do the LPC encoding?


#13

QBOX Pro and the OS X speech synth :smiley:


#14

:slight_smile:

Did you try BlueWizard?


#15

No… I wasn’t even aware of its existence when I developed Plaits!


#16

Any plans for anything else LPC-related in the future? It seems to be flavour-of-the-month at the moment :wink:

Quick question: can the float ‘rate’ value be used directly as a multiplier for the voiced source/f0 phase-accumulator value, or is some conversion required?


#17

No you should use the same value for shifting the pitch up/down to compensate the playback rate adjust.


#18

Thank you :slight_smile:


#19

Just started implementing this. My LPC playback code is integer-only, so currently, it’s working, but I’m having to cast the output of the LPC filter from int to float to use the blep function.

I’m sure it’s possible to convert the two blep functions to integer operation, but do you think it’s worth it?

Also, can I ask if you use any special scaling function for the ‘rate’ value. Since pitch is exponential, I’m tempted to use an exponential scale, but then the centre value of the parameter doesn’t correspond to the centre of the range.

Sorry for the stupid questions…


#20

-2 to +2 octaves (0.25 to 4).

Integer version of polyblep functions: