## MillerPuckette's "Switch&Ramp" reworked in gen~

Jun 20 2013 | 6:09 am
[Edit: Most current version of patch:
]
I got obsessed with figuring out MillerPuckette's "Switch&Ramp" technique:
'stead of using snapshot~(wanted to use gen~'s 'history' object), basically, trying to offset the phase at the point of retrigger by the absdiff between 2 samples at the boundary of abrupt change. This creates continuity in the waveform at the point of retrigger. Then ramp the amount of offset gradually back to normal. 0-delay, clickless retriggering... so to speak... :p
However, the offset wave, until it ramps back to normal, has a chance of rising above 1. or falling below -1. which would cause clipping at dac, so i used tanh before the output(probably could use something more advanced and better there, but to my ears, it's actually quite useable...)... maybe in the future, i'll try to scale the output by some relation to the absdiff... or try some type of compression...
The fade time, which i call "fade-back-to-normal" time is determined by the "32" commented in the gen~ subpatcher, if you find the fade portion too dirty, you could try larger values(the 32 is the number of samples it takes to fade from the offset back to normal).
[Edit: no wait, i'm wrong... samplerate/32 = the number of samples it takes to fade... :{p.... higher numbers make for shorter fades... shit, so i guess '128' thru '2048' make for better fades... i kinda like the soft/lopassed click that comes from 2048... i digress... try different values yourself :) ]
Hope it's useful. Feel free to learn me good on more efficient/better methods ;D

• Jun 20 2013 | 10:49 am
and here's another version, this time without tanh at the output, instead i scale the signal for the duration of the fading-offset by a ratio...
uh... kinda just experimented with the math in my head... tried to find the maximum difference between each of the 2 samples and digital-clip range(-1,1), then created a ratio between those 2 differences, and finally created a ratio between that ratio and the absdiff of the 2 samples. somehow it scales the signal nicely...i think... plus to introduce this scaling ratio gradually(because abruptly would cause another kind of click), i drive it with a sin envelope...
you ever notice that when people try to explain their max patches, it sounds like they're talking to themselves and it's a bit like describing music whereby the words just make no effing sense whatsoever and you wish the person would just shut up and show you the art already?! >:D
ok, good point, so here it is:
i like it so much, i almost feel arrogant enough to suggest it should be part of the gen~ examples... but alas, i'm sure there are folks out there who can do it better, and although it has 0-delay(by that i simply mean, no fade-in/out time), the clicks are merely tamed into slight distortion for a short period of time that's barely noticeable and therefore this isn't some end-all-be-all solution that could be implemented in something more advanced to create for example, granular-synthesis based time-stretch with no windowing artifacts :p
(haven't tried... but i'm pretty sure...)
and besides, that scaling function is just my own...almost 'guess-work', and could be improved since it was birthed from so much trial and error/experimentation(i may have known what i wanted, but it's not like i knew exactly how to get it, hehehe).
still, i've been looking at that page from MillerPuckette's book for over a year now, couldn't figure it out... it feels pretty cathartic all of a sudden(i feel like i even improved on it).
anyways, thanks for indulging my ego for a second there :p
• Jun 20 2013 | 12:47 pm
Hi
Congratulations, and nice work. It's great to put the theory into practice - although MP's work is very much practice-driven - working out the numbers and symbols into usable sounds and processes is strangely rewarding.
"you ever notice that when people try to explain their max patches, it sounds like they’re talking to themselves and it’s a bit like describing music whereby the words just make no effing sense whatsoever and you wish the person would just shut up and show you the art already"
very LOL!
Brendan
• Jun 20 2013 | 12:57 pm
Sounds like a very interesting concept. I tested out the second patch, unfortunately I found that both modes were rather clicky. Does it rely on certain Audio settings?
• Jun 20 2013 | 4:17 pm
That sounds gangbusters!
I also love that it's just on the audio side, so no additional logic is required for using it.
I was doing something to declick my jumps, but it required all this support around the playback object so things would work right.
• Jun 20 2013 | 4:34 pm
@karaokaze
I believe you seek for too much philosophy in this method. It's simple and with limited efficiency. The trigger have to be perfectly in sync with the second EG (and click~ driven from scheduler can be late). My approach below, comments appreciated.
PS. methinks there are only two kinds of people/posts on this forum: those who ask questions and those indulging... ;-)
• Jun 20 2013 | 6:09 pm
Hey,
can you avoid saturation efficiently with this method ? I mean, by adding an offset to an attack signal, it could very well go over 1...
• Jun 20 2013 | 6:37 pm
avoid saturation? I don't think so. This is technique for canceling discontinuities, and I wouldn't expect anything more. This method is meant rather for sounds with sharp/bright attack ("any artifacts arising from this technique are more likely to be hidden by the new sound’s onset." - Puckette, p.90), so my example with lo-freq sine generator is just for emphasizing the difference.
• Jun 20 2013 | 7:01 pm
@Andrzej
"I believe you seek for too much philosophy in this method."
haha, i believe you seek for too many words in your writing :D
("philosophy"? there is hardly any 'philosophy' going on here. Miller's book is too technical to read like Socrates. really? wtf?)
i tried your method on the vibes-a1.aif sound which i originally used. if you use cycle~ it's an easy periodic waveform(no matter how bassy you go, it's still too obvious and easy). With an actual recorded sample like vibes, there's a bit more noise and unpredictable wavering. I hear the same saturation/distortion as with my tanh(in fact, pressing really fast, your gen version gives a click every now and then when used on recorded samples).
nice to see the changes, though. (i'll try your method more and see if i can get rid of that occasional click)
P.S. methinks you and me BOTH are definitely of the second variety you mentioned ;D
@Vichug
"can you avoid saturation efficiently with this method ?"
not with Miller Puckette's method as such but eventually, as i tried with my second attempt, we could figure out a way to scale the offset wave according to how far away it is offset from its original phase. and this would definitely avoid saturation, but i haven't figured out the proper scaling math(which is the main part i wanted help with but Andrzej probably thought "wtf is all this extra bullshit for" without realizing why i put it there :D).
Try and try again, will get it sounding nice eventually.
@Rodrigo and everyone else
Thanks for the support. :)
• Jun 20 2013 | 9:27 pm
@farfisavox
sorry, didn't see your post at first.
my settings are vector size of 64, overdrive on, in-audio-interrupt on, in the Audio/DSP settings(try the first patch, then, maybe it's also cleaner, or Andrzej's).
• Jun 21 2013 | 12:56 am
@karaokaze
it's nice you found "edit" button ;-)
TBH at first I thought "wtf ys al dis ekstra bulszit for", then I thought you somehow enhanced Puckette's method, but after closer look I came back to my first suspicion (for some sample configuration it adds half-sine distortion). If you want to ensure the signal won't overdrive put limiter after gen~. There always will be some "saturation" – the waveform is already distorted (by slicing).
How did you connect your "vibes-a1"? esp. trigger signal? button->click~? i'm sure this part is responsible for "occasional click".
• Jun 21 2013 | 1:15 am
"There always will be some “saturation” – the waveform is already distorted (by slicing)."
and i'm saying that you aren't dreaming big or trying hard enough :)
we can scale the wave after offset for just a short time to account for this, we just haven't thought of how yet(you nor i... i understand what you say about half-sine distortion but there must be a way to perfectly compress the offset until it ramps back down to normal).
your limiter and the whole adsr~ defeats the point and makes it inefficient. i'm saying there's a way to get this without amp-envelope and without distortion. Miller just didn't write about it, so you don't bother thinking beyond it :p
we agree to disagree.
"it’s nice you found “edit” button ;-)"
ya, totally, after all, i don't want to sound as condescending and dismissive as you do ;-)
• Jun 21 2013 | 3:34 am
@karaokaze
"
and i’m saying that you aren’t dreaming big or trying hard enough :) we can scale the wave after offset for just a short time to account for this, we just haven’t thought of how yet(you nor i… i understand what you say about half-sine distortion but there must be a way to perfectly compress the offset until it ramps back down to normal). your limiter and the whole adsr~ defeats the point and makes it inefficient. i’m saying there’s a way to get this without amp-envelope and without distortion. Miller just didn’t write about it, so you don’t bother thinking beyond it :p
"
I must admit you know me very well...
"
“it’s nice you found “edit” button ;-)” ya, totally, after all, i don’t want to sound as condescending and dismissive as you do ;-)
"
Excuse me for sounding patronizing. It wasn't intended. Just my clumsy linguistic skills. "Philosophy" maybe wasn't the best word, but the beauty of MP method is its simplicity. You try to find some coefficient/function ("offset") which would produce perfectly smooth wave transition. But to achieve this you have to know future samples, not available at the point of transition.You can guess them, you can try to predict them, but I don't believe there is a general solution to this problem. Maybe I'm wrong. Prove it, and I would be grateful, as I could learn something.
No offence, but I hoped you are able to get some value from a critical view.
Thank you for pointing interesting topic, and opportunity to get back to Puckette's book.
• Jun 21 2013 | 6:30 am
No worries, i will keep at it and take your suggestions into account.
It is simply that time of the month for me, when a man starts to bleed out of the vagina in his heart. Please excuse me :D
(i think also, in the my second example, instead of the additional-beginning sine envelope, i could use the same 1->0 ramp i had originally(which you also used) to ramp the scaling as well as the offset back to normal, but in that case, just need to add another offset for the start of the scaled portion... i'll try it out, otherwise, i like your tries and my tanh, s'all good)
just to explain where my problem with wording occured, yes, it was the word "philosophy" but also the word "indulge", you see, this thread is not a frivolous use of time or an indulgence. this is something i badly need to figure out.
Thanks for your patience :)
• Jun 21 2013 | 10:22 am
@karaokaze
(i think also, in the my second example, instead of the additional-beginning sine envelope, i could use the same 1->0 ramp i had originally(which you also used) to ramp the scaling as well as the offset back to normal, but in that case, just need to add another offset for the start of the scaled portion… i’ll try it out, otherwise, i like your tries and my tanh, s’all good)
this sounds like you want just simple EG (short attack with dur equal to ramp) before S&R.
@everybody_else esp. @c74
this thread is a living proof of how much private messaging is needed
@karaokaze (privately)
It is simply that time of the month for me, when a man starts to bleed out of the vagina in his heart. Please excuse me :D
[...]
just to explain where my problem with wording occured, yes, it was the word “philosophy” but also the word “indulge”, you see, this thread is not a frivolous use of time or an indulgence. this is something i badly need to figure out.
when I wrote about asking/indulging distinction I was referring to myself, as I didn't ask any question, moreover I answered unasked. Just to help others and/or show off. Hence that magic semicolon-minus-parenthesis.
Besides some gore details of your physiology (you shared with us above :D), I know nothing about you, so that was nothing personal. Take it easy, my friend. Let's assume this issue is sorted out.
BTW this thread is not a frivolous use of time, as it wasn't eg. preparing test case for your vs my approach.
• Jun 21 2013 | 6:49 pm
"when I wrote about asking/indulging distinction I was referring to myself, as I didn’t ask any question, moreover I answered unasked. Just to help others and/or show off."
ah! then it was certainly my mistake, for i thought you were implying that when i wrote "indulge" that i was actually serious about indulging my ego, when really i was just trying to humble myself after claiming this shitty patch should go in the 'examples' folder.
thanks for explaining(maybe next time, you could just at least be clear about whom you are referring to... just that little bit would help understand you better... i'm sure others may have understood but it was completely over my head(which is not a difficult thing to do ;D)).
again sorry, for taking it the wrong way(my father goes to Palestine at the risk of his own life to perform surgery for small children in need, for free, because they cannot get to hospitals expediently enough for badly needed medical care due to Israeli blockades... and also does the same in Haiti, Venezuela, Niger and other countries in need...
all that is to say, living in his shadow makes me feel like a waste of life as is, and therefore, i have a weakness for being called 'indulgent' or anything remotely frivolous(my posts here sometimes maybe obnoxious but they are not a waste of time: they help me become more brave)).
The issue is indeed sorted, thanks for bravely facing it with me :)
(and they got rid of pms! damn, that was stupid. i mean i would not initiate them but if someone wanted to tame my obnoxiousness, they could initiate a pm and that would certainly be the way to do it, because i would certainly keep it to pms after that *nudge-nudge-wink-wink* ;D)
• Jun 22 2013 | 3:32 am
Perhaps the next step in this is to add some sort of transient detection to it, for dealing with signals where there isn't a known discontinuity. It seems like a pretty handy utility to have in the Max ecosystem.
• Jun 22 2013 | 8:33 am
Thanks Peter, will look into it.
One final post before i leave this for awhile...
I finally figured out the exact kind of scaling math i wanted.
Bare with me while i try to explain it a bit in case others might see what i'm doing and think of better math solutions... (there are also comments in the gen~ patcher to explain further, too, if anyone's interested).
precursor definitions:
'previous sample' = sample before abrupt change;
'current sample' = sample after abrupt change;
'clipping boundary' = the boundaries at which digital audio clips(1,-1)
'offset output' = the output after the current sample is reset to the same phase/amplitude as the previous sample(to create continuity)
I thought a way to keep from clipping(without any need for 'lookahead') is to assume a 'maximum allowable headroom' between the offset output and the nearest clipping boundary, and scale everything to fit within that.
To accomplish this, given that the 'current sample' has a certain maximum distance to a clipping boundary, i use that to form a ratio with the actual headroom between the 'previous sample' and its nearest clipping boundary... i then apply this scaling-ratio/multiplier to the offset output depending on how high it rises above the threshold set by the absolute value of the 'previous sample'.
It sounds pretty much the same, but at least i finally did it EXACTLY how i wanted it :) (and now i have some extra knowledge of how to create another kind of compression. i hereby call this form of compression: "raja-the-whiny-little-bitch-auto-compression". Don't forget that name! It should go down in electronic music history exactly as is >:D)
Thanks for being patient with my post(for those that are curious: i did all this because it helps me to visualize the math in max/msp/gen, then translate that into pseudo-code(see the gen~ patcher) which i will then translate into supercollider eventually(i will post the supercollider code here, too, when it's completed just for posterity :p).
...oh, and I believe Andrzej is right: there will always be some kind of click/distortion because of such an abrupt change.
Edit: although, someone could probably create some type of spline-interpolation which might do the trick too....
• Jun 26 2013 | 2:30 am
Nope, still going at it.
Thank You For Your Patience(with my obnoxious ranty posts) Everyone. This was pretty frustrating to figure out, but i really knew there had to be some way to tame the spikes created when applying Miller's method to recorded-samples and now I am finally done here.
This is final: Andrzej's math combined with my compressor to create a nice simplified version.
And, with help from Daniel and Alessandro on the SC forums, here's the simple version(without my compressor) in SuperCollider.
Retype the part in quotes from the line:
Platform.userHomeDir +/+ "/xs/1/Reticynth"
to whatever soundfile you want starting from your user directory, execute the code, and type keys 1 through 8 to retrigger:
SynthDef(\swtchrmpdemo, { | buf, retrig |
var step = BufSamples.kr(buf)/8.0;
var triga = K2A.ar(\reset.tr);
var rampenv = Env([1, 1, 0], [0, 512/SampleRate.ir]);
var ramp = EnvGen.ar(rampenv, triga);
var phasetrack = Phasor.ar(triga, BufRateScale.kr(buf), 0, BufSamples.kr(buf), retrig*step);
var playhead = BufRd.ar(1,buf,phasetrack,0,1);
var diff = Latch.ar(HPZ1.ar(playhead, 2), triga);
var ply = playhead - (diff * ramp);
Out.ar(0, ply!2);
}).add;
s.waitForBoot({
var win = Window.new("KeyPressCatcher", Rect(100, 100, 200, 40)).front;
b = Buffer.new(s, 44100, 1);
b.allocReadChannel(Platform.userHomeDir +/+ "/xs/1/Reticynth", 0, -1, 0, completionMessage:
{arg buf;
x = Synth.new(\swtchrmpdemo, ["buf": buf, "retrig": 0, "reset": 1]);
win.front;
});
win.view.keyDownAction = {|a, b, c, d, e|
s.listSendBundle(s.latency, [["/n_set", x.nodeID, "retrig", e - 28],
["/n_set", x.nodeID,"reset", 1]]);
}
})
• Jun 26 2013 | 2:35 am
No wait... one more. This thread is a testament to the importance of Max. You see, I thought i could leave Max once i started understanding SuperCollider. But then Cycling74 came out with gen~ (fuckers! just when i think i'm out...) and now it helps visualize math in a way i couldn't before, so that when i go to code larger apps with the swiftness afforded by how concise and powerful SC is, i can trace through the math first in max to fully understand what's happening at signal level(i am not smart enough to know how to visualize it in this way all within SC yet... :p).
So there ya have it :)
Apologies again to Andrzej. And Thanks Again To Everyone.
• Jun 26 2013 | 9:03 am
"Thank You For Your Patience"
we're here because we want, thanks be to you !
• Jun 26 2013 | 5:04 pm
ah, found your audio settings - huh, that works really nicely! :)
You might want to cross-post this over to the guys and girls at the monome forum; un-clicking MLR would be very handy.
• Jun 26 2013 | 5:33 pm
Sounded interesting so I took a shot. I used an exponential ramp (I like them that way), but a linear ramp version is included. Neither is perfect, for two reasons: 1) the output can end up outside the -1,1 range if the signal wavelength is longer than the ramp duration (likely), and 2) a discontinuity of phase typically also implies a discontinuity of slope, so there is still some low frequency pop. I find the clipping is less likely and the pop more wide-band (less focused) with an exponential envelope.
• Jun 26 2013 | 6:46 pm
Thanks All!
@Wetterberg
ya, it's there and was actually inspired by tehn who posted a max patching challenge there about this. but that was years ago before gen~ so it wasn't 'til now that i thought i could do it(Miller's solution to use snapshot~(in PD or Max) seems to work best with EG output from adsrs...like Andrzej's example, but gen~ makes it easier to do it all in the signal).
since i'm hoping to make a small SuperCollider tutorial for monome(involving a simple version of MLR), i wanted to try and figure this out specifically for that :D
(you know my mind well)
@Graham
Thanks so much! I will study the code to better understand it.
With the exception of the clipping beyond -1,1, sounds good overall...
Quick but simple-perhaps-stupid question:
not sure if this was explained in the gen~ doc/vignettes but... in your codebox, the line:
"History lastinput, env; ...."
Does this mean that 'lastinput' and 'env' are both history objects? (edit: ah i see by the code in the gen~ sidebar that they are, nvm)
And how can i tell by that code(whether in the sidebar or in your expr), which gen~ "in"(let) they are taking history from? (or why wouldn't i assume that 'env' is just as well taking history from 'in2'?)
(This LUA-style variable-management is a bit confusing for me, any guidance to clear up this confusion is most appreciated.)
Thanks again.
• Jun 27 2013 | 12:45 pm
And how can i tell by that code(whether in the sidebar or in your expr), which gen~ “in”(let) they are taking history from? (or why wouldn’t i assume that ‘env’ is just as well taking history from ‘in2′?)
I'm not entirely sure I understand the question... but maybe a bit of explanation will help? History doesn't take input from any [in], unless you tell it to. In GenExpr, you simply assign to the history name to update it. So whatever code assigns to "env", is what updates the "History env". You can even assign to it in several different places (e.g. in different if/else blocks), or not assign to it at all (in which case it behaves just like a Param). GenExpr is closer to the underlying C++ code in that sense: a History is just a member variable of 64-bit float type, accessible to get/set (read/write) anywhere in the code.
• Jun 27 2013 | 8:20 pm
Ah, i understand it perfectly now, Thank You Graham!
(my question was born of confusion where i only assumed 'history' objects had to be associated with a certain input signal path... but your description of the assignment/coding-context puts it all in clear perspective now)
• Jul 22 2013 | 11:09 pm
Is this patch not working for anyone?
When I first came across it I could hear the difference, but at the moment, everything is clicking. I've not used it since 6.1.3 (I think).
• Jul 23 2013 | 3:28 am
@Rodrigo:
Just downloaded a fresh copy of Max6.1.3(most current version available from front-page download-link) and ran it in both 32-bit and 64-bit(OSX), both work well with the patch. I even tried with vector size of 256 and 512(not just the usual 64 i use), and it worked great, wooohooo! (didn't realize the vs didn't matter here)
@Anyone Interested:
One thing i notice is that, within Audio/DSP Status/Settings window, you absolutely need Scheduler 'InOverdrive' and 'InAudioInterrupt' checked ON for this to work properly, otherwise, you'll get clicks and pops.
also, make sure you're using the most current one i posted, which i'll post again here, and now that i see we can edit ALL our old posts(thank you Cycling74! :D), i'll also add it to the very first post in this thread:
Just to reiterate, because there was alot of confusion early on about what i was trying to do: The difference between mine and everyone else's(Graham's, Andrzej's, and Miller Puckette's) is mine will tame the signal properly at the offset so that when run through a compressor or the like(for mastering of your recordings and such), the processing will not be tripped up by a spike in an unwanted range beyond the nominal level the signal was at before... you can see this clearly in the above patch by pressing key #6 on your comp keyboard and notice the others will spike above a threshold of +/-0.5, while mine("switch&ramp with auto-compression"), will not spike beyond that(as Graham pointed out however, there is still the chance of a drastic change in slope which can also cause clicks, albeit softer sounding ones). That is not to say anything bad about Graham's and Andrzej's(and obviously not Miller's as he is the inspiration for all), just that i had a very different goal(something more specific in mind for professional performance/recording situations), theirs are actually quite nice in terms of efficiency and slickness of implementation.
If the patch is working correctly, you should hear a very big difference between the first 'regular clicky output' and the others.
@Anyone interested over time, but specifically Rodrigo for right now:
Let me know more details if you run into probs, i'll see if i can be of better assistance.
[Edit: note @myself - this type of thing would be great to write into an MSP external(plus a UGen for SC)... remember to do :p]
• Jul 23 2013 | 8:04 am
That's what it is, it works in audio interrupt.
That's problematic as the rest of my patch doesn't play too nice with that box ticked, much more for sharing the patch, it becomes more inconsistent.
Is it because of the [t b f] above groove/click?
• Jul 23 2013 | 9:47 am
ya, that would be my guess...
but then, if that were the case, this solution shouldn't cause clicks/pops more than the other version:
so i'm not entirely sure, will investigate further...
"much more for sharing the patch"
folks can easily use the 'adstatus' object to share the patch appropriately and keep consistent(that's no reason!). but i understand if you have other things in your app that don't like that box ticked :)
• Jul 23 2013 | 5:16 pm
That one works a-ok. Thanks for that.
It's exactly that. My patch gets all grungy with interrupt on, though overdrive is needed for all the monome stuff.
• Jul 23 2013 | 7:16 pm
That last one works for you?!
It doesn't work well for me... hmmm... i'm confused... if it works for you, though, great! but try it out a bit more, let me know if you run into more clicks than usual(i'm hearing more clicks than the original patch... when retriggering really fast, maybe something with my settings, though, or something i'm not understanding bout groove's sync outlet, or maybe outside gen~ the 'delta~' -> '
• Jul 23 2013 | 8:04 pm
oops i'm an idiot, 'delta~'->'
• Jul 23 2013 | 8:56 pm
@Rodrigo
Here ya go, i didn't think i'd figure this out, and it's kind of a hack(if you are trying to use this with really long files or files at really slow speeds it might not work as well because my detection is lame and inexact: "delta~" -> "abs~" -> ">~ 0.0001") but that's the best i can do outside of gen~ or a C-style programming language :p, aside from this, i might try to roll a groove~-like playback mechanism within the gen~ patcher itself and make a nice encapsulated all-in-one tool for simple pop/click-reduced sample-retriggering:
Works great for me on all soundfiles i've tried(the longest file i tried is a 96k24-bit basssynth file of 55seconds, tested playing as slow as 0.0625 speed, no probs there either... so this tool should actually be quite practical :D), at all samplerates(44100-96k), all vector sizes(1-2048), with OR without 'overdrive' and 'audio interrupt' on, and in both 32-bit and 64-bit Max.
Thanks for inspiring me to investigate this and make sure it works under all settings(and i didn't even have to alter my gen~ patcher :).
*a grand sense of completion sweeps over me*
• Jul 23 2013 | 9:11 pm
Nice! Yeah that works perfectly, though checking it again (not exhaustively) the previous one worked for me too.
Very very useful tool this is.
• Sep 10 2013 | 10:57 am
Hi Karaokaze,
This is awesome! Thanks!
Joe
• Sep 11 2013 | 12:34 am
You're most welcome :)
• Sep 11 2013 | 12:36 am
oops... i meant to say... You're most welcome :)