producción

Phreaks

Descargar 2010_condense_phreaks.zip

Créditos

  • Código — NoRecess
  • Gráficos — Ced
  • Música — Tom et Jerry

Créditos tal como aparecen en Pouet.

Esta demo se basa en un único efecto: un player de animaciones avanzado. Emplea delta-packing con precisión de píxel y antialiasing. Requirió una versión especial de Exomizer (el packer), además de escribir una toolchain propia en el PC para generar los datos. La demo se construyó con el compilador PhrozenC, y se creó por completo bajo mi propio IDE.

El algoritmo explicado

Phreaks es una demo construida en torno a un efecto único: un reproductor de secuencias de animación. Mucha gente me ha pedido que explique todo el proceso de creación que hay detrás. Este artículo recorre las distintas etapas de desarrollo por las que pasó (y la lógica que las sostiene). Espero que sirva para darse cuenta de que detrás de una demo suele haber una cantidad enorme de trabajo — y no, no es tan fácil hacerla, aunque se suponga que es un simple reproductor de animación…

Escrito en la época del lanzamiento, y conservado tal cual.

El origen

Todo arrancó de una producción anterior, Phat 2. Esa demo también llevaba un reproductor de animación como el de Phreaks; su principio era rápido y fácil de implementar. Por desgracia, el resultado era visualmente desagradable, porque se basaba en bloques, así que la resolución era bastante gruesa.

Técnicamente, cada frame de animación se guardaba en un búfer aparte. Los datos de textura eran en realidad una combinación de los opcodes PUSH BC, PUSH DE, PUSH HL, PUSH AF y EXX. Cada frame se comprimía por separado con el packer BitBuster V1.2 de Team Bomba. El reproductor, corriendo en el Amstrad CPC, descomprimía entonces un frame de la animación y ejecutaba los opcodes para mostrarlo en pantalla.

Lo que había que mejorar respecto a Phat 2

Cuando salió Phat 2, mucha gente me preguntó cómo lo había hecho. Enseguida me di cuenta de que mi enfoque —bueno, sí— no estaba apretando los límites. Había margen. Tengo una regla personal con las demos: no hacer ni reutilizar dos veces el mismo efecto. Así que sabía que el siguiente terreno de mejora era la gestión de memoria (para guardar muchos más frames), pero también el aspecto visual, que había que mejorar subiendo la resolución (por entonces seguía pensando en una solución a base de bloques, pero con bloques más pequeños). Y empecé a prototipar la cosa…

Los datos

Lo primero era conseguir datos. Al principio pensé en hacerlo todo desde cero: frame a frame, podía producir deformaciones de un logo con la excelente herramienta gráfica Paint.net. Hice unas pruebas, no me convencieron, y sabía que la gente esperaba gente bailando. Así que acabé buscando por la red datos que pudieran servir para una demo. Y fue Apple (y sus anuncios) quien me alegró el día: gracias a ellos encontré este vídeo en YouTube:

Sin ser el mejor vídeo que he visto, pensé que sus distintas fases de animación podían dar un resultado interesante. Sabía que tenía que quedarme con él y concentrarme en la mejor reproducción posible en el Amstrad CPC.

Conversión de los datos brutos

Necesitaba un descargador de vídeos de YouTube. Usé SaveVideoDownload, pero no lo recomiendo y no pongo enlaces: parece de esas extensiones de navegador que hacen bastante más de lo que dicen — sospecho que instala spyware, entre otras cosas. Aun así hizo el trabajo, y obtuve el vídeo completo en .mpg.

Con el vídeo en la mano, me hacía falta un extractor de bitmaps: para cada frame del vídeo .mpg quería obtener una lista de archivos .bmp. Usé VideoLAN Player (VLC) para ese proceso. No recuerdo los pasos exactos que seguí en el programa, pero sé que hicieron falta ajustes retorcidos (¿y códecs?) para que funcionara como quería; lo único que importa aquí es saber que con él la extracción de bitmaps es posible.

Después hice una limpieza de los frames: ordenar miles de bitmaps es, desde luego, un proceso largo.

Luego usé la herramienta IrfanViewer para reducir cada frame a 2 colores y redimensionarlos a 768 x 205 (mi resolución «panorámica» preferida en el CPC, aquí en modo de vídeo 2).

Por último cargué cada frame en Paint.net y le apliqué un efecto de desenfoque. Era un paso importante: los frames se habían redimensionado y venían de una compresión MPEG original, lo que dejaba, triste consecuencia, un montón de artefactos alrededor del personaje. El desenfoque redondeaba al personaje. Al aplicarlo, por desgracia añadía colores nuevos a la imagen, así que había que volver a reducir a 2 colores; todo eso a mano (la herramienta no ofrecía procesos por lotes para eso), un trabajo verdaderamente penoso de llevar a cabo (mucho tiempo y mucha concentración).

