Using a list to control the order of a matrixctrl∼ object
Dear community,
I am currently working on a state machine for an interactive sound installation. As I couldn't really find tutorials for a state machine in MaxMSP, just some stuff about the markov object, which I felt didn't really fit for what I wanted to do, I have decided to try it with simple if statements etc. I am now working myself from one question to the other, and the first one I couldn't answer myself is how I can use a list created with a zl.group object to bang things in the correct order, while also defining a time until it goes from one to the other.
To give you a short description of my project, I have five people sitting in a circle. They all put a heartbeat sensor on, probably at different times. When a heartbeat is detected the first person will hear their pulse for a minute, then it switches to the second the third etc. It can be that there are just 3 people for example. After the individual part the last one will stay on and the others will appear again, so that they are on all together for about 5 more minutes. In my patcher you will find already the broader states but the individual heartbeat section seemed to be a bit more difficult, also jumping from the individual to the collective part. In my patcher are pink boxes with more question, but for now I would like to focus on this one. Here I have a screenshot and a patcher below:
I have also formulated the different states within the patcher:

hi, this is difficult to answer, but i'll try, hopefully others here can help me out:
"how to check if there is still a person present?, or if a persons pulse is cone for a while what to do then? start new?
building in some moments inbetween to check?"
you could set up one global timer(clocker) to keep polling for the existence of any events, if it doesn't get reset after awhile because no events have arrived, then you can start everything new(i'd recommend starting new to keep it simple for yourself, starting new is always exciting :D)
"but it should also stop if its 3?
integrate zl.group/list again?"
^i think you might actually have a 4th state, that is a pause between sessions(?)... so that in state4 it stops if a sound is playing already after 5 minutes, otherwise, if it's already been stopped after playing for 5 minutes, reset a counter to count 3 minutes down before starting again(or maybe this could just be a branching condition for the last state)... or maybe you have 3 states which are individual to each voice, while there are 2 global states(in-session or pause-for-3minutes-between-sessions).
"how to reset whole process?? -> get to state 0"
^i think you already have this in place? but basically, figure out the branching condition that ends everything first: does it stop after 5 minutes of sound, pause for 3 minutes, and then reset? (<-then this should happen in order at the end of detecting nothing for a certain amount of time)
"how to block the process before it can start again?"
^so maybe this question is the answer to the last, but i didn't quite understand it: you can 'gate' whatever process, and make sure there's a finite condition that controls the gate... this is where you need to separate a global state for everything from each individual state.
i think what can help you more than anything at this point in your learning is to study how to make abstractions in Max:
encapsulate one single person's set of interactions, within one single voice or 'abstraction'(to do this, you'll need to plan ahead and decide, what 'global' parameters need to be communicated to this abstraction, and set that aside as something to be communicated from/to the outside somehow: in other words, certain things like counting how many people are involved, who started first to route to which sound or mix-channel, how much time passes before reset of the entire system if no one is around, etc... will need to be tracked globally, while others are specific to each individual's participation)
instantiate that abstraction five times and route communication to the global state machine to test(there may be the concept of a smaller state-machine within your abstraction to keep track of how many heart-beats before starting the sound individual to that voice, etc... but the global one will track the most important data to structure the entire experience in a nicely interactive way across all five participants)
once you have an abstraction with all the encapsulated functions specific to an individual, it will help you see what can be separated to/from the outside in a global way more: maybe you'll decide it's better to have one 'clocker' per individual(right now, it looks like you have two... i'm not sure you need that... you can just leave one on per voice indefinitely, and then simply reset all of them using a global one which would be tracking the 3 minute pause between sessions... unless i've misunderstood the structuring, either way, you'll see it more clearly yourself once you restructure it to encapsulate individual processes from the global tracking of them all)...
...you'll be able to look at it separately and see more clearly how to make it more clean, too... for example, you might decide that everywhere you've placed an "if $i1==[some number] then [1/bang]" can be replaced with "sel [some-number] [some-other-number]"(this way with 'sel' objects, you can change the number being watched for on-the-fly and 'sel' is made to output bangs to begin with so there's no need for if/else to do that...), making it easier to use just one 'clocker' per voice (but... this is not to say there is only one correct way, just to say that by exploring your own design in more properly encapsulated ways, you'll see where you can make it more efficient on your own)
i feel there used to be more docs on making abstractions, but here's some to get you started, there may be others out there perhaps on youtube:
state-machines are a general concept and can be made in too many ways within max to write a proper tutorial on them(you can even make state-machines purely with 'pattrstorage' object(just storing presets for each state)... my favorite way to make state-machine is to use branched 'if/else/if-else' statements within the non-signal version of 'gen'(but this can get a bit advanced to explain, and is better learned by learning coding in general)... and other ways include using 'coll' or 'dict' to define states and address them... even the 'sel' object can be used at the heart of the simplest state-machine designs)
in any case, although you have a complex idea here to be able to help you all in one go... if you learn to encapsulate one single individual's functions within an abstraction that can be copied over five times, then you'll have a much easier time organizing this patch for everything you need it to do(especially, in being able to organize the behavior of your global state-machine better). hope this can help 🍵
[EDIT: and my apologies! this was probably your main question:
how can I use the list, to play them after each other in the right order?
i suppose i was meaning, in my above explanation, that this might become clearer after making abstractions, but i must admit, i haven't studied your patch long enough to see how you're getting the ordering into zl.group to begin with... so without knowing the entire details, i'd take a guess and say you could, once zl.group has the correct ordering, use the 'iter' object to iterate the grouped list, and then use 'route' or 'sel' object to route those ordered numbers to their appropriate '... ... inc' message for the matrix~ object
...but you're going to also need to decide how to deal with the situation if there are less than five participants: maybe they should start, not as a condition of exactly 5 participants being registered into zl.group by a particular order, but just by whichever one registers heart-beats first?]
only skimmed the text... a state/transition table... is only a table... no need for if/then/else constructs.... for example you could use a [coll]... it will output data stored together with the index and you can call the index by requesting exactly those numbers or strings.
Thank you 👽IT W∆S ∆LIENZ👽 this is super mega helpful! I haven't expected to receive answers to so many of my questions in the patcher, but you really gave me a lot of good tips and I will immidiately try to look into them, test them and hopefully implement them in my patcher :).
Also ROMAN THILENIUS thank you for highlighting the coll object. I think this is indeed something to check out and use in my project ;).
THANK YOU BOTH SO MUCH!
Hey you two,
I am coming back to you as I have now a state machine that works more or less good. I have decided to simplify the whole process to make it a bit easier for myself (I am still learning by doing and getting into both coding basics and maxmsp is more difficult than I thought ;)).
So now the state machine consists of 3 bigger states:
state 0: everything is off no sensor can be triggered, after 3 minutes go to state 1
state 1: a sensor can be triggered, if 1 sensor is triggered go to state 2
state 2: is divided into 3 states again
state 2.1: trigger the sounds of the sensor on in its right order (coll) until list is complete -> state 2.2
state 2.2: stay in that state for x time per x participants -> state 2.3
state 2.3: trigger sounds off in its right order (coll) until it's complete + 1 more minute -> state 0
According to your tips I have decided to use the coll object, even though it was hard for me to find tutorials or information about the questions I had, which is one reason why I simplifyied things. Also I didn't get into abstractions but already started encapsulating things to make things clearer and better to understand. I didn't clear up everything as it still helped me to understand the conditions.
So, as I was still running in some errors I was wondering if one of you might be able to point me in some more concrete directions where I could look at. Specifically more infor to the coll would be amazing.
Here some of the questions I had:
Sometimes when I am still in state 2.1 a sensor can be triggered later, but even though it becomes part of the list it sometimes is not outputted and starts with the first element of the list again. is there a way to go around this?
I have implemented a check to see if sensor is still on and if not to remove the sensor from the list, how can this affect also state 2.2 and how could a person that joins in later in state 2.2 still join in? Is there a trick or anything to figure out how to do this?
In state 2.3 is it possible to trigger the list in its reverse order?
Well, thank you very much already in advance! Best, Laura.
apologies if this doesn't answer all your questions at first... but i went ahead and created a version of your patch with bpatcher-style abstraction for the first 5 'voices'... this can help you get into the ease of creating abstractions:
to help see what i did at a glance, i took this portion and 'encapsulated' it at first just to see where the connections were, at the same time, copied and pasted it into a new file:

