Polyphonic morphable wavetable oscillator

wavetable

#1

I was watching this video about a new eurorack module, it's a morphable wavetable oscillator

Has anyone tried making something like it? I understand making the wavetables is half the job.
Has anyone created something shareable that could do the same thing (with maybe just a few wavetables for the moment) ?
I use single cycle wavetable synths on axoloti everyday, but I havn't found (or managed to make) a nice oscillator subpatch that includes smooth morphing along the wavetables.

P.S I know anon5189335 made something, but it's not online anymore (since he left) and I could have continued this thread, but as time and versions have gone by, I thought I'd start a new thread


#2

@johannes was talking about anti-aliasing techniques the other day, and dropped a link to a technique that could work for arbitrary waves, with a bit of pre-processing of the wavetables. Maybe it's time someone had a go at applying some of the principles outlined.

I'd tackle it myself, but sadly I'm tied up with developing another set of custom objects at the moment.

Oh, and I'm stuck at primary-school level maths, sadly...

a|x


#3

Ive just added to the community library my wavetable objects :slight_smile:
https://sebiik.github.io/community.axoloti.com.backup/t/thetechnobear-contributions

these make it a bit easier to load wavetables and also deals with a number of 'gotchas' for playing back wavetables, and also supports crossfading between waves.

Ive been having a lot of fun with these.
there is some 'skill' in producing decent wavetables, Ive been recently looking at Tone2 Icarus which has some really nice options for this, a lot more 'musical' than Serum.
I plan to do a short video showing the process of creating wavetables.

but in brief its pretty trivial, using audacity

  • start audacity
  • load wave file
  • make sure its mono, or convert to mono (split stereo to mono, then delete one track)
  • now we need to ensure size is a power of 2
    menu -> effects -> change speed
    the trick here is you need to set new length (ignore the rest)
    ensure new length is on samples (its the drop down next to it)
    you can type in the length of the wave file you want. (remember 2^N!)
    e.g. if your file contains 4 waves, and you want them sized at 512 samples each, then type in here 4 x 512 = 2048, in wave/wavetable load you will specify 4 and 512.
  • now to save as raw,
    file -> export audio
    format = other uncompressed ,
    options -> header = RAW (header-less) , encoding = signed 16 bit PCM, ok
  • save file where you want it.
  • axolotl wave/wavetable load, specify number of waves, and size of EACH, and then use choose to select raw file

done :slight_smile:

there are other options include ffmpeg (command line) and audio term (windows, very powerful but not easy to use)

tips/notes:

  • icarus, serum (and others) use 2048 for each wave, you can then set number of waves, I think for serum is 256 by default. (with icarus you can make as many as you want up to 256, but can have less)
  • size of each wave must be a power of 2, and also the number of waves is a power of 2... this is for 'efficiency' of processing.
  • forget sample rate... this is irrelevant when dealing with single cycle waves/wave tables.
  • you can use audacity (and others no doubt!) to combine wave files, just paste them on to the end , but make sure you size them first, especially if the waves are of different sizes.

anti-aliasing

they will alias at high frequencies - I looked at a technique for avoiding this, but it seems to involved re-synthesizing the wavetable for different frequencies, and from what I read that can in itself introduce artefacts. also you then need to store multiple copies of the wavetables... so memory goes really quickly. I may give it a go when I have time, but the above actually seems to work quite nicely for me.

(I suspect you can do the high frequency trick 'manually' by creating high frequency forms of the wavetable in other software, and then using the appropriate wavetable in axoloti according to note number, but again, not experimented with this.)

Ive not looked at what @johannes suggested, it may or may not be the same... and also couple be outside my current DSP knowledge :slight_smile:


#4

I think the antialiasing technique @johannes mentioned doesn't involve multiple waveform versions, but does require pre-processing of the waves, and extra code to decide them when playing them back.

I do t know if there is a builtin function in Audacity to do the required pre-processing (but suspect not).

a|x


#5

do you have a link for this?
... the only method I found was FFT->strip harmonics -> IFFT, repeat for different frequencies. this could be preprocessed, but then you are required to store the resulting waveforms from the IFFT... you wouldn't need that many i guess. but the FFTs are needed to be with large number of bins to retain any resemblance to the original source.


#6

I think I may have been confusing a discussion here with one on another forum.

Try:
http://www.idmt.fraunhofer.de/content/dam/idmt/en/documents/Personal%20Websites/Franck/publications/dafx2012_hoiws_talk.pdf

There are other links, but about to go out. Will look them up later.

a|x


#7

