Mientras exploraba osciladores personalizados para el sintetizador Minilogue xd usando el logue-sdk, me encontré con un problema: En Linux, no existe una herramienta gráfica oficial para cargar unidades en los sintetizadores de la familia logue, ni gestionar una colección.
KORG distribuye el Sound Librarian exclusivamente para Windows y macOS. En Linux, la única vía oficial es la línea de comandos mediante logue-cli. Y aunque la herramienta funciona perfectamente, tener que escribir comandos, recordar parámetros y parsear salidas de texto cada vez que querés probar un oscilador se vuelve tedioso.
QLogueLibrarian nació para resolver eso: una interfaz gráfica minimalista y multiplataforma —construida con Qt6, QtQuick/QML y CMake— que actúa como un wrapper visual de logue-cli.
Este articulo detalla y fundamenta la motivación, los requerimientos funcionales y no funcionales, las decisiones arquitectonicas y de diseño, y las tecnologias usadas.
Motivación técnica
- Linux no tiene Sound Librarian. No existe una alternativa gráfica oficial ni de terceros para usuarios de Linux.
- MIDI funciona mejor en Linux. Los controladores USB-MIDI de KORG son notoriamente problemáticos en Windows (conflictos de drivers con OS). En Linux, los dispositivos de la serie logue se reconocen automáticamente como dispositivos ALSA MIDI estándar —sin necesidad de instalar nada.
En otras palabras: Linux tiene la base técnica ideal para trabajar con estos sintetizadores, pero carece de la herramienta gráfica que lo haga accesible. QLogueLibrarian cierra esa brecha.
Atributos de calidad
Usabilidad
La interfaz prioriza la intuición y la fluidez: abstrae al usuario de los detalles de bajo nivel (línea de comandos, gestión de puertos MIDI) sin sacrificar el control cuando es necesario. La persistencia de la configuración reduce la fricción en el flujo de trabajo, permitiendo que el proceso de subida de unidades sea rápido y repetible.
Visibilidad
El sistema expone información relevante sobre las unidades —metadatos extraídos de los archivos y estado de la carga—, permitiendo al usuario tomar decisiones informadas sin necesidad de recurrir a herramientas externas o inspeccionar manualmente los archivos.
Extensibilidad
El diseño modular, con responsabilidades claramente separadas (MVC), facilita la incorporación de nuevas funcionalidades —como soporte para otros formatos de unidad o integración con bases de datos de terceros— sin afectar al núcleo existente. El sistema está preparado para evolucionar sin requerir reescrituras mayores.
Estabilidad estructural
El sistema preserva su integridad arquitectónica a lo largo del tiempo, incluso cuando se modifican o extienden sus componentes. Se prioriza una estructura clara y bien delimitada —con interfaces explícitas y responsabilidades separadas— por encima de la flexibilidad inmediata.
Portabilidad y distribución
El sistema está diseñado para facilitar su distribución y empaquetado en diferentes plataformas. La aplicación minimiza las dependencias externas, reduciendo la fricción en la instalación y el uso. Las dependencias necesarias se resuelven mediante los gestores de paquetes estándar de cada sistema operativo, o bien mediante bundles específicos según corresponda. De este modo, el empaquetado y la instalación se integran de forma natural en el ecosistema de cada plataforma.
Funcionalidad
La aplicación expone las funcionalidades principales de logue-cli, sumándole gestion de coleccion de unidades con visualizacion de metadatos, de forma similar a Sound Librarian.
- Detección de puertos MIDI: identifica automáticamente los puertos de entrada y salida disponibles, seleccionando los correspondientes a la familia
logue. - Subida de unidades: permite seleccionar un archivo de unidad (
.prlgunit,.mnlgxdunit,.ntkdigunit, etc.) y el slot de destino en el sintetizador. - Explorador de librería de plugins: organiza y lista las unidades disponibles en un directorio configurable, facilitando su selección y carga.
- Visualización de metadatos: muestra información extraída del archivo de unidad (nombre, versión, plataforma, parámetros, etc.) para que el usuario pueda evaluar su contenido sin inspeccionar manualmente el archivo.
- Configuración persistente: guarda la ruta al ejecutable
logue-cliy al directorio de plugins, restaurándolos al reiniciar la aplicación. - Registro en vivo: muestra la salida de
logue-clien un panel de log dentro de la interfaz, permitiendo supervisar el proceso de carga y diagnosticar posibles errores.
Elección de tecnologías: Qt6 y CMake
La elección de Qt6 como framework de interfaz gráfica responde a la necesidad de una base multiplataforma sólida, que permita ejecutar la aplicación en Windows, macOS y Linux. Qt no solo ofrece una capa de abstracción consistente para los tres sistemas operativos, sino que su integración con Qt Quick / QML permite definir la interfaz de forma declarativa, separando la lógica de presentación del backend en C++.
Qt ha posicionado QML como su tecnología de interfaz a largo plazo. Mientras que los widgets clásicos de Qt siguen siendo una opción válida y estable, el desarrollo activo y las nuevas funcionalidades se concentran en el ecosistema QML. Esto incluye mejoras en el rendimiento del scene graph, soporte para aceleración por GPU, y herramientas como Qt Design Studio para la colaboración entre diseñadores y desarrolladores. Optar por QML no es solo una elección técnica para el presente; es una decisión que alinea el proyecto con la dirección futura del framework.
QML permite construir interfaces modernas y fluidas, con animaciones y transiciones nativas, sin requerir código imperativo en C++. El modelo de bindings reactivos simplifica la comunicación entre la interfaz y el backend: cuando el estado de la aplicación cambia, la vista se actualiza automáticamente sin necesidad de llamadas manuales de sincronización. Además, QML promueve una separación natural entre la lógica de negocio (en C++) y la presentación (en QML), lo que facilita el mantenimiento y la evolución de la interfaz sin afectar al núcleo de la aplicación.
En cuanto a la construcción del proyecto, se utiliza CMake. Qt6 abandonó qmake como herramienta principal y adoptó CMake como sistema de construcción de primera clase, lo que simplifica la gestión de dependencias y la generación de proyectos para múltiples sistemas de construcción (Makefiles, Ninja, Visual Studio, etc.). CMake es el estándar de facto en el ecosistema C++ y permite que otros desarrolladores comprendan y modifiquen la configuración sin necesidad de aprender herramientas propietarias.
La combinación de Qt6 y CMake contribuye directamente al atributo de calidad de portabilidad: permite empaquetar la aplicación como un distributable autónomo por plataforma (AppImage, bundle, ejecutable portátil) que el usuario final puede ejecutar sin instalar dependencias adicionales, reduciendo la fricción en la adopción y asegurando un comportamiento predecible en cualquier entorno.
Decisiones arquitectonicas
Wrapper sobre logue-cli, no acceso directo al hardware
La app no interactua con el sintetizador directamente. En cambio, ejecuta logue-cli como proceso externo via QProcess y parsea su salida de texto.
| Ventajas | Desventajas |
| No hay necesidad de reverse-engineering del protocolo SysEx, se usa herramienta oficial. Compatibilidad con cualquier version futura de logue-cli que respete la interfaz de comandos. | Dependencia de que el usuario tenga logue-cli instalado separadamente (documentado explicitamente en el README), por limitaciones de licencia, no se puede distribuir logue-cli directamente en QLogueLibrarian.Lógica adicional de proceso externo y parseo de resultado. |
El puntapie inicial y lema de la aplicación es una GUI para facilitar la operación con logue-cli. Para su versión inicial esta elección es consistente, pero si en el futuro se piensa añadir nuevas funcionalidades, como fetch desde el dispositivo, se reevaluará la implementación de una solución nativa, un enfoque sin proceso externo. Existen proyectos implementados en python que ofrecen funcionalidad relevante, como https://github.com/gazzar/loguetools, los mismos se pueden usar como base.
Arquitectura MVC
Por lo general, cuando desarrollo una aplicación Qt que involucra modelado de datos y visualización, opto por MVC como patrón base. La clave no está en elegir una buena receta conocida, sino en aplicarla correctamente en cada desarrollo. He encontrado aplicaciones con un diseño que en las palabras era MVC, pero que en la práctica no, resultando en un diseño confuso y sin los beneficios esperados.
Por lo tanto, MVC no es generar clases con nombres Model, View y Controller y luego acoplar diferentes responsabilidades dentro de cada una. Es un patrón que requiere que el desarrollador lo aplique con disciplina, pensando en la extensibilidad, el desacople y la cohesión.
Variantes de MVC consideradas
MVP + Business layer
En el MVC clásico, la vista conoce modelo, a través del Patron Observer. En varios proyectos laborales anteriores, opté por una variante de MVC, el MVP, donde el Presenter/Controller actúa como el único mediador entre el Modelo (o capa de servicio) y la Vista, manteniendo a la Vista completamente desacoplada. En este esquema, la lógica de negocio reside en una capa de servicio (Logic), que orquesta las clases del modelo. El Controller tiene la responsabilidad principal de ser el interlocutor entre Logic y la Vista. Como consecuencia de este rol, también contiene lógica de presentación —conversiones, formateo, validación de interfaz— para adaptar los datos a la vista, pero su propósito fundamental no es contener lógica de negocio, sino mediar la comunicación.
Para operaciones que no implican lógica de negocio —como actualizar la selección de un elemento en la lista, modificar un campo de formulario o cambiar un estado de la interfaz— el Controller puede actualizar el modelo directamente, sin pasar por Logic. Esto evita la creación de métodos “wrapper” triviales en la capa de servicio, reduciendo el código innecesario y manteniendo la agilidad. Para operaciones que implican lógica de negocio, el Controller siempre delega en Logic.
classDiagram
class Vista {
<<Interfaz de usuario>>
}
class Controller {
<<Intermediario / Presenter>>
}
class Logic {
<<Capa de servicio>>
}
class Modelo {
<<Datos de dominio>>
}
Vista --> Controller : envía acciones del usuario
Controller --> Logic : delega lógica de negocio
Logic --> Modelo : orquesta / modifica
Controller ..> Modelo : actualiza directamente (solo UI state)
Logic ..> Controller : emite señales (operación/error)
Controller ..> Vista : actualiza UI vía bindings / señales
note for Controller "Contiene lógica de presentación:<br>conversiones, formateo,<br>validación de interfaz"
note for Logic "Contiene lógica de negocio:<br>procesos, reglas,<br>persistencia"
note for Controller "Escucha señales de Logic<br>para refrescar la vista"En este diseño, ante un crecimiento significativo de la aplicación, es posible segmentar la lógica de negocio en varios servicios, generando lo que se conoce como una arquitectura HMVC (Hierarchical Model-View-Controller), o múltiples tríadas MVC. En este esquema, cada módulo (Logic) tiene su propio Controlador y cada Controlador sus vistas asociadas. Esta estructura segmentada en módulos evita la creación de clases Logic y Controller monolíticas, manteniendo la aplicación modular, escalable y más fácil de mantener a largo plazo.
MVC clásico
El diseño basado en MVP (donde la vista desconoce por completo el modelo) funciona bien en entornos con flujos imperativos y widgets tradicionales. Sin embargo, al adoptar QML como tecnología de interfaz, ese esquema choca con el paradigma declarativo del framework. En QML, la vista se construye mediante bindings que se enlazan directamente a propiedades observables (Q_PROPERTY con NOTIFY).
Ocultar el modelo tras un controlador obligaría a duplicar cada una de sus propiedades en el controlador, generando una capa de indirección que no aporta valor y que introduce código boilerplate innecesario. Por el contrario, exponer el modelo directamente a QML a través de Q_PROPERTY permite que la vista lea el estado de forma reactiva y eficiente, sin necesidad de intermediarios. El binding reemplaza al patrón Observer del MVC clásico.
Para las operaciones de escritura, se mantiene un gateway (Controller unidireccional) que valida y delega en la capa de servicio (Logic). De este modo, la arquitectura final conserva la separación de responsabilidades del diseño original, pero se adapta al paradigma declarativo de QML: lectura directa del modelo via bindings, escritura controlada a través del controlador. Es una variante híbrida que reconoce las particularidades de la herramienta sin renunciar a los principios de desacoplamiento y cohesión.
classDiagram
class Vista_QML {
<<Declarativa / Reactiva>>
}
class Modelo {
<<Q_PROPERTY + NOTIFY>>
}
class Logic {
<<Capa de servicio>>
}
class Controller {
<<Gateway (unidireccional)>>
}
Vista_QML --> Modelo : bindea (lectura directa)
Vista_QML --> Controller : invoca comandos (escritura)
Controller --> Logic : delega lógica de negocio
Controller ..> Modelo : actualiza directamente (solo UI state)
Logic --> Modelo : modifica estado (negocio)
Modelo --> Vista_QML : NOTIFY actualiza bindings
note for Vista_QML "Lee el modelo mediante Q_PROPERTY<br>y bindings declarativos.<br>No contiene lógica de negocio."
note for Controller "Único punto de entrada<br>para operaciones de escritura.<br>Valida y traduce errores.<br>Puede actualizar UI state sin pasar por Logic."
note for Modelo "Expone propiedades observables.<br>La vista se suscribe vía bindings.<br>Reemplaza al Observer clásico."
note for Logic "Contiene lógica de negocio.<br>Orquesta procesos y persistencia."El tradeoff entre reactividad y acoplamiento
El beneficio más tangible de exponer el modelo directamente a QML es la eliminación de código de sincronización manual: cuando Logic actualiza library, statusText o inPorts, la vista se actualiza sola mediante los bindings declarativos. Esto reduce drásticamente el código boilerplate, elimina una fuente común de errores (olvidar refrescar la interfaz) y mantiene el flujo de datos claro y predecible. La contrapartida es un mayor acoplamiento entre la vista y el modelo: QML conoce la estructura de Logic y sus propiedades. Si el modelo cambia (ej. se renombra una propiedad o se modifica su semántica), la vista debe actualizarse en consecuencia. Este acoplamiento no es inherentemente malo —es una decisión de diseño— pero debe reconocerse. En QLogueLibrarian, se valoró que el beneficio en productividad y claridad supera el costo del acoplamiento, especialmente porque el modelo es estable y cambia con poca frecuencia.
Cabe señalar que QML no impide un enfoque MVP (donde el controlador oculta el modelo); es perfectamente viable. Sin embargo, habría requerido que el controlador expusiera una fachada de
Q_PROPERTYque ocultara el modelo y adaptara su estado a las necesidades de la vista. Esto no es una duplicación, sino una capa de indirección legítima. Para este proyecto, se valoró que esa capa no aportaba suficiente beneficio frente al costo de mantenerla, especialmente porque el modelo es estable y su estructura no cambia con frecuencia. La decisión final fue consciente: aceptar un acoplamiento controlado entre la vista y el modelo a cambio de una reactividad automática y un código más conciso, alineándose con el paradigma declarativo de QML.
¿Sobrediseño o inversión estratégica?
Una pregunta legítima que podría hacerse al leer este artículo es: ¿no es demasiada arquitectura para una aplicación que básicamente ejecuta un comando y parsea texto?.
La respuesta está en que el proyecto no nació con un conjunto fijo de requisitos. Lo que comienza como un wrapper gráfico para logue-cli podría —y probablemente debería— crecer. Funcionalidades como fetch de presets desde el dispositivo, integración con Git para descarga de presets de usuarios, gestión de colecciónes y soporte para múltiples dispositivos (mas allá de los soportados por logue-cli), son extensiones naturales que encajan en el dominio de la herramienta. Ninguna de ellas estaba en el alcance inicial, pero todas son previsibles.
Decisiones de diseño
Reutilización de componentes del ecosistema
Un principio general en el diseño de aplicaciones es reutilizar las clases y mecanismos que las librerías incluidas ya proveen, siempre que se ajusten a los requerimientos del proyecto. Qt es esencialmente una librería de GUI que fue extendiéndose para ofrecer funcionalidades generales para aplicaciones desktop: networking, gestión de settings, threads, etc. Al ser una librería muy grande, antes de plantear el desarrollo de una nueva clase, o considerar la inclusión de una nueva librería, conviene buscar si existe en Qt una clase que implemente la funcionalidad requerida.
Esto reduce la cantidad de código propio, simplifica el mantenimiento y asegura un comportamiento predecible al delegar en componentes probados y documentados. Además, aprovechar las clases estándar de Qt facilita la integración con el ecosistema y reduce la curva de aprendizaje para nuevos colaboradores.
Casos concretos:
- Persistencia multiplataforma con QSettings — reemplazo de archivos de configuración propios o glib::KeyFile. Persiste la ruta de
logue-cliy el directorio de plugins sin código condicional por plataforma (Registry, plist, INI gestionados internamente). - Ejecución asíncrona de procesos con QProcess — reemplazo de
popen/std::system/fork+exec. Permite correrlogue-cli probeylogue-cli loadsin bloquear la GUI, con notificaciones vía signals (started, finished, errorOccurred). - Navegación del filesystem con QDir y QFileInfo — reemplazo de
opendir/readdir/statPOSIX. El escaneo del directorio de plugins se reduce aentryInfoListcon filtros y orden declarativos, devolviendo una colección de QFileInfo lista para usar. - Parsing de texto con QRegularExpression — reemplazo de
std::regex(más lento) o PCRE (dependencia externa). Permite escanear la salida delogue-cliconglobalMatchy extraer grupos capturados, incluso en modo multilínea. - Parseo de JSON con QJsonDocument / QJsonObject / QJsonArray — reemplazo de
nlohmann/jsonorapidjson. Leemanifest.jsondesde los ZIP de unidades y reporta errores de parseo con posición exacta víaQJsonParseError.
Gestión del proyecto y buenas prácticas
Un proyecto de software no es solo su código fuente. La forma en que se estructura, se gestionan las dependencias y se automatiza la construcción es tan importante como la lógica de la aplicación misma. A continuación, se detallan algunas de las decisiones y buenas prácticas aplicadas en QLogueLibrarian para asegurar un desarrollo ordenado y mantenible.
Gestión de dependencias con submódulos de Git
Un desafío común en el desarrollo de software es cómo integrar bibliotecas externas. QLogueLibrarian utiliza submódulos de Git para gestionar su única dependencia externa directa: la biblioteca miniz.
Un submódulo de Git es, esencialmente, un puntero a un commit específico de otro repositorio de Git. Esto permite:
- Reproducibilidad: Al fijar una versión específica de la dependencia, se garantiza que cualquier persona que clone el proyecto obtendrá la misma versión de la biblioteca, evitando “bugs” o cambios de API inesperados que podrían introducirse en versiones futuras.
- Historial separado: El repositorio de la dependencia mantiene su propio historial de cambios. El proyecto principal solo registra qué versión de esa dependencia está utilizando.
Si bien los submódulos tienen una curva de aprendizaje, ofrecen un control preciso sobre las dependencias, lo cual es fundamental para la estabilidad a largo plazo del proyecto.
Integración con CMake
El sistema de construcción del proyecto utiliza CMake, que es el estándar de facto en el ecosistema C++ y el sistema de primera clase para Qt6 desde que este abandonó qmake. El archivo CMakeLists.txt refleja varias decisiones concretas que facilitan el mantenimiento, la portabilidad y la reproducibilidad.
Estructura de directorios y gestión de dependencias
Las dependencias externas se alojan en un directorio dedicado (external/miniz), que contiene el submódulo de Git de la biblioteca miniz (para manejo de archivos ZIP). A diferencia de la práctica habitual de buscar la biblioteca en el sistema con find_package, en este proyecto se optó por incluir la dependencia directamente mediante add_subdirectory(external/miniz). Esta decisión es deliberada: garantiza que todos los desarrolladores utilicen la misma versión de la biblioteca (pinned en el submódulo) sin depender de que esté instalada en el sistema, lo que simplifica la configuración inicial y evita problemas de compatibilidad.
Instalación y empaquetado
Se incluyen reglas de instalación para el binario y la licencia, preparando el proyecto para ser empaquetado con CPack o para generar un AppImage (mediante herramientas externas como linuxdeploy). La nota en el CMakeLists.txt indica que el AppImage se construye con un script separado (build-appimage.sh), pero la infraestructura de CMake ya está configurada para soportar la instalación estándar.
Prácticas adicionales
- Estructura de directorios clara: la separación en
src/model,src/logic,src/controller,src/viewysrc/qmlrefleja la arquitectura MVC y facilita la navegación. - Uso de
target_include_directories: se añadesrccomo directorio de includes para simplificar los includes relativos (ej.#include "model/UnitInfo.h"). - Vinculación explícita: las dependencias se declaran con
target_link_librariescon visibilidadPRIVATEpara evitar contaminar otros módulos.
Distribución
El mecanismo de distribución actual para Linux es AppImage, disponible en las releases del repositorio. La imagen se descarga y ejecuta sin necesidad de instalación, sin permisos de superusuario y sin modificar el sistema.
La elección de AppImage responde a una limitación estructural del ecosistema Linux: la fragmentación de versiones de bibliotecas entre distribuciones. Un binario compilado contra una versión específica de Qt, libc++ o glibc puede fallar al ejecutarse en un sistema con versiones diferentes, generando errores de enlace dinámico que impiden la ejecución. AppImage resuelve esto empaquetando el binario junto con sus dependencias en un único archivo ejecutable que se monta en tiempo de ejecución, evitando conflictos con las bibliotecas del sistema.
En el futuro, se podría extender la distribución a otros formatos según la demanda: bundle para macOS (ya soportado por Qt6), ejecutable portátil para Windows (con las DLLs empaquetadas). Por ahora, AppImage cubre el caso de uso principal: usuarios de Linux que trabajan con la familia logue y necesitan una herramienta gráfica fiable, fácil de instalar y sin fricción.
El proceso de generación del AppImage está automatizado mediante un script (build-appimage.sh) que utiliza linuxdeploy y linuxdeploy-plugin-qt para empaquetar el binario junto con las bibliotecas Qt y otras dependencias necesarias, produciendo un archivo único ejecutable en cualquier distribución Linux moderna.
Recursos
Video de demostración
Repositorio con instrucciones de instalación: https://github.com/deibit-dev/QLogueLibrarian

Dejá una respuesta