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.
I tend to use [delta~] [
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.
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?
Everything is possible :)
Unfortunately, it doesn't work for reversed playback.
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)
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:
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.
@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.
@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.
@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...)
“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.
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.)
That's what I meant. Does it have something to do with rounding off the values also ?
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~
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.
@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?
@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.
Hi,
I realize that I'm almost 10 years late, but figured I would post my solution here (see attached) in case it might be useful.
It uses the transport to detect the end of loops, and avoids triggering that detection on startloop by intercepting it and starting the loop 1ms away.
Thanks.
Answer to original request to play one-shot sample
is to use play~ instead, and avoid unneeded complications
with groove~