The technique is known as DPW, I think (couldn't remember the name, earlier).

There's probably example sourcecode available.

The maths is way over my head, sadly.

a|x


#8

Here's the discussion I confused with one here, from the Mutable Instruments forum (look for pichettes posts):

http://mutable-instruments.net/forum/discussion/7163/sinc-interpolation/p1

Links to this paper:
http://dafx.ca/proceedings/papers/p_169.pdf

a|x


#9

interesting...
I did some digging, and think I understand the basic concept of DPW, but unfortunately, I think the 'meat' of how to integrate the 'generic' wavetables is detailed in a paper called "Higher-Order Integrated Wavetable and Sampling Synthesis" , so I think id have to buy that, as I can't find any details/implementations.
the only implementations/examples of dpw I could find were using specific waveforms... so they 'know' the integration function..
the p169 paper seems to just give examples, not how to do it, over and above 'integrate the wavetable'

frankly though, the maths is probably beyond me, without some working examples, so unlikely I will pursue this, unless I stumble on some more details somewhere.

besides, for my current purposes, my wavetables were fine when used in 'reasonable pitch ranges' and using a bit of LP filtering where necessary.


#10

ok, so kept digging and found this...
http://forum.cockos.com/showthread.php?p=1715341

desc:test PIWT
/* T.Rochebois 08/2016
  based on code developped at IEF/AXIS lab around 1997.
*/
slider1:32<4,512,1>Table length
// wavetable params
slider3:1<0,10>-R0
slider5:1<0,10>-R1
slider6:5.3<0,10>-I1
slider8:2<0,10>-R2
slider9:0.4<0,10>-I2
slider23:10<0.2,10>Glide rate
// ___________________________________________________________________
@init
function PIWT_init(nMax) instance(n x y z w dp _dp _dp3) (
  this.nMax = nMax;
  n = nMax;
  x = ad; ad += n;
  y = ad; ad += n;
  z = ad; ad += n;
  w = ad; ad += n;
  dp = n*440/srate;
  _dp = 1 / dp; 
  _dp3 = _dp * _dp * _dp;
);
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
function PIWT_dcRemove(t n) local(i m)(
  m = 0;  i = 0; loop(n, m += t[i]; i += 1; );
  m /= n; i = 0; loop(n, t[i] -= m; i += 1; );
);
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _   
function PIWT_integ()
  instance(x n y z w z y x)
  local(i)(
  PIWT_dcRemove(x, n);
  i = 0; loop(n-1, y[i+1] = y[i] + x[i];                i += 1;  );
  PIWT_dcRemove(y, n);
  i = 0; loop(n-1, z[i+1] = z[i] + y[i] + (1/2) * x[i]; i += 1;  );
  PIWT_dcRemove(z, n);
  i = 0; loop(n,
    y[i] *= 1/2;
    x[i] *= 1/6;
    i < n-1 ? w[i+1] = w[i] + z[i] + y[i] + x[i];
    i += 1;
  );
  PIWT_dcRemove(w, n);
);
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
// Call before aProc if there is a discontinuity of dp _dp
function PIWT_disc() 
  instance(n dp _dp _dp3 p out w z y x w0 w1 w2) local(p0 a)(
  p -= 2*dp; p += n * (p < 0); p -= n * (p >= n); p0 = p|0; a = p - p0; 
  w0 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
  p += dp; p -= n*(p>=n); p0 = p|0; a = p-p0;
  w1 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
  p += dp; p -= n*(p>=n); p0 = p|0; a = p-p0;
  w2 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
);
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
function PIWT_aProc()
  instance(n dp _dp _dp3 p out w z y x w0 w1 w2 w3) local(p0 a)(
  p += dp; p -= n*(p>=n); p0 = p|0; a = p-p0;
  w3 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
  out = (w3 - w0 + 3 * (w1 - w2)) * _dp3;
  w0 = w1; w1 = w2; w2 = w3;
  out;
);
// ___________________________________________________________________
// Init the wave table
_srate = 1 / srate;
piwt.PIWT_init(512);
  dpLfo = 2*$pi*5.5*32/srate;
// ___________________________________________________________________
@slider
slider20 != aslider20 ? (
  dpc = (piwt.n*440/srate)*2^((slider20-69)*(1/12));
  abs(slider20 - aslider20) >= 12 ?  piwt.PIWT_disc();
  aslider20 = slider20;
);
slider1 !== aslider1 || slider3 != aslider3 
 || slider5 != aslider5 || slider6 != aslider6
 || slider8 != aslider8 || slider9 != aslider9 ? (
  slider1 = min(512,max(4,slider1|=0));
  piwt.n = n = slider1;
  piwt.dp =dpg = dpc = (piwt.n*440/srate)*2^((note-69)*(1/12));
  i = 0; loop(n,
    phi = 2*$pi*i/n;
    piwt.x[i] = sin(slider3 * phi 
      + slider6 * sin(slider5 * phi
        + slider9 * sin(slider8 * phi))); // <--- Rich wavetable
    i+=1;
  );
  piwt.PIWT_integ();
  piwt.PIWT_disc();
  aslider1 = slider1;  aslider3 = slider3;
  aslider5 = slider5;  aslider6 = slider6;
  aslider8 = slider8;  aslider9 = slider9;
);
// ___________________________________________________________________
@block
while (midirecv(offset, msg1, msg23)) (
  msg2 = msg23 & 0x7F; msg3 = msg23 >> 8; status = msg1 & $xF0;
  status == $x80 ? ( status = $x90; msg3 = 0; );  // note off
  status == $x90 ? (                              // note on
    msg3 == 0 ? (msg2 == note ? gate = gate2 = 0; )
  : ( 
      note = msg2;
      dpc = (piwt.n*440*_srate) * 2 ^ ((note - 69) * (1/12));
      gate2 == 0 ? (dpg = dpc;);
      gate2 = msg3 * (1/127);
      gate = sqrt(gate2);   
    );
  );
  midisend(offset, msg1, msg23);
);
// ___________________________________________________________________
@sample
k <= 0 ? (
  k = 32;
  pLfo += dpLfo; pLfo -= 2*$pi*(pLfo>0);
  dpg  += 32*_srate*slider23 * (dpc - dpg);
  piwt.dp = dpg * (1+ 0.01*sin(pLfo));
  piwt._dp = 1 / piwt.dp;
  piwt._dp3 = piwt._dp * piwt._dp * piwt._dp;
  piwt.PIWT_disc();
);
k -= 1;
env += (gate>env ? 0.01: 0.0001) * (gate - env);
spl0 = spl1 = min(2,max(-2,env*piwt.PIWT_aProc()));

I'll take a look at this, see if I can understand whats going on :slight_smile:


#11

Hello,

Eh, that's one of my JSFX plugin :wink:

That's a 3rd order differentiation of a pre integrated wavetable:

  • The @slider section calculates a wavetable (based on some kind of phase modulation but anything is possible here as long as it generates a wavetable)
  • The function PIWT_integ does the pre integration step :
    • The function is considered as a set of polynomial segments (not isolated samples)
    • The pre integration consist in calculating the polynomial coefficients for the 3rd order integrated function.
    • x y z and w are tables that contain the polynomial coefficients for the third order integration.
  • PIWT_aProc is the main function
    • p is the phase
    • dp is the phase increment
    • p0 is the integer part of the phase: aka the segment number
    • a is the float part of the phase
    • w3 is the more recent value of the 3rd order integral ( w3 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0])); )
    • w2 w1 w0 are past value of the integral
    • out = (w3 - w0 + 3 * (w1 - w2)) * _dp3; is a third order differentiator with normalisation by 1 / dp^3

