Oct 6
Jim at 10:08 Friday
Tim: this is the second week that we haven't met.
I sent a note last week talking about a possible
Thursday afternoon time instead - maybe that would
be better. (Did you think we had already changed the time?
I thought we were still discussing that.)
In any case, missing two weeks in a row is really not good.
I spent some time looking at your code, and
the libraries that you're building it on,
and now see the following:
(1) You're using python's Collection object to look for note or rhythm patterns which occur multiple times. As you say, their built-in will only count two things the same if they really are exactly the same. Since real life isn't like that, you will need to first "round-off" those patterns so that (say) (1.2, 2.1, 3.1) and (1.1, 2.05, 3.3) both become (1,2,3) and are therefore the same. This example is trivial, but that's the idea. You'll need to understand what the typical values are in your data, and how fuzzy you want the round-off to be so as to make things that are close turn into things that are the same.
More specifically, you would do something like
#### your code (I don't understand your choices here for sub ranges)
for i in range(len(note_parameter_list)//2, 1, -1): #each possible sublist length
subnotelist_coll.append([str(x[j:j+i]) for j in range(0, len(note_parameter_list)-(i-1))])
### invent a new roundoff function and invoke it here ###
subnotelist_coll = fuzzy_roundoff(subnotelist_coll)
#### your code
#make a Counter object for each list of possible motifs
cnt = [Counter(minilist) for minilist in subnotelist_coll]
(2) The python-osc library that you're using is built on top of python's SocketServer, so most of the "how do I put this in a loop" is done for you within those frameworks. As in their example, your code should use their Dispatcher object to "map" a handler function (which you need to define) to their server. Then within for example their serve_forever loop, that handler function will get called in response to an event such as an arriving message. You'll have to decide which of the several options you want to use (one process per request vs one process per type of message vs etc) based on some tests of how long it takes to analyze and/or process things. That's going to take some trial and error. In your case, if you are doing more work after (say) each phrase, then you may have only one handler that does more work every once in a while - maybe in another process, depending on what you want to happen when. I suppose you could also use their existing event framework by firing off another osc event of a different sort as needed, to be caught by a different handler.
In your code I see
dispatcher = dispatcher.Dispatcher()
dispatcher.map("/note", storeNewNote, "note")
which is telling their server to call your storeNewNote() function
from within their server.serve_forever() loop.
However, you don't have a storeNewNote function. You do have a store_new_note ...
maybe that's a naming error. (I haven't looked at what the API for the args
to that handler is supposed to be or whether yours is doing the right thing.)
If the only type of osc message you're getting is "/note", then the only
part of your code that is being invoked from server_forever() is that function.
It could do something else too, like "if end_of_phrase() ..." do other stuff.
I strongly suggest that you test the server stuff with a really simple example
(e.g. play the same tune, or play every other note, or echo the input an
octave down) to make sure you can use this client/server/network infrastructure.
Jim says
We should go over your motif_detection and you can explain
what it does and what the "exact match" issue is.
Likewise for the question of real-time architecture,
we should talk about how long the various pieces take to run.
Setting up multiple processes to handle different tasks
in a real-time coding project is one possibility - the
advantage is that you can let the operating system
do the "whose turn is it" part, and be doing things
that are (apparently) simultaneous, e.g. storing
the incoming data and outputing a new signal.
The main issue with that approach is the question
of how you share and manage the data that all
these parts need, and whether it needs to be "locked"
one one process needs to change it. That can get messy.
The other approach is to have a "while True: " loop
in your main program, and then each time through
look at each possible sub-task that may need doing.
If they are fast, just do them. If not, your code
could queue up future tasks (function calls)
and/or try to do a piece of a task. The Arduino
"setup" and "loop" functions are like that.
Let's talk about this face to face. You will
probably need to do some timing tests
to find out how long the different parts take,
before you can understand what approach you need.
Tim says
The main code contribution for this week has been a motif_detection function to find the most common (and largest) subsequence of an array of notes. Depending on an argument, it can either look for rhythmic subsequences or pitched subsequences. The biggest problem is that the values have to be exact to find one of these motifs, and I can't be that exact when I'm performing. Sequences with small differences should be considered similar enough to register as equivalent. I'm not entirely sure how to approach this.
I'm still trying to put together something with Hidden Markov Models. I'm thinking that that will only be used for the generative functions to create short phrases.
I'm not sure what to do about the structure of the main agent file. It's constantly receiving values, performing calculations, and sending values, and I don't know how to have all these processes running at the same time. Would multi-threading be the answer? Is there something much simpler I'm just not thinking of?