Pygame read MIDI input - python

I referenced the Pygame MIDI documentation and this code to try to get MIDI input to work.
The MIDI Interface (Avid Eleven Rack) receives MIDI data from my MIDI controller just fine in my audio software (Pro Tools). Using Pygame, however, I can not seem to read any information at all.
Source Code
import pygame
from pygame.locals import *
from pygame import midi
class MidiInput():
def __init__(self):
# variables
self.elevenRackInID = 2
# init methods
pygame.init()
pygame.midi.init()
self.midiInput = pygame.midi.Input(self.elevenRackInID, 100)
def run(self):
# print(pygame.midi.Input(3, 100))
# for i in range(10):
# print(pygame.midi.get_device_info(i), i)
self.read = self.midiInput.read(100)
# self.convert = pygame.midi.midis2events(self.read, self.elevenRackInID)
print(self.read)
test = MidiInput()
while True:
test.run()
The only thing printed to the console are empty square brackets:
[]
Additional Info
I just checked again: the input ID is the right one and it is in fact an input.
"self.midiInput.poll()" returns False. So according to the Pygame documentation there is no data coming in.
You can see the data, poll and device info below:
data: [] || poll: False || device info: (b'MMSystem', b'Eleven Rack', 1, 0, 1)
A list of all my MIDI devices according to Pygame (with indexes):
(b'MMSystem', b'Microsoft MIDI Mapper', 0, 1, 0) 0
(b'MMSystem', b'External', 1, 0, 0) 1
(b'MMSystem', b'Eleven Rack', 1, 0, 1) 2
(b'MMSystem', b'Maschine Mikro MK2 In', 1, 0, 0) 3
(b'MMSystem', b'Microsoft GS Wavetable Synth', 0, 1, 0) 4
(b'MMSystem', b'External', 0, 1, 0) 5
(b'MMSystem', b'Eleven Rack', 0, 1, 0) 6
(b'MMSystem', b'Maschine Mikro MK2 Out', 0, 1, 0) 7
Any help or suggestions are greatly appreciated!

I got an answer in another forum. It turns out that there is an example file which shows how to get the code to work.
So if someone else stumbles over this problem here is the useful part of example code:
import sys
import os
import pygame as pg
import pygame.midi
def print_device_info():
pygame.midi.init()
_print_device_info()
pygame.midi.quit()
def _print_device_info():
for i in range(pygame.midi.get_count()):
r = pygame.midi.get_device_info(i)
(interf, name, input, output, opened) = r
in_out = ""
if input:
in_out = "(input)"
if output:
in_out = "(output)"
print(
"%2i: interface :%s:, name :%s:, opened :%s: %s"
% (i, interf, name, opened, in_out)
)
def input_main(device_id=None):
pg.init()
pg.fastevent.init()
event_get = pg.fastevent.get
event_post = pg.fastevent.post
pygame.midi.init()
_print_device_info()
if device_id is None:
input_id = pygame.midi.get_default_input_id()
else:
input_id = device_id
print("using input_id :%s:" % input_id)
i = pygame.midi.Input(input_id)
pg.display.set_mode((1, 1))
going = True
while going:
events = event_get()
for e in events:
if e.type in [pg.QUIT]:
going = False
if e.type in [pg.KEYDOWN]:
going = False
if e.type in [pygame.midi.MIDIIN]:
print(e)
if i.poll():
midi_events = i.read(10)
# convert them into pygame events.
midi_evs = pygame.midi.midis2events(midi_events, i.device_id)
for m_e in midi_evs:
event_post(m_e)
del i
pygame.midi.quit()
You can find the file yourself in this directory:
C:\Users\myUser\AppData\Roaming\Python\Python37\site-packages\pygame\examples\midi.py
Replace 'myUser' with your Win username. Also, 'Python37' can vary on the version of Python you have installed.

I don't believe the code posted below by Leonhard W is usable: neither the pygame.midi poll() method nor the pygame.midi read() method are blocking. The result is that CPU consumption goes through the roof (~50%).
Of course in practice the code to read the MIDI events would be run in a separate thread, though this won't help with CPU consumption.
In response to another very useful comment elsewhere, I've taken a look at the Mido library (https://mido.readthedocs.io/en/latest/index.html#). This provides blocking read methods and with just a few lines of code I can look for messages from a MIDI controller keyboard and pass them onto a MIDI synth.
import mido
names = mido.get_input_names()
print(names)
out_port = mido.open_output()
with mido.open_input(names[0]) as inport:
for msg in inport:
out_port.send(msg)
The only downside is that I'm getting a significant delay (perhaps 1/4s) between hitting the key and hearing the note. Oh well, onwards and upwards.

