Retro 80s Tape Wow & Flutter with faust
-
@Morphoice Nice work!
Jatin Chowdhurdy has a good paper on magnetic distortion and how hysteresis works:
https://dafx.de/paper-archive/details/Z5Ow_E5YHVxvndTAyeEuzAThey also have a GPL repo and free plugin for this on their Github and website:
https://github.com/jatinchowdhury18/AnalogTapeModel
https://chowdsp.com/products.html#tape@Lindon is right though it really does just introduce a lot of delay.
I am working a an analog model of the Echoplex EP-4, I can share my method for wow and flutter here once it is done.
-
@Lindon weird, it does indeed in faust IDE, but works fine in a faust scriptnode as a plugin, i mussed have messed up somewhere inbetween
-
there you go, the smoothing fucked up two of the sliders, now it works as it should, no more delay
import ("stdfaust.lib"); wow = os.lf_trianglepos(hslider("wow",0.5,0,2,0.01)) : si.smooth(0.9999) * 1000.; wow_intensity = hslider("wow_intensity",0.3,0,1,0.01):si.smooth(0.9999); flutter = os.lf_trianglepos(hslider("flutter",8,2,20,0.01)) : si.smooth(0.9999) * 1000.; flutter_intensity = hslider("flutter_intensity",0.1,0,1,0.01):si.smooth(0.9999); hiss = hslider("hiss",0.1,0,1,0.01); drive = hslider("drive",0,0,1,0.001) ; tanh(x) = x * (27 + x * x) / (27 + 9 * x * x); saturator = ef.dryWetMixerConstantPower(drive,tanh * drive * 20 : co.limiter_1176_R4_mono: tanh ); tapenoise = no.pink_noise*0.6 + no.sparse_noise(10)*0.2 : fi.bandpass(1,600,20600)/5 + os.triangle(60)*0.05 + os.oscsin(528)*0.003 + os.oscsin(110)*0.002; wowflutter = de.fdelay1(ma.SR, 100. + wow*(wow_intensity) + flutter*(flutter_intensity/5)) <: saturator : fi.lowpass(2,8000) + tapenoise * hiss; process = wowflutter, wowflutter;
-
@HISEnberg there's an interesting paper about the echoplex somewhere, it should get you started, I'll drop you the link if I can find it again
-
This post is deleted! -
@Morphoice thanks, it might be the one I am basing my research on, by Julius Smith :)
-
@Morphoice Nice that is much better, well done.
I thought it might be nice to introduce some randomization to the modulation so it isn't always static. I also thought some type of envelope follower on the input to attenuate the hiss would be nice, but I didn't complete it.
import ("stdfaust.lib"); import ("analyzers.lib"); // Wow and Flutter random_mod = hslider("random_mod", 0.002, 0, 0.01, 0.0001); wow = os.lf_trianglepos(hslider("wow", 0.5, 0, 2, 0.01)) + no.noise * random_mod : si.smooth(0.9999) * 1000.; wow_intensity = hslider("wow_intensity", 0.3, 0, 1, 0.01) : si.smooth(0.9999); flutter = (os.lf_trianglepos(hslider("flutter", 8, 2, 20, 0.01)) + no.noise * random_mod) * (0.8 + os.oscsin(0.1) * 0.2) : si.smooth(0.9999) * 1000.; flutter_intensity = hslider("flutter_intensity", 0.1, 0, 1, 0.01) : si.smooth(0.9999); // Noise hiss = hslider("hiss", 0.1, 0, 1, 0.01); tapenoise = (no.pink_noise * (0.6 + os.lf_triangle(0.1) * 0.05) + no.sparse_noise(10) * (0.2 + os.lf_triangle(0.05) * 0.05)) : fi.highpass(1, 50) : fi.lowpass(1, 15000) / 5 + os.triangle(60) * 0.05 + os.oscsin(528) * 0.003 + os.oscsin(110) * 0.002; // Saturator drive = hslider("drive", 0, 0, 1, 0.001); tanh(x) = x * (27 + x * x) / (27 + 9 * x * x); saturator = ef.dryWetMixerConstantPower(drive, tanh * drive * 20 : co.limiter_1176_R4_mono : tanh); /* // Envelope Follower for Noise Modulation t_attack = 0.005; // Attack time in seconds (5 ms) t_release = 0.05; // Release time in seconds (50 ms) signal_level = an.amp_follower_ar(t_attack, t_release); // Dynamic Noise Control modulated_noise = tapenoise * (1 - signal_level) * hiss; */ // Combine Wow, Flutter, and Noise // Substitue the tapenoise * hiss for an attenuated tapenoise (modulated_noise) wowflutter = de.fdelay1(ma.SR, 100. + wow*(wow_intensity) + flutter*(flutter_intensity/5)) <: saturator : fi.lowpass(2,8000) + tapenoise * hiss; // Output process = wowflutter, wowflutter;
-
@HISEnberg brilliant thanks. I had already considered to add some randomness something like
rate = ma.SR/10000.0; // new random value every 100 samples (ma.SR from maths.lib) process = no.lfnoiseN(3,rate)*0.5 + no.lfnoiseN(3,400)*0.1;
instead of a fixed wow frequency. (this just being a test to create a low frequency random noise)
also trying to figure out some ways to "degrade" the signal. I've indeed studied Jatin's work on the tape model but it's giving me a hard time to adapt to faust -
@Morphoice Same here, it is insightful but I am still making my way through it.
I suppose for degrading, you could consider frequency-dependent saturation? I think tape saturation ought to effect low frequencies more than high ones.
Other than that, a slight compression at the end.
I also considered something like an "Tape Age" slider which would represent newer to older tape. This could probably be implemented with another noise source and LPF, but some type of saturation would be nice too. Also it would cause greater wow and flutter, but this get's a little unwieldy so I think the values need to be readjusted.Another thing I did was introduce a bit of stereo processing, so the modulations on the left and right were somewhat offset. I find it helps create a better stereo field but this isn't necessarily something tape does.
import ("stdfaust.lib"); import ("analyzers.lib"); // Tape Age Control tape_age = hslider("Tape Age", 0.0, 0.0, 1.0, 0.01); // 0 = New, 1 = Old // Wow and Flutter random_mod = hslider("random_mod", 0.002, 0, 0.01, 0.0001); wow = (os.lf_trianglepos(hslider("wow", 0.5, 0, 2, 0.01)) + no.noise * random_mod + os.oscsin(0.3) * 0.0005) : si.smooth(0.9999) * 1000.; flutter = ((os.lf_trianglepos(hslider("flutter", 8, 2, 20, 0.01)) + no.noise * random_mod) * (0.8 + os.oscsin(0.1) * 0.2) + os.oscsin(15) * 0.0003) : si.smooth(0.9999) * 1000.; wow_intensity = hslider("Wow Intensity", 0.3, 0, 1, 0.01) * (1 + tape_age * 0.5) : si.smooth(0.9999); // Scaled by tape age flutter_intensity = hslider("Flutter Intensity", 0.1, 0, 1, 0.01) * (1 + tape_age * 0.5) : si.smooth(0.9999); // Scaled by tape age // Stereo Spread stereo = hslider("Stereo Spread", 0.1, 0, 1, 0.01); wow_left = wow; wow_right = wow + no.noise * (stereo * 0.001); // Subtle difference flutter_left = flutter; flutter_right = flutter + no.noise * (stereo); // Subtle difference // Noise hiss = hslider("Hiss", 0.1, 0, 1, 0.01); tapenoise = (no.pink_noise * (0.6 + os.lf_triangle(0.1) * 0.05) + no.sparse_noise(10) * (0.2 + os.lf_triangle(0.05) * 0.05) + os.oscsin(50) * 0.01) : fi.highpass(1, 50) : fi.lowpass(1, 15000) / 5 + os.triangle(60) * 0.05 + os.oscsin(528) * 0.003 + os.oscsin(110) * 0.002; // Apply Tape Age to Noise aged_tapenoise = tapenoise : fi.lowpass(1, 15000 - tape_age * 8000) * (1 + tape_age * 0.5); // Aged noise // Saturator drive = hslider("Drive", 0, 0, 1, 0.001) + tape_age * 0.2; // Increased drive with tape age tanh(x) = x * (27 + x * x) / (27 + 9 * x * x); saturator = ef.dryWetMixerConstantPower(drive, tanh * drive * 20 : co.limiter_1176_R4_mono : tanh); // Combine Wow, Flutter, and Noise wowflutterLeft = de.fdelay1(ma.SR, 100. + wow_left * wow_intensity + flutter_left * (flutter_intensity / 5)) <: saturator : fi.lowpass(2, 8000) + aged_tapenoise * hiss; wowflutterRight = de.fdelay1(ma.SR, 100. + wow_right * wow_intensity + flutter_right * (flutter_intensity / 5)) <: saturator : fi.lowpass(2, 8000) + aged_tapenoise * hiss; // Output process = wowflutterLeft, wowflutterRight;
-
@HISEnberg I've thought about stereo processing, but it would imply having two mono tape machines... a single stereo tape would always run the same speed and flutter/vibrate the same as it's a single stereo head picking up the signal...
again though this would offer a bit of variance and introduce an abstract option to go more into experimental soundsas for the degrading it's really the hysteresis I want to tackle, as all the normal saturation circuits we use are just a "cheat"... also having some transformer things like the Kush Omega Transformer
-
@Morphoice yes I thought of that too regarding the stereo processing. I suppose it would be useful if you want to retain the original, stereo signal input. Otherwise it's just for experimentation.
-
@HISEnberg @Morphoice - as I mentioned before if your seriously thinking of emulating tape machines you will need to account for head bump - and probably make it variable as different machines from different manufacturers had different amounts in different freq...
-
@Lindon good point. I could add some eq to get close to an average tape response curve. Not really looking to model specific tape machines, just want to have that wobbly 80s retro sound much like baby audio's VHS
-
I've been doing some work to extract the real transport from multiple machines. I'm not terribly convinced by the quasi-random shapes most people use to model wow.
In terms of hysteresis and the frequency response of tape, everyone else here is correct: Jatin's work (chow tape) is a good place to start.
-
@Morphoice That reflects my use case as well, I am more interested in experimenting with (and pushing) the potentials of tape styled-modulation.
I agree with you that the saturation element is queue here, I will let you know if I make any headway with the hysteresis (though I remember reading its full implementation is super CPU intensive).
I suppose a nifty feature to explore could be tape startup, rewind, and different tape speeds.
-
Good work !
-
@griffinboy I agree. As the wow results from the revolving reel or cassette it's rather periodic than random, the random wow makes more sense to me on a tape delay which has a bunch of lose tapeloop in a cartridge
-
@HISEnberg sadly I'm far from being able to implement the hysteresis myself or port it to faust somehow, but if you ever manage to do so please let me know. the question then again is, though, does the end-user even hear the difference of is a simple saturator sufficient. I'm not even sure all the big companies like Waves tape emulation plugins even have something like exact hysteresis. Jatin points out the various solvers to calculate a hysteresis loop and they are indeed quite cpu heavy. maybe the UAD plugins did such on thing on their DSP
-
If you want a c++ node for it (works in scriptnode) I can lend you mine.
3% cpu usage when you oversample it, if I remember correctly. It's a clone of Chow tape's hysteresis, with some minor alterations and no dependancies. -
@griffinboy I'd love to take a look at it, though I've never used a C++ node before and will probably fail terribly at getting it running lol