I’m currently working on some CAC tools for myself and noticed a couple weird/unexpected things:
Calling multiple .send_floats()/int/etc. from Python at once eats all but the last sent message. I looked at the Poly implementation and saw that I should expect this, but it might be helpful to have it explicitly stated that in order to send multiple messages at once to the “same” address you need to use PolyPal or write your own implementation. I’m happy to edit the documentation to reflect this if y’all would like!
In the same vein, for high block sizes (I’ve found that WSL works best at block sizes >512) there are some weird outcomes when trying to schedule rhythms, which I’m assuming is due to messages being “received” at the start of each block.
Is there some way to have sample-accurate scheduling yet? If not, are there plans to include this in the future?
Also, while doing all this, I found Poly’s use of callbacks to be much more intuitive than using the Messenger.notify_update() method for lists. I added notify_callback() and update_callback() methods to the Messenger struct on my branch and have found them pretty useful. Using a callback also removes the need to have a list as a struct property just to hold the data received by the message. I’d be happy to make a pull request for this if there would be interest!
Yes. You have to use the PolyPal for this. We had a version of this working at one point that didn’t use PolyPal and the solution is known, but it is tricky to reimplement. Would love you to edit the docs. It is often hard to see things when you made them.
Yes. The messages have to be processed at the top of the block. There really isn’t any way around this. You don’t know when the samples will be processed inside the block. at 512 with an sr of 48k, your messages are processed every 10.6ms, so jitter is noticeable.
Per above, there is no way to make the message processing more accurate than a block because you don’t know when the samples will be processed in the block. Generally, the samples will be processed very quickly after the block is processed, followed by a longer period of the cpu waiting for the next block. You have two solutions:
One nice thing is you can run all your CAC code in Mojo. Annoying, but possible.
Run in Non-Realtime. Basically, save all your messages to a json file and render it. I can run the block size down to 2 samples on my computer. So not sample accurate, but within 2 samples. This is because of how the messaging system works: it needs to process samples on the block and clear the cache on the next sample. Look at “WavetableOscNRT.py” and change the blocksize to 2.
Would love a PR for the callback. That sounds useful.
Ah, that makes sense! I’ll check out the NRT stuff, that sounds really useful! 2 samples is more than serviceable.
I’ll get around to editing the docs today (at some point), and make a PR for the callback messages (I’ll make an overload for each different List[data type] to match the current implementation).
edit: Idk how I missed the Messenger example, I definitely had looked at it before… I think it would be nice to have a jupyter notebook for the Messenger example though (and possibly include PolyPal in it?). I’ll throw one together and include it in the PR/update the current MessengerExample file to reflect the changes for the callback as well.
The problem with Jupyter notebooks is there is all this metadata with those files that gets updated every time you run anything in the file. Kind of a git nightmare…but otherwise they are great.
Asking because I really don’t know, but would moving notebooks into a git submodule make them more manageable? Maybe making a “Tutorials” folder that’s only notebooks that use the examples graphs. At least then the metadata wouldn’t be clogging up the git history of the main repo(?).
If there’s interest in this but not as part of the main repo I might start a repo that has tutorial notebooks in it.
I think they’re a really great way to be able to show how these things are working. It should work to just clone the repo into an MMMAudio folder. I have just a rough draft of a Messenger one at the moment but I think it shows the benefit of having them like this. I’ll probably make a main branch and a drafts branch when I actually finish the Messenger one.
I added two tutorials to the repo and will update it periodically in the future. Let me know if there’s anything that would be nice to have more of a step-by-step walkthrough for and I’ll make a notebook for it! Feel free to give them a look if you want to give any feedback!
Last fall when we were iterating on the Messenger system, I had a way that injected any incoming message directly into the memory location in Mojo as soon as it arrives, bypassing the Dictionary that holds the messages until the top of the next block. It does this by building a dict of id->pointer. This would be as sample accurate as you could get. It would also mean that messages that arrived within the same block wouldn’t overwrite each other, effectively masking the earlier message.
The reason we decided against it at that time was because the memory addresses of variables changes between a struct’s init and it’s first iteration of .next, which made it a bit of gymnastics to create that Dict. Essentially there had to be “init-like” code in the .next function. I haven’t tested recently, but I imagine this is still the case.
I still imagine there’s a way to implement this elegantly. Now that Mojo is 1.0 (or, I suppose 1.1 now), it might be worth taking a look at? But I think it would be quite disruptive to the code base so I think it should wait a bit like maybe til the summer.