edited the numbered values to a replaceable argument "#1":

then instantiate that file as a 'bpatcher'(very quickly, you can create an object called 'bpatcher' but with an argument of @name that's the name of the file, so in this case, created a new object as "bpatcher @name BASEMilVoice.maxpat @args 1" for voice #1, and "bpatcher @name BASEMilVoice.maxpat @args 2" for voice #2, etc... feel free to try creating "bpatcher @name BASEMilVoice.maxpat @args 6" for the sixth voice to see how it works, you can then resize it to see the whole thing, and then hold Shift+Command on Mac, or Shift+Ctrl on Windows, while clicking down on the bpatcher interface and moving the mouse around to move the entire bpatcher-loaded interface of objects to a desired position within the bpatcher frame... the args and file loaded can also all be set within a blank bpatcher's inspector)

abstractions can be in the form of a 'bpatcher' like this for anything where you actually want to see the interface, or else they can just be saved into a file and loaded like a normal object that has the same name of the file(careful never to name any abstractions as anything similar to other objects you may have, max can get confused)
[Edit: one last important thing about abstractions - they should either be in the same folder as the parent/loading-patcher(taken care of here by being zipped into a folder already), or placed within max's search path(options->file prefs...) in order to load properly]
another thing i did is put a trigger object into your 'check', just helps me feel more sure about the order of operations there:

i will keep looking at your patch(and hopefully Roman and others might chime in about your questions), but in the meantime, maybe you could try this '3.1' version, just to confirm that it works the same as your 3.0 version?
then we can start from something that's more effective in instituting changes across different parts of the system.
...and at least i can give an answer to your very last question:
3) In state 2.3 is it possible to trigger the list in its reverse order?
just looking real quickly, i think to do that, you'll need to keep track of the very last item in the list, and use the 'goto' message to set the indexing to that last index(unless you've already arrived at that last item each time by natural progression), then use 'prev' message to go in reverse(someone correct me if i'm wrong, i've not used 'coll' extensively like this in awhile 😅)
hope this can help a bit :)
maybe others can chime in on general ideas about organizing this better, but i'll give some additional thoughts:
now the state machine consists of 3 bigger states
looking more closely to this, i think this is not a state-machine, but rather, just a sequence or progression.
the problem you're running into with the first two questions i wasn't able to answer so far, is that you're organizing everything around the idea that one thing happens first, before another, in a 'sequence'.
but instead, you need to create conditions that control how things enter a state, and can only exit that state once in it(this is a 'finite state machine': https://en.wikipedia.org/wiki/Finite-state_machine ). the usage of coll, is just to represent things by symbols(the index number could be like an address or name for a condition, while the value stored in that index could reference some function that responds to that condition... i think instead, coll is just being used in your patch, not as a state-machine, but just to enter and remove participants).
makes this one difficult to answer:
how can this affect also state 2.2 and how could a person that joins in later in state 2.2 still join in?
if this 'check' is not affecting state 2.2 but it needs to, then maybe things need to be reorganized so that this check(and other functions like it) are either 'reset' at the beginning of each progressed 'stage', or made to be included within a list of functions that are specific to each 'state', or possibly something else i'm not seeing.
i should also mention, i don't have the sensors and setup you do, so i have no way of testing it all the way through. on forums, we can only guide you to the general ideas: you have to decide for yourself if a 'state-machine' is what you're really after, or just a 'sequence' of stages where only certain specific functions can be addressed; people mostly only need a 'state-machine' when the states don't necessarily have a progression to them, if you can order your states into 1, 2, 2.1, 2.2, 2.3, 3, and that never goes outside that ordering, this is better dealt with as a sequence of functions(some of the functions then need to be globally accessible to all stages of the progression), rather than as a 'state-machine'.
i hope this makes sense and can help. i totally understand your frustration, apologies i can't be of more swift and easy help, this is more about getting the design of the system right before putting everything in place: often that's the most difficult part.
Hi 👽IT W∆S ∆LIENZ👽,
First of all thank you for responding to my message!
Also, thank you for making the abstractions, it does look much more tidy. I have just tried it and it works, so I will now try to follow your instructions and create the sixth one ;).
To answer you about my question 3), I have used the prev trigger beforehand, but was wondering if there is an easier way to just reverse the whole list and go through it via the next message, but yeah I guess the previous message also works.
And, you are right, I am using coll to create a list of the participants, instead of using it for conditions and functions. To be honest this whole world is still very new to me and understanding everything just takes me a lot of time. But I get your point and it totally makes sense. I haven't thought this way before.
I think I need to indeed figure out if I want to keep everything in sequences, or if I want to make everything new to define conditions to go from state to state also inbetween. I think it's actually the better option would be the state machine as it would help me to work around my question number 2). But I am questioning if this is maybe to much too learn at once?
For the sensor set up, I have to admit that I also didn't test it with my equipment yet, I have just simulated it so far. I wanted to get rid of small errors first and then run some test with the first prototype. But to be honest I am already very impressed how helpful everyone here in the forum is so I totally get that its up to me in the end to make things work ;).
So I will try to figure out hwo to continue and then I'll probably chirp in with questions again.
THANK YOU SO MUCH for your help!
i'm glad to be of help :)
just a quick note about this:
maybe to much too learn at once?
no, not at all, i notice you learn pretty fast! :D if anything, i fail at describing which path to take because i'm unable to guess at how the whole system operates within the actual context(i don't trust myself to understand your patch at a glance, either), i'm also not very fast at learning how to communicate these things.
the state-machine vs. sequence path, is just a matter of deciding your own style of organization, then working it out clearly(perhaps even on paper with block diagrams to list all the functions relevant to each state). keep going with it, it'll make itself apparent to you when it's ready(not just a matter of techniques, but also of 'manifestation'... doing it in just the right way for your particular style in this particular context). cheers 🍻
a bump to get some other perspectives on this(i didn't mean to take up so much space)... maybe others in the community can help out here:
my thought is that maybe there's a global 'sequence', but then an individual 'state-machine' that can be addressed via the global sequence... i'm not sure how to advise on the structuring of the patch to that point, maybe others here have examples of state machines they made? or can describe how they address key/index-value pairs where the value matches to functions/operations/abstractions/etc.?
Hello you two, and the rest of the community ;)
I am back on my file and with the beautiful abstractions 👽IT W∆S ∆LIENZ👽 made and the tips from both of you. So far, I have managed to avoid most of my issues. I have also thought about the question: is this a state machine and no it is not. It is indeed going from sequence to sequence and never goes back in a previous sequence, except for sequence 0.
Unfortunately, I am still struggling with one thing: the coll object.
For example six people attach a sensor. All heartbeats are detected and thus trigger a message that is being stored in the coll object. The heartbeats can now be triggered after each other according to the order of the list. Now, one person leaves the session which triggeres a "remove" message and removes the message from the coll list. In the list the amount of messages is correct, but it still triggers the amount of messages from before. So instead of five, six are being triggered, which means the first one(s) are triggered again and thus turn off, which they shouldn't.
I guess the coll object is to slow to realize that the amount of the messages stored declined. Therefore my question would be, is there a way to go around this?
One other thing I still didn't figure out is, how I can integrate if a person joins in sequence 2. In the first sequence it seems to be still possible to some point, but in the second one it's not.
I have attached the most current version and implemented some metros that can be triggered to simulate the heartbeats. I also tried to explain every step and thing and hope it's understandable and I didn't forget anything ;). The yellow comments are again my questions connected to the part in the code.
I am happy for all kind of suggestions. THANK YOU.
P.S.: Not sure if it is smart to post here, in case no one answers I will make a new post.
you can use grab to get coll length

