Advanced Looper (question(s))
So I’ve been rebuilding the core parts of my main performance max patch slowly and I’ve finally come to the gnarliest part of my code, that needs the most amount of reworking. The main looper/sampler.
I’ve been using a groove/poke combo for a few years now as the main looper and it kind of does what I want, but it’s not perfect and adding some new features puts me into new territory for me, so I decided to ask the forum for help here.
First, what the basic spec is:
-a varispeed dynamically sized looper than can overdub
-can change speed/direction while recording
-can jump arbitrarily while recording
So my looper does that now, though it doesn’t declick all of those things.
Things I want to add, and have no idea how to approach:
-oversampled (so halfspeed still sounds crisp)
-play "past zero" (as in play forward from 0.9 to 0.1 in a loop)
-not click anywhere (at start/end, while jumping/recording)
-The undo layers I have literally now idea how to go about that. I’m presuming you record to separate buffers and have them playback simultaneously but that seems quite messy and hard to sync.
-Oversampling I kind of understand, just stick it in a poly~, but where I’m fuzzy about is the math. In my looper below I’m driving poke via groove’s sync outlet by multiplying it by the total samples in the buffer. When oversampling, do you put the buffer inside the poly~ and adjust the math so it’s twice as much? (ie a 1sec buffer would have 88200 samples in it?).
-The "play past zero" thing has kicked my butt for a bit as groove does not work that way at all, which leads me to believe that I need a different playback/interpolator. But which?
-The ‘declicking’ is a multilayered problem, so no clear answer here.
Any help/suggestions in terms of objects/approaches would be welcome.
Here’s a basic version of the looper I use. This is quite old and the code has changed a bit since then, but it’s so entangled with other bits of code it’s easier to just put this isolated code here.
----------begin_max5_patcher---------- 4950.3oc6c08bqaak+Ye+qf0SmoIoJpDeCrusy1G59z1o8wLYxPIQayboE0H Q4ab6D+29RfCHEojHEHEIMkqychr.kDIvObNGbv4K7u+xc2uH42B2cu2+k2O 4c2c+6ub2clKouvc112c+yA+1x3fclu18qC+Vxhe89YvGkF9aolKuwaU3x3n keM+SVu+4j8owgoleFxd0MAoKeJZ8i+x1vkoviUIm6OyiRn5+vD5Ww9y889Y 6OIZk4Aj8P+QgJ+t+Px5z0AOGZ9n+6sQAwkdtQqyerX6EgdR5qaBgm486hdb c1uo3gnue6h9WlOFgy5Ck5tgasvgEOx91QwguDtcWTx5RCt6tOXylRW9tR+D MH9qIlaDeVwkhVCWBUbosguDk+6O7EC1lAZoYH19svH92j76gO72+R9C+.hh YZPjqLuR8MvqrDjlME+XbxxuFZPV+7KlrIbcz5MaC2EtNMH01KJ93UgODrON 8WNOTU8yeHXYXs+3yNuc28OtMZUxZcmnxuTe47G2O4gLCM8qGFLluw5fMm4G uKabre2hfsZTcQbXIZhLZ2jj3peTwDYFKPv5nmCRCSifNK1u3lF87lsQqSq7 fBWGjcOdZ2xsIwwUtUvm7xY9jUYy0KC+VzpzmL2qxPY1WOZS9Tv8EXzpnGC2 kV8ZoAOtq5UpvSWlHrLuckq2DOdU97+rGp70OKWdcb5LCoHRoIZ7H34rpb5G ysSDkeR0S4TGW+447Myb+7Q23xzy4exu+kuj+lY8ONlI.5sq.JQ9D.KAtaxw RMOAKocDKQMgkGIE88DNCiyvljsu4guZBTLxH4DgoWBUwckBkbafp+ge7541 4D231QpOvb6YZFs4sLI7dn4cmkmwM.pDVWGeIhSD6iMw4O7VFI1UilveHpKh lzgf7b5flYZOksfje1+PWMlhMZzKtn3SwGdBT70yt6aDiR3WbsnO.zmld0Ut fCreGXKPXVCDerZgD+VshxxjmeNbsUo7ICXXIebFMDcCMNGAxPAHvCudDw2I k6TJGQDoS5DeoA6gsoFGsttckY5s5O+7nvtj8aWli34Ks6Usmmskwzn0E6Z+ mNnfZlFwk+hOEsZU0cICarbmdapfAAbZNqs8Y8NPbuOilD8YsNoN0m0aU8nu 36FNKZCNOM5yXG6xSmdrqfLhNY5xHWY.Oizk2MX1UAcmgS8cqO6LC3zg+y0t rdQwdoO68yeor0queWvKgq9krGQ1x0+RPZ51nE6SgkKKa38q0FyW1F1svvqm sSzvCutG5iwIKBhstZn32dF669kCfn4UXp+JbZS1NTJLHg6NrQhLZVZMrCuI G1fDikCa5.njq51wnxe80rNZzxf33W8zOhUy7RdIb6p8KVjADy7dIXaztMg5 q+m1Z7CTn22dJJNz60j8dY3Tx1U+oYdwIIaB29Gxu8Z1wkI6AUEwmEy8qGyY Xvn4f6wjfQKnk.8G193hJdQn7z.Ce1og+VX7KgoYizyNUfNGRS6DRWqOCyMa aqoAQ9FHfwLzhbRSDgz150PTcDgk262GKWFBaNRYLYCAYreCQh+zkge5xPG2 bdUgoU4y+VPTp2CIa8B1uJJwKMw6gfUgdYrWdeG64ceu2hvrOMza2S6SyT53 QujGdvJGs78qhDTRWMC.hZ8tinjsJoxiLDvIhS6Kysgd2MK406AWLH78x9zA yt08oyyg61E7X3YAxt67AoDDIIN.iHYCv3fXa2ofIxWEFG7pG6ZARrgkldYm 3LHQTvhf0ONIHHuZe0B33kIHQ7OpDjYJj1j3QrSvH3xlSCkswwmhFxwYiHbN ldsgN08ZS+3jBXCdN.GhoqSJvtZ6N5zwrtT1smccwtZ8brq1084nUaRx3XrD BJArdfR+GlpnQ0G4va+ZdeONGg9ry1BFOs7S.xEd.wMnuM3eZ.6OPFv1DEs+ XGrgMBrmJSXz3Meas0XDaTeY+vAvH10ALmIdXas0U4Hi0UkrFQG+VhNjIfI9 CV8q62kpsyztPvzTY++2B1t5uXMn+YsZOssVsGgHf0kfDxvrg.tuqVsW1Qxt AkppNpIbCnfhAaO27GFtIpIBoubXzIaCpOvkp699.vP5fDnb3.c98bWw.Pn9 BUFCYO+fGwO6+ZOjfsxgo.xznXGZegHODmDjNJhjWGrY2SIou4cPjYagFDjr bxF82EhLcwl5Xf9wNvAgAa26DGDBOI4fpUdhlAp0.hvWThHgRgzEnF.QNIAj 5VvVuLs1g5dYeaun0dOtMI4kv6a6BxTePeOB3SPxo7QMthrx+8aI45PlBmqo 8oVFJzZLgH.xEP8DEscPhTN8fDq1c6dc8xViFHIFrGoQCXApknAe5gFaCeNi Uw6u9+zdr.Yy8.0g7ItEXAd5gEZoGFwHZezqki77tViJ4oGHExOCHXIbGVDS PRjvm2j9p2h8O7PnYmPdYpvXvo1Kf0l0JVArmYi0MCNja.ZlcAOuINr8DNDr 0UG9cBa3hI29Aq59V2UTg.AegcImS8aaEE8485NBGCs2ZulaDJB7bn3xpxR8 uoTkUq412R2DzdqPw7sI7J+PBuVGnPll6P9aAunCsomeKuOnSD3UaC9V4gdU .wFBelPTaYRLDse+TlHAJlvvpYm8cnRnx18wP3GlKCINXQX7hGqby3brfxz2 BNU3yL2LIBI3rp2rWRiV90cmb2tX3pBIoLFas7CDcgjyO2wkUs1VGFyZvppf SXwrBBgJTffwQiRVW4Y4OqxKEt259iftJeuxchLJ8mSVEdzT6ARNVyaydViu INZWZClyBMmoXHov9Y4zcU53BpdBV2wQRJAQ0uSv3RNwNN5.A9h8ooIq6p7t C6+pI6KSamQ1sh46QQX+QTGViC1DNAr.Low8hqT2Rl2Syj9VqMBrENf.Xu4j FP02IMvrAXs+5ynB+tlQEDN0AvgOgynh5r7YlBy6RSddW6IaHXBXvFCzHZTS QEsm8S2rwzBn5XnOs81.0ZtBt7P5gTqWD32RhYx5GYesZnXnWDQXvtr3Mt1h p+EzbLoyrQzCCa7dJZi96zQ4OJzk86BQMVNC+CSI3CYc4IF8Y9z7Y9zLNwQZ 9jDww3HsWBiz2qJcRN8nKiTzzuNmzTt6710BSXGK9hXwG65c0OTNtwZOZBtd VQJE659ic9RLc.yj0gaRhCeSCBWIjxMjoxKCoSo5c0vTjebMjVwcKrtwTS7b Srabv99wNntu8JVMH7XGqwiYryOcJ9RNWTRneFR2ejBo6v3L0SZughDk17Js w3iiHmzwXZM3xWCes01BwBJDHeDUGqyWEPosFGxuohjwry8mQ.j9yYZ00kXw U.1LBVLj1XDJio2dFfMX0x275hCXKYydZimLCR5sWNRrI4qYpr1fuo8ujEy3 .oRylklv5XBRLbibv4n1wtWCAaJtoPoD1PIGhbglchGU1WjGEVUczhlikwgA su.EkCOf2brQucMFlmQljFlOM4wGiC6pucOj960SVvKXM1DrMaXmFt8W.i0U l8qM0lowaXBNzDKqIw0qLL4SfgYCpa0dle6bL3pkKTB3vjaQ0sRy1Ej+buEm GZHMoyknTnb07plHBpOKMYydODO1wb8wpxEhVBlpS9H+lJf2PDLoio+DDH99 ftEnFWxXRBIYTeYO0BgckbiU4gofNWvlUrNwoqPbdwPY6+d2x.qLy4plxdUe yCvpShMOXT0EdQN3PylDa+jdhuRbVIE9Lob1YeGp5vrmT3IO5u5QpXSDO+Gu vdFNXmklzDRVlQG7Hpnl8VMzIkzU.T0rNAjHRMtMhlRMVfWGILa7T0nxRsdc ThyQqvXrd518qW9VWWlfBtEfRaBfXhau8fpq.ucc8hbrAz+RznVXzaPqVnKt CcnzE.bTPNDSZtvvRt8HXxVMXkVlsWeHZFxbVvHghlyK+9BoFkrEcSbvqsOa 2.i4.qPQDsLwkbHoLNihBTFRqc.l4qLpIHPBNU3fZBueau2xYI7u716EnIv9 dC+WqBpagG+KJGACo2GLlo0s+9yGg53dMINqTVRbOYEgCwPfnl0xrYkMAys2 mBhenCI0qnzVMZYBaxTSOTvT946LMMsFcoZN8CeGy88NJpBaiZbGDUwQ2tVh zdHv5zvDOAFlqJQwTPXL2uiZ5Ut9gRqaXS5ug8wQystY1WZydS2B0mQysVMl EAK+pWbxiQK6ppvJV4fPtt8N0VCJQ6XMi4lNptsmQ8fc5HvQvLQ84wjvmg0c ODDkodK7h5bom1pgCxwn6kLDGG349mYpTf9upCaY1bgRvPbi.TDBQvRmJM5p a5Ridim2Ay6dg5WaK9NgmX+OpkZdM+9CcleGgswcgqb7ngjiu5h92r0ueKZB oskCG4x3OD0u+ASDprfkWehwzBYnrOrxPulbNAgIcEPEeJD87HJXDDhiIEUm OvN9OKgn1hcAxMon7ODBQaJUy7tBldlXNShXBQFHxoyUDp.SbgVUNDv5jgD8 5vT4bLggzk8F8V4EBwAwnMx96i9DTajPUhUZLTolqvYKNwcAUE29X50k.yR9 bkhfEJs6llK4XAm6RpLODGER+83fW+K+yzjMdeGxKYqm+2OkNbjDy4XeEFqC dLxbEWPnNgTjVmd6+elNk9np7umaD1+20deWVacTI78SnreOi5gHT9Bpg5IC PnZGHeMo7e2od9G1SC4uyWS6flRvTlDHoTxk.OlhQTDWPI7.fR+sf3G79mZO 3MN.0SQqqZXytrfoo.lLnkXgyHx9XISF9vbjatl6DM+6uexkP19noSh+56Zu Vd6c.TIcLWkOJw2sghJliJVBQ5CZYSJe4wNU3o87L0wCaBwnJV1pn4Cac82M eXWb4wdX26G4dGOt4Yp6ma1DrbNiw3b5rB7n3Ri8.Wb6cVCp8tga0Q.TWmrX ESVHjnzjkuwHWEWZjmrv2dmieZOmzuyUiPeF45YOnOpqB+oEgCAISgvLZKI5 f4.xu5XSd0+5VbhzefARGIf1wclzegcbWb0wdbicc9V0wgM0TYazwnQtjjRR XJt76wv1IVS+oCqIwwoJQGYLgCaDHW5KmW81xwfhaKLTGrxDezKWQFPv2QPn KG0tbM0JjAoPtKT98Z6pYOGAKYoswFCbUMUdG4XUr4RqAMPB1bjffQxB00Nb oQdX6pZpmgD48hkk6pv0NdvPqOBSLF2kTR5ZIksKt9HOU4ZwtZBc.F65dAlN DWxdVR3mUmqIU04p17BNiB4h4Eb8mHHHkC0Ckw8rbw7cpZmP.fNlivBSmJto h3y5XCpn.6o7.GKak.xVyW9mUVW.H0FYzRGiTUaQgr.PWCJX1VG5dGymcBgg qi6pZD1z3F493VG1.l9rLOMNyaM.ifilTZXUhlFA8W+QRcn+n3siRBp6aDnR 3q7KZL.3Im6P+m1RNAKULAN5NAa2VoAyVgAPFZESqAXrQblVweTnUppmYc5f SaI2GxpQmJu3Uk2Znje3zHXBgnDVKodyQTHurrYfYoFbHCSAFSNZXHdOhtri CsdTPAxk9CocPMlZ1gHTTtID+CUnaaChMAXs073JsnRaFeapJxlVCv7.0EA7 rwSHhKSCb430c7cAe3rQrC4hTeJY75PNozkoth0JsM4l0XQ1TZSxKZMDBibR PK0e7.Um5OiX2QO6guzbrrcSwTjQxFBDEJP7hVCwTLA4.YJtkB30mbt5NMWA 9B7PqAaHboYA7kTFu5QGYwYsoBQDXV42UtjeT2TmMgS4kN75ssn1z6UA6dhg Gvo0KgIrwDRfE1gLfuZKh8LHEDsYZ89AIjIlzC93NGwlkuwdSqAad3hpNzx8 NaWITWxBYE1Swz5caHHPcZHXKDwXg+osfCOPDil2XHFbRGWisE6V0tHF1tqJ Eqn060.fKGMaE4nhfsb2pVlTXQELX5HTYGsN1i.5Ht+eWrdkZ75NtPvUqJTn ZLFnX7zfxEspwx10+g0R.NcXrne+6TmmS5Xm2+buB0XL6bRkRM13NppcAlqY TQOswXrpCoWLvww7PfglwHv5yJYQqgXD3hPIx3YhFhKRkN1NNNnjB3sKnv5v Ymz.CojuMfCFFU4cw1SsT4K6JnZMF8gjVx+3VHIsDYjo0PL17cQ8qwXWA14P J2d3xvKZ8dMra8lBrTgVUoy2TvvruFJ4ZzBn+4+o8gRR8Bcjkxwp0u0vGCj1 .ToCppdQit1e8GWh3.ZK2Mk0PITLPOSjEsF..k4jIWGuk0XLmVVa7z9m6JaO d53vNCHRFm9C109yHgODW6OiD4iSzyi35FNEZHpwi+R3LA83XNGUEVm9QhNA J9BLbgw97OtE0lTu4R6GF+F3xfSMdF5QQc04T3V.1J.rYPjI4KgREZ4V1iFJ LjB0UagDn7plCy1ZHlHbYj2VprpCbHzCq1hvAeUgTm1Ba2TjUONJ48afWq0a QtLxYRxbh3Lsw4GyB743Az.0JmrTv3IgUIbMXFGIIrtDJGpwK1aHNY.e731e tnJmnQL1abMl0ZU.NZDyvA+eoqJH4sFBWFgbZSEsbDPLqPv8gzghJKZMD66U 45HXj7IB009y3n1OyEYbX5zBeTcxmfLITDXDxSaQUzxQehZXrgIF4JXiFoXw qO5OGaEFh.hhcPcHHbTMstHhBIxxQkJecm+nRj+QkG+SKM90UV7ydN+9W9+w 7bZor -----------end_max5_patcher-----------
"The undo layers I have literally now idea how to go about that."
if you have a set amount of maximum layers(say up to 4), you could also write to separate buffers, then copy/mix to one buffer for playback only(there’s an mxj object that copies from buffer to buffer)… if that makes sense(separate your design thinking between playback and record stages… buffers used to keep recording don’t necessarily need to be the same ones plaid back from).
also, what if you created a recording mechanism, whereby every new recording simply created a new buffer on the fly. using gen~ you can switch to newly created buffers(and then destroy old unused ones)… processing or mixing them into even newer ones… meh… i’m daydreaming. just a random thought… maybe not worth the effort :p
but this might solve clicking where random recording points are chosen then plaid back from other random points…at least at the recording stage(one window for each on-the-fly sized buffer… or declicking at the playback stage only if these on-the-fly buffers were used in full… so many options to think about).
declicking at playback stage is easier, you’ll figure that out :)
(i’m assuming you want freedom to go wherever in the buffer~ at both recording and playback stages… so declicking at recording stage maybe necessary… otherwise, with quantization boundaries, you wouldn’t need any of this and could just declick at playback stage…)
ya, something other than groove~… signal-based play-position control… play~ and wave~ have some kind of interpolation :p
index~ does not…
depends on how you want to send control signal…
BUT! i highly recommend gen~!
in that case, check out the ‘sample’ object(the index attribute opens it up for different control options too)…
and this way you could build in your own amp-envelope for declicking too…
either way, whether in gen~ or using play~/wave~/index~(or even 2dwave~ if you feel adventurous), play-passed-zero is just a wraparound function: in gen~ you can use ‘wrap’ or roll your own fully organic grass fed solution, or in generic msp, pong~ is pretty helpful for wrapping, else roll your own using something like this logic:
if x > max, x = x – max, else etc. etc. etc. :D
(no idea about oversampling :p)
just my 2cents
edit: must keep track of that "keep a log of this edit" thing always checked on default now on these forums… weird :p
- This reply was modified 6 months by Raja_The_Resident_Asswipe.
Thanks for that!
I was thinking, after making the post, about using something like polybuffer and a poly-based playback object. A bit memory expensive, but not so big a deal nowadays. So recording 4 layers and playing back 4 layers would be 4 individual playback objects going at once (via poly). I didn’t think to mix/bounce as I went.
That makes more sense than having a single long buffer to play non-linear bits from.
And thinking of declicking for recording and playback stages independently is a good idea. There will have to be declicking at both as jumping arbitrarily will happen at both (without quantization). There will be some mlr-style 8 slice jumping, but most of it won’t be that.
For most of this stuff I will likely have to be in gen as I’ll need tight sample control to declick all the jumping around. I’m getting better at straight code now too (learning a lot of js/ruby lately) so that isn’t as scary as it once appeared.
ye ! with bpolybuffer you can add remove buffer easily, and keep one buffer to write the sum of the other buffers in polybuffer ! maybe the first polybuffer’s buffer – it’s possible that no poly~ based playback is needed ; even so you cna add poly voices on the fly, this might be not a so good idea though because the totality of poly voices need be reconstructed when one does that iirc. i’m also trying to create a similar mechanism so this topic is welcome :)
for oversampling : math is right afaik, one second at 88200 would allow twice the quality in case of a slowing down. an "up 2" argument will do hte trick. Then i don’t know however, if you play htat back form your main patcher, you buffer will have 88200 samples so you will have to play it at twice the speed to have normal speed… i think… this needs testing…
So in that scenario, every time an undo layer is peeled away it would require instantly re-summing all other layers to the ‘playback’ buffer?
I tried doing some testing with the upsampling thing a while back but didn’t manage to figure out. My poly knowledge was shittier then, so I wasn’t able to determine wether I was fucking up or it wasn’t working like I thought (ie buffer outside of the poly).
Hey Rodrigo, how’s the progress going with this? I’m determined to make my own looper/live-sampler for performances with acoustic instruments, and I’m learning more about Max (I’m novice) to this end. I came across your trusty old looper last year, on the thread you posted when you originally made it, and it was an inspiration. I’m looking forward to seeing what you come up with now in 2014.
Here are some ideas i’ve been thinking about for the last few months: a workstation that will allow me to control the pich/speed/size/amplitude/etc parameters of ~3 separate loopers, with each looper capable of overdubbing. This is so the loopers can each be seperate sizes, relating to one another in ratios (or not). This way the rhythms and pitches in the seperate loop banks interact in interesting ways (e.g. evolving counterpoint) over time. Also, I think it’d be great if a looper was able to start playback while it was still recording, e.g. in Bach’s canons when halfway through the exposition of a melody, another permutation of it is looped under the original, but at a different pitch. Last, I’d like to make minute real-time adjustments to the pitch of my loopers, for example raise the pitch of the sample in a looper up by a semitone for a second so that it temporarily changes how it harmonizes with another loop, and then have the option of saving my parameter changes to the loops or making them temporary.
Any thoughts on this?
I’ve started some work on it, but it will be slow going for the time being. Going to have to do a lot in gen, for all the driving stuff, and have to figure out the logic for it all before anything else.
Any progress in this? I’ve been studying gen~ and have found a bunch of gen~ looper examples on the forums, and would be interested in how you are organizing yours to achieve this high-quality timestretching and de-clicking.
Not really. At the moment I’m commissioning something to build something like what I want as an external (or gen patch), as quite a bit of it is beyond my knowledge (and definitely my free time!).
I will share what I have when it’s done too either way.
Wow that’s really generous of you–thank you.