Question about gen~ 'chopper' examples

Rodrigo's icon

I really dig the sound of the basic gen~ chopper example, and just now noticed the chopper_repeat version has been changed quite drastically (not looked at it in a while). It sounds amazing, and I like the extended control.

One thing I can't figure out (I don't really understand the code behind either gen~) is how to make the 'repeat' version behave like the regular version when no audio is playing. With both having the capture parameter always engaged, when you stop audio input going into the regular chopper it freezes on the current segment, and this produces a single static pitch (often inaudible). I really like that behavior, and use it often. The repeater one (regardless of how I tweak it, including de-commenting stuff in the codebox guts) always stops on a static/gristle type sound.

I suppose I can tie the 'hold' parameter to incoming level (peakamp~) but I'm wondering if there's a better way to go about that. Or if that's what the regular chopper is actually doing (holding the last segment until audio comes back in).

Graham Wakefield's icon

I'll try and explain what's happening with both of these examples. There's a recording section, which continuously captures new grains bounded at zero-crossings (wavesets), and a playback section, which continuously plays back the captured segments one-by-one.

Each grain is written into / read from a different channel of the (Data segment_data), and the length of each grain is recorded in (Data segment_length). It supports up to 64 grains.

The recording section is always writing into one of the grains (one of the segment_data channels), according to the write_segment variable. It counts the zero-crossings (in crossing_count variable), and if enough zero-crossings have occurred, it stores the grain length, switches to the next recording grain, and resets the recording counters. There's some extra logic in there to ignore segments that are too long, and to make sure it doesn't start writing into a currently playing grain.

The playback section is always playing back from one of the grains, according to the play_segment variable. Most of the work is in determining which grain to play next, when the current grain (with repeats) has finished. There's a bunch of different strategies in the code that can be uncommented, from random selections, random walks, ordered playback etc. Most of them include extra logic to make sure that it doesn't start playing the grain that is currently being recorded.

But to match the original chopper example, the strategy is just to play back the most recently recorded grain, which in code looks like this:

    play_segment = wrap(write_segment - 1, 0, num_segments);

This means take the index of the currently recording grain, minus one to get the previously recorded grain (which is therefore the most recently completed one), and then wrapped into the range of possible grains (so grain index -1 safely becomes grain index 63).

So just comment out the part that is doing the random walk, and put that line in instead.

