// pymonome // python applications made easy

pymonome is a tiny serialosc-friendly module for developing monome applications in python. while python itself is hardly suitable for realtime audio processing, it’s a great language for prototyping, scripting external applications, midi stuff, etc.

get pymonome: https://github.com/artfwo/pymonome
grid studies tutorial: https://monome.org/docs/grid-studies/python/
old forum thread: http://monome.org/community/discussion/comment/211934

let’s resume the discussion.

6 Likes

and immediately bumping this with some news. git version now supports splitting grids and running python apps side-by-side. splitting requires a little bit of boilerplate code (check out examples/splitter.py), and i’m currently looking for a way of automating wrapping existing apps in both pages and sections with less effort.

splitting can also be combined with paging (and there are 2 ways to switch pages now). this actually opens up a bunch of interesting ways to run many apps on a single monome, but that’s something i haven’t explored yet. stay tuned for updates!

3 Likes

hi artfwo,

loving what the pymonome library does - am currently getting a few things running on a raspberry pi so that I can set up some DIY eurorack integration for my legacy 40h grids.

I’m keen to either do some spanning and/or have multiple grids running different applications (I have three 40h’s all together).

How do I specify which app connects to which grid? At the moment, following the grid studies example, I only get the first grid that serialosc finds. I can see that there’s something in pymonome for specifying a port or an id (I think) but I can’t quite grok how to use the code without seeing an example.

thanks in advance for any pointers…

1 Like

hey @chailight, thanks for trying out pymonome. sorry, but there’s no easy way to span grids in pymonome yet. i’m currently doing a major library rewrite with grids decoupled from the app logic, which will allow spanning and other kinds of stackable ‘behaviors’ on top of the physical grids. hopefully i’ll be able to release it soon :slight_smile:

different applications on multiple grids case is possible though. let’s assume, you have an application class called Bridge, and want to run it on 3 different grids. you can do this if you pass a dict of monome ids and the corresponding app factories to create_serialosc_connection like this:

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    coro = monome.create_serialosc_connection({
        'a40h-458': lambda: Bridge(bridge_port=48081, app_port=48001, app_prefix='/rove'),
        'a40h-002': lambda: Bridge(bridge_port=58080, app_port=58000, app_prefix='/toad'),
        'm0001754': lambda: Bridge(bridge_port=8080, app_port=8000, app_prefix='/duplex'),
        '*': lambda: Hello(),
    }, loop=loop)
    loop.run_until_complete(coro)
    loop.run_forever()

note the special key '*' which links the Hello app to any additional grid you connect. the above is actually a real-world example with an osc<>osc bridge for certain legacy apps which don’t use serialosc discovery mechanism. hope this helps to get you started!

1 Like

Thanks @artfwo. This has helped a bit - I can get apps running on more than one grid now (in fact the code I had before would have also worked, but I didn’t realise that the second instance of the sequencer that was being automatically fired up by the factory method wash crashing because it was trying to open an already open MIDI port.

I can see what you mean about spanning being difficult with this current library, in that you’d need to set up some kind of sync between the apps that are running independently on one each grid. Although I guess the thing to do would be to just build the rendering layer as a common app on each grid and have the app logic sitting in the main thread of the python program?

I probably need to read up more on factories and asyncio .

If you have the full code of the Bridge example available somewhere that might help me understand the code you’ve pasted above a little more deeply.

In fact any other examples of pymonome apps would be really handy.

As an aside, I’m currently having issues with setting the rotation of one of the grids - just doesn’t seem to make much difference what setting I put into the relevant config file, the grid studies app runs at 90 degrees or 270 degrees off - entries of 180 or 0 don’t seem to make any difference. But I doubt this is related to pymonome - unless the app is failing to read the orientation from the second grid when it launches… is that possible?

[quote=“chailight, post:5, topic:467”]If you have the full code of the Bridge example available somewhere that might help me understand the code you’ve pasted above a little more deeply.

In fact any other examples of pymonome apps would be really handy.
[/quote]
The bridge example is linked above. There are other apps too.

i think rotation is handled at serialosc level, but i have never used it, so i can’t help with it, sorry. make sure you 1) are editing serialosc configs with monome unplugged. 2) setting rotation changes keypress coordinates.