Tras días de trabajo (sí…), mis datos estaban listos para pasar por mis propias herramientas de conversión…

De los bitmaps a los datos de animación (a la manera de Phat 2)

Mi primer enfoque fue el más elemental: comprimir una pantalla entera de datos con Exomizer y descomprimirla en tiempo de ejecución. Usaba el modo de vídeo 2 del Amstrad CPC, el que da la mejor resolución posible. Visualmente impresionante al ejecutarse en el Amstrad CPC, pero muy lento y con un consumo de RAM enorme: se llevaba nada menos que 3 banks completas de RAM extendida (17 KB x 3). Inservible tal cual, y al principio pensé que un reproductor de animación con una resolución tan grande era algo que se me escapaba. Pero insistí…

De los bitmaps a los datos de animación (a base de scanlines)

Sabía que en el fondo quería precisión de píxel. Así que pensé enseguida en usar scanlines horizontales para guardar los datos de imagen. Para cada línea de la imagen guardaba una lista de valores enteros X1 y X2 (los bordes de una línea), que describían líneas horizontales. Comprimido con Exomizer, el resultado era mucho mejor que con el algoritmo anterior: acabó siendo dos veces mejor en uso de memoria, pero seguía siendo muy lento en ejecución.

Overflow —que fue un gran apoyo durante todo el proceso de la demo— me aconsejó entonces usar la inversión de píxeles. Con ese truco solo tenía las posiciones X1, y X2 pasaba a ser una conmutación entre píxel encendido y apagado. Mejor en memoria, pero todavía demasiado grande: unos 30 KB para una sola animación.

Hice muchos ajustes pequeños en mi formato de datos para ganar memoria, pero desde luego no estaba listo para el estreno.

De los bitmaps a los datos de animación (con delta-packing, a la manera de Phat 2)

Y luego estaba eso que todo el mundo me señaló tras la salida de Phat 2: el delta-packing. El procedimiento consiste en mostrar el primer frame de la animación completo y, en el siguiente, cambiar el estado solo de los píxeles que han cambiado. Para conseguirlo dediqué un tiempo a reescribir mis herramientas y generar los datos correspondientes. Decidí entonces volver a un efecto a base de bloques, a la Phat 2. El resultado, tras 2 semanas de trabajo, fue simplemente asombroso: era rápido, y la misma secuencia de animación que venía usando desde el principio cabía por debajo de 17 KB. En ese punto del desarrollo me di cuenta de que era capaz de guardar algo así como 2 veces el contenido de la demo Phat 2 en la memoria del CPC.

Pero mi objetivo era hacer algo visualmente mejor que Phat 2, así que ahora tocaba pensar en mejorar la resolución…

De los bitmaps a los datos de animación (delta-packing al estilo Phat 2, con mejoras)

Me puse entonces a mejorar el algoritmo que tenía. Decidí codificar de forma especial todos esos bloques que quedaban visualmente llenos o vacíos: digamos que un grupo de bloques de 8x8 se guardaría en realidad como un bloque de 1x1 bajo un identificador único especial. Para cada frame hice además una lista de sprites que se mostrarían alrededor de todo el personaje «hecho de bloques» dibujado en pantalla. Introduje muchos ajustes para generar datos que Exomizer comprimiera mejor, y los resultados fueron bastante convincentes. La secuencia de animación que al principio ocupaba 3 banks de RAM ocupaba ahora 14 KB con el mismo aspecto; para mí, eso era impresionante.

Fue el momento en que tuve claramente en la cabeza el proceso de crear una demo basada en este algoritmo. Seguía siendo en modo de vídeo 2 del Amstrad CPC.

Reorganizar los datos de animación (con una versión de Exomizer modificada por Hicks/Vanity)

Por entonces noté que Exomizer daba mejores resultados con un búfer grande y lineal que con varios pequeños. Quería modificar la implementación de Exomizer para que admitiera el cambio de búfer de origen al vuelo. Piensa en el caso de una secuencia de animación que ocupa 30 KB de memoria una vez comprimida: estaría bien poder guardar ese búfer en las banks separadas de RAM extendida (que son de 17 KB).

Estuve comentando con Hicks/Vanity esa necesidad tan particular con Exomizer, y accedió amablemente a escribir una rutina así. Cuando recibí su trabajo, y tras algunas correcciones de errores, la rutina quedó integrada por completo en la demo. Como estaba previsto, los datos se redujeron aún más: la animación de 14 KB pasó a ocupar 11.

De los bitmaps a los datos de animación (¡el verdadero algoritmo de Phreaks, por fin!)

