Ejemplo sencillo con python3, gtk+3 y jack-dbus

#1 el 10/06/2012
Publicidad
Y sigo con esto de aprender en profundidad esto de jack dbus.
Si bien mis logros en la materia son más bien escasos y no son comparables con los grandes maestros de la programación de este foro y otros dedicados al tema. Creo que es bueno hacer público este proceso para aquellos que se inicien en programación y necesiten un ejemplo sencillo

Para ejecutar este script se necesitan los siguientes paquetes (en Archlinux, no he probado otras distribuciones):
- python-gobject (en ubuntu creo que es python3-gi
- python-dbus (en ubuntu creo que es dbus-python)
- python-dbus-common
- jack2 o jack2-dbus

jack debe estar funcionando (con dbus habilitado) para que el script funcione.

Alguien escribió:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Listado de puertos de jack en GTK+ 3
"""

###########################################
### Primero conectamos a Dbus ###
###########################################
import dbus
bus = dbus.SessionBus()

jack_control = bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")

port_list = jack_control.GetAllPorts()

############################################
### Ahora la Gui ###
############################################

from gi.repository import Gtk

class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Listado de Puertos Jack")
self.set_default_size(200, 200)
model = self.__create_model(port_list)

sw = Gtk.ScrolledWindow()
treeView = Gtk.TreeView(model)

cellRenderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Puertos", cellRenderer, text=0)
treeView.append_column(column)

sw.add(treeView)
self.add(sw)

def __create_model(self, item_list):
model = Gtk.ListStore(str)
for item in item_list:
model.append([item])
return model


win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

ojo para verlo con indentación lo dejo en pastebin:
http://pastebin.com/KH4pcrSA
En caso contrario solo se producirá un error.

El resultado será una lista de los puertos en jack hecha con GTK+3.
Sé que es poco lo que hace este script, pero para mí ha sido un aprendizaje bien grande lograr esto. Si he seguido tutoriales de python 2, wxwidget, y algo de GTK+2, incluso había hecho pequeños scripts útiles para mí, pero con la aparición de Python3 y GTK+3 ha habido un cambio muy profundo para mí que me ha significado un re-aprendizaje de este tema, no soy muy hábil para esto de la programación y tampoco es mi área, pero espero sacarle provecho a este aprendizaje y a la vez que otras personas se les facilite el proceso.

Ahora me enfocaré en desarrollar alguna aplicación un tanto más útil.

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#2 el 10/06/2012
Funciona!

No está mal, no?
Subir
#3 el 11/06/2012
me falta algo que no me entra en la cabeza: Las Señales. No sé cómo se manejan las señales en dbus.

Hasta el momento sé usar los métodos, que son fáciles (son funciones comunes y corrientes)

Pero las señales son dinámicas, van cambiando. Por ejemplo, la lista de puertos que aparece con este script es estática, si se abre un nuevo programa el script no lo detectaría. Pero hay una serie de señales que permitirían detectar un cambio en el gráfico de conexiones, algunas son: GraphChanged, PortAppeared, PortDisappeared, PortConnected, PortDisconnected y PortRenamed. Aquí hay alguna información entregada por QDbus

Alguien escribió:
signal void org.jackaudio.JackPatchbay.GraphChanged(qulonglong new_graph_version)
signal void org.jackaudio.JackPatchbay.PortAppeared(qulonglong new_graph_version, qulonglong client_id, QString client_name, qulonglong port_id, QString port_name, uint port_flags, uint port_type)
signal void org.jackaudio.JackPatchbay.PortDisappeared(qulonglong new_graph_version, qulonglong client_id, QString client_name, qulonglong port_id, QString port_name)
signal void org.jackaudio.JackPatchbay.PortRenamed(qulonglong new_graph_version, qulonglong port_id, qulonglong client_id, QString client_name, QString port_old_name, QString port_new_name)
signal void org.jackaudio.JackPatchbay.PortsConnected(qulonglong new_graph_version, qulonglong client1_id, QString client1_name, qulonglong port1_id, QString port1_name, qulonglong client2_id, QString client2_name, qulonglong port2_id, QString port2_name, qulonglong connection_id)
signal void org.jackaudio.JackPatchbay.PortsDisconnected(qulonglong new_graph_version, qulonglong client1_id, QString client1_name, qulonglong port1_id, QString port1_name, qulonglong client2_id, QString client2_name, qulonglong port2_id, QString port2_name, qulonglong connection_id)


mi problema es que estas señales deben integrarse en el loop principal (main loop) de la aplicación. Pero una aplicación GTK tiene su propio Loop principal.

Bueno, esta semana salgo de vacaciones, a ver si aprovecho de aprender a usar dbus bien. Por mientras, para los interesados, encontré este tutorial, es muy completo, aunque un poco técnico:
http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#4 el 12/06/2012
El dbus puede engancharse al loop de glib. Lo explica en el tuto que has enlazado, y aquí tienes un ejemplo:

http://excid3.com/blog/an-actually-decent-python-dbus-tutorial/

El loop de gtk es simplemente el de glib con algún añadido para integrar los eventos propios de gtk.

Ánimo, y pregunta cualquier duda que tengas.
Subir
#5 el 12/06/2012
Enhorabuena Veguita.

Salut
Subir
#6 el 14/06/2012
Gracias por las respuestas, en especial por el tutorial de lgarrido...

El cuento es el siguiente. Quiero hacer un Patchbay que no sea tan sofisticado como Catia, Patchage o Qjackctl, pero en cambio que permita acceder a presets con atajos de teclados. El tema es para los que tocamos teclado en vivo, si tienes el teclado conectado a Qsynth y necesitas cambiarlo rápidamente a ZynAddSubFX se pueda hacer apretando una tecla en el teclado. Espero tener algún avance de aquí al 23 de Junio ya que ese día tengo tocar en una peña folclórica.

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#7 el 15/06/2012
ánimo! :D

"Si la facilidad de uso fuera el único requisito, todos estaríamos moviéndonos en triciclos".

-Douglas Engelbart

Subir
#8 el 15/06/2012
veguita escribió:
apretando una tecla en el teclado.


¿En el teclado midi o en el teclado del portátil?

Tiene su miga el proyectillo, sobre todo la parte de gestionar los presets. Ánimo con ello, por si te sirve de algo te comento alguna alternativa.

Si es el teclado del portátil yo intentaría añadirle los atajos de teclado a qjackctl o a algún otro patchbay que ya exista, así te ahorras el 90% del trabajo que vas a tener que hacer (averiguar los puertos existentes, permitir al usuario conectarlos y desconectarlos, grabar y cargar ficheros con configuraciones...)

En qjackctl tendrías que añadir objetos tipo QAction, asignarles una tecla y enganchar su señal a un slot que haga una llamada a la parte del código de qjackctl que carga y activa los presets, probablemente también un QSignalMapper para asignarle un índice a cada acción y así simplificar el código. Pero tendrías que investigar dónde está cada cosa en el código de qjackctl. A lo mejor le puedes pedir a Rui que te eche una mano y te diga dónde tienes que tocar.

Otra opción más cutrecilla pero para salir del paso sería un grabador/reproductor de macros de eventos, tipo gnee, usando sólo eventos de teclado:

http://itupw056.itu.chalmers.se/project-xnee/?q=node&q=gnee-screenshot
Subir
#9 el 15/06/2012
He tenido un pequeño progreso con esto de las señales en dbus y python.
Aquí dejo un pequeño script que ejecuta una función cada vez que cambia el gráfico de correcciones:
http://pastebin.com/5ux5BXCx
Lamentablemente el resultado que obtengo es un error:
Alguien escribió:
ERROR:dbus.connection:Exception in handler for D-Bus signal:
Traceback (most recent call last):
File "/usr/lib/python3.2/site-packages/dbus/connection.py", line 230, in maybe_handle_message
self._handler(*args, **kwargs)
TypeError: 'NoneType' object is not callable


Si alguien puede señalar el error que estoy cometiendo y su solución se lo agradecería mucho :)

Respondiendo a las sugerencias de lgarrido

lgarrido escribió:
¿En el teclado midi o en el teclado del portátil?


El teclado del portátil.
Supón el siguiente escenario:
Alguien escribió:
Estás tocando un tema en vivo con dos teclados midi. El "teclado midi 1" está conectado a ZynAddSubFX y el "teclado midi 2" está conectado a LinuxSampler. Luego necesitas cambiar rápidamente el "teclado midi 1" a AZR3 y el "teclado midi 2" a un plugin LV2 en un host tipo zynjacku o jalv y conectarlo a un procesador de efectos tipo rakarrack

bueno mi idea es que se pueda crear un preset con un esquema de conexiones y asignarlo a la tecla "q" del teclado del portátil, luego otro preset asignado a la tecla "w", otro a la tecla "e". Y así con todas las letras de un teclado querty

lgarrido escribió:
Si es el teclado del portátil yo intentaría añadirle los atajos de teclado a qjackctl o a algún otro patchbay que ya exista

Sé que es un proyecto muy ambicioso sobre todo para un amateur, pero creo que es más fácil empezar un programa de cero que contribuir a uno que ya existe. Me encantaría aportar ideas a otros proyectos, pero lamentablemente toda mi formación en informática es 100% autodidacta.

De todas formas existen soluciones mucho más sencillas para este problema. Por ejemplo el escritorio fluxbox tiene un sistema muy sencillo para añadir atajos de teclado, se pueden crear atajos de teclado que definan conexiones de jack con dbus-send.

de hecho ya había comentado esta idea en este otro hilo:
http://www.hispasonic.com/foros/controlar-jack-qdbus/403295

Bueno de todas formas no es un proyecto con miras a ser algo tan profesional tampoco, es solo algo para solucionar un problema concreto que tengo cada vez que voy a ensayar o cada vez que toco en vivo.

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#10 el 15/06/2012
Ya arreglé el error :)

Aquí el código corregido
Alguien escribió:
from gi.repository import Gtk
import dbus
from dbus.mainloop.glib import DBusGMainLoop

DBusGMainLoop(set_as_default=True)

bus = dbus.SessionBus()
jack_control = bus.get_object ("org.jackaudio.service", "/org/jackaudio/Controller")
jack_control_interface = dbus.Interface(jack_control, "org.jackaudio.JackPatchbay")

def printPorts(new_graph_version):
port_list = jack_control_interface.GetAllPorts()
print(port_list)

jack_control_interface.connect_to_signal("GraphChanged", printPorts)

Gtk.main()

y por pastebin para que vean la indentación:

http://pastebin.com/fGaFZnN1

Con esto ya tengo toda la información necesaria para empezar con mi proyecto. Ojalá que pueda hacer algo bueno de esto :)

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#11 el 16/06/2012
Y aquí tengo otra actualización del ejemplo del principio, es el mismo código que al principio, pero esta vez se actualiza cada vez que se añade un nuevo puerto :) Aunque, no se acutaliza si los puertos se borran :( me dio flojera seguir con esto.

No sé si me equivoco pero la señal "PortAppeared" devuelve 7 parámetros : new_graph_version, client_id, client_name, port_id, port_name, port_flags y port_type

Sé que si port_type es 0 es un puerto de audio, y si es 1 es MIDI. Pero no sé cómo saber si el puerto es de entrada o salida. Creo que si port_flags es impar es entrada, y si es par es de salida.

Lo dejo como Pastebin
http://pastebin.com/cUHVMw7S

e inserto el código, pero recuerden que Hispasonic borra los espacios de indentación:
Alguien escribió:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

####################################
### Módulos ###
####################################
import dbus
from gi.repository import Gtk
from dbus.mainloop.glib import DBusGMainLoop


# Hay que hacer este llamado antes de definir el bus
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

####################################
### Bus ###
####################################
bus = dbus.SessionBus()
jack_control_obj = bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
jack_control_ifa = dbus.Interface(jack_control_obj, "org.jackaudio.JackPatchbay")

#port_list = jack_control_ifa.GetAllPorts()

graph = jack_control_ifa.GetGraph(0)

for item in graph[1]:
print(item[0], item[1])
for subitem in item[2]:
print("-> ", subitem[0], subitem[1], subitem[2], subitem[3])

def printNewPort(new_graph_version, client_id, client_name, port_id, port_name, port_flags, port_type):
print("GV:", new_graph_version, ", client_id:", client_id, ", client_name: ", client_name, ", port_id: ", port_id, ", port_name: ", port_name, ", port_type", port_type)

jack_control_ifa.connect_to_signal("PortAppeared", printNewPort)

####################################
### GUI ###
####################################
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Listado de Puertos Jack")
self.set_default_size(200, 200)
self.model = self.__create_model(graph[1])

sw = Gtk.ScrolledWindow()
treeView = Gtk.TreeView(self.model)

cellRenderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("ID", cellRenderer, text=0)
column1 = Gtk.TreeViewColumn("Puertos", cellRenderer, text=1)
treeView.append_column(column)
treeView.append_column(column1)

sw.add(treeView)
self.add(sw)

jack_control_ifa.connect_to_signal("ClientAppeared", self.__add_client)
jack_control_ifa.connect_to_signal("PortAppeared", self.__add_port)

def __create_model(self, item_list):
model = Gtk.TreeStore(int, str)
for client in item_list:
iter = model.append(None)
model.set(iter,
0, client[0],
1, client[1])
for port in client[2]:
child_iter = model.append(iter)
model.set(child_iter,
0, port[0],
1, port[1])
return model

def __add_client (self, new_graph_version, client_id, client_name):
iter = self.model.append(None)
self.lastItem = iter
self.model.set(iter,
0, client_id,
1, client_name)

def __add_port (self, new_graph_version, client_id, client_name, port_id, port_name, port_flags, port_type):
child_iter = self.model.append(self.lastItem)
self.model.set(child_iter,
0, port_id,
1, port_name)

win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#12 el 17/06/2012
veguita escribió:
Creo que si port_flags es impar es entrada, y si es par es de salida.


No exactamente, tienes que hacer OR con los valores del enum JackPortFlags:

http://jackaudio.org/files/docs/html/types_8h.html#acbcada380e9dfdd5bff1296e7156f478
Subir
#13 el 20/06/2012
Gracias por la respuesta lgarrido. Encontré esto

Alguien escribió:
enum JackPortFlags {
JackPortIsInput = 0x1,
JackPortIsOutput = 0x2,
JackPortIsPhysical = 0x4,
JackPortCanMonitor = 0x8,
JackPortIsTerminal = 0x10
}


Esto está en la siguiente dirección:
http://jackaudio.org/files/docs/html/types_8h.html#enum-members

Todos los números son pares excepto JackPortIsInput, por lo tanto, solo los puertos de entrada darán un valor impar en JackPortFlags. De todas formas mi método es incorrecto pero funciona, así que cómo yo soy mediocre, lo dejaré así por el momento XD

Lamentablemente estoy aprendiendo y no me entra en la cabeza esto del OR-ing, pensaba que funcionaba como los permisos en chmod. Pero he visto que funciona distinto :( mi no entender :estonova:

Bueno de todas formas he mejorado mi ejemplo, corregí los errores que tenía, ahora si hace lo que debiera hacer:
http://pastebin.com/PmQ8NZp3

Ojo que lo que quería lograr con este ejemplo era hacer un script lo más sencillo posible que cree un listado de puertos de Jack, con DBus, GTK+3 y Python 3. Más que nada para que sirva como punto de partida a scripts más complejos. Espero que les guste

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
#14 el 20/06/2012
Es una máscara de flags binarias, si te fijas en los números hexadecimales son todos de 1 bit: 00000001, 00000010, 00000100, etc. Efectivamente, es muy similar al tema de los permisos del sistema de archivos.

La operación OR se usa cuando programas un cliente jack que crea sus propios puertos, tienes que hacer un OR de todas las flags que quieres ponerle a cada uno. Esto también lo haces cuando combinas todos los permisos de un fichero, donde se suele abreviar utilizando el sistema octal porque se hacen grupos de tres bits (rwx).

En tu caso lo que quieres no es construir la máscara de un puerto, sino detectar si determinadas flags están activas en una máscara ya dada. Para eso usas la operación AND binaria (en python el operador &) con la flag que quieras, lo que te fuerza a 0 todos los bits de la máscara menos el que te interesa, así que el resultado final es distinto de 0 o no.

if((port[2] & 0x1) != 0) es un puerto de entrada
if((port[2] & 0x2) != 0) es un puerto de salida
if((port[2] & 0x4) != 0) es un puerto físico

El bit que indica si es un puerto de entrada es el menos significativo, por eso coincide con la condición de par/impar.

En principio lo normal es que un puerto tenga activado o bien la flag de entrada o bien la de salida, pero nada impide que tenga las dos activadas o ninguna de ellas.

No es buena práctica utilizar directamente el valor numérico de las flags, porque no hay garantía de que no vaya a cambiar en una versión futura de la API, aunque sería muy raro. Pero en este caso al hacerlo a través de dbus no sé cómo podrías evitarlo, he buscado un poco en la documentación de la interfaz dbus de jack y no encuentro nada que permita utilizar nombres simbólicos para las flags. Si utilizaras pyjack podrías usar jack.IsInput en vez de 0x1, pero entonces ya no necesitarías dbus para nada porque podrías acceder a la API nativa a través de pyjack.
Subir
#15 el 20/06/2012
lgarrido escribió:
if((port[2] & 0x1) != 0) es un puerto de entrada
if((port[2] & 0x2) != 0) es un puerto de salida

Muchas gracias lgarrido, por fin me ha quedado claro
He corregido el script utilizando tu método:
http://pastebin.com/Fkmc5Y4y

Adicionalmente, hice una pequeña función en python que transforma un número entero en una lista de flags, aunque no usé esta función en el script.
Aquí está (reemplacé los espacios iniciales por guiones bajos _ por el tema de la indentación):
Alguien escribió:
def GetFlags (JackPortFlags):
__PortFlags = ["JackPortIsInput", "JackPortIsOutput", "JackPortIsPhysical", "JackPortCanMonitor", "JackPortIsTerminal"]
__NFlags = len(PortFlags)
__bJackPortFlags = bin(JackPortFlags)[2:]
__bJackPortFlags = "0"*(NFlags-len(bJackPortFlags)) + bJackPortFlags
__return [PortFlags[::-1][a] for a in range(0,NFlags-1) if bJackPortFlags[a] == "1"]


Esta función devuelve una lista de flags, por ejemplo si el número de flags es 22, invocamos la función GetFlags(22), y devolvería:
Alguien escribió:
["JackPortIsTerminal", "JackPortIsPhysical", "JackPortIsOutput"]

Para saber si el puerto es de entrada o salida se puede usar:
Alguien escribió:
"JackPortIsOutput" in GetFlags(22)

Debería devolver Verdadero o Falso.
Evidentemente el método de lgarrido es mucho más sencillo, pero es posible que llegue a necesitar un listado de flags en forma de array, así que esta función quedará como Plan B.

lgarrido escribió:
En principio lo normal es que un puerto tenga activado o bien la flag de entrada o bien la de salida, pero nada impide que tenga las dos activadas o ninguna de ellas.

Es cierto, pero la especificación de la Api de Jack dice que el Flag de entrada y el de salida son mutuamente excluyentes, es decir, solo se puede usar uno de los dos. Ahora no sé que pasa si colocas los dos, supongo que te da un error.

lgarrido escribió:
Si utilizaras pyjack podrías usar jack.IsInput en vez de 0x1, pero entonces ya no necesitarías dbus para nada porque podrías acceder a la API nativa a través de pyjack.

Es una buena opción, pero creo (corrígeme si me equivoco) que pyjack trabaja con jack1 y python2. Mi idea es trabajar con software actual (jack2, python3, gtk+3, etc.), así evito tener que reescribir mi script en caso que las versiones estables actuales se conviertan en el estándar.
Otra opción sería usar los módulos python que vienen con cadence, de FalkTX.

"tengo una soledad tan concurrida que puedo organizarla como una procesión"
Mario Benedetti

Subir
Respuesta rápida
Identíficate o regístrate para poder responder en este hilo.