Determining when groove~ reaches end of loop?

    Nov 15 2010 | 12:14 am
    Hey all,
    I'm working on a sampler which uses waveform~ to let you easily select the portion of a .wav file to loop through groove~. The problem is that I don't actually want the audio to loop, I just want an easy way to play a fragment of the file once. Also, each time the corresponding button for that sample is pressed, the sample should simply restart if it is still playing.
    I've been trying to figure out how to do this using delta~/edge and/or snapshot~ but snapshot~ is not accurate enough, and delta~/edge reports a change when the button is pressed (for a restart.)
    I feel like this should be a pretty straightforward problem, but can't seem to figure it out (I'm pretty new to Max.) Does anybody have some pointers?

    • Nov 15 2010 | 12:40 am
      one way of doing it:
      hope it helps.
    • Nov 15 2010 | 12:50 am
      I tend to use [delta~] [
    • Nov 15 2010 | 1:26 am
      raja you're a life saver - I had tried a bunch of things very similar to that, but each one was just missing one piece :)
      Thanks a bunch.
    • May 21 2013 | 11:57 pm
      I need a solution banging in the end of a loop and NOT banging in the start (i.e. immediately after "startloop" message). Both forward and backward playback cases. Preferably not banging on the stop.
      Forward playback: [==~ 1.][edge~] works fine. Backward playback: ???
      Is that possible?
    • May 22 2013 | 1:01 am
      Everything is possible :)
    • May 22 2013 | 1:29 am
      Unfortunately, it doesn't work for reversed playback.
    • May 22 2013 | 4:10 am
      You can detect the shift with delta~ -> abs~ -> >~ 0.5 It'll work for ± values.
      FYI: if you send the message "loop 0" to groove~ it will continue playing to the end of the buffer. (may be useful if your loop length is always the same size as the buffer~)
      Alternatively, you could use a sah~ based mechanism to get it sample accurate.
      Do you really need to stop your loop or just stop hearing it? (I ask because those lead in different directions--most of the time it's the latter)
    • May 22 2013 | 4:49 am
      Gentlemen, I want to clarify it once again. There is no problem in detecting shifts. There is problem with looped groove driven by negative sig~ - once you trigger it with "startloop", any detection logic immediately bangs. That is unacceptable. Of course, I could resolve it with some gate mechanism around startloop message, but I'm hoping for an easier solution involving groove's sync outlet only.
      Here is an example, please, take a look:
    • May 22 2013 | 5:44 am
      Double-check, but mine cuts it off fine in reverse. (after the loop finishes playing once through, is what you're looking for, right?)
      You have to turn off the toggle, of course, but it works here. It doesn't stop the loop, but it mutes its output.
    • May 22 2013 | 5:58 am
      Wow, back from 2010, when i was still 'Raja' and was all friendly and shit, instead of becoming.... The Max Anti-Christ! Muahahahahahahahahahaaaaa!!!!!!
      Fleee for your lives, Serious Types!
      But seriously :p
      You're problem is in the way groove~ is internally coded and you'll have to create a special solution that gates/skips the first bang on reverse playback somehow.
      Attach a scope~ object to the sync outlet of groove~, you'll notice whenever you stop it, it doesn't reliably set back to 0, it stays on the very last sample-position(0-1) it played before the stop, which means you generally can't use any of the detection methods here reliably(you would have to stop reliably at the same position... what they should have done is coded the sync outlet to snap back to 0 on stop, just like the actual audio coming out of groove~ is zeroed on stop.... OR have some way like a "set" message to set the current playback position without actual playback.... FURTHERMORE! if you attach a "==~ 0." -> "edge~" set of objects, to the sync outlet, and also use a scope~ to see the sync signal, you'll notice it doesn't have to go anywhere near 0 to create false triggers(i'm testing with reverse playback, that is)...craziness, not sure why that is...)
      Anyhooo... where was I? oh yes, create a special solution for that one beginning bang, and you'll be fine :D
      Edit: @Peter, i think his problem is that it creates a false-trigger(even your solution does for me) right when you start the reverse playback...
    • May 22 2013 | 6:42 am
      @Tokyo: I meant that he should solve it at the signal rate with no triggers at control rate. (or the timing of the triggers at control rate shouldn't matter)
      For the ==~ issue: you may not be seeing the full 0.0000000something_else because number~ truncates its display to fit. You shouldn't count on it going back to 0 at the start of each loop, since these type of incrementors (0-1 like phasor~) often look something like this in C code: (assuming that x initially was 0)
      x += phase_increment; x -= (x>=1); // wrap when reaching 1 x += (x
      (x --> delta~ ---> abs~ --> >~ 0.5 will reliably indicate a transition from the last sample to the first (if forward), or the first sample to the last (if backward). You can adjust the 0.5 value as you need to; really it just has to be greater than the phase_increment value, and that will take care of starting and stopping at weird places. (You'd have to calculate the phase_increment value, but it's not terrible. You could just do it via a sample and hold and a delay (so when it stops changing, sample and hold the delayed value from the previous sample). Alternatively, you could use some slightly larger value like 0.01 (assuming that your loop is always going to be at least 100 samples long, easy enough to enforce that))
      The reason why this wraparound behavior happens has to do with interpolation between the end of the wavetable and the first sample. It doesn't make as much sense when you're thinking about a large buffer, but when you're dealing with a waveform for an oscillator, it makes a bit more sense. Resetting to 0 works fine when your wavelength is an integer (in terms of samples), but a lot of frequencies aren't. Middle C has a wavelength that's 168.561508 samples long, for instance. Since we have to process at the single-sample level, it means that about 56% of the iterations through that phasor (0-1ish signal) will be 169 samples long, and the rest will be 168 samples long.
      I'd also add that this is not an MSP-specific thing; this is common to all DSP code. The sample-rate is constant, so you have to make compromises to make things work. You also find out that there are some things that you can rely on, however, and use those solutions in place of others that seem like they should theoretically work.
      Anyways, this is an overly long explanation (and hopefully not over serious), but there are reasons for these behaviors. If they still seem odd, I'd offer the following strategies:
      1. If you keep things in the signal domain as much as possible for synchronized code, a lot of problems disappear. 2. Only convert from signal to control rate if timing doesn't matter.
    • May 22 2013 | 7:44 am
      @Tokyo: Thanks! Your guess on my problem is correct - it's false-triggerring "loop end detection mechanism" on the reverse playback. And I trust you about filtering the beginning bang as the only solution. Awkward. Alas.
      @Peter: Thanks for paying attention! Seems to be we play in different leagues and just don't get each other. I can't stay in signal domain all throughout the code. More than this, I'm sticking to scheduler, message ordering and simple control logic. Pretty sure I'm mostly hammering nails with microscopes, but it works to the moment.
      Guys, I'll ask you two personally to check my device when it's done just to get you laugh out loud.
    • May 30 2013 | 7:15 pm
      "You shouldn’t count on it going back to 0 at the start of each loop"
      Ya, i remember this is true for phasor~ and the like.
      "The reason why this wraparound behavior happens has to do with interpolation between the end of the wavetable and the first sample."
      Ah, good explanation, forgot about these applications of the idea.
      But aside from Empytree's issues, my personal issue with "==~" was not so much about the sync-signal not reaching 0, and more to do with the "==~" detecting 0 from the sync-signal where there was no 0(scope~ showed the signal not even nearing 0).... but this is a moot point since i was only trying to help, and don't need this to work for myself... i just don't like it when scope~ shows me one thing and the detection/conditional-signal objects react to something else which i can't see or snoop anywhere :p
      But cool, Peter! Thanks for reminding me about why it never reaches 0(and that's it's true in most environments, i'm rusty on Max/MSP these days because i've switched to SuperCollider, but SC also has similar sync-signal behavior, albeit a bit more easily and reliably dealt with using other objects available).
      Post it up when it's done, it's often easier to help someone debug a finished idea rather than one that's still developing.
    • May 30 2013 | 7:40 pm
      @Tokyo: The other thing re: scope~ is that scope~'s display isn't sample accurate; it's showing you a group of samples, so there may be a 0 in there that isn't visible. Agreed that this is less than desirable behavior.
      Plot~ can show this, however. Here's an example with a square wave at the Nyquist frequency: (and an example of scope~ not showing it...)
    • Jun 01 2013 | 8:33 am
      Thanks, Peter, did not know about the new Max6 plot~ 'til now. And this reminds me: there's the old object capture~, too (i had even forgotten what it was called :p ...but glad i finally remembered, though, plot~ looks much nicer :)
    • Jun 01 2013 | 9:09 am
      “You shouldn’t count on it going back to 0 at the start of each loop” Am I wrong or it may also never reach 1. ??? I think I had noticed this in a device I worked on.
    • Jun 01 2013 | 9:26 pm
      Stephane, you are correct: it MAY also never reach 1, mainly because of what Peter wrote: "The reason why this wraparound behavior happens has to do with interpolation between the end of the wavetable and the first sample."
      If however, the situation arises where the interpolation between the first and last sample just happens to avoid truncation/rounding issues, it's still possible to reach either 1 or 0(just not reliably for every case of looped-length).
    • Jun 02 2013 | 4:19 pm
      You can test for either case via ==~ into +=~ into number~. (It depends on how the wrapping is done for whether you see 1, but I would expect not to see it as an exact value, since that should wrap to a phase of 0.)
    • Jun 02 2013 | 7:26 pm
      That's what I meant. Does it have something to do with rounding off the values also ?
    • Jun 03 2013 | 1:48 am
      You'll typically see the phase incremented and then wrapped.
      dx = 1/frequency;
      x += dx; // increment phase
      // Generally, only expect a 1 for output if output happens between the phase increment and the wrap
      // There are several ways to wrap: x %= 1; // Modulo, problematic because of negative values // (could add some very large positive integer first) // (also presumably involves a divide which may be inefficient)
      x -= (x > 1); // These only work when dx can't exceed ±1... x += (x < 0); // ...(Could happen with FM)
      In gen~ you can also use wrap. There are faster ways to do the wrapping via bit-manipulation, but those are not accessible (afaik) in gen~
    • Jun 15 2014 | 4:26 pm
      Hi to all. I'm a rather new Max user. I encountered the "not able to get a bang at the end of loop when playing backwards" problem. Here is the simple solution I made up after two hours of pulling my hair off: Just use 2 groove objects ! Send to both of them the same min loop, max loop, startloop, stop entries. Of course, one of them will get the playback increment (let's say "x"), and the other one will get abs(x) as playback increment. In other words: Get two grooves objects, have them groove the same buffer, with the exact same parameters, excepting the fact one of them will alway play forward. Use one to get sound, and the other one ("forward") to get the bang you need at the end of loop (when reaching "1"). I expect that if you run this method while grooving a veeeery long buffer, there might be a slight delay between both buffers, but who would want to do that ?
      Hope this helps.
    • Jun 15 2014 | 9:51 pm
      ^can't tell for sure without seeing your solution, but if that's what you did, you could also just use one groove~ object and take the signal from the sync-outlet and only send it through a '!-~ 1.' object when in reverse(that changes a ramp moving from 1 to 0, back to a ramp moving from 0 to 1). (this may cause extra bangs if you switch from forward to reverse in the middle, depending on how you're getting your bang, but the same would happen anyways with 2 groove~ objects i'd think...) anyways, just something to try, thought i'd mention since i know this feeling: "after two hours of pulling my hair off" ;D
    • Aug 03 2015 | 5:16 pm
      @peter when I try to automate the process of turning off the loop in your patch, (for use in a one shot sampler) playback triggers the loop twice? Is there a way around this? I can't seem to find a way of using Groove~ for a one shot sampler, but where the sample can be adjusted using waveform?
    • Aug 17 2015 | 8:34 pm
      @Noisey I was looking into this as well and found that Peter's example works as a one shot sampler with [!-~ 1.] ---> [ [edge~]...., as Raja suggested. See attached.