Setting the crossings param to 1, stretch to 1, min_length to 1 and rate to 3 sounds almost the same as the old chopper, maybe a little softer (I probably didn't avoid all the possible clicks in the original chopper example...)

Setting min_length to about 300 and rate to 1, leads to an almost perfect reconstruction.

Max Patch
Copy patch and select New From Clipboard in Max.

I've added a way to switch between playback strategies. Having a lot of fun playing with this:

Rodrigo's icon

Thanks for this!

I did notice all the commented code but didn't play with it much. It's amazing the difference it makes. Very powerful option/choice/control.

I don't know if I'm doing something wrong, but there seems to be some wicked clicking in this (heavily based on the stretch parameter).

Graham Wakefield's icon
Max Patch
Copy patch and select New From Clipboard in Max.

Here I've updated it to deal with the clickiness at switching/looping segments. There's an extra sample recorded in the loop, and the playback cross-fades the extra samples according to the fractional sample index. Seems to get almost perfect reconstructions, which is nice! Also sounds pretty much like the original chopper example with the right settings.

Rodrigo's icon

Badass. This is quickly becoming my favorite "effect" in terms of range/complexity. Great stuff.

Graham Wakefield's icon

Yeah, I've been having a lot of fun with this too.

The next-to-last outlet gives the current grain duration, which can be inverted to give you a frequency estimator too. Now that the durations are sub-sample accurate, the frequency estimation isn't too bad either. For example, it can be fun to also use play_index / play_len as a phasor to drive another waveform, for waveform substitution.

There's a ton of other things that could be done with it.

The input signal could be low-pass filtered before being sent to the trigger detector (while the grain actually records the un-filtered input), so that high-frequency fizz doesn't create lots of tiny grains. It would be important to keep the same phase delay for the filtered and unfiltered parts, which could be achieved with the crossover filter example.
Or the trigger section could be modified to be sensitive not just to zero-crossings, but also paired with other analysis properties, such as spectral changes.

I made the analysis also store the average loudness of a segment, which could be used to compress or expand the playback amplitudes...

It wouldn't be hard to add more than one playback section, for multiple overlapping grains. Multiple recorders (for sampling more than one source at a time) would be trickier, but possible.

Omitting grains, or inserting small pauses between grains, may introduce some interesting distortions or formant-like effects.

Instead of a fixed number of repetitions, it could repeat until a new grain has been recorded (in 'recent' mode). That should preserve the ordering without jumps.

Another thing to try would be to adjust the playback rate according to the grain length, so that it makes a continuous and controlable 'tone'. I added that in the one below, as well as some different ways of changing the pitch over grain repetitions.

Max Patch
Copy patch and select New From Clipboard in Max.

Plenty of more ideas could be drawn from Trevor Wishart's 'On Sonic Art' [1], or the waveset tools in the Composer's Desktop Project [2], and so on.
[1] http://books.google.com/books/about/On_Sonic_Art.html?id=h2J9v3hx_FgC
[2] http://people.bath.ac.uk/masjpf/CDP/docsrefs.htm#XLST

Jan Jeffer's icon

Hi,

gen~.chopper_repeat / max 6.1.3

Once i release the 'capture' button I get a really nice, click free loop that I would like to write into an external buffer for further processing

however, i'm a bit stuck and think the answer to either of these questions might help.

how do i return the total length (in samples) of all 64 play segments?
or
is there anyway of outputting a bang once play segment 63 has finished?

sorry if these questions have obvious answers.

thanks

Graham Wakefield's icon

You could keep track of the total duration by updating a History object in the if(is_complete) section. E.g., where it currently states

len = write_index + offset - prev_offset - 1;
poke(length_data, len, write_segment, 0);

You could replace it with (assuming History total_length is declared above):

prev_length = peek(length_data, write_segment, 0);
len = write_index + offset - prev_offset - 1;
total_length = total_length - prev_length + len;
poke(length_data, len, write_segment, 0);

That updates the total length every time a new segment is recorded. Then you can simply output that from another outlet. (see below)

Max Patch
Copy patch and select New From Clipboard in Max.

There's no way to output a bang from gen~, but you can very simply test according to the value of the write segment (e.g. [==~ 63] -> [change]) to get a trigger signal.

.quasar's icon

@GRAHAM WAKEFIELD : I'm trying to implement the waveset ommission technique you suggested in the chopper_repeat patch but after several unsuccessful attempts (I tried several methods to add a counter in the playback section) I wanted to ask you if you could share some light on how you would do it .

That would be asbolutely great, otherwise I'll keep on trying to hack this perfect patch ;) so many waveset techniques to try implementing in it...

.quasar's icon

Well, I think I figured how to do it from scratch.

Here's the patch !

Max Patch
Copy patch and select New From Clipboard in Max.

Jan Jeffer's icon

Hope this thread isn't too old to post on, just wanted to say that I have been using this patch for a good few years now and love it but still don't fully understand it!

I have made a few hacks, including a fractal player (kinda!!)

else if (playmode < 2) {

            a = fractalseed;
            a = 0 + (num_segments * ( a * a) + a);

            newdirection = scale(a, -2, 2, -1, 1);
            out8 = newdirection;
            play_segment = wrap(play_segment + newdirection, 0, num_segments);

            // caveat: don't play what is currently being written:
            if (write_segment == play_segment) {
                play_segment = wrap(write_segment - 1, 0, num_segments);

    
            }

What I would love to be able to do is quickly delete the contents of the data object, or reset the patch if you will - I have been using the 'reset' message but I'm running the patch on a Mod Dwarf now so that approach no longer works... I am guessing I would have to 'poke' silence into the data object?

Would also love to be able to randomly mute slices to thin out the texture, or as Graham mentions above adding some space between grains - but nearly 10 years on I'm no closer to achieving this!!

Any thoughts welcome!

vojko v's icon

I have tweaked the Chopper Repeat example to replace the Pitched Mode with a kind of a "Chord" mode where you can fill an outside buffer~ with values of freqency periods so the gen~ code looks through that buffer~ and inside it finds the value from which is nearest to the playlength of the current playing segment and uses it to adjust the frequency of the playback.
That seems to work OK.
But I also tried to add random panning of the segments, specifically the Equal distance panning scheme from the MSP Panning tutorial #1.
Problem is there are a LOT of artifacts when the random panning is applied, especially at small segment lengths and small repeat values.
I presume the artifacts are coming from clicks that occur when randomly changing the value of the panning, but it is way beyond my gen~ skills how to stop them. I believe some kind of crossfading is required to do it.
Does anyone have an idea how to achieve a clean random panning?

Max Patch
Copy patch and select New From Clipboard in Max.

vojko v's icon

Managed to do it by taking the panning out of the codebox to classic gen~ and doing crossfading by using the go.background.change abstraction from the Generating and Organizing sound book, feels kind of wrong to share the patch.