Hi:
I owned a TX81Z for 25 years (I just sold it a few months ago because I moved overseas).
I don't think you need to implement those extra waveforms, of course if you want to do it is allright but I don't think that they add anything interesting to the sound.
First of all there is no factory patches in the TX81Z that uses those extra waveforms, also I never found a third party patch that uses them either.
I tried several times to make patches with them and found that always add an unpleasant buzzy tone in the upper harmonics, increases the alliasing, and most of them don't sound much different to a sine in the lower harmonics.
May be using them with a low pass filter at the output could make more sense as you can filter out the buzziness.
Anyway I'm in the process of doing a TX81Z implementation myself, at some point my goal will be to make it accurate in other aspects (envelope times and behaviour, modulation depths, etc) so I can reproduce some of the TX81Z sounds I love (and miss).
I will try your patch soon
TX81Z algorithms
there is no factory patches in the TX81Z that uses those extra waveforms
Are you sure? There's a gearslutz thread with synth-nerds arguing that the famous "lately bass"-preset can't be faithfully reproduced by another FM synth because it uses one of the extra waveforms. I love that sound on some cheesy 90s songs, so I'll probably try to patch it on the axoloti.
I love the TX81Z, I own one and especially like the midi delay effect.
I have been working on a midi delay based on it, I'm pretty new to Axoloti so I guess it is pretty crude and could be reproduced in a better way, I also added some parameters i thought could be useful for me.
If you are working on the reproduction of this section from the TX81Z, I would be interested in seeing it!
When I released this object, i planned to make some sort of tutorial but time flies...
So, it is a nice occasion to start one (and to work again on its XT / cross table extension).
I will focus on the functions that can be used for the classic waveforms found in DX/TX synths.
The waveform stored in the gen object are calculated and preprocessed for anti aliasing at initialization of the patch. Once calculated they are "static", hence a gain in CPU.
The syntax is C code.
In fact, it is a line of C code that is inserted in the object in its init procedure.
x is the float variable that varies from 0 to 1.
The function sin1(x) is a sine that makes a cycle when x goes from 0 to 1 (so you don't need to bother with pi coefficients).
sin1(x) // generates a one cycle sine wave
sin1(0.5f * x) // generates half a sine wave
sin1(0.25f * x) // generates a quarter sine wave
sin1(x)+0.5f*sin1(7*x) // generates two harmonics (one and seven)
tri(x) is a triangle function, it can be provided with a secondary parameter that sets its symmetry.
It can be used the same way as the sin1(x) function.
tri(x, 0.333f) + 0.5f * tri(7*x)
A very useful trick is the C ternary operator
condition ? value for true : value for false
conditionnal ternary operator on wikipedia
Thanks to it, it is possible to set one portion of the waveform with a formula and an other portion with another.
x<0.5f ? sin1(2*x) : tri(2*x,0.3333f)
I have just uploaded the new XT (cross table) objects.
You can give it a try.
Sync library and Help -> library -> community -> tiar -> XT -> gen8
The gen8 is a cross table generator with 8 formulas/waveforms.
I formulated the 8 TX81Z in there.
Note: a single XT/gen8 can be used by many XT/osc .
That's great stuff, thanks a lot. Super awesome that you included smooth crossfades between waves! The wave distortion part is also interesting. This could become my new favourite oscillator object. Cheers!
Dude, thank you so much! I have been out of the country and unable to work on Axo coding but I'm going to mess around with your stuff tonight and see what happens. Do you know of any good resources for understanding the effects of aliasing in this context? I'm only aware of it in the context of pixels
Thanks to @borututuforte @Sebo and @Paulus for your contributions too, hopefully between the lot of us we can make a really accurate patch!
In "distortion mode", the XT/osc object acts like a look up table. So the functions put in the XT/gen8 can be used as distortion functions.
In an image, aliasing occurs when a pixel represents the value of the image on a point.
To avoid aliasing, the pixel should represent the mean value under the surface of the pixel.
For audio, with a fixed sample rate, it is the same principle.
When a sample represents a instant in time, you may have aliasing.
If you calculate the mean value of the signal during a sample time interval, you can anti alias.
The integro differential method i use here, is based on this principle.
Other methods exist, each one with its + and -.
Thanks, that makes total sense.
I've updated my patch to use your oscillators and it can now make some very crazy sounds, these oscillators are awesome. It's a bit more resource intensive, but that's to be expected. Still could probably do 2 note polyphony so it beats the Volca FM on that point
Here's the updated file:
TX81Z_gen8.axp (48.3 KB)
You make me doubt about it and I checked the Lately Bass sysex file with one TX81Z editor that I still have, just to see what waveforms are used (I don't have the synth so I can hear it) and indeed one of the operators uses a non-sine wave (the spheric one), so I was wrong...
Now, I'm not shure if I had the wrong idea that none of the patches uses the extra waveforms because most files I checked didn't use them and just missed the ones that does, or just I see that just a few use them and just tought there was exceptions and forget about them... It was a long time when I use the module intensively...
Sorry about the mistake.
Maybe, you can gain some CPU by using DrJustice drj/switch/fm4op_alg
I think that for operator 4 (the one with feedback but no modulation input) you can use tiar/osc/SelfPM
Nice one, I'll give the fm4op_alg a go and see if that helps. I can't seem to find your SelfPM object in my library, though.
Switching from 'interp' to 'vintage' mode in the gen8 object also knocked off a good few DSP load percentages.
I have made some modifications to @DrJustice fm4op_alg (removed the output clipper, the "feedback leaves" and "inleted" the algorithm selector). I put my version in tiar/mux
Here is a patch that uses it along with SelfPM osc as op4
(you should sync library before using it to get the modified fm4op_alg)
TX81Z_tiar.axp (14.3 KB)
The CPU load is almost the same but i think that we save some RAM.
I don't seem to have too much aliasing even with OP4 feedback and peaky waveforms.
The "vintage" mode provides "stepy" waveforms. The waveform is still anti aliased, but with small wavetable size (64 and below), they can give a specific early digital flavor to the sound (phenomenon known as "imaging").
On my Axoloti 25% -> 21% cpu.
you can also bitshift the phase of the sine oscillator and let it choose from tables with phase and amplitude offsets like:
int phs[4]={3,2,1,0};
int ofs[4]={1,1,-1,-1};
uint32_t phase;
phase+=freq;
int32_t r;
int32_t pos=phase>>30; //turns the phase into an integer from 0-3
int32_t p2=phase+(phs[pos]<<30); // adds the phase offset to the current phase
SINE2TINERP(p2,r)
r=r>>4;
r+=ofs[pos]<<27; // adds an amplitude offset to the sine so negative becomes positive and positive becomes negative.
outlet_out=r;
This way, the CPU will be very low, but you will get lot of aliasing, which is not a bad thing if you want to replicate the TX81Z.
Edit: Sorry, meant to reply to @SirSickSik.
I did some messing around with your code and was able to get 4/8 of the waveforms working with a similar method. I think the starting phase of these waveforms might be wrong but they look right on the scope. Here are my modifications:
int phs[4] ={0,2,0,2};
int flp[4] ={1,1,1,1}; //used to invert a part of the waveform
int mute[4] ={0,0,0,0}; //used to determine wether a particular part of the waveform should be set to 0
Phase += (freq + inlet_freq);
int32_t r;
int32_t pos=Phase>>30; //turns the phase into an integer from 0-3
int32_t p2 = Phase + (inlet_phase<<4) + (phs[pos]<<30);
SINE2TINTERP(p2,r);
r ^= flp[pos]?(1<<31):0;
wave_out= mute[pos]>0?0:(r>>4);
/*
Sine:
int phs[4] ={0,0,0,0};
int mute[4] ={0,0,0,0};
int flp[4] ={0,0,0,0};
Inverted Sine:
int phs[4] ={0,2,0,2};
int mute[4] ={0,0,0,0};
int flp[4] ={1,1,1,1};
Half Sine:
int phs[4]={3,2,1,3};
int on[4] ={0,1,1,0};
int flp[4]={0,0,0,0};
Inverted Half Sine:
int phs[4]={0,2,1,2};
int on[4] ={0,1,1,0};
int flp[4]={1,0,0,1};
*/
I just wanted to show how this could be done in a way that could be made universal. I wrote this code from my head, not being totally sure whether it were the correct values. Good to see you got the point!
Anyways, to make the different waveforms, you can combine the tables in several ways:
-use one long table containing all the values of the different waveforms, but add an offset of 4 for each waveform:
int phs[8] ={0,0,0,0,0,2,0,2}; // put all the values into one table, make sure it's big enough to fit all of them
int mute[8] ={0,0,0,0,0,0,0,0};
int flp[8] ={0,0,0,0,1,1,1,1};
int wave=param_waveform<<2;
int32_t pos=(Phase>>30)+wave; //turns the phase into an integer from 0-3 and adds an offset for each waveform
you can also make a combined array (not sure what the correct technical term is for this), like
phs[8][4]=
{
{0,0,0,0},
{3,2,1,3},
{0,2,1,2},
etc...
}
the waveform selector would then select from the 8 different tables, each containing 4 values.
Anyways, these tables could of course be used to do all kinds of different things, eg. self-fm amounts:
-store the waveform output in the memory, so you can use it in the next cycle to do frequency modulation. Don't forget to use a hp-filter to remove DC-offsets, to prevent it from pitch-shifting away from it's base frequency.
-use different waveforms for output and self-fm, multiplying the phase of either one of them with an integer value creates harmonic overtones which can further increase the amount of spectra you can create. Also, giving the phase an offset can give totally different results, even though you're using the same waveform. Eg. in the case of a normal sine, no offset will generate a pulse-wave, but a 45 degrees offset will generate a saw-like waveform.
-perhaps a randomise function of the tables to find other interesting waveforms. Here, a hp-filter over the output is advisable, as some might create waveforms with an offset.
-use another table to multiply the phase to create seperate harmonic overtones in the 4 parts of the phase.
-another thing that might be interesting is to use the actual phase-offset, amplitude and flip values (eg. (1<<30)=45 degrees) and morph between the table values to morph between waveforms.
int a,b;
int32_t mix,value;
a=(uint64_t)param_morph*8>>27;//turns the parameter value in the range of 0-7
b=(a+1)%8;//offset of 1 to select the value of the next waveform
mix=((uint32_t)param_morph*8<>1;//gives the remainder without using the bits used by the wave-selection (a/b) and turns it into a range of 0-(1<<31)
value=phs[a][0]+(___SMMUL(phs[b][0]-phs[a][0],mix)<<1);//mixes between wave selection by a and selection by b.
ps I normally use a simple lp/hp filter calculation for dc-offset removal:
hp+=wave-hp>>9; (simple 6dB lowpass function around 30hz if I'm right, not sure though, but works well enough for self-fm, sometimes I even use 8,7 or 6, which will increase the frequency of the hp filter)
wave-=hp; (subtract lowpass from original to create hp-filtered wave)
I made a quick randomisable tone generator based on this, using some of the ideas I posted. Did show me where I made a mistake in my previous code I posted. I calculated the phase offset without taking into consideration that the phase it was added to, was itself of course still giving an extra offset all the time. Fixed this by doing:
p2=phase&((1<<30)-1)+phs[pos];
This way you can enter the phase positions as where the sine should start instead of recalculating with the current phase offset
Also tried to soften the waveform a bit by adding the waveform amplitude difference at moments of table-changes to a value that constantly tries to slowly return to 0.
something like this.axp (6.3 KB)