Related

Problems using SDL_QueryTexture through PySDL2

I am using PySDL2 and I am coding a little script that load a image on a windows but I am getting this error message "ctypes.ArgumentError: argument 4: : expected LP_c_int instance instead of int" when i use this function "SDL_QueryTexture". This is my code:
"""Simple example for using sdl2 directly."""
import os
import sys
import ctypes
from sdl2 import *
def run():
SDL_Init(SDL_INIT_VIDEO)
window = SDL_CreateWindow(b"Hello World",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
459, 536, SDL_WINDOW_SHOWN)
render = SDL_CreateRenderer(window, -1, 0)
fname = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"resources", "self-control.bmp")
imageSurface = SDL_LoadBMP(fname.encode("utf-8"))
imageTexture = SDL_CreateTextureFromSurface(render, imageSurface)
SDL_FreeSurface(imageSurface)
sourceRectangle = SDL_Rect()
destinationRectangle = SDL_Rect()
SDL_QueryTexture(imageTexture, None, None, sourceRectangle.w, sourceRectangle.h)
destinationRectangle.x = sourceRectangle.x = 0
destinationRectangle.y = sourceRectangle.y = 0
destinationRectangle.w = sourceRectangle.w
destinationRectangle.h = sourceRectangle.h
SDL_RenderCopy(render, imageTexture, sourceRectangle, destinationRectangle)
running = True
event = sdl2.SDL_Event()
while running:
while SDL_PollEvent(ctypes.byref(event)) != 0:
if event.type == sdl2.SDL_QUIT:
running = False
break
SDL_Delay(10)
SDL_DestroyWindow(window)
SDL_Quit()
return 0
if __name__ == "__main__":
sys.exit(run())
I know is something related to ctypes, i hope someone can help me.
SDL_QueryTexture gets pointers to ints to write result to, you cannot simply pass int here. Workaround would be something like
w = ctypes.c_int()
h = ctypes.c_int()
SDL_QueryTexture(imageTexture, None, None, w, h)
And then getting result from w.value and h.value.
However you already have a surface, why not just read width and height from it?
imageSurface.contents.w, imageSurface.contents.h

xlib ungrab_pointer - still can't "use" the pointer in other windows

I might have missunderstood my problem but I thought display.ungrab_pointer would release the semi exclusive right on the cursor.
After I start the following program, I won't be able to select text in any window. I can still click to set focus on the window but for instance, I can't mark text in FireFox or Sublime Text Editor.
the desired result would be that it doesn't "steal" the mouse at all, so I can use it while I record movements. From what I've learned, X11 simply copies and forwards the events to everyone who's registered as a listener, so I don't get why it would lock up the cursor the way it does.
import select
from time import time
from threading import *
from Xlib.display import Display
from Xlib import X
from Xlib.ext.xtest import fake_input
from Xlib.ext import record
from Xlib.protocol import rq
def handle_event(event):
print(event)
return True
display = Display(':0')
ctx = display.record_create_context(
0,
[record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': (X.ButtonPressMask, X.ButtonReleaseMask),
'errors': (0, 0),
'client_started': False,
'client_died': False,
}]
)
class worker(Thread):
def __init__(self, display, ctx):
self.display = display
self.ctx = ctx
Thread.__init__(self)
self.start()
def run(self):
display.screen().root.grab_pointer(True, X.ButtonPressMask | X.ButtonReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime)
display.record_enable_context(self.ctx, handle_event)
print 'Recieved exit.. ungrabing pointer'
handle = worker(display, ctx)
start = time()
while time()-start < 5:
# Wait for display to send something, or a timeout of one second
readable, w, e = select.select([display], [], [], 1)
# if no files are ready to be read, it's an timeout
if not readable:
print('Got no events')
break
# if display is readable, handle as many events as have been recieved
elif display in readable:
i = display.pending_events()
while i > 0:
event = display.next_event()
handle_event(event)
i = i - 1
handle._Thread__stop()
display.record_free_context(ctx)
display.record_disable_context(ctx)
display.ungrab_pointer(X.CurrentTime)
display.flush()
Aside from this being a shitty way to terminate threads, what have I misunderstood?
I would think releasing the context and the ungrab_pointer would do the trick.
The program terminates as expected after 5 seconds, and does print out my events as it should. It's just that it doesn't "give back" the mouse cursor.