The problem with third order DPW is that it needs double precision.

I tried to summarize some ideas (including anti alias of distortions) in a small doc here :
http://stash.reaper.fm/28384/DifferentiatedPolynomialWaveTables.pdf

Maybe it is possible to do some 3rd order DPW by using some 64bit fixed point arithmetic...
but I'm still quite new to the Axoloti and a little clumsy with fixed point.


#12

Is really that important to use third order integration? Couldn't you just stop to second order?

As for operation with 64bits, i fear it's not possible, not with floating point or fixed point, that's because of the architecture of the cortex M4. (Actually you can use 64bit floats, but they will make the cpu fly up into the sky)

What if you work with a decimated wavetable? (8bit)
I mean, if you need more precision i guess it's always relative to something else. If you aim to produce a 8-bit wavetable, having a 32-bit workspace should be plenty.


#13

Well,
with third order integration, as far as i experimented with JSFX/Reaper, at 48kHz there is almost no audible aliasing.

I think i will start with first order (I think that Johannes already implemented some first order DPW to generate bandlimited square). And give second order a try.

My goal is to implement a wavetable osc that can be phase modulated.
=> DPW with audio rate divisions.

I don't think that using a 8 bit table will be helpful.

The accuracy problems are both at the numerator and denominator sides.
The numerator is a difference of two polynomials.
The denominator is a difference of two consecutive modulated phases.

I think that the length of the table has a greater impact on the artefacts.
Maybe, some consistency tricks between the error in the numerator and denominator can help...
Well, I'll have to experiment somewhat.


#14

I'm watching this thread with interest.

Could you envisage the Axoloti being able to record samples and convert them to the band-limited wavetables you mention? It wouldn't necessarily have to do the pre-processing in realtime, but the ability to record a snippet of audio, then automatically create a wavetable from it would be great!

From my point of view, that's the killer feature most hardware wavetable synths lack (except for the Waldorf NW1 Eurorack module).

a|x


#15

@toneburst
You can do this with my wavetable patch, from the community library. Instead of using the sine/saw career subpatch you could use an input. I all ready experimented a bit with making wavetables out of all kinds of samples, internally in Axoloti. Works pretty well. In some cases it is favourable to add a window function to remove click, especially from movies clips, etc.

