MOM - mother with oled screen and multipage support v1.2

mom_banner

MOM is yet another mother patch, but that I made for my developing needs.
If you don’t know what this is, think of it like a Organelle emulator, that let you run and develop organelle patches on your computer.

FEATURES:

  • Pretty accurate representation of the organelle screen. There is support for /oled/gFlip /oled/gClear /oled/gSetPixel /oled/gLine /oled/gBox /oled/gFillArea /oled/gCircle /oled/gFilledCircle /oled/gPrintln /oled/gInvertArea /oled/gInvertLine /oled/gShowInfoBar, screenLines and a bunch of other calls.
  • Support of the note buttons, aux, encoder, vol, foot pedal, knobs, led light, etc. All mapped to keyboard keys.
  • It’s tiny and cute :slight_smile:

How to launch the SCREEN

  • The emulated screen is made with pygame. To launch it you will need to do one of these:
    A - If you are on Windows just run oled.exe and that’s it.
    B - If you are not on Windows, or you don’t want to run a executable, you will need to have installed Python 3, and the pygame and python-osc modules. Once you have installed all this, just run oled.py.

  • the OLED toglebox on MOM.pd is for updating/restarting the screen. Be sure to have it checked.

UPDATE 1.1:

  • Change the name to something less e d g y
  • Added a .exe for those people in windows that don’t want to install python.
  • Now there is support for multipage patches!
    I test it with Zone and Jeraphy and they run fine.
    Keep in mind that to be able to run these patches on desktop, you should disable their versioncheck object! To do this, just bypass it, and connect [loadbang] directly to the [t b b] object like this:
    bypass

UPDATE 1.2:

– OLED Window’s size can be set from the script.
– Added a panic button.
– Fix bugs in the encoder. Now it send the correct calls.
– Added a reboot button for the Oled.
– Added expression pedal.

Some test of the screen:

gif_drawave
gif_pianelle

gif_poke
gif_pong
gif_roll

DOWNLOAD from patchstorage

16 Likes

This looks incredible. Thanks a lot for sharing this!

Thanks for creating this. Run on OSX and all fine except OLED. All dependencies running fine, but never got the Oled working. Comms fine. A trigger object had the wrong type but fixed. Would have liked to try the latest version but it doesn;t seem available anymore. Many thanks, P

Hello! thanks for your interest.
Also, I take this chance to say sorry I have been out of touch with the community. I’m still trying to stabilize with the pandemic, and were not able to recover my organelle. So sorry for all the unanswered questions everywhere.

About Mom: some patches were a little finicky to turn on the screen. Try different sequences of: starting Mom, then the patch, then the olen window, then the ON oled on mom, then restart the oled from the Mom patch. If not work, try starting the oled first, then the patch you want, or reboot the patch, after you have the oled listening to messages.

As you notice, apparently patchstorage links are broken…
I uploaded Mom and all my releases and unreleased patches on github. So this are the new links:

---- MOM ----

---- OTHER PATCHES AND INSTRUMENTS ----

2 Likes

Hi, no problem at all and thanks for your reply. I hope you are felling much better. Thanks for the links. I will try the different combinations of starting the components and see what happens. I have everything installed now so hopefully will manage to get it working. It is a great idea, thanks, P

1 Like

Hi, did you manage to make it work under OSX ? I aslo get an error even though pygame and python-osc are installed. It kind off worked once but now the frame juste won’t load. It works fine in debian though.

That’s what I get if it can help anyone understand what’s wrong