How to send a rumble effect to a device using python evdev

I like to send a rumble effect to a device using python evdev.
This should be achieved with the upload_effect() function, which requires a buffer object as input.
This is what capabilities() reveals:
('EV_FF', 21L): [
(['FF_EFFECT_MIN', 'FF_RUMBLE'], 80L),
('FF_PERIODIC', 81L),
(['FF_SQUARE', 'FF_WAVEFORM_MIN'], 88L),
('FF_TRIANGLE', 89L),
('FF_SINE', 90L),
('FF_GAIN', 96L),
],
How do I create that buffer?
Python-evdev 1.1.0 supports force-feedback effect uploads. Here's an example from the documentation:
from evdev import ecodes, InputDevice, ff
# Find first EV_FF capable event device (that we have permissions
# to use).
for name in evdev.list_devices():
dev = InputDevice(name)
if ecodes.EV_FF in dev.capabilities():
break
rumble = ff.Rumble(strong_magnitude=0x0000, weak_magnitude=0xffff)
effect_type = ff.EffectType(ff_rumble_effect=rumble)
duration_ms = 1000
effect = ff.Effect(
ecodes.FF_RUMBLE, -1, 0,
ff.Trigger(0, 0),
ff.Replay(duration_ms, 0),
effect_type
)
repeat_count = 1
effect_id = dev.upload_effect(effect)
dev.write(ecodes.EV_FF, effect_id, repeat_count)
dev.erase_effect(effect_id)

No video mode large enough error in pygame when using 1,8" TFT via a RPi

