Bienvenidos. En esta ocasión vamos a hablar de autómatas celulares y de máquinas de estado finito. Los autómatas celulares son un tipo de esquema binario que define una célula como un agente cuyo estado puede ser uno o cero. Cada célula puede cambiar su estado en una evolución en el tiempo, en relación con su posición y estado, y la posición y estado de sus vecinos. Definida en una sola dimensión, un autómata celular comprende dos vecinos, uno a la derecha y otro a la izquierda. Su definición se comprende mejor si lo mostramos con una imagen. Vean esta bonita imagen. Definamos un autómata celular de una sola dimensión. Tiene dos vecinos, uno a la derecha y otro a la izquierda. Esto define la vecindad de la célula o "neighborhood". Una vecindad de otro tamaño podría definirse, digamos, tomando en cuenta los siguientes vecinos: habría dos vecinos a la derecha y dos vecinos a la izquierda. Cada célula puede estar en uno de los estados, uno o cero, y dependiendo del estado en que se encuentre la célula y sus vecinos, será el estado que adopte la célula en la siguiente evolución. Por ejemplo, el siguiente patrón define una evolución. Es decir, el siguiente valor que adopte la célula será uno, en el caso de que su vecina a la izquierda se encuentre en el estado uno, la célula misma en estado cero y la vecina a la derecha esté en el estado uno. Tenemos que el esquema de evolución se puede representar con tres bits, lo que significa que podemos tener ocho posibles combinaciones de estados. Cada posible combinación se le asigna una evolución, a esto se le llama regla. Hay 256 reglas diferentes y se conocen por el número que representa en sistema binario. Por ejemplo, esta sería la regla 1 y esta la regla 88. Algunas reglas generan patrones evolutivos que se van repitiendo. Estos son algunos de los patrones que se pueden generar. Comencemos generando un arreglo inicial con células en estado cero, menos la del medio. Primero definimos el número de autómatas que vamos a utilizar, 48 en este caso. Luego, llenamos un arreglo en el cual todos comienzan en el estado cero, menos el autómata del medio, que se encuentra en estado uno. Este tipo de estado inicial se utiliza para ilustrar de la forma más sencilla el tipo de patrón que las células generan. También puedes utilizar otro tipo de patrón inicial controlando la relación estadística entre unos y ceros. Por ejemplo, con "wchoose" declaramos que la posibilidad de elegir un cero es de 75 por ciento, y de elegir uno es 25 por ciento. Para definir la regla vamos a utilizar un diccionario. Un tipo de contenedor parecido a los arreglos pero que asocia en pares de elementos. En este diccionario estamos asociando el valor de cada caso con un estado. Con los siguientes comandos, podemos obtener el valor de evolución de cada caso. A continuación, escribimos una función para aplicar la regla y hacer una evolución. Nuestra función va a ser recursiva, lo que significa que la función se manda a llamar desde dentro de sí misma, lo que podría causar un bucle infinito de ejecución si no implementamos un mecanismo para decirle cuándo parar. Para eso, vamos a implementar una variable global que lleve la cuenta de cuántas recursiones hemos hecho. Al llegar al límite declarado, la función se interrumpe. Vamos a llamar la variable "step". También vamos a hacer una variable global para ir guardando los resultados de cada iteración. Bien, nombremos nuestra función. Nuestra función va a estar asignada a la variable "update". La función recibe como argumento el patrón inicial de los autómatas celulares, la regla definida dentro del diccionario y el número de recursiones que vamos a llevar a cabo. Ahora declaremos algunas variables. "Key" va a obtener el estado del contexto de la célula. Es decir, el estado de la célula a la derecha, el estado de la célula misma y el estado de la célula a la izquierda. Esto va a ocurrir para cada célula en el arreglo inicial. Dentro de un bucle ".do", vamos a iterar a través de todas las células que se encuentran dentro de nuestro patrón inicial. Para la primera célula, vamos a considerar la célula a la izquierda como la última célula del arreglo. Para la última célula, vamos a considerar la célula a la derecha como la primera célula del arreglo. Ahora incrementamos nuestro contador de recursiones. Si pasa el número de recursiones solicitado, puede abandonar la función. De otra manera, vuelve a llamarse a sí misma. Cada vez que se ejecuta la función, el patrón resultante queda en el arreglo "new pattern", que es utilizado para llamar la función dentro de sí misma para la siguiente recursión, momento en el cual se agrega el arreglo "output metrics" que va a contener todas las evoluciones. Echemos a andar el código. Comenzamos restableciendo el contador de pasos a cero, "step" igual a cero, y generamos una nueva variable para alojar los resultados del autómata celular. Finalmente, echamos a andar la función recursiva. En este caso, le estamos pidiendo 100 recursiones con la regla 150 y "start pattern". En el output del post window podrás ver el patrón que se genera de esta regla. Declaremos algunos synths para hacer sonar el autómata. Leámoslo en orden y sonemos el "Synth/acell" cada vez que hay un uno. Conforme leamos el arreglo, vamos a avanzar nota en una escala que escojamos. Este synth nos servirá de acompañamiento. Escojamos una escala. Ahora leamos los elementos del "output metrics" con una rutina mediante el método ".do". Noten cómo utilizo el método ".fork" para llamar la rutina. Escuchemos. Bien, pasemos a las máquinas de estado finito. Las máquinas de estado finito son una especie de autómata que define cierta cantidad de estados que puede tomar, así como las condiciones para transitar de un estado a otro. Los estados son representados como círculos y las transiciones como flechas conectoras entre los círculos. Es importante destacar la direccionalidad de las flechas, pues las transiciones no siempre pueden ser de ida y vuelta. Generalmente, las máquinas de estado finito definen un estado inicial o "idle". En SuperCollider existe un patrón que implementa ya una máquina de estados. Se llama PFSM por sus siglas en inglés: "Final State Machine". En este ejemplo, vamos a implementar una máquina de estado finito para generar diferentes secciones. PFSM recibe como entrada un arreglo de arreglos, cuyo primer elemento es el estado inicial de nuestra máquina de estado finito. Nota cómo le agregamos un símbolo de gato al principio de los arreglos. Vamos a definir un Pbind como item dentro de cada estado, lo que implica que al darle play los Pbinds van a sonar. Cada Pbind implementa una sección diferente. Veamos. Primero, tenemos la definición del estado inicial. Separado por una coma, definimos el item que regresa a la función cuando está en el primer estado, el cero. El siguiente arreglo, también separado por una coma, define los estados a los cuales puede transitar en su siguiente paso. Este es el item que regresa a la función cuando está en el segundo estado, el uno. Y finalmente, este arreglo define los estados a los cuales puedes transitar en su siguiente paso. Ejecutamos el Pbind. Puedes notar cómo los Pbinds van transitando de uno a otro, de acuerdo a los arreglos que definimos. Para terminar, sólo me gustaría mencionar que no es necesario que sean Pbinds los items de un PFSM. Puedes utilizar valores o arreglos que después leas dentro de una rutina, utilizando el método ".next", como se muestra en el siguiente ejemplo. Los ejemplos que hemos mostrado aquí son bastante sencillos. Sin embargo, ustedes los pueden seguir trabajando para generar sus propios experimentos.