Yeah I thought about this, but I am a bit stuck in how to use that information?
nope, coll is not "too slow". the majority of objects (and their use cases) let you send around thousands of messages within 0.01 milliseconds on a modern computer, and timing/stack issues do today not even happen with javascript. only when you convert between numbers and audio signals several times you can run into that kind of thing.
do not take this criticism too serious, but houston has a little problem with the way you ask questions. :)
for example you ask "i am struggling with the coll object, for example i have a group of six sensors and six persons"... this is like 50 questions at once and leaves a lot of room for speculations what has to be done. :)
and i am afraid my answer with the state/transition table based on coll was in a simmilar manner not ideal, too.
one should have probably added, that the inclusion of conditions into such a table is of course not obvious or automatic, it is rather something you have to freely define yourself and then work on its interpretation somewhere outside the coll.
for example you could use coll as the "database" for a little markov chain by writing index =current state, and the, a list of possible next states, each followed by a number expressing the percentage of probability like so:
A, B 5 C 5;
B, A 3 C 7;
C, A 9 B 10;
and of course as soon as the number of states or the percentages change, it can be modified by adding or removing entries.
however, the meaning of the data is your job. one could as well use "shares" instead of "percent"
A, B 1 C 1;
B, A 3 C 7;
C, A 9 B 10;
or you could write down every "hit" and then outside the coll you later just need to choose one of them randomly - this is often what you want to do when you use given datassets (such as a music composition or pixel of an image file as analysed input):
A, B B C B B B C C C C;
B, C C C C C C C A A A;
C, A A B B A A B B A A B B B B A;
so i guess what i am trying to say is that learning to use [coll] and solving the logical problem "what do i need to do?" to make a machine model is two completely different things, and trying to do that at the same time (how do i use this object with people and sensors?) can get very difficult.
think 10 miutes about what you need to do, then get a cup of coffe, then look 5 minutes into the coll helpfile and learn about what it can do, then get another coffe, then see if it can be useful for the job.
one more coffee and you´re ready for consultations and guidance.
if it still does not work, switch to red wine.
Yeah I thought about this, but I am a bit stuck in how to use that information?
this was probably a bit of my lack of foresight, but also, i must admit, since i get busy with other stuff i can't always remember the way the patch works and have to try and guess at certain things to get an answer out there expediently...
i'm guessing this can be added somewhere within the abstraction itself, somewhere like this:

