Determining when groove~ reaches end of loop?

Nov 15, 2010 at 12:14am

Determining when groove~ reaches end of loop?

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?

Thanks.

#53321
Nov 15, 2010 at 12:40am

one way of doing it:

– Pasted Max Patch, click to expand. –

hope it helps.

#191818
Nov 15, 2010 at 12:50am

I tend to use [delta~] [< =~ 0] [edge~] if that helps.

#191819
Nov 15, 2010 at 1:26am

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.

#191820
May 21, 2013 at 4:57pm

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?

#250188
May 21, 2013 at 6:01pm

Everything is possible :)

<code>

– Pasted Max Patch, click to expand. –

</code>

#250194
May 21, 2013 at 6:29pm

Unfortunately, it doesn’t work for reversed playback.

<code>

– Pasted Max Patch, click to expand. –

</code>

#250195
May 21, 2013 at 9:10pm

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)

<code>

– Pasted Max Patch, click to expand. –

</code>

#250198
May 21, 2013 at 9:49pm

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:

– Pasted Max Patch, click to expand. –
#250199
May 21, 2013 at 10:44pm

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.

#250202
May 21, 2013 at 10:58pm

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…

#250203
May 21, 2013 at 11:42pm

@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<0); // wrap when below 0 (for negative phase_increment values)

(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.

#250205
May 22, 2013 at 12:44am

@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.

#250215
May 30, 2013 at 12:15pm

@Peter

“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).

@Empytree

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.

#251256
May 30, 2013 at 12:40pm

@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…)

<code>

– Pasted Max Patch, click to expand. –

</code>

#251259
Jun 1, 2013 at 1:33am

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 :)

-Raja

#251358
Jun 1, 2013 at 2:09am

“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.

#251362
Jun 1, 2013 at 2:26pm

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).

#251400
Jun 2, 2013 at 9:19am

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.)

#251430
Jun 2, 2013 at 12:26pm

That’s what I meant. Does it have something to do with rounding off the values also ?

#251434
Jun 2, 2013 at 6:48pm

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~

#251448

You must be logged in to reply to this topic.