Blockquote
pygame 2.0.1 (SDL 2.0.14, Python 3.9.1)
Hello from the pygame community. Contribute - pygame wiki
assets/organelle.ttf
2021-01-29 00:00:47.668 Python[1558:131288] WARNING: NSWindow drag regions should only be invalidated on the Main Thread! This will throw an exception in the future. Called from (
0 AppKit 0x00007fff32448607 -[NSWindow(NSWindow_Theme) _postWindowNeedsToResetDragMarginsUnlessPostingDisabled] + 378
1 AppKit 0x00007fff3246a27e -[NSThemeFrame _tileTitlebarAndRedisplay:] + 98
2 AppKit 0x00007fff32494ace -[NSTitledFrame _titleDidChange] + 217
3 AppKit 0x00007fff3249457c -[NSTitledFrame setTitle:] + 713
4 AppKit 0x00007fff32494204 -[NSThemeFrame setTitle:] + 50
5 AppKit 0x00007fff32457f6a -[NSWindow _dosetTitle:andDefeatWrap:] + 210
6 libSDL2-2.0.dylib 0x000000010ca1d35e Cocoa_SetWindowTitle + 110
7 display.cpython-39-darwin.so 0x000000010cbfeecf pg_set_caption + 159
8 Python 0x000000010c303f7d cfunction_call + 125
9 Python 0x000000010c2c526d _PyObject_MakeTpCall + 365
10 Python 0x000000010c39b99c call_function + 876
11 Python 0x000000010c398e33 _PyEval_EvalFrameDefault + 25219
12 Python 0x000000010c2c5a38 function_code_fastcall + 104
13 Python 0x000000010c399543 _PyEval_EvalFrameDefault + 27027
14 Python 0x000000010c2c5a38 function_code_fastcall + 104
15 Python 0x000000010c39b90c call_function + 732
16 Python 0x000000010c398e12 _PyEval_EvalFrameDefault + 25186
17 Python 0x000000010c2c5a38 function_code_fastcall + 104
18 Python 0x000000010c39b90c call_function + 732
19 Python 0x000000010c398e12 _PyEval_EvalFrameDefault + 25186
20 Python 0x000000010c2c5a38 function_code_fastcall + 104
21 Python 0x000000010c2c7ac2 method_vectorcall + 274
22 Python 0x000000010c4402b6 t_bootstrap + 70
23 Python 0x000000010c3f1a69 pythread_wrapper + 25
24 libsystem_pthread.dylib 0x00007fff60f2b2eb _pthread_body + 126
25 libsystem_pthread.dylib 0x00007fff60f2e249 _pthread_start + 66
26 libsystem_pthread.dylib 0x00007fff60f2a40d thread_start + 13
)
2021-01-29 00:00:47.668 Python[1558:131288] WARNING: nextEventMatchingMask should only be called from the Main Thread! This will throw an exception in the future.
2021-01-29 00:00:47.669 Python[1558:131285] !!! BUG: The current event queue and the main event queue are not the same. Events will not be handled correctly. This is probably because _TSGetMainThread was called for the first time off the main thread.
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
2021-01-29 00:00:47.673 Python[1558:131285] pid(1558)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!
Serving on (‘127.0.0.1’, 3000)
^CTraceback (most recent call last):
File “/Users/clemens/Downloads/Organelle/organelle-emulator-master/oled.py”, line 204, in
server.serve_forever()
File “/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/socketserver.py”, line 232, in serve_forever
ready = selector.select(poll_interval)
File “/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/selectors.py”, line 416, in select
fd_event_list = self._selector.poll(timeout)

I haven’t got it working at all, in fact, I gave up as it was wasting my time. Am using “Stepmum” instead and works straight away without all the config nonesense…

I got this working on OSX; see this pull request on the repository:

the issue is that only the main thread can display on osx. My workaround was to put the drawing in the main thread, and in the thread that the socket is receiving on queue an event that contains the drawing instruction. (and then in the main/drawing thread dispatch the instruction to the right function in the main event loop)
thanks for all the work @varicela

edit: also check out my “downstream” branch for added gWaveform and gInvert support

1 Like

It should world on OSX now :slight_smile:
thanks to @sebshader for fixing it!

1 Like

Does anybody else having problems getting the display to work? or even MOM? I tried it with the Chill Time Patch; in stepmother everything works fine.

I have Python 3.10.0, and pygame and python-osc is installed. I tried different combinations of opening the different components, but no display at all.

While opening the MOM patch PD states: trigger: i: bad type

Maybe I should mention that I have actually not a big plan of Python of PD, I just try to fiddle around with some patches directly on my computer. So maybe thats a dumb question, but maybe someone can help me gettin this to work. :slight_smile:

all the best!

PS: when I open the oled.py a window opens with the following:

import threading
import pygame
import pygame.gfxdraw
import pygame.freetype
from pythonosc import dispatcher
from pythonosc import osc_server
import os.path

SCALERATIO = 2 #size of the pixels. must be an integer

pygame.freetype.init()
fontPath = "assets/organelle.ttf"
print(fontPath)
oFont8 = pygame.freetype.Font(fontPath, 8)
oFont16 = pygame.freetype.Font(fontPath, 16)
oFont24 = pygame.freetype.Font(fontPath, 24)
oFont32 = pygame.freetype.Font(fontPath, 32)
windowSize = (128*SCALERATIO,64*SCALERATIO)

def createOled():
    pygame.display.init() 

    icon = pygame.image.load("assets/icon.png")
    pygame.display.set_icon(icon)
    pygame.display.set_caption('Organelle OLED')

    while True:
        event = pygame.event.wait()
        if event.type == pygame.QUIT:
            server.socket.close() #close server and let close the console
            break


t = threading.Thread(target=createOled)
t.daemon = True
t.start()

s = pygame.Surface((128,64), pygame.SRCALPHA, 32)
sWindow = pygame.Surface(windowSize, pygame.SRCALPHA, 32)
screen = pygame.display.set_mode(windowSize)
screen.fill((32,32,32))

def gClear(unused_addr, unused_1):
    global s
    pygame.draw.rect(s, (32,32,32), (0, 0, 128, 64), 0)

def gSetPixel(unused_addr, x, y, c):
    global s
    pygame.draw.line(s, (255,255,255) if c == 1 else (32,32,32), (x, y), (x, y))

def gLine(unused_addr, x1, y1, x2, y2, c):
    global s
    pygame.draw.line(s, (255,255,255) if c == 1 else (32,32,32), (x1, y1), (x2, y2))

def gBox(unused_addr, x1, y1, x2, y2, c):
    global s
    pygame.draw.rect(s, (255,255,255) if c == 1 else (32,32,32), (x1, y1, x2, y2), 1)

def gFillArea(unused_addr, x1, y1, x2, y2, c):
    global s
    pygame.draw.rect(s, (255,255,255) if c == 1 else (32,32,32), (x1, y1, x2, y2), 0)

def gCircle(unused_addr, x, y, size, c):
    global s
    pygame.draw.circle(s, (255,255,255) if c == 1 else (32,32,32), (int(x), int(y)), int(size), 1)

def gFilledCircle(unused_addr, x, y, size, c):
    global s
    pygame.draw.circle(s, (255,255,255) if c == 1 else (32,32,32), (int(x), int(y)), int(size), 0)

def gFlip(unused_addr):
    global s
    global screen

    pygame.transform.scale(s, windowSize, sWindow)

    screen.blit(sWindow, (0, 0))
    pygame.display.flip()

def gInvertArea(unused_addr, x, y, w, h): #/oled/gInvertArea 3 3 $1 121 9
    global s
    global screen

    gFlip("unused_addr")
    inv = pygame.Surface((w, h), pygame.SRCALPHA)
    inv.fill((255,255,255,255))
    inv.blit(s, (- x, -y), None, pygame.BLEND_RGB_SUB)
    s.blit(inv, (x, y), None)
    gFlip("unused_addr")

def gInvertLine(unused_addr, n): #/oled/gInvertLine 0
    if n == 0:
        gInvertArea("unused_addr", 0, 8, 128, 9)
    elif n == 1:
        gInvertArea("unused_addr", 0, 20, 128, 9)
    elif n == 2:
        gInvertArea("unused_addr", 0, 32, 128, 9)
    elif n == 3:
        gInvertArea("unused_addr", 0, 44, 128, 9)
    elif n == 4:
        gInvertArea("unused_addr", 0, 56, 128, 9)