I also made a wavetable subpatch, which can morph between several waves. Here you go:
4 morph FOR COM 1.axp (27.7 KB)

You can use this version with a "multi wavetable", with more than 1 wave. Using @thetechnobear wavetable object it is pretty easy to set up.

If you for example have a 128 waves wavetable you can, with the index knobs, select 4 waves to morph between, with MORPH knob. And there is also an octave switch for each wave for extra fun :slight_smile:

You can also make a wavetable consisting of only 4 waves and moprh between them. This is very "Sd-ram friendly", you get a lot of different waves and only use very little sd ram.

Id love if someone made a dedicated object like this, that were more efficient. And maybe with more waves to morph between.


New Antialiasing Technique?
#16

if you going down to single cycles, isn't this pretty much granular synthesis?
if your doing longer 'phrases' then this is slicing?

or are you thinking of something else?

my wavetable object could be relatively easily adapted to live input, it just using a table ... and it needs to know the table side (same for recording into a buffer, so no difference there) and number of waves... this currently is done at compile time (for efficiency /compiler optimisation), but make a 'variable' version, slightly less efficient is a tiny code change.

I think personally the issue, when you get into recording , is how does the user control this, and see whats going on... if you look at something like the push2 or octatrack they display the waves and allow the users to navigate thru it.... and I think you really need this to be useful.
squashing this all into the current scope type control is ok, as for now, but really we need more control than this.. e.g. bigger displays and more importantly it needs to be completely disconnected from the PC/Mac java UI to support the standalone nature of axoloti.
What I want to do, is have viewable display buffers, so that things like axocontrol and push2 could have this kind of 'interactive nature', and the user to be able to patch the interaction - but more on this at a later date :wink:


#17

if you for example use 128X 2048 waves = 262144 samples.. Which is around 5 seconds. Then you can make a wavetable out of a word or something like that. This sound a bit different than granular synthesis. Both are fun, though :slight_smile:


#18

well, I guess sample playback, granular synthesis and wavetables are all related, basically chunking up waves and moving the origin point :slight_smile: (some granular synths are pretty flexible with grain sizes )

anyway, Id wonder how you'd get reasonable cross points for record audio, its not like its going to 'magically' zero cross (or ends match directions) , so its going to be pretty crunchy.
I kind of thing of wavetables as being 'prepared' such that the individual waves are 'nicely formed', and even arranged in a manner, such that morphing makes sense.

hmmm... gives me an idea, this could be done with recorded data, if you did a bit of analysis ... a bit like is done with slicing. might be a slight delay, before the wavetable would be 'ready', as it would have to be done off the audio thread, to ensure no audio glitches.


#20

Yeah related, but not the same. Wavetables are tuned, som they work the same way as an oscillator, you can play it like any oscillator, with the tonal benefits of an oscillator. Most granular stuff is a bit more diffuse. Maybe some granular effects you can sync the grainsizes to clock, but that is not the same as using it as a tunable oscillator. That is one of the main differences between wavetables and granular.

Not that hard to do. Use a window function when recording, so you get the fade in and out. As mentioned above, this is really important when trying to use for example audio from a movie, which has got complicated audio signal. In that case it can be hard to find a zero crossing, epsecially one that fits into the grid of the wavetable indexing.... So you have to make them yourself. I would suggest a custom window function for this, where you can control the fade in /out. The ones from fac.lib. have too much fade in/out for wavetable, IMO. If I remember correct, this is also how Serum does it.

End match; what do you mean by that? Length? If length, check my wavetable creator patch. It sets the length of how long of a wavetable you like to record. By default it is set to 262144 samples.

Yes, it can, check my patch in com lib. I have all ready made a recording wavetable thingy. But didnt work on it for a long time. Its all about doing calcuation before hand :slight_smile: You use the oscillator tuned to fit in a table, using my example for 262144 wavetable(128X2048), the waves have to be 2048 samples long. The only way I got to do that was making a subpatch for it... Took a bit of timeto get it tuned... Actually I think i spend a WHOLE freakign weekend getting that right!!!!!

You can find that in my wavetable creator patch. It is called "Math tune" or something like that. It can be used to tune an oscillator to fir PERFECTLY into a table. Check with scopes, that the waveform doesnt move...Again this took LONG time go get right.... When this is set up, you add the windows function, which has to be controlled by the "table tuned" osc, so it makes fade in/out for each wave in the wavetable.


#21

I was looking at the Oscitron Eurorack module by Soulby synths today - you can do the wavetable record thing on it. I think it synchs the wave recording to crossing through zero so they are always complete cycles. Don't know coding (yet! :)) but the code is available onlinee, so someone might want to have a look at how it's been done.