1 Like

Thanks again @artfwo - I got my head around rotations and getting multiple apps running.

My aim is still spanning, but even apart from spanning, what I’m also keen on is having a common tempo running into whatever apps I have running on multiple grids.

Looking at the grid-studies sequencer, I can see that the play co-routine just takes a 0.1 sec sleep action as it’s “clock”.

I’m trying to understand how I could set a tempo within the main loop which is then received by the apps allocated to the grids.

if the play co-routine where to change from being a loop (with an embedded call to sleep) into a step function which I just call from within the main loop (with a call to sleep) then is that heading in the right direction?

In other words, instead of each application having it’s own internal loop with sleep that sets the tempo for that app, the “loop with sleep” moves up to the main event loop, and each time around the loop, there’s a call to the “step” function for each app - this means each app advances one step each time around the loop - in other words there’s a common tempo driving both apps.

I’ll experiment with this idea and report back, but if there are others who are more familiar with asyncio in python, or if there are existing examples to have a look at, I’m all ears.

right. check out “clocks” module from pymonome-apps - it does exactly what you described. instead of calling asyncio.sleep(), create a clock (RtMidiClock or InaccurateTempoClock) like following:

import asyncio
import monome
import clocks

class MyApp(monome.Monome):
    def __init__(self):
        super().__init__('/monome')
        self.clock = clocks.InaccurateTempoClock(120)

    @asyncio.coroutine
    def play(self):
        while True:
            # do something here
            yield from self.clock.sync(6)

note that you can pass an argument to clock.sync(), e.g. clock.sync(6) and it will cause the loop to continue at every 6th “tick”. in my modules, ticks are simply midi clock beats (there are 24 beats per quarter-note for the given tempo).

if any other app is syncing to the same clock and divisor as above (6), both of them can trigger an event at the same time. make sure you have the same clock in all the simultaneously running apps though. to do that, do not create clocks in app constructor. instead, create them early and pass them later to your apps.

InaccurateTempoClock uses the non-realtime asyncio.sleep() function and thus is “inaccurate”. RtMidiClock can sync to external midi clock and also supports “start” and “stop” events, which is good for syncing your pymonome apps to an external DAW or whatever :slight_smile:

Awesome. I’d looked at the clock app before but it didn’t quite sink in. The rays.py example + your explanation above made it clear.

I’ve got a basic kind of spanning working now - not a general spanning mechanism, but by having two versions of a sequencer app allocated to separate separate grids, but both running on a common clock, each grid knows when it should be “active” (e.g. either on steps 1-8 or steps 9-16).

Sometimes I stop and think I should just drop cash on a new 128 and not bother with these things, but there’s nothing “wrong” with the existing grids, so I’m loath to just abandon them, plus I keep on learning more by solving these problems, which seems to be very much the spirit of monome…

thanks again for your help @artfwo - without a helping hand I’d either be a lot further away from my goal, or a lot closer to giving up and replacing existing kit.

once things are a bit tidier I’ll get this code up somewhere for others in case that’s ever useful…

great :slight_smile: i also have 2 arduinomes which i still use sometimes. please bump the thread when you get your code ready, i’d love to see what you come up with.

Quick update: after some further adjustments on code to handle rotation (the two 8x8 grids I have are in one case but aren’t in the same orientation) - I’ve got a basic 16x7 sequencer running with position display on the top row, and I can drive my modular with this via a mutable instruments CV Pal.

Next steps will be to add additional pages for velocity and duration, and maybe octave, and then move on to allowing switching between multiple patterns will do some tidy up of code.

I’ll tidy up the code a bit and put it up on GitHub sometime this week.

Before I do, @artfwo - is there any examples of the RtMidi clock being used? So far I’m just running off the internal clock, but ultimately I’d like to sync this with the rest of my rig, so running off a MIDI sync signal into the Raspberry Pi would be ideal.

I had a look at the RtMidi clock but I don’t quite understand how it works, or what I should be initialising it with.

I was expecting to feed it the midi interface number of the incoming MIDI signal, but that doesn’t seem to be what it’s looking for.

Apologies if I’ve missed something obvious.

Many thanks,

Mark

