[.uhm] Karplus-Strong in Hive

Official support for: u-he.com
User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Post Sun Sep 16, 2018 6:07 am

In reference to this thread, which introduces wavetable synthesis to Hive, I'd like to start a couple of threads where enthusiastic folks can discuss specific methods of Wavetable generation using .uhm files.

To recall: .uhm files contain scripts which create and manipulate wavetables. To my best knowledge, these are the most powerful tool to synthesize wavetables from scratch. The possibilities exceed anything one could do inside a visual editor, and it does not rely on tedious workarounds to turn samples into wavetables. It's very powerful.

I want to encourage people to try things out. It's not overly difficult to start tweaking away. As someone recently said: Just do it!

Note that I deliberately do not post downloadable files. Please don't post any either. Let's keep hacking away rather than just consuming.

#----------------------------------------------------------------------

The basic principle of Karplus-Strong Synthesis is, there's a delay which is filled with noise. A lowpass filter in the feedback thins out the spectrum until only the fundamental (or just silence) remains after a while. The result sounds somewhere between cembalo, guitar and all sorts of plucks. The method has been altered and refined over the years to create bowed string sounds, mallets, tubes, woodwinds and abstract disharmonic tones. While naturally, wavetable synthesis can only reproduce a harmonic spectrum, many of the effects of KS and ist successors can be mimicked, and maybe even improved upon.

In the beginning of this thread I would like to start with a very basic concept and maybe we can move over to more complex tones later on.

So let's get started!

Chapter 1: Simple KS

Copy following lines in a text file which you save as "Simple KS.uhm" somewhere in your Hive Wavetables directory:

Code: Select all

Info "Simple Karplus-Strong-like wavetable. Use Auto and adjust to taste."
NumFrames = 101
Seed = 1346
Wave end=0 "rand * 2 - 1"
Wave start=1 end=100 "y + 0.15 * ( main_fi(frame-1, index) - y)"
Normalize base=all
Now, let's go through this line by line:

Code: Select all

Info "Simple Karplus-Strong-like wavetable. Use Auto and adjust to taste."
^ This will tweet something into the Info field of Hive's wavetable page.

Code: Select all

NumFrames = 101
^ 101 frames is my preferred length for wavetables, as frames 0-100 correspond to teh Wavetable knob's positions 0-100

Code: Select all

Seed = 1346
^ setting the random seed for the rand/rands/randf operations. Experiment with this for slight variations in tone, which will be more prominent in following examples.

Code: Select all

Wave end=0 "rand * 2 - 1"
^ Easy: Frame 0 is filled with white noise between -1 and +1

Code: Select all

Wave start=1 end=100 "y + 0.15 * ( main_fi(frame-1, index) - y)"
^ Here's the magic. We treat our wavetable frames like a filtered delay with 100% feedback. This line consists of two structural elements: a lowpass filter and a delay. The lowpass is a very simple 6-dB lowpass filter of the form

y = y + 0.15 * ( x - y )

And for each frame, the x in it always points to the same sample in the previous frame:

main_fi(frame-1, index)

If you're scratching your head right now, it really is utterly simple. Experiment with the 0.15 to see how it changes the spectral damping of the wavetable. Stay between 0.01 and 0.99.

Code: Select all

Normalize base=all
^ Always a good idea. Try "base=each" to make the dampened frames much louder. But this sill also revela a problem with Wavetable Synthesis which I'll address in the next chapter: The spectrum has a lot of energy in the high overtones. When you play a high note, those parts of the spectrum get filtered out so that high notes seem to "fade in" whereas bass notes sound natural.

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 6:30 am

Chapter 2: Improved Spectrum and Phase

Before you start with this, please play around with the script from Chapter 1.

Two things can be noticed: First of all, while scanning through the wavetable, the resulting waveform seems to shift to the right. Secondly, high notes sound much quieter or even somehow "fading in". In this second chapter I'll offer a remedy for both, taking Karplus-Strong-like synthesis a step further in Hive.

Code: Select all

Info "Karplus-Strong-like wavetable with balanced noise spectrum and phase correction"
NumFrames = 101
Seed = 73616
Spectrum start=0 end=0 "rand / (1 + 10 * phase)"
Phase start=0 end=0 "rands * 2 * pi - pi"
Wave start=1 "y + 0.15 * ( main_fi(frame-1, index) - y)"
Spectrum lowest=0 highest=0 "0" // remove DC offset
Phase "rands * 2 * pi - pi" // set all phases identical to frame 0
Normalize base=all
This is pretty much the same script and same sound, but a few things have been improved.

Code: Select all