def gPrintln(unused_addr, x, y, size, c, *txtToPrint):
    global s
    global oFont

    txt = ""

    for word in txtToPrint:
        if type(word) == float:
            word = str(int(word))
        txt = txt + word + " " 

    if size == 8:
        oFont8.render_to(s, (x, y), txt, (255,255,255) if c == 1 else (32,32,32))
    elif size == 16:
        oFont16.render_to(s, (x, y), txt, (255,255,255) if c == 1 else (32,32,32))
    elif size == 24:
        oFont24.render_to(s, (x, y), txt, (255,255,255) if c == 1 else (32,32,32))
    elif size == 32:
        oFont32.render_to(s, (x, y), txt, (255,255,255) if c == 1 else (32,32,32))

def gCleanln(unused_addr, n):
    if n == 1:
        gFillArea("unused_addr", 0, 8, 128, 9, 0)
    elif n == 2:
        gFillArea("unused_addr", 0, 20, 128, 9, 0)
    elif n == 3:
        gFillArea("unused_addr", 0, 32, 128, 9, 0)
    elif n == 4:
        gFillArea("unused_addr", 0, 44, 128, 9, 0)
    elif n == 5:
        gFillArea("unused_addr", 0, 56, 128, 9, 0)

def gDrawInfoBar(unused_addr, inL, inR, outL, outR):
    global s
    global oFont

    inL = int(inL)
    inR = int(inR)
    outL = int(outL)
    outR = int(outR)

    if inR < 0:
        inR = 0
    if inR > 11:
        inR = 11
    if inL < 0:
        inL = 0;
    if inL > 11:
        inL = 11;

    if outR < 0:
        outR = 0
    if outR > 11:
        outR = 11
    if outL < 0:
        outL = 0
    if outL > 11:
        outL = 11

    gFillArea('unused_addr', 0, 0, 128, 8, 0)

    gPrintln("unused_addr", 0, 0, 8, 1, "I")
    gPrintln("unused_addr", 64, 0, 8, 1, "O")

    for i in range(11):
        gFillArea('unused_addr', (i * 5) + 8, 1, 1, 2, 1)
        gFillArea('unused_addr', (i * 5) + 8, 5, 1, 2, 1)
    for i in range(inR):
        gFillArea('unused_addr', (i * 5) + 7, 0, 3, 4, 1);
    for i in range(inL):
        gFillArea('unused_addr', (i * 5) + 7, 4, 3, 4, 1);

    for i in range(11):
        gFillArea('unused_addr', (i * 5) + 74, 1, 1, 2, 1)
        gFillArea('unused_addr', (i * 5) + 74, 5, 1, 2, 1)
    for i in range(outR):
        gFillArea('unused_addr', (i * 5) + 73, 0, 3, 4, 1);
    for i in range(outL):
        gFillArea('unused_addr', (i * 5) + 73, 4, 3, 4, 1);

    gFlip("unused_addr")


dispatcher = dispatcher.Dispatcher()
dispatcher.map("/gClear", gClear)
dispatcher.map("/gFlip", gFlip)
dispatcher.map("/gSetPixel", gSetPixel)
dispatcher.map("/gLine", gLine)
dispatcher.map("/gBox", gBox)
dispatcher.map("/gFillArea", gFillArea)
dispatcher.map("/gCircle", gCircle)
dispatcher.map("/gFilledCircle", gFilledCircle)
dispatcher.map("/gPrintln", gPrintln)
dispatcher.map("/gCleanln", gCleanln)
dispatcher.map("/gInvertArea", gInvertArea)
dispatcher.map("/gInvertLine", gInvertLine)
dispatcher.map("/gDrawInfoBar", gDrawInfoBar)


server = osc_server.ThreadingOSCUDPServer(("localhost", 3000), dispatcher)
print("Serving on {}".format(server.server_address))
server.serve_forever()

you have to run oled.py with python. like python3 oled.py from a shell (command line/terminal prompt)

and if you want to fix the trigger errors change all "i"s in trigger objects to “f”

also it looks like you have the old version of the code before the threading change and therefore it wouldn’t work on osx anyways

Hey, I know this is old, but how do you actually run any user patches with this? I have MOM and the oled running successfully.

Edit: Ah, it’s easy. Just open any patch’s main.pd from within the MOM.pd window…