Pasaban los días: simplemente necesitaba desconectar un poco. Pero seguía pensando en una forma mejor de reducir el tamaño de los datos. 11 KB para mi test unitario estaba bien, pero una demo sería difícilmente viable con datos tan grandes. Y un día se me ocurrió la idea. En el fondo, el efecto sigue siendo a base de bloques. Para cada frame usaba una lista de sprites para describir bloques precisos al píxel. Me dije: ¿por qué no analizar todas las imágenes que componen la secuencia de animación entera y sacar de ahí una lista de sprites compartida a lo largo de toda la animación? Y lo hice.

Adapté mis herramientas para ese proceso. Me costó encontrar la resolución óptima de los sprites: si eran demasiado grandes, salía una lista infinita de sprites distintos; si eran demasiado pequeños, había demasiados índices de sprite que guardar en los datos. El tamaño óptimo que encontré fue 2x5. Esa resolución no me resultaba cómoda en modo de vídeo 2 del Amstrad CPC, pero de pronto se volvía atractiva en modo de vídeo 0, donde un byte de VRAM representa 2 píxeles.

El resultado final: para mi secuencia de animación de prueba bastaban 108 tiles únicos. Eso me permitía guardar los índices de tile en un solo byte, otra muy buena noticia. Una vez más tuve que retocar mi formato de datos para obtener mejores resultados al comprimir con Exomizer. El resultado: la animación ocupaba solo 6 KB de memoria…

Los grandes fracasos en cuanto a compresión

Por encima de todo este trabajo intenté introducir el volteo horizontal y vertical de los tiles. Tenía 2 bits en algún sitio, al referenciar un tile, para llevar esa información. Producía datos brutos más pequeños, pero por desgracia acabó dando una compresión de Exomizer peor que si no hubiera hecho nada.

Descubrí además que todo el código dedicado al tratamiento especial de los bloques llenos o vacíos era, una vez comprimido, menos eficiente que usar un simple sprite. Buena noticia en memoria, pero había dedicado tanto tiempo a pulir eso que me decepcionó haber trabajado tanto para… nada.

Por último, de todos los ajustes que tuve que manejar, saqué que guardar listas de píxeles encendidos/apagados no era eficiente: me fue mucho mejor razonando en inversiones de píxel — si el píxel está encendido, se apaga (y al contrario).

Ir más lejos con el renderizado: el antialiasing

Al principio pensaba que el antialiasing sería obvio para todo el mundo, pero parece que pocos pillaron de verdad el truco :) Así que merece una explicación.

No es un post-efecto aplicado una vez renderizado el frame completo. Sería muy lento de ejecutar y probablemente se comería tanta memoria que prefiero no pensarlo :)

Así que aquí está el secreto sucio: como se cuenta en este artículo, el efecto es a base de tiles. No fue una decisión al azar usar un ancho de 2 píxeles para los tiles. Como consecuencia, para una línea horizontal de tile solo hay 4 configuraciones posibles: PÍXEL APAGADO + PÍXEL APAGADO, PÍXEL ENCENDIDO + PÍXEL APAGADO, PÍXEL APAGADO + PÍXEL ENCENDIDO, PÍXEL ENCENDIDO + PÍXEL ENCENDIDO. En la demo Phreaks eso se traduce respectivamente en 0 + 0, 1 + 0, 0 + 1, 2 + 2 (0, 1 y 2 son índices de paleta). 1 + 0 y 0 + 1 se usan siempre al principio y al final de la línea. Piénsalo: ¡eso simula visualmente un efecto de antialiasing!

Ir más lejos con el renderizado: el modo invertido

El modo invertido consiste en mostrar la animación con todos los píxeles volteados horizontalmente. Ese truco aporta cierta variación al efecto visual. En la demo Phreaks se logra volteando horizontalmente todos los tiles (en resumen, PÍXEL ENCENDIDO + PÍXEL APAGADO pasa a ser PÍXEL APAGADO + PÍXEL ENCENDIDO, y PÍXEL APAGADO + PÍXEL ENCENDIDO pasa a ser PÍXEL ENCENDIDO + PÍXEL APAGADO, ¡algo que se ejecuta muy rápido gracias al modo de vídeo 0 del Amstrad CPC!). Además, al mostrar una línea horizontal de tiles hay que invertir también la posición («X = ancho de pantalla - X»).

Conclusión

Al final me llevó 4 meses de trabajo conseguir que la secuencia de animación funcionara como quería. Resultaba bastante emocionante creer que el formato de datos definitivo estaba encontrado y volver unos días más tarde con ideas nuevas y mejores.