It’s works pretty much like InaccurateTempoClock. By default it creates a virtual midi port, which you have to connect to a master clock application (manually). It won’t work when not connected. Then you just call yield from clock.sync(N), where N is the clock divisor. E.g. if N == 6, the instruction will return on 0th, 6th, 12th, 18th, tick and so on. There are 24 ticks per quarter note.

Thanks artfwo. What I wasn’t sure about is how to connect it to an actual midi signal. If I understand you correctly, when the RTMidi clock is instantiated, it creates a virtual port, which I would need to connect to the physical device via a separate MIDI routing app (like MidiYoke or whatever is equivalent)?

Would there be a way to hard code it to connect directly to a physical port (assuming the device is already plugged in and always gets the same ID)?

I’m aiming to be able to boot up the Raspberry Pi headless and just have the sequencer start running, so am trying to keep interactive setup to a minimum if I can.

Thanks as always

Ok cool - got my head around rtmidi - I had to install cython and rtmidi2 via pip - didn’t realise at first that it was missing, and I hadn’t quite grokked how it worked (or the various flavours).

Anyway, just this morning got the monome sequencer running on the Raspberry Pi clocked to an MC505, feeding into the Pi via a USB midi interface.

I made some changes to clicks.py. Instead of opening a virtual midi port I specify a physical port number - I hard coded the port number but I can see how the available_in_ports function could be used to enable port selection by name. In any case, got that plugged in and the sequencer now tracks to the tempo on the MC505.

Need to adjust things a bit as the python sequencer is running at about 1/4 of the tempo of the MC505, but that’s easily fixed I think.

Will share code soon

Right. You have to open the port using open_port() directly. Note, that this is unstable in case your midi gear is connected/disconnected in the process. That’s why I prefer a virtual port. Port connection and session management in my case is handled by external software (qjackctl, LASH, etc.)

Woah, discourse warns that the last reply to this topic was 419 days ago. Long time no see :slight_smile:

Anyway, some news. Sadly, the latest pymonome update breaks compatibility with previous versions. Basically you have to inherit monome.App instead of monome.Monome and use member property self.grid for working with the LEDs. This is because grid and app logic are now decoupled, BUT it allows new and interesting use-cases, e.g. stackable grid behaviour modifiers (think mirroring, rotating, key quantizing, gesture recorders), grid spanning and slightly more sensible code to make this work.

The latest version is available via pip. Grid studies tutorial is being updated. Suggestions and issues are welcome as usual!

4 Likes

thanks millions @artfwo!

just merged it to the monome/docs repo and pushed to the site.

2 Likes

Hi @artfwo - this is great! Using the earlier version I’d managed to put together a solution to spanning my two 64 grids by essentially creating a virtual grid that sends and receives from a separate server process that handles the offsets and the actual grids, but there are a few stability issues (my fault I’m sure), which is why I’ve not yet pushed it out to anyone else.

I’ll aim to port what I’ve been working on to the new library over the next few weeks.

Looking at the code and the examples, I’ve not yet grokked how to do the spanning. Do I inherit from monome.App and add a second grid member property? Or is spanning better handled through the Pages class - i.e. a Page that spans multiple physical grids?

I’ve only looked briefly so forgive me if I’m way off track. I’ll look more into it over the next couple of days and bounce back here if I am stuck - mostly I just want to say thanks for pushing this library forward - it is really useful, and I hope I’ll have something to share in return before long…

I’d rather not do that, Pages (PageManagers) are specifically tailored for multiple off-screen grid surfaces.

Spanning would be a bit trickier. Try copying GridWrapper and modifying it for working with 2 grids, possibly with 2 inner event_handler objects. An application should be able to work transparently with this new object, as if it was a real grid.

Then subclass SerialOsc and override on_device_added() and on_device_removed() to track your 64 grids. As soon as they both are present, create the Spanner on top of them and attach your app to the Spanner grid. Please feel free to ping me if you have any further questions. Thanks for the feedback, it really matters :slight_smile:

Thanks! That makes perfect sense. In a way the virtual grid class I created works the way you suggest, so I can probably just adjust that code to use the appropriate GridWrappwr methods and properties.

I’ll let you know how I go and if it works well enough then i’ll be more than happy to contribute an example and whatever other code seems useful.

Stay tuned.

1 Like