Módulo 2.1: Memoria Persistente, Estructuras de Datos (`[table]`, `[coll]`, `[dict]`) y Manejo de Estado
“Un algoritmo sin memoria es solo una función reactiva; con memoria se convierte en un sistema musical vivo.”
— Robert Rowe, Machine Musicianship
1. Fundamento Teórico: Los Tres Niveles de Persistencia en Computación Musical
(Inspirado en Todd Winkler, MIT Press y Cipriani & Giri, Vol. 2)
En la interacción en tiempo real, un sistema musical no puede depender únicamente de los datos que viajan volátiles por los cables. Necesitamos almacenar tres categorías de información:
2. Objetos de Almacenamiento Clásicos: [value], [table] y [coll]
A. Memoria Global Compartida: [value] ([v])
- Almacena un único valor (número o lista corta) asociado a un nombre global inmutable.
[v mi_tempo]: Cualquier objeto en cualquier subpatch o ventana que se llame[v mi_tempo]comparte el mismo puntero de memoria. Modificarlo en un extremo lo actualiza instantáneamente en el otro sin tirar cables.
B. Arreglos Indexados: [table]
- Almacena pares
(índice, valor)de números enteros. - Posee una ventana gráfica nativa para dibujar curvas de transferencia matemática (curvas de respuesta de pedales, mapeos de sensores, tablas armónicas).
- Acceso : Enviar un índice al inlet izquierdo devuelve el valor correspondiente en un solo ciclo de reloj.
C. La Base de Datos Clásica: [coll] (Collection)
- Es la navaja suiza histórica de Max para composiciones complejas.
- Cada fila tiene una clave única (número o símbolo) y un vector de datos.
- Admite modos de búsqueda directa, lectura secuencial automática (
next,prev), ordenamiento por clave o por valores, y persistencia directa en archivos de texto legibles.
3. El Estándar Moderno: Árboles Jerárquicos con [dict] y JSON
En aplicaciones de gran escala (como Max for Live o sintetizadores con cientos de parámetros), las listas planas de [coll] se quedan cortas.
El ecosistema [dict] de Cycling ‘74 introduce estructuras de datos basadas en JSON nativo dentro de Max:
- Permite crear diccionarios anidados:
{"preset_name": "Ambient Pad 01","filter": {"cutoff": 1850.5,"resonance": 0.72,"mode": "lowpass"},"lfo": {"rate": 0.25,"depth": 0.6}}
La Sintaxis de Rutas (Dot-Notation y Slash-Notation):
Para consultar o modificar datos dentro de [dict], no necesitas recorrer el árbol manualmente. Usas rutas:
get filter::cutoff──► Devuelve1850.5.set filter::resonance 0.85──► Modifica el valor sin tocar el resto del árbol.
4. Bajo el Capó: Diccionarios en C y Paso por Referencia (dict.route.c)
Mirando las entrañas del Max SDK en sources/max-sdk/source/dictionary/dict.route/dict.route.c:
El Problema de la Clonación de Memoria
Si un diccionario contiene 10.000 parámetros o la partitura entera de una sinfonía, copiar todo el JSON de un objeto a otro a través de un cable congelaría el hilo de audio.
La Solución de Cycling ‘74: Paso por Nombre Registrado
En C, Max no envía el contenido del diccionario por el cable; envía únicamente el puntero al símbolo del diccionario registrado:
void dict_route_dictionary(t_dict_route* x, t_symbol* s) { // Busca el diccionario en la tabla maestra de memoria global t_dictionary* d = dictobj_findregistered_retain(s); if (!d) { object_error((t_object*)x, "unable to reference dictionary named %s", s); return; }}Comportamientos Clave en la Gestión de Instancias: Nombres Efímeros y Diccionarios Anónimos
Un factor crítico en el ciclo de vida de los datos estructurados en Max se manifiesta en el mensaje de error:
"unable to reference dictionary named u123456789".
Mecanismos subyacentes y pautas de diseño:
- Diccionarios Anónimos vs. Nombrados:
- Al instanciar un
[dict]sin argumentos, Max le asigna un identificador efímero generado dinámicamente (useguido de un valor hash único). - Si dicho sub-árbol se crea de forma transitoria o se interrumpe la referencia, el objeto es recolectado por el gestor de memoria interno, provocando que cualquier
[dict.unpack]o[dict.view]aguas abajo falle al intentar acceder a un puntero ya invalidado. - Criterio de diseño: En arquitecturas de producción, se debe asignar siempre un identificador explícito a los diccionarios principales (
[dict mi_sintetizador]).
- Al instanciar un
- Consultas Directas (
get) vs. Desempaquetado (dict.unpack):- Para consultar claves anidadas en profundidad (por ejemplo,
filter::resonance), el enfoque óptimo consiste en despachar mensajesget path::to::keydirectos en lugar de encadenar múltiples[dict.unpack]. Este método reduce la sobrecarga de despacho gráfico y no requiere instanciar receptores intermedios.
- Para consultar claves anidadas en profundidad (por ejemplo,
- Manejo de Arreglos de Diccionarios:
[dict.unpack]no procesa de forma plana una lista de objetos JSON[ { "id": 1 }, { "id": 2 } ].- Se debe iterar formalmente mediante
[dict.iter]o direccionar por índice explícito:get features[0]::id.
Principio Arquitectónico: Cuando conectas un cable entre objetos
dict, viaja un puntero de 8 bytes, no megabytes de datos. La manipulación de árboles gigantes en Max es instantánea y de costo cero.
Fundamentación Teórica (Federico Schumacher — Tesis Doctoral AMI, UNAM/CMMAS): En su investigación sobre la calificación de datos simbólicos, Schumacher formaliza cómo la representación jerárquica en diccionarios y matrices (
dictycoll) resulta indispensable para almacenar no solo los eventos de control brutos (alturas, velocidades y duraciones), sino los descriptores de Music Information Retrieval (MIR) asociados: entropía melódica, perfiles de contorno y coeficientes de similitud cronotónica. Esta arquitectura de memoria desacoplada permite calificar y re-ordenar clips generativos en tiempo real sin interferir con el Scheduler de reproducción.
4 Escenarios de la Vida Real (Casos de Estudio)
Abre el parche interactivo:
book/patches/modulo-02/laboratorio_04_persistencia.maxpat
Escenario 1: Tabla de Escalamiento No Lineal de Sensibilidad con [table]
- El Problema: La respuesta de la velocidad de las teclas de un controlador suele ser lineal (0 a 127), pero la audición humana del volumen es logarítmica. Tocando suave casi no se oye y tocando medio ya suena muy fuerte.
- La Solución: Una curva de transferencia en
[table]. Mapeamos la entrada lineal a una curva exponencial dibujada a mano. El acceso es en sin consumo de CPU.
Escenario 2: Secuenciador Basado en Base de Datos con [coll]
- El Problema: Necesitamos almacenar una secuencia polifónica compleja con notas, velocidades y duraciones, y poder reproducirla hacia adelante, hacia atrás o saltar a compases específicos.
- La Solución:
[coll]con claves numéricas indexadas. Con el mensajenextrecorremos la partitura paso a paso, y con un número saltamos a cualquier evento de inmediato.
Escenario 3: Gramáticas Musicales y Modelado de Markov con [dict]
(Inspirado en Robert Rowe, Machine Musicianship, y Todd Winkler, Composing Interactive Music).
- El Problema: Al construir un agente de improvisación armónica, las tablas planas no permiten representar probabilidades condicionales dependientes del acorde anterior ni almacenar jerarquías complejas de transiciones.
- La Solución: Representar el autómata probabilístico en un
[dict]jerárquico:
{ "tónica": { "alturas": [60, 64, 67], "probabilidades": { "subdominante": 0.35, "dominante": 0.55, "tónica": 0.10 } }, "dominante": { "alturas": [67, 71, 74], "probabilidades": { "tónica": 0.85, "subdominante": 0.15 } }}- Max consulta los pesos de transición directamente en sin cables:
get tónica::probabilidades::dominante - La persistencia se comparte instantáneamente entre polifonías
[poly~], scripts[js]y el motor de síntesis sin duplicar estructuras en memoria RAM.
Escenario 4: Base de Datos de Presets Estructurados con [dict]
- El Problema: Un sintetizador tiene 4 módulos (Oscilador, Filtro, LFO, Efectos). Guardar los parámetros en variables sueltas hace imposible exportar o compartir presets.
- La Solución:
[dict]con jerarquía de claves. Un solo botónwriteexporta el estado completo a un archivo.jsonen disco legible por humanos y compatible con cualquier aplicación externa.
Escenario 4: Modificación Parcial con Rutas Anidadas (dict.unpack y get)
- El Problema: Solo queremos alterar la frecuencia de corte del filtro sin recargar ni alterar los otros 50 parámetros del sintetizador.
- La Solución: Enviar el mensaje
set filter::cutoff 800directamente al[dict]. La memoria interna se actualiza limpiamente sin mutaciones destructivas colaterales.
3 Ejercicios Prácticos de Laboratorio
Realiza estos ejercicios en tu copia de Max utilizando el parche laboratorio_04_persistencia.maxpat:
Ejercicio 1: El Cuantizador de Escalas con [table]
- Objetivo: Construye un corrector de afinación MIDI.
- Desafío: Llena una tabla de 128 posiciones donde cada nota cromática entrante se redirija a la nota de la escala mayor más cercana (ej. si entra 61, devuelve 60 o 62).
- Requisito: Al tocar cualquier teclado MIDI desordenado, la salida debe sonar 100% diatónica en tiempo real.
Ejercicio 2: Grabador / Reproductor de Eventos en Vivo con [coll]
- Objetivo: Graba una secuencia de notas improvisada por el usuario con sus marcas de tiempo exactas.
- Desafío: Al presionar “Record”, utiliza
[timer]para medir el delta entre notas y guárdalas en[coll]con formatoíndice, nota vel delta;. Al presionar “Play”, reproduce la secuencia con el timing exacto usando[pipe].
Ejercicio 3: Serializador JSON de Sesión con [dict]
- Objetivo: Construye un panel de administración de usuario que guarde: nombre del artista, BPM actual, escala seleccionada y volumen maestro.
- Desafío: Implementa botones para “Guardar en Disco” (
write session.json) y “Cargar desde Disco” (read session.json), y verifica que al reabrir el parche los valores se restauren automáticamente.
Resumen de Principios Arquitectónicos
[table]para arrays numéricos : ideal para tablas de ondas, mapeo de curvas y cuantización directa.[coll]para bases de datos relacionales simples: perfecto para secuencias de partituras, listas de acordes y eventos en el tiempo.[dict]es el estándar de oro de la industria: maneja árboles JSON anidados legibles, exportables y universales.- Paso por referencia de diccionarios: los objetos
dictcomparten la memoria de sus datos; solo viajan punteros de 8 bytes, protegiendo al procesador de clonaciones lentas.