Valve Distortion
Hi
totally new to max, but im building a guitar processor for a project, im struggling on how to build a distortion effect in max. i would preferably want to create a valve distortion effect using soft clipping. i was wondering if anyone had done this before, please be gentle, like i said im new
thanks
Welcome, you should really do a search of the form. There are loads of ideas for different distortions.
One of my favourites is the simple tanh~
Check it out
Best,
John.
It is incredible how deep the resources are on this website - check out the community/articles section and keep scrolling and scrolling down and you will find Darwin Gross's articles on guitar processing. I have not read them all but this would be a darn good starting point!
Good luck! Happy processing in Max!
Gord
Also, if you go the tanh~ route, consider the following:
- tanh is an expensive calculation. Make a lookup table for x in [-4,4], since it's -1 / 1 outside that range and you'll save a ton of CPU. (lookup~) If you have gen~, you might even try doing the lookup with spline interpolation.
- Oversample your lookup inside a poly~ (even if you just use tanh~, do this) to improve the quality and reduce intermodulation distortion.
Good luck!
I tried making a lookup table with tanh using a line~ into a tanh~ and then into a record, and it seems to have worked up until I write it to a file. The file it outputs is just 1024 samples of 1.0. Before and after writing it to a table though, the buffer I'm using displays the wave table I'd expect. Can anyone figure out what's going on?
I hadn't actually tried the distortion in the guitar patch since I've used the overdrive~ object before and wasn't blown away. The usage in that patch is pretty effective (with a filter in between).
I'm surprised we haven't seen more gen~ stuff for valve distortion, tape saturation and things like that yet.
Here's the more or less standard approach to generating static functions. You'll notice that there's no signal rate stuff going on. (or you could do it with only signal rate functions, but in general, pick one or the other, not a combination of the two because of the potential for timing errors)
@Rodrigo: I think it's a question of the types of algorithms; static non-linearities are easy enough without gen~, though their quality is not the greatest. The other issue which gen~ doesn't address explicitly (though Max6 definitely does) is oversampling (via poly~). There's also a higher learning curve with gen~, since it requires more low-level knowledge of DSP. I've been playing around with non-linearities inside traditional filters; tanh works will inside of a reson loop.
I didn't realize you couldn't oversample in gen~. I've never really used it as it's over my head.
That looks great, I'll have a play with this later when I get home.
edit: sorry for the nooby question here, but how to you implement the lookup table version of tahn? (in terms of adding distortion like the first example posted)
Thanks for the annotated touch up Pete! Helped a lot
"but how to you implement the lookup table version of tahn?"
- use [tanh] and run a series of ascending numbers from 0-1 through it
- use peek~ to write it into a buffer~
- make a buffir~with the same name as the buffer~, see buffir~helpfile.
- like someone said, upsample it in a poly~to reduce the problem between latency and quality.
beg your pardon, buffir~ should read lookup~
^^
Ah that's pretty awesome. I've never used lookup~ before.
So at low gain settings this table version of tanh sounds pretty crappy, is that due to the intermodulation/upsample stuff?
Regular tanh seems to fair alright for low gain stuff.
@Rodrigo, you need to build the table with bipolar values; it will sound like crap if you use 0-1 to build your table. (since it's now bent in a weird way and unipolar. (Use the code that I posted above)
For tanh, use a value from -4,4 to build your table. That's the point where tanh's value is asymptotically close enough to -1,1 that you can just clip it to -1,1 after that.
You can scale your input before it hits lookup~ if you like. This will adjust the extremeness of the effect. You should also consider post-gain to adjust for differences in volume. Other typical tricks used are to add a slight DC offset so you get even harmonics as well as odd. You can use a highpass filter to remove the DC afterwards.
I used your example from above and just added a lookup~
I might just be in over my head code wise, but are you saying to make a -4/4 table, then clip it to -1/1 (literally using clip~)?
I would add a post gain control to bring the volume back in line with whatever the boost was adding.
That's an interesting trick (adding DC then removing it). So you would just add a [+~] to the audio signal before the lookup~/tanh~ (a little? a lot? or is this a 'tunable' thing like gain), then a straight highpass after the lookup~?
Then there's the upsampled poly trick.
Any other nuggets of distortion creating gold that stops short of gen~ stuff?
I think we just need to collectively get one reaaally good gen~ patch with a lot of inlets to tweak stuff and a buffer~ loader thing. That would be an excellent place to start for our own nasty patches as well.
That would indeed be badass. It's crazy how complicated tone shaping stuff can be.
Here's a bit more elaborate of an example:
and TanhVox~:
Man there's a lot to take in in that patch! Thank you!
The start of the signal path confused me until I figured the delay is to probably keep phase alignment with the cross fading input gain (a nice touch!). With regards to that part, wouldn't running the "dry" signal into normalize flatten it's dynamic range a bit?
I ran the output of sfplay right into delay and ran the normalized version into the envelope follower and it sonds alright.
The +/- gan thing is really interesting too. I'm wondering if it would be best to control asymmetrically using a couple of scale objects and a single num box.
So much to play with, and this not even into eq/filter territory (I'm tempted to duplicate this whole section and run it in series with a one pole it be middle (like the gtr patch).
Glad to help. Normalize will normalize it to the highest peak. It's handy for signals like electric guitar that are pretty stable in terms of dynamic range. It also makes sure that you get some peaks on the envelope follower. You could do this all with gain, but this works much quicker. I like to use asymmetric envelope followers like slide~ after average~ so that you get the decay, but the attack is still crisp.
The reason for the scale with the asymmetry is that I wanted to smooth the discontinuity in gain levels. You can try different crossover ranges (or even no cross-over range) by setting the values for clip~ and scale~'s input range in parallel.
I'd highly recommend the last approach you mentioned. This is the basis of some of the early digital distortion effects. Speaking of which, there was a paper not too long ago (ICMC Proceedings or maybe CMJ?) that summarized a lot of the approaches to digital distortion. It's totally worth a read. I'd bet some of the early patents have also expired, so Google may be your friend on that.
Yeah each little section of the patch is a nice little educational resource. I'll try doing that series/filter thing to get some joy. It's also more computationally expensive than I had realized, so I might use a more straight ahead version (not unsampled, or smaller table etc..) in my main (already big) patch as I can't afford to give up 6% just for distortion. Unless I wrap the whole thing up in a poly and mute it when I'm not using it. Hmm.
I'll have a look around for the paper.
Here's the paper:
And the dude's thesis:
https://ccrma.stanford.edu/~dtyeh/papers/DavidYehThesissinglesided.pdf
Both waaay too heady for me, and believe it or not, not a single compressed max patch example in either to copy!
Also found this which has more practical info:
http://music.columbia.edu/cmc/music-dsp/FAQs/guitar_distortion_FAQ.html
Just bumping this for any new distortion related revelations...
I've leaned back towards an overdrive/onepole/overdrive type system since tahn~ distortion seems to have some brittleness to it (or at least how I was implementing it).
Would be amazing to have a convolution-based tape/saturation type effect for more subtle/warming effects. (similar to the tritone digital colortone pro type thing).
there are so many ways to approach this, largely depending on your input signal. i have many many of these non-linear saturation type equations stored up over the years - i'll post some if i get the time.
oversampling is key. and making input / output gains simple eq's / filters rather than just linear gain controls, and as peter says, including non-linearities in your filters.
the one advantage of using cpu + equations rather than lookup tables is being able to modify them dynamically. attached is a visualisation of this i did this morning, i hope it is interesting. requires max 6.0.4.
Man that sounds really nice, quite simple too. I'd definitely love to see more of your examples if you wouldn't mind sharing.
ok, here are two more.
one is another Tom Szilagyi plugin port, still in progress and needs work. << single patch
the other is a Julius Smith cubic non-linear saturation curve which Peter McCulloch told me about, so kudos to him. << .zip folder, abstraction, open the .maxhelp file first
there is no great need for these to be in gen~ - i just happen to be accumulating gen~ things at the moment as i have become obsessed. also, much much easier porting C-code that way. hence, max 6.0.4 required.
also, one more thing - disclaimer: i have no idea what i am doing.
More good stuff. Very handy patches to throw around at the end of signal paths. Is it possible to wrap an upsampled poly in another poly for dsp muting?
Also, for the sake of tidying things up, is there any reason why the gen stuff is all over the place with cubic one? Like the gen object refers to an actual gen file, which then refers to another gen file.
Is there a reason why the body/text of cubicnl.genexpr isn't written into a codebox straight up?
hi.
no need to put it in another poly~, you can just mute the poly~ it is already in. from the abstraction prepend 'poly' to get there.
well, there is no good reason why the version i posted was all separated / abstracted like that, other than it was pasted together from a larger project where that does make sense. although, for me i always abstract - so that i can get to different versions of the code from wherever i am patching - e.g. any GenExpr code with a simple include. that is the main reason why all this is gen-ified, rather than just an MSP patch.
attached is another version which only requires itself and the poly~ file, all other code embedded. it also makes muting explicitly clear in the helpfile. hope it is of use.
Yeah I thought that after posting it. My main patch is starting to hover around the 20-25% CPU usage so I'm trying to be more careful/efficient with the things I add, though I'm probably creating a misleading situation for myself when I turn on all the CPU heavy stuff at once and get the % way up.
That makes sense for just using 'include' messages. Also trying to be mindful of the file count in my patch as it's already at 22 supporting files (polys and pffts mainly).
I've have a gander at this when I get home. It does sound really good this one, and has a nice range of control. I found that harmonics above 0.01 seem to aggressively gate the signal, so I've left that super low.
mute~ is handy as shit! I've never seen it before. It would've saved me tons of hardship as I've been wrapping everything expensive in a poly~ which takes forever......
pcontrol is also worth knowing about.
yes, as peter says, [pcontrol] is also very useful if you have other things going on. in true cycling74 style, the best [pcontrol] example is in the [mute~] helpfile. in fact they are some of the oldest objects i think. [mute~]/[pass~] are great - but you can get burned with third party externals that have not utilised them correctly (whereas [poly~]/[pcontrol] always get them). and NEVER FORGET YOUR INTERNAL [PASS~].
@rodrigo, you mention "harmonics above 0.01 seem to aggressively gate..." - yes, it is actually a brutal offset to the signal, and you are looking at values between 0.001 and 0.009 really, although you can achieve special effects higher, which is why i left the range that way. it is dcblocked on output.
i am glad you like the effect and that you may use it.
@Wetterberg, and anyone else interested, attached is a collection of about 20 or so waveshaping algos side-by-side, to hack and re-use should you be interested. and you can use peter mcculloch's buffer writing method in this thread to turn them into lookups if you need.
I don't know why I've never come across it before, I just learned the poly/mute method and went with that I guess. It was always so tedious to make something, then wrap it up in a poly just to be able to mute the dsp.
Re: the gating/harmonics. I ended up using 0.008 I think, but as a fixed value.
Ooh, I'll take a look at that waveshaper stuff.
this waveshaper stuff is great and a very nice resource to be able to dip into. Thanks a lot. I can see hours of noise to come .
@stkr: This is awesome. Many thanks. Please consider making this part of your toolbox, as it'd be a great resource for others.
this is most excellent. I'm way too fond of way too many of these shapers already... :)
fantastic patches, thanks!
as a wise man once said: "in distortion lies truth" :)
well, those are some great responses. maybe i need to look into this whole scary 'toolbox' thing.
Most definitely! There's definitely a lack of these kind of 'bread and butter' effects.
The only thing would be since all the files are clumped together in bigger gens it would be very inefficient for people with only the gen runtime (me included) as they can't single out the one they want to use.
I only wish I was working more patches that need distortion.
Not specifically distortion related but since it was mentioned in this thread, I'm having a hard time muting the dsp in a subpatch (on startup) that contains an FFTease object. Mute~ wasn't working so I tried the pcontrol method and still nothing. I can mute it manually but on load it turns on from default.
I can wrap it in a poly I guess, but the mute/pcontrol method is so much more convenient.
ok, it appears that this toolbox thing works in 'stealth mode', i.e., i have no idea if it is up or not. but this seems to work at this link:
i added a .genexpr file, so that if you do not have a Gen license you can at least see the code in full and port to patching land yourself if needed. although, if that really does not sound fun, let me know which are your 'favourites' and i'll try and put them all into separate basic abstractions for you.
@rodrigo, that is annoying about the muting, yes. usually this is because of an error in the third party object. however, those objects are made by one of the worlds external-making geniuses, so i doubt that. maybe it is a max6 issue. poly~ the only way for that unfortunately.
I saw it come up on the rss feed so it's 'out there'. Thanks again for putting those things together, amazing resource.
Yeah I just threw it in poly~ and sorted it out.
Hmm, the upsampled cubic distortion seems to enjoy blowing up my audio for some reason.
Every now and again I'll load up my patch and the moment I engage the distortion (muted with mute~/pass~) I got a pop, my meter goes (and stays) red, and I have no audio, until I turn off the dsp and turn it back on again.
Any thoughts on what could be causing this?
hi rodrigo. sorry about this. i had used that abstraction a lot myself before posting so i thought it would be fine for others.
i have been trying to reproduce your problem and cannot. at first i thought it might be an issue with NAN's or denormals but it seems not. i may have found some strange behaviour of the [pass~] object though.
could you tell me:
- are you using [cnld4~] or cnld8~]?
- are you using the abstraction's own mute~/pass~ system? or do you mean you have it inside yet another mute~/pass~ system of your own?
- do you have your own gain ramping system on the muting command?
if you are on [cnld8~] and using its own mute~/pass~, could you try instead using the internal [poly~]'s muting as shown in the helpfile, and report back? feel free to pm me if you'd rather.
in the meantime, i will try and get to the bottom of it some more.
Yeah it's weird. It's been quite inconsistent in terms of when it happens so that makes it hard to pinpoint. I have lots of other modules that are mute/pass'd and this is the only one that seems to blow up, though it could perhaps be related to what comes before or after it.
I'm using the 8 version.
I'm using the mute/pass that's there (sort of).
I'm using a line~ and !-~ to bypass the audio (fading dry out and fading wet in).
Not sure what you mean about the internal poly muting. I've not tried just muting the poly directly. It'd be nice free up all the DSP (the biquads/onpole) but if that makes it more reliable then I'm all for it.
Here's the (slightly reworked) implementation I'm using.
Inspired by the amazing patches in the thread i had a go myself :) First in 5 but having upgraded last week i ported it to gen but i'm not too sure about the codebox-algorythm? It sounds different...
FRid
great thread! I am a bit late to the party but big thanks to everyone for the info!
That's what I have.