Teletype - non-repeating RAND/RRAND?

HI all,

I’m hitting a wall with something I’m trying to get done using Teletype to trigger Just Friends in Plume mode. Basically, I’m trying to use a single script to trigger each of the JF voices in a random order, and a second script to silence all of the voices. The issue I’m running into though, is that the first script will sometimes try to trigger a voice which is already speaking. Is there a way to use some logic here so that the script will trigger a random voice, but try again if the chosen voice is already speaking?

Would the JF.NOTE command not give you what you want? Basically, this plays one of the available voices. Only when all 6 voices are occupied will it retriever a currently active voice. The one downside of this is that you don’t get to choose which voice is working for each part of the sequence. But, even this could be worked around cleverly.

@Bigtime_Snacks, if you can post your script so we can see how you’re currently calling / assigning random voices, that would help us guide the suggestions for next steps :slight_smile:

Of course!

It’s incredibly basic
Script 1:

JF.TR RRAND 1 6

Script 2:

JF.TR 0 0

ah. gotcha.

the RAND and RRAND ops aren’t non-repeating, so you’re definitely going to get re-triggers before you get through all the voices.

i’ll think on this a bit, but i’ll also generalize the thread title to attract more ideas from others :slight_smile:

1 Like

IIUC, you want “random sampling without replacement.” In this context (workalike of RAND) it seems nontrivial to me.

(thinking about opcode inementation here, not scripting)

The standard method with N values, is to make an array of size N filled with the values, and shuffle it every N samples. (If adjacent endpoints of new and previous shuffled states are the same, and you don’t want them to be, rotate the new shuffled state I guess.)

The problem with that algo here, if N is the full range of values in TT, that N is very big compared to the cpu speed and RAM size. Shuffling is too costly.

The problem with just re-rolling until you get a new value, is that there is no upper bound on how many re-rolls you need. And sometimes it will take a lot, because this function will be called uncountable times and we run into the law of very large numbers. We don’t like non-deterministic compute times in soft-realtime systems.

If you are OK with occasional repetitions then it seems fine to re-roll no more than K times. Then your likelihood of repetition is 1/(16000 * K) or something.

If you’re never ok with it I would consider a different approach, like building a smaller sequence of values for randomization and using a shuffle.

Now that I said I’ll that: since in this specific case you’re talking about a very small number of values, the shuffle method is clearly the way to do it. How to structure that as an opcode I’m not sure. Maybe it just has a fixed, small number of values, like N=16 or something.

And hm, since statistical randomness probably isn’t a requirement, maybe some kind of simple hashing function could do the trick …

[ed]… as @scanner_darkly shows below!

4 Likes

Yeah, seems like an op to shuffle a whole pattern might be more generally useful and allow you to easily script something like this. Syntax thoughts? P.SHUF / PN.SHUF?

As far as figuring out if one of JF’s generators is still running, there’s not a way to do this with i2c, or currently to query anything about JF’s state. The note allocator in the JF code knows, it would be kind of cool to be able to query a bitmask indicating which generators are ready to be retriggered or something. Perhaps also achievable with some external patching involving a comparator, but that’s gonna get involved for all 6 channels.

3 Likes

You could randomly fill a pattern with six values using the pattern and loop op’s, then count and refresh that every six times. Accordingly to what @zebra is describing here:

a simple way to have non repeating random values:

X % + X RRAND 1 5 6

add a random value between 1 and 5 - this guarantees it will be different, then use modulo to get it into the 0-5 range again.

to use it with JF.TR increment it by 1:

JF.TR + X 1
5 Likes

Here’s a straight-ahead implementation of this using the active pattern as the memory of past states (so you could store values 1 through 6 in the active pattern and trigger script 1 after every 6 P.NEXTs).

# 1
J - P.L 1
L J 0: X I; Y RAND I; $ 2

# 2
I P X; P X P Y; P Y I

For this I would probably use a linear feedback shift register (a very predictable hashing function), if you want the full range and don’t mind it repeating after 2**16 values.

1 Like

This is great!

It’s doing exactly what I want it to in a test scene, once I move it into my actual scene though it no longer appears to be shuffling the pattern. Here’s my scene:

Summary

M
M 469
Z IN
TR.P 1

I
IN.SCALE 2 6
JF.MODE 0; SCRIPT 6

#1
SCRIPT Z

#2
J - P.L 1
L J 0: X I; Y RAND I; $ 2
IF EQ P J P 0: P PUSH P J

#3
JF.TR 0 0

#6 
JF.TUNE 2 6 5
JF.TUNE 3 4 3
JF.TUNE 4 3 2
JF.TUNE 5 9 5
JF.TUNE 6 2 1
1 Like

I realized this line

was actually totally wrong for trying to rotate the pattern if the endpoints are the same. It will never evaluate to true because the pattern has already been shuffled, and if it did PUSH is not what you want (it would change the length of the pattern). So I deleted it from my post above.

How are you triggering script 2 here? Looks like it relies on polling IN into Z, so you expect it to be triggered when IN is at 0 V and you trigger script 1?

Ah, important context! This is all for a percussion/synth piece I’m working on, much of the synth action is relegated to my feet. So I have an expression pedal going into the input and a foot pedal going into 1. The expression pedal will be used to decide which script gets run by the foot pedal.

I just realized that

L J 0: X I; Y RAND I; $ 2

is in script 2, which is trying to just call script 2 again J times, which doesn’t do anything, because the essential part of the shuffling action in my example was

# 2
I P X; P X P Y; P Y I

being called inside that loop to swap pattern elements X and Y.

1 Like

Aha! apologies, I hadn’t taken the time to fully understand your script before testing and had forgotten that “$” means to call another script. I hadn’t adjusted that part to account for a different position in my scene

This is doing exactly what I want it to now. Thank you so much for the time and attention!

2 Likes