First off i just want to mention that i'm not actually sure that this questions is asked on the correct StackExchange, since it is a lot of different topics involved i might need to ask it somewhere else. I do believe it boils down to a problem somewhere between Python (pygame) and SDL.
What i've done:
I've hooked up a Sainsmart 1,8" TFT to my Raspberry Pi running on a
MINIBIAN image (a small footprint version of Raspbian), i've got the
TFT to work since i can send console output to it and display images
using fbi (writes directly to the frame buffer).
Installed and loaded the fbtft drivers (Linux Framebuffer drivers for small TFT LCD display modules)
From dmesg:
[ 12.377397] graphics fb1: fb_st7735r frame buffer, 128x160, 40 KiB video memory, 4 KiB DMA buffer memory, fps=20, spi0.0 at 32 MHz
The problem:
What i now want to test is to display a clock on my TFT using pygame, the code i've got is (borrowed from http://gerfficient.com/2014/02/12/connecting-1-8-tft-lcd-to-raspberry-pi/):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import time
import pygame
time_stamp_prev=0
os.environ["SDL_FBDEV"] = "/dev/fb1"
os.environ['SDL_VIDEODRIVER']="fbcon"
def displaytext(text,size,line,color,clearscreen):
if clearscreen:
screen.fill((0,0,0))
font = pygame.font.Font(None,size)
text = font.render(text,0,color)
rotated = pygame.transform.rotate(text,90)
textpos = rotated.get_rect()
textpos.centery = 80
if line == 1:
textpos.centerx = 99
screen.blit(rotated,textpos)
elif line == 2:
textpos.centerx = 61
screen.blit(rotated,textpos)
elif line == 3:
textpos.centerx = 25
screen.blit(rotated,textpos)
def main():
global screen
pygame.init()
pygame.mouse.set_visible(0)
size = width,height = 128,160
screen = pygame.display.set_mode(size)
while True:
displaytext(time.strftime("%d.%m.%Y",time.gmtime()),40,1,(255,255,255),True)
displaytext(time.strftime("%H:%M:%S",time.gmtime()),40,2,(255,255,255),False)
displaytext("gerfficient.com",20,3,(100,100,255),False)
pygame.display.flip()
time.sleep(1)
if __name__ == '__main__':
main()
The error i get is this:
Traceback (most recent call last):
File "test.py", line 19, in <module>
pygame.display.set_mode()
pygame.error: No video mode large enough for 128x160
When i'm displaying more info via pygame and run print pygame.display.Info() i get:
<VideoInfo(hw = 1, wm = 0,video_mem = 40
blit_hw = 0, blit_hw_CC = 0, blit_hw_A = 0,
blit_sw = 0, blit_sw_CC = 0, blit_sw_A = 0,
bitsize = 16, bytesize = 2,
masks = (63488, 2016, 31, 0),
shifts = (11, 5, 0, 0),
losses = (3, 2, 3, 8),
current_w = 128, current_h = 160
>
Output of fbset -i -fb /dev/fb1:
mode "128x160"
geometry 128 160 128 160 16
timings 0 0 0 0 0 0 0
nonstd 1
rgba 5/11,6/5,5/0,0/0
endmode
Frame buffer device information:
Name : fb_st7735r
Address : 0
Size : 40960
Type : PACKED PIXELS
Visual : TRUECOLOR
XPanStep : 0
YPanStep : 0
YWrapStep : 0
LineLength : 256
Accelerator : No
When checking the two SDL environment variables they're set correctly.
It seems that the when i use /dev/fb1 (the TFT) as the frame buffer device pygame/SDL picks that information up but still can't use it via pygame.
Just to mention, i installed the package python-pygame via the Raspbian repositories.
The versions of pygame and SDL i'm using:
ii python-pygame 1.9.1release+dfsg-8
ii libsdl-image1.2:armhf 1.2.12-2
ii libsdl-mixer1.2:armhf 1.2.12-3
ii libsdl-ttf2.0-0:armhf 2.0.11-2
ii libsdl1.2-dev 1.2.15-5
ii libsdl1.2debian:armhf 1.2.15-5
Change
size = width,height = 128,160
to
size = width,height = 160,128
The problem solved itself, what i did was to restart the RPi, since then the TFT screen have worked flawlessly with the pygame module.
I think the problem could be found somewhere between Python and the kernel module fbtft that i've been using for this project, or maybe in the way i installed and initialized the kernel module. This is only speculations from my part.
I didn't mention the fact that i was using a third party driver package, stupid of me, there's a small hint that i use these drivers in the dmesg output but nothing more.
I will update the question with this information.
It is most likely that you were missing the mode item in /etc/fb.modes .
I had this issue with a 128x128 display.
You can get the mode via
fbset -i
And then simply put it's output into /etc/fb.modes .
Hope that helps...

Concurrent functions running in separate process using pygame and multiprocessing

Suppose we want to drive an autonomous car by predicting image labels from a previous set of images and labels collected (A Machine Learning application). For this task, the car is connected via bluetooth serial (rfcomm) to the Host Computer (A PC with *NIX) and the images are streamed directly from an Android phone using IP Webcam, meanwhile, the PC is running a program that links this two functions, displaying the captured images in a drawing environment created by pygame, and sending the instructions back to the car using serial.
At the moment, I've tried to implement those processes using the multiprocessing module, the seemed to work, but when I execute the client, the drawing function (if __name__ == '__main__') works after the getKeyPress() function ends.
The question is: It is possible to parallelize or synchronize the drawing fuinction enclosed within the if __name__ == '__main__' with the process declared in getKyPress(), such that the program works in two independent processes?
Here's the implemented code so far:
import urllib
import time
import os
import sys
import serial
import signal
import multiprocessing
import numpy as np
import scipy
import scipy.io as sio
import matplotlib.image as mpimg
from pygame.locals import *
PORT = '/dev/rfcomm0'
SPEED = 115200
ser = serial.Serial(PORT)
status = False
move = None
targets = []
inputs = []
tic = False
def getKeyPress():
import pygame
pygame.init()
global targets
global status
while not status:
pygame.event.pump()
keys = pygame.key.get_pressed()
targets, status = processOutputs(targets, keys)
targets = np.array(targets)
targets = flattenMatrix(targets)
sio.savemat('targets.mat', {'targets':targets})
def rgb2gray(rgb):
r, g, b = np.rollaxis(rgb[...,:3], axis = -1)
return 0.299 * r + 0.587 * g + 0.114 * b
def processImages(inputX, inputs):
inputX = flattenMatrix(inputX)
if len(inputs) == 0:
inputs = inputX
elif inputs.shape[1] >= 1:
inputs = np.hstack((inputs, inputX))
return inputs
def flattenMatrix(mat):
mat = mat.flatten(1)
mat = mat.reshape((len(mat), 1))
return mat
def send_command(val):
connection = serial.Serial( PORT,
SPEED,
timeout=0,
stopbits=serial.STOPBITS_TWO
)
connection.write(val)
connection.close()
def processOutputs(targets, keys):
global move
global status
global tic
status = False
keypress = ['K_p', 'K_UP', 'K_LEFT', 'K_DOWN', 'K_RIGHT']
labels = [1, 2, 3, 4, 5]
commands = ['p', 'w', 'r', 'j', 's']
text = ['S', 'Up', 'Left', 'Down', 'Right']
if keys[K_q]:
status = True
return targets, status
else:
for i, j, k, g in zip(keypress, labels, commands, text):
cmd = compile('cond = keys['+i+']', '<string>', 'exec')
exec cmd
if cond:
move = g
targets.append(j)
send_command(k)
break
send_command('p')
return targets, status
targetProcess = multiprocessing.Process(target=getKeyPress)
targetProcess.daemon = True
targetProcess.start()
if __name__ == '__main__':
import pygame
pygame.init()
w = 288
h = 352
size=(w,h)
screen = pygame.display.set_mode(size)
c = pygame.time.Clock() # create a clock object for timing
pygame.display.set_caption('Driver')
ubuntu = pygame.font.match_font('Ubuntu')
font = pygame.font.Font(ubuntu, 13)
inputs = []
try:
while not status:
urllib.urlretrieve("http://192.168.0.10:8080/shot.jpg", "input.jpg")
try:
inputX = mpimg.imread('input.jpg')
except IOError:
status = True
inputX = rgb2gray(inputX)/255
out = inputX.copy()
out = scipy.misc.imresize(out, (352, 288), interp='bicubic', mode=None)
scipy.misc.imsave('input.png', out)
inputs = processImages(inputX, inputs)
print inputs.shape[1]
img=pygame.image.load('input.png')
screen.blit(img,(0,0))
pygame.display.flip()
c.tick(1)
if move != None:
text = font.render(move, False, (255, 128, 255), (0, 0, 0))
textRect = text.get_rect()
textRect.centerx = 20 #screen.get_rect().centerx
textRect.centery = 20 #screen.get_rect().centery
screen.blit(text, textRect)
pygame.display.update()
if status:
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
except KeyboardInterrupt:
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
targetProcess.join()
sio.savemat('inputs.mat', {'inputs':inputs})
Thanks in advance.
I would personally suggest writing this without using the multiprocessing module: it uses fork() which has unspecified effects with most complex libraries, like in this case pygame.
You should try to write this as two completely separate programs. It forces you to think about what data needs to go from one to the other, which is both a bad and a good thing (as it may clarify things). You can use some inter-process communication facility, like the stdin/stdout pipe; e.g. in one program (the "main" one) you start the other as a sub-process like this:
popen = subprocess.Popen([sys.executable, '-u', 'my_subproc.py'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(The -u is for unbuffered.)
Then read/write the data to popen.stdin/popen.stdout in the parent process, and to sys.stdin/sys.stdout in the subprocess. The simplest example would be if the two processes only need a synchronization signal, e.g. the parent process waits in a loop for the subprocess to say "next please". To do this the subprocess does print 'next please', and the parent process does popen.stdin.readline(). (The print goes to sys.stdin in the subprocess.)
Unrelated small note:
keypress = ['K_p', ...]
...
cmd = compile('cond = keys['+i+']', '<string>', 'exec')
exec cmd
if cond:
This looks like very heavy code to just do:
keypress = [K_p, ...] # not strings, directly the values
...
if keys[i]:
My suggestion is to use separate threads.
#At the beginning
import threading
#Instead of def getKeyPress()
class getKeyPress(threading.Thread):
def run(self):
import pygame
pygame.init()
global targets
global status
while not status:
pygame.event.pump()
keys = pygame.key.get_pressed()
targets, status = processOutputs(targets, keys)
targets = np.array(targets)
targets = flattenMatrix(targets)
sio.savemat('targets.mat', {'targets':targets})
#Instead of
#targetProcess = multiprocessing.Process(target=getKeyPress)
#targetProcess.daemon = True
#targetProcess.start()
gkp = getKeyPress()
gkp.start()
An alternative would be creating two different scripts and using sockets to handle the inter-process communication.

Categories

Resources