Reversible patterns

Hi everyone,
I’m noob-ish with supercollider and I’m curious whether there’s a convenient way to set up a reversible pattern, meaning a pattern that i can pause, play backwards and forward as stream, to then resume the execution from the paused point. I’m thinking I must use a stream for this kind of operation, but maybe someone else has figured out a nice way to stick all this inside a Pdef? My idea is to try with Pwalk, except I have no idea on how to point at the index of each value outputted by the playing pattern, which would be convenient given that the arrays of pitches/durations i’m using as sequences have tens of thousands entries and I need to keep a solid reference to where I’m at to resume the “normal” execution from that point - reason why I’m considering to research a bit more how to do this with a streams instead of patterns.
The starting point is something like this:

Pdef(\noteseq, Pbind(
\instrument, \synth,
\freq, Pseq(~pitches, 1),
\dur, Pseq(~dur, 1),
)).play;

~pitches and ~dur are two arrays of equal size, one storing frequency values and the other storing durations.

Hope that this makes sense, appreciate all inputs on the matter.

  • Robin

I’m sure there are more elegant solutions but this seems to work:

Pdef(\direction,1)
Pdef(\tune, Pbind(\freq, Pwalk((100,110 .. 1800), Pdef(\direction))));
Pdef(\tune).play; ///wait here while it walks a little
Pdef(\direction,-1);
Pdef(\tune).pause;
Pdef(\tune,resume;

Try Pscratch from the ddwPatterns Quark.

[Pscratch] performs a random walk over a lazy stream of values (unlike Pwalk which takes an eagerly-evaluated list of possible values). This is like scratching a record over an audio stream; hence, Pscratch.

Quarks.install("ddwPatterns")

This works very well!! I see you’re using \direction as a stepPattern argument for Pwalk, that’s interesting. I didn’t know it could work with 1 / -1, i was trying hard to use Pwalk’s third argument directionPattern to control the direction. Very cool, thanks a lot! Does this have any downsides that I’m not seeing rn?

Apologies for the basic question, but once posted
ddwPatterns installed
→ Quark: ddwPatterns[1.0]
Does this mean I have this package installed? If so, how do I fetch Pscratch?

Yes, it’s installed.

After making any change to the class library (including installing or removing a quark), you need to recompile the class library (Language menu).

hjh

great, thanks! I got an error after recompiling the class library:
ERROR: Superclass ‘Pdup’ of class ‘Psmartstutter’ is not defined in any file.
/Users/Robin/Library/Application Support/SuperCollider/downloaded-quarks/ddwPatterns/ddwFilterPatterns.sc
ERROR: There is a discrepancy.

I “fixed” it by commenting out the class Psmartstutter in the ddwFilterPatterns.sc file… not sure whether this is a proper solution, but the library recompiled correctly afterwards.

Can I ask you where did you get documentation about Pscratch? it’s undocumented in the supercollider helpfile

You might be using an out-of-date SuperCollider version. I’ll have to check the details tomorrow, but my quark is referring to the current name Pdup. If it’s undefined in your environment, it would suggest that you’re using an older version prior to the name change for that class.

Unfortunately much of my library is not well documented and that’s unlikely to change. I estimate it would take 4-6 months of letting coursework slide and not working on music to document everything.

There may be old-style html help in a Help/ folder in the quark, which you could open in a browser.

hjh

I see, I didn’t realize this was your library! I’ll have to do a bit of housekeeping before properly trying it out but thank you for the help and the info. No worries about the documentation either, most of what I’d need you posted it already :slight_smile:

Just to confirm: According to the git history for this change, the name changed to Pdup in v. 3.12.2.

hjh

1 Like

documenting your library, at a basic level at least, perhaps a fun project for an advanced student… :wink:

I did check the Help/ folder – many of the classes have old-style HTML help files, just that I haven’t had time to convert them.

Having just pushed out a couple of quarks, I’m reminded how time-consuming thorough documentation is: to explain every parameter, boundary conditions, examples take time to code and debug. I do make it a practice now not to release anything without documentation… unfortunately I didn’t do this early on and now I have a large deficit. (Another aspect may be to prune away some classes that I don’t use anymore.)

hjh

2 Likes

There’s an HTML help file hosted on quark.sccode.org. Some old quarks that are still useful haven’t been updated to use the newer .schelp format still use HTML help files. In that case, it’s usually worth typing the name of the quark/class into a search engine (if you don’t want to look for them on your local filesystem). Another example of this is wslib.

It definitely takes a bit of effort to learn, but there is lots of good documentation on how patterns work internally, and sometimes it’s actually easier to look at the (in this case well commented) source code directly instead of relying on old, possibly outdated/incorrect documentation. And if you get stuck, you’re always welcome to ask for help on here!

1 Like

All good comments, but for any of the ddw quarks, I’d have to recommend using the repositories under my user account instead:

GitHub - supercollider-quarks/ddwPatterns: Various new pattern classes and enhancements. – very likely to be out of date

GitHub - jamshark70/ddwPatterns: Various new pattern classes and enhancements. – maintained, current

hjh

1 Like

Thanks a lot! Until a few days ago I didn’t even know about the existence of this forum… you people are awesome

Going through this again, I’m wondering if a similar process of pausing and change of direction could be set up on a Tdef? (not sure if it’s pertinent to the thread anymore)
My inner logic hits a wall when trying to formalize a “reverse” counter in a looped function, inside a Tdef, since realistically it doesn’t make much sense to reverse a task, at least not through a control of the counter number… I have few ideas (for instance, creating two halves of array when pausing (array 1 extended from 0 to paused index, and array 2 extended from paused index to array.size) then determining whether to play array1.reverse. or array2. It feels like a huge overkill, but scouting around I haven’t found accessible ways to reverse a task. Any idea about a possible way out?
here a segment of the code where I’m trying to implement this.
“”"
Tdef(\tdeftest,
{~array.size.do
{arg i;
var string;
string = ~array[i].postln;
1.wait;
}});
“”"
Ideally I’d be able to pause the task, then count i backwards. But I really have no clue how this could be done. Can the argument “i” be a global variable, determined elsewhere? (possibly in another routine/task independent from this process)

How about:

~direction = 1;
~array = Array.fill(100,{10.0.rand});
a = Task{var index= 50; loop{ ~array.wrapAt(index).postln; 1.wait; index = index + ~direction }}
a.play;
~direction = -1;
a.pause;
1 Like

Yea this is literally what I meant. Thanks for the concise syntax. It’s still inexplicably confusing for me to grasp what the counter does in a looping function, and why such example works just smooth, but it doesn’t if I use an argument as a counter (providing a global variable ~counter from outside, for example) :confused: my experience in Max and Pure Data helps only to some extent, but these kind of tricks in Supercollider make almost no sense to me. I don’t know, maybe I need just more experience, but if you found this piece of code inside a wider text resource that I could take a look at, maybe it could help! Appreciate pointing me at the right direction anyway :slight_smile:

the example will work fine if you define a counter outside -

~direction = 1;
~index = 50;
~array = (0..99);
a = Task{ loop{ 
     ~array.wrapAt(~index).postln; 
     1.wait; 
     index = index + ~direction
}};
a.play;
~direction = -1;
a.pause;

basically we are using ~index as in index into the array -

we can add whatever we want to ~index - or set it: ~index = 20. I use wrapAt to index into the array so that we wrap around when the counter gets bigger than the array size or less than zero - you could also use clipAt if you prefer. Checkout the help for Array for more details.

Inside the loop we add ~direction before waiting and we can change that at anytime as well.

So its not a “counter” object that always increases when you loop around - its just a variable hanging around in the environment holding its value unless we tell it to change.

The Task gives us .play and .pause methods and that’s about it!

2 Likes

One thing that really helps when reading or writing code is to keep a mental map of the current state of the system.

For every statement, every operation, there’s an expected “before” and" after" status. E.g., index = index + ~direction: before status = “index = a number”; after status = “index = the next number.”

Writing a line of code, then, is about understanding the status that you have before this operation, and the status you want to have after the operation, and from that you can figure out which operation to do. (And, a bug is when your understanding of these statuses was incomplete or incorrect.)

“it doesn’t if I use an argument as a counter” – the concept that you might be missing here is that the status inside a function is local and temporary.

a = { |counter|
	counter = counter + 1;
};

In this function, counter is effectively created when you call the function, and destroyed when the function exits. Thus, anything that you do to the variable counter is lost after the function returns.

a.value(1);
i op counter
1 enter function 1
2 + 1 (calc’ed 2) 1
3 counter = 2
4 return 2 then counter is destroyed

In this case, the result 2 is available by way of the function’s return value (like an [outlet]), but the internal state of the function is gone. Because counter is local to the function, the counter = reassignment cannot have any effect on any variables outside of the function.

~counter = 1;
a.value(~counter);  // 2
~counter  // 1

This is correct, because no new values got assigned into ~counter. counter exists only inside – therefore it cannot be the same as ~counter – therefore counter = does not touch ~counter.

Also note that SC never passes variables by reference, only by value. There is no way to pass ~counter as a variable into the function. You can only pass its current value. (Consider this: If your expectation is that ~counter should be updated, what should happen to ~counter if you run a.value(~counter * 2)?)

Pd/Max are completely missing this concept of local/temporary scope. In Pd/Max, every object exists all the time. This is actually a weakness of these environments.

At risk of muddying the waters further – the counter itself does not need to be external to the task. It needs only to be persistent. Regular function scope = local and temporary. Task/Routine scope = local but persistent. In fact semiquaver’s first example was written that way (which I think is better). It’s usually better not to expose internal state unless absolutely necessary.

This question seems to be based on an assumption that using a Tdef is radically different from using a pattern.

In fact, Task/Tdef are only mechanisms for organizing flow of control. They place no limits on the data processing within. So you can definitely use streams made from patterns here too.

~direction = 1;

(
Tdef(\x, {
	var stream = Pscratch(
		pattern: Pseries(0, 1, inf),
		stepPattern: Pfunc { ~direction }
	).asStream;  // 'asStream' is important!
	loop {
		stream.next.postln;
		1.0.wait;
	}
}).play;
)

~direction = -1;

~direction = 1;

Tdef(\x).stop;

hjh

3 Likes