Spectrum start=0 end=0 "rand / (1 + 10 * phase)"
Instead of white noise, we populate the first frame with a noise spectrum which is damped for higher overtones. Basically, each FFT-bin of our wavetable frame has a random volume, but it's divided by 1 for the fundamental and 11 for the highest overtone, as in the Spectrum command, the "phase"-variable goes from 0 to 1 from fundamental to "Nyqvist".

While this is a pretty cool spectrum for a KS-based wavetable, it's also somewhat "saw-ish" due to the strict phase relation of all overtones. One can hear this particularly in bass notes, where phase information becomes more apparent to the human ear. Hence:

Code: Select all

Phase start=0 end=0 "rands * 2 * pi - pi"
Phase (not to be confused with the "phase" variable in the formula parser) goes from -pi to +pi for each FFT bin. We simply randomize it for a more KS-ish feel.

The Wave command does the same delay/filtering as in the example of Chapter 1.

Code: Select all

Spectrum lowest=0 highest=0 "0" // remove DC offset
^ This trick is a staple in our arsenal, we just address FFT bin 0 of our wavetable and set it to 0.

Code: Select all

Phase "rands * 2 * pi - pi" // set all phases identical to frame 0
^ now, this line removes the "shift to the right" which we can observe with the simple filtering method. This shift will introduce pitching when the wavetable is scanned quick enough. While this is a typical artefact of Karplus-Strong synthesis, we can get rid of it easily in our Wavetable synthesis! Quite bluntly, we simply set all FFT-bins in all frames to the same phase information as we did for the Frame 0 above.

Now, this wavetable suffers much less from a "fade-in" effect on high notes, and it also remedies the phase-shift issue and its resulting pitch effect.

(Note: Setting Hive's WT interpolation to "zero phase" renders all of this effort useless, but it's a quick way to verify how phase information matters)

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 6:31 am

Also...

Code: Select all

Spectrum start=0 end=0 "rand / (1 + 10 * phase)"
Experiment with different multipliers! Try 2. Try 100.

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 6:33 am

More Chapters soon, but let's see what questions you have and what ideas there are.

This is meant to inspire. I also hope some of you will post their own scripts!

User avatar
Delta Sign
KVRist
486 posts since 22 Jun, 2018

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 7:10 am

Man, I can't wait to get more into this. I only used the scripting for basic morphing, normalization and basic things like that so far.
I hope people get into this and we'll see a bunch of great scripts and wavetables :tu:
This seems to be a really powerful tool!
Urs wrote:

Code: Select all

Phase "rands * 2 * pi - pi" // set all phases identical to frame 0
^ now, this line removes the "shift to the right" which we can observe with the simple filtering method.
This just blew my mind. Those phase offsets can often be an annoying problem when creating wavetables. I just checked it out, and sure enough, something as simple as "Phase "0"" will literally set the phase of all frames to 0.

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 7:39 am

Delta Sign wrote:This just blew my mind. Those phase offsets can often be an annoying problem when creating wavetables. I just checked it out, and sure enough, something as simple as "Phase "0"" will literally set the phase of all frames to 0.
That's the same as using Hive's "zero phase" interpolator. As I mentioned before, it can sound a tad stale.

A funky trick is to move one half of harmonics back and the other half forward, possibly even at different rates. This can create very alive sounding transitions without an overall effect on pitch. It's more like a detune within a single oscillator.

User avatar
lunardigs
KVRist
243 posts since 6 Jun, 2016 from Austin, Texas

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 12:40 pm

This video gives a cursory overview of Karplus Strong Synthesis with some tangible sounds/examples.
He explains that Karplus Strong is much more accurate and practical with a computer, but is demonstrated here on Eurorack for visual.
https://www.youtube.com/watch?v=QRfynqT3VZk
Psst ... abique, whurs the latest builds brah?

User avatar
Touch The Universe
KVRAF
3345 posts since 2 Oct, 2008

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 12:47 pm

Is it possible to start at level "0" here for the explanations. Maybe expand on the structure of the formula's themselves at the basic fundamental levels. How can a wavetable come from this?

Image

What is happening to the table here when we adjust the numbers. Is it going up or down a coordinate plan?

Don't really know what to ask, just I'd wish it was a bit clearer what is going on behind the structure of the formula. I could probably experiment all day tweaking the numbers but I think it would help if it was clearer what I was doing. Maybe if someone who is not a "genius" for lack of a better word, could explain it.
High Quality Soundsets for Lush-101, Hive, Electra 2, Diversion, Halion, Largo, Rapid, Dune II, and Spire.
http://www.touch-the-universe.com/

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 1:48 pm

First off, you need this:

https://uhedownloads-heckmannaudiogmb.n ... ge_WIP.pdf

Then, maybe a simple hint at how it works. Maybe a bit of C-style code shows how the expressions in those commands are applied to every sample.

Take this line:

Code: Select all

Wave start=0 end=100 "sin(2*pi*phase)"
It will internally be evaluated like this:

Code: Select all

for ( int frame=0; frame<=100; frame++)
{
	float table = frame/100;
	float y = 0.f;
	for( int index = 0; index <= 2047; index++ )
	{
		float phase = index/2048;
		float x = wavetable[ frame ][ index ];
		y = sin(2*pi*phase);
		wavetable[ frame ][ index ] = y;
	}
}
Likewise, the following line

Code: Select all

Spectrum start=5 end=17 "1/(phase+0.1)"
will internally be evaluated like this:

Code: Select all

for ( int frame=5; frame<=17; frame++)
{
	float table = (frame-5)/(17-5);
	float y = 0.f;	
	for( int index = 1; index <= 1024; index++ )
	{
		float phase = index/1024;
		float x = wavetable[ frame ][ index ];
		y = 1/(phase+0.1);
		wavetable[ frame ][ index ] = y;
	}

	inverseFFT( wavetable[ frame ] );
}
It is by far not that simple, but it is the essence of how the scripting engine evaluates expressions per sample.

For each sample in the wavetable (or each magnitude, or each FFT-bin's phase information [Note: FFT-bin is a term of Fast Fourier Transfor, but in this context also refers to harmonic/overtone... they're interchangeable terms]) it will set a few variables according to frame, index, phase etc. and then it will evaluate the term/expression with these particular values. The resulting value will then be written into the wavetable (according to the blend function, but that's for another thread maybe).

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 1:53 pm

That said, the following line will actually work:

Wave "phase^2"

This corresponds to y = x^2, because in the wavetable, "phase" is your x axis, but it only goes from 0 to 1.

If you want a waveform made of a parabola shape, you need to squeeze twice as much into it and shift to the middle:

Wave "(2*phase-1)^2"

This should result in a shape that pretty much resembles the red line in your graph.

User avatar
Delta Sign
KVRist
486 posts since 22 Jun, 2018

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 2:53 pm

Urs wrote:
Delta Sign wrote:This just blew my mind. Those phase offsets can often be an annoying problem when creating wavetables. I just checked it out, and sure enough, something as simple as "Phase "0"" will literally set the phase of all frames to 0.
That's the same as using Hive's "zero phase" interpolator. As I mentioned before, it can sound a tad stale.
Ah that's interesting to know! Yeah, in most cases it can kinda turn things into same-ish sounding wavetables, but it can also be very useful at times.

Anyway, I'm proud to announce I just wrote my very first script :hihi:

This is a thing I like to do in Pure Data. I think we talked about this before in some other thread. It's basically very basic phase distortion to apply PWM to any waveform.

Code: Select all

NumFrames=256

Wave start=0 end=255 "sin(2*pi*phase^(frame+1))"
This just uses a sine wave for now and it could be improved a lot further, but I wanted to keep it as simple as possible for now.

I just lost my weekend to the Hive demo without even getting into the scripting much. I feel like if I get into it, I'm never going to leave this room again :hihi:

User avatar
Delta Sign
KVRist
486 posts since 22 Jun, 2018

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 3:04 pm

A better version with a more gradual change:

Code: Select all

NumFrames=256

Wave start=0 end=255 "sin(2*pi*phase^(1/(frame/40+1)))"
I feel like that could have been done a lot simpler, but hey, it works :hihi:

User avatar
drzhnn
KVRist
390 posts since 1 Jan, 2013 from Saint-Petersburg, Russia

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 4:35 pm

Delta Sign wrote:A better version with a more gradual change:

Code: Select all

NumFrames=256

Wave start=0 end=255 "sin(2*pi*phase^(1/(frame/40+1)))"
I feel like that could have been done a lot simpler, but hey, it works :hihi:
Your script looks very smart and sounds lovely!

My trigonometry skills are like sine is something that is good for subbass, and cosine is similar to sine but starts with a click, so it's good for percussion elements. So I'll stick to the provided KS example for now. Here's my remix:

Code: Select all

Info "Karplus-Strong-like wavetable with pluck articulation"
NumFrames = 101

Seed = 12611
Wave start=0 end=0 "rand / (1 + 10 * phase)"  // attack impulse
Phase start=0 end=0 "rands * 2 * pi - pi"
Wave start=1 end=5 "y + 0.10 * ( main_fi(frame-1, index) - y)" // attack portion

Seed = 811564 // new seed for body portion
Wave start=6 end=6 "rand / (1 + 10 * phase)" // body impulse
Wave start=7 "y + 0.20 * ( main_fi(frame-1, index) - y)" // body portion

Spectrum lowest=0 highest=0 "0" // remove DC offset
Phase "rands * 2 * pi - pi" // set all phases identical to frame 0

Normalize start=0 end=0 db=0 // normalize attack impulse
Normalize start=1 end=5 db=-6 // normalize attack portion
Normalize start=6 db=3 // normalize body portion

User avatar
Delta Sign
KVRist
486 posts since 22 Jun, 2018

Re: [.uhm] Karplus-Strong in Hive

Post Sun Sep 16, 2018 5:32 pm

drzhnn wrote:
Delta Sign wrote:A better version with a more gradual change:

Code: Select all

NumFrames=256

Wave start=0 end=255 "sin(2*pi*phase^(1/(frame/40+1)))"
I feel like that could have been done a lot simpler, but hey, it works :hihi:
Your script looks very smart and sounds lovely!
Yeah, I like to use that technique a lot in Pure Data and Reaktor, especially in combination wih other things like FM. My implementation here is probably not very smart though :hihi:
drzhnn wrote:My trigonometry skills are like sine is something that is good for subbass, and cosine is similar to sine but starts with a click, so it's good for percussion elements. So I'll stick to the provided KS example for now. Here's my remix:

Code: Select all

Info "Karplus-Strong-like wavetable with pluck articulation"
NumFrames = 101

Seed = 12611
Wave start=0 end=0 "rand / (1 + 10 * phase)"  // attack impulse
Phase start=0 end=0 "rands * 2 * pi - pi"
Wave start=1 end=5 "y + 0.10 * ( main_fi(frame-1, index) - y)" // attack portion

Seed = 811564 // new seed for body portion
Wave start=6 end=6 "rand / (1 + 10 * phase)" // body impulse
Wave start=7 "y + 0.20 * ( main_fi(frame-1, index) - y)" // body portion

Spectrum lowest=0 highest=0 "0" // remove DC offset
Phase "rands * 2 * pi - pi" // set all phases identical to frame 0

Normalize start=0 end=0 db=0 // normalize attack impulse
Normalize start=1 end=5 db=-6 // normalize attack portion
Normalize start=6 db=3 // normalize body portion
That actually sounds really cool :tu:
I'm really impressed by how flexible and powerful the scripting really is!

I just came up with my first useful script, I think :hihi:
Again, this probably could have been done simpler.

Code: Select all

Info "A simple 2OP FM wavetable with independent controls for index and ratio. Set the MultiTable count to 4. Wavetable position controls the index and MultiTable position controls the ratio."

NumFrames=256

Wave start=0 end=63 "sin(2*pi*(phase+(sin(2*pi*phase)*(frame/16))))"
Wave start=64 end=127 "sin(2*pi*(phase+(sin(4*pi*phase)*((frame-64)/16))))"
Wave start=128 end=191 "sin(2*pi*(phase+(sin(8*pi*phase)*((frame-128)/16))))"
Wave start=192 end=255 "sin(2*pi*(phase+(sin(16*pi*phase)*((frame-192)/16))))"

Normalize base=each
It's basically a simple 2OP FM synth! Set the MultiTable count to 4 for it to work correctly. The wavetable position controls the FM index, while the MultiTable position controls the FM ratio.
This sounds super nice at lower octaves! Leaving the MultiTable count at 1 also works for instant wub wubs :hihi:

Edit: I should also mention, the FM modulator pitch doesn't actually change, it just blends between the different ratios. This way you don't get any inharmonic overtones, which sounds quite cool imo.

User avatar
Urs
u-he
22515 posts since 8 Aug, 2002 from Berlin

Re: [.uhm] Karplus-Strong in Hive

Post Mon Sep 17, 2018 12:20 am

Yay! Keep them coming!

I'll start separate threads for FM and mabye one or two other topics shortly, I was just obsessed with KS yesterday. Check the [.uhm] prefix.

Also, regardig my reference to FFT (Fast Fourier Transform), I edited my above post: Note: FFT-bin is a term of Fast Fourier Transform, but in this context also refers to harmonic/overtone... they're interchangeable terms.

Put in more detail: For wavetables, the Fast Fourier Transform separates the waveform into each harmonic, of which we know phase and magnitude (magnitude = loudness). Two exceptions: DC, or "harmonic 0" in this case, has no phase, it just has sign and magnitude, and so does the highest harmonic ("harmonic 1024" or "Nyqvist", if you will). In FFT lingo, each of these pairs of magnitude and phase is referred to as a "bin". In the (very particular) case of wavetables FFT-bins and harmonics (overtones, partials) are interchangeable. Sometimes 0 is used as the index of the fundamental frequency, sometimes 1 is (K1, K2, K3...). In our case (Spectrum and Phase commands), the fundamental frequency has the index 1 while DC has index 0. As DC is usually an unwanted phenomenon, the Spectrum command defaults to Lowest=1 for its processing, leaving DC untouched.

Ok, a lot ot take in. Let's just stick to the term "harmonics" when talking about Spectrum and Phase commands :)

Return to “u-he”