basically, i think it's just a matter of modifying the abstraction i made to incorporate more linkages of functions(i couldn't foresee that after something is removed, some other things need to be updated)...
another way to put it: you have created the subpatcher named "p check" that is vital to most of this already, now you just have to extend that to be more like a "p check_and_adjust", by linking actions to whatever it checks for.
and then, the answer to your other question might become more apparent after that:
One other thing I still didn't figure out is, how I can integrate if a person joins in sequence 2. In the first sequence it seems to be still possible to some point, but in the second one it's not.
^one way might be to rewrite the single abstraction to keep track of sequencer states(?), another way(probably quicker to put in place with the patch you have as is now, but less reusable/reworkable) might be to add some sort of tracking within sequence 2 to directly modify the counter for amount of participants and adjust any other linked functions as a result of the person added at that stage(?)...
but i'm unclear as to what happens in the sequences, the toggles you added don't seem to be hooked up(maybe you forgot to upload your modified version of the abstraction i made?) so i was unable to simulate(and i might be a little rushed to look real carefully right now), but hopefully this can be enough hints to help you mod the abstraction for your first problem about length adjustments after 'remove'...
if you still can't figure out the second part, post back here again, we'll keep working at it :)
Yep Roman, right as allways.
I have no time to dig and analyse that patchers,
but Laura - you did not provide precise infos about the whole scenario,
in short and simpe way which would make it possible to construct collection
of data and their analyses an easy task.
You mention coll, but nothing about structure, and
what is the length you need to get ?
Number of indexes ?
Number of values in one line ?
At the end, why don't you record everything into sort of live recording,
having track per person, in a timeline which records individual data.
Recording can take place into coll, mtr, midifile in detonate ... soooo many options.
That little bit of a logic at the beginning, where individuals
have to sit for nn minutes etc is a easy done.