Como se ve, generar los datos fue un trabajo enorme. Eso es lo que la gente no suele percibir al ver una demo. En realidad estoy bastante orgulloso de todo lo que hice alrededor, incluidos los fracasos, y contando todos los pasos necesarios para llegar al resultado final. Aprieté de verdad los límites de lo que creía capaz de hacer con el Amstrad CPC. Lo que más me gusta pensar es que esos datos no se habrían podido generar en un Amstrad CPC de la época: exige demasiado cálculo, demasiados datos — es muy de 2011, en mi cabeza. Y por eso resulta tan interesante seguir con el Amstrad CPC…

Espero que ahora veas la demo Phreaks como una mejora de verdad en el (pequeño) mundo de las demos de CPC :)

YouTube :

Demo Phreaks en YouTube

Verlo en YouTube

Presentación

Ocho imágenes, capturadas desde la imagen de disquete de la release.

El texto de apertura: here we are, once again a new demo for you guys

Un bailarín en azul sobre bandas verdes

El texto: State of the Art, adaptado en Amstrad CPC

El texto: 195 fotogramas, antialias al píxel, delta packing

Un bailarín en blanco sobre bandas azul claro

El texto de los greetings, a Arkos y Vanity

Un bailarín en azul sobre bandas naranjas

Un bailarín en azul sobre bandas amarillas

Comentarios de Pouet

Los comentarios siguientes se dejaron en Pouet.net sobre Phreaks. Se citan tal como se escribieron. Copiados de esa página el 17 de agosto de 2026.

Nice BASIC demo xD (No, really, guys, the demo is cool!)

Devilmarkus, 2010-12-16 · rulez

Youtube link : http://www.youtube.com/watch?v=N7o4sDbSQmg

norecess, 2010-12-16

Cool! Anims are slow. I like the antialiasing.

Optimus, 2010-12-16 · rulez

meh

kusma, 2010-12-16

Nice demo! But S.O.T.A. fans like me will always prefer dancing girls!

ham, 2010-12-16

Nice job!

Soundy, 2010-12-16 · rulez

technically cool i guess, designwise meh, music is annoying. Piggie

shock__, 2010-12-16

Just to explain a little bit.. The major thing to retain in this prod is not the FX itself ; it’s relatively trivial and everyone with some Z80 knowledge would be able to accomplish the same. No, the real thing is the number of UNIQUE frames, which is 195. Think about it : Amstrad CPC has 128Kb, remove 40Kb for video-memory (with the special resolution I use), remove 8Kb for the music (and its player), remove another 6Kb for the sprites displayed on top of the FX, then remove once again another 5Kb for the background, another 5Kb for the font, another 11Kb for the code itself… Let’s calculate… we have 53Kb free. So the 195 frames are packed in 53Kb. 20 frames is super easy to manage ; 50 frames : you need a good data structure then.. 195 frames is kind of big numbers here. Hope you realize that :)

norecess, 2010-12-17

Nice

Buckethead, 2010-12-17 · rulez

195 frames or not, this is pretty boring. basically, what kusma said.

StingRay, 2010-12-17

Mostrar los 26 comentariosMostrar menos comentarios

Actually, the prog displays a single effect during its whole demonstration but i really like the anti-aliasing of the frames. Design is nice; same goes for the tune. Congrats to all involved; you managed to release (another) nice product for CPC just before the fall of 2010! Well done guys!

voxy, 2010-12-17 · rulez

Well done, you did it in the right time… What Stingray said ” it’s a pretty boring.” Graphics and music don’t suit this demo.

Beb, 2010-12-17

The visuals are boring, maybe you should have taken some wacky swirls or zoomer instead. The music is very nice though.

Exin, 2010-12-18 · rulez

well, why not?!

ɧ4ɾɗվ., 2010-12-18 · rulez

for the player

Shazz^TRSi, 2010-12-18 · rulez

The design is really… horrible. Ok for the perf, 195 frames in 53kb, but I must admit that “animations” are not my cup of tea (cf. Phat 2)…

Hicks, 2010-12-18

The technical achievement is definitely worth a thumb up! The animations seem a bit disconnected from the music though.

Rouquemoute, 2010-12-19 · rulez

Phreaks would have been an awesome demo if Condense hired a designer…

toms, 2010-12-20

watchable

comankh, 2010-12-20

+ generally positive feelings + nice packing - slow depacking & drawing for 4 mhz

Skate, 2011-01-08 · rulez

video please?

guardian ٩๏̯͡๏۶, 2011-01-08

@Guardian: Youtube.

Gʀɪʍʍy, 2011-01-09

Nice demo.

Octoate, 2011-03-21 · rulez

Models of the animation are not my kind of stuff but 195 frames of this size is not usual on CPC, I guess.

Eldrik, 2012-04-15 · rulez

Thumb up for the technical achievement. However, it would be far better (beauty and speed) too see with a cleaner design. I’m looking forward to a new demo

krusty, 2012-04-15 · rulez

good work norecess!

guardian ٩๏̯͡๏۶, 2013-03-04 · rulez