Ejercicio obligatorio 5

Fecha de entrega: Domingo 12 de julio

Introducción

Endianness

Distintas arquitecturas de procesador se diferencian según cómo se almacenan en memoria los distintos bytes de un entero.

Supongamos que se quiere guardar en memoria el entero 0xDDCCBBAA en una arquitectura de tipo big-endian el mismo se guardará como {0xDD, 0xCC, 0xBB, 0xAA} mientras que en una arquitectura de tipo little-endian se almacenará como {0xAA, 0xBB, 0xCC, 0xDD}. Es decir, mientras en big-endian se almacena primero el byte más pesado, en little-endian se almacena primero el byte menos pesado.

El formato MIDI

Ya sabemos (ver EJ4) que un archivo MIDI está compuesto por un encabezado, que define las particularidades de ese archivo, y luego por pistas que cada una corresponde a un instrumento particular. Cada pista consiste en una sucesión de eventos que codifican las cosas que suceden en la pista.

El archivo MIDI es un formato binario donde todos los valores numéricos son enteros sin signo y se almacenan según convención big-endian.

../_images/20201_ej5_1.png

Imagen: Archivo MIDI con N pistas.

Encabezado

El encabezado del archivo tiene la siguiente estructura:

Campo

Sgnificado

Tipo

Valores

Header ID

Identifica un archivo MIDI

uint32_t

Vale siempre 0x4D546864

Size

Tamaño del encabezado

uint32_t

Vale siempre 6

Format

Formato de archivo

uint16_t

0: Pista única 1: Múltiples pistas sincrónicas 2: Múltiples piastas asincrónicas

N of tracks

Número de pistas

uint16_t

Numérico

PPQ

Número pulsos por negra

uint16_t

Numérico

Pista

Campo

Sgnificado

Tipo

Valores

Track ID

Identifica una pista MIDI

uint32_t

Vale siempre 0x4D54726B

Size (S)

Tamaño de la pista

uint32_t

Numérico

Eventos

Eventos de la pista

char[S]

Ver eventos

Eventos

La estructura de cualquier evento de una pista es una secuencia: delta de tiempo + evento.

Todos los eventos se encuentran precedidos por un valor de delta de tiempo, que indica a cuánto tiempo del evento anterior ocurre el actual.

En la codificación MIDI los valores de tiempo tienen una cantidad de bytes variables (nunca más de 4). Los primeros bytes del número tienen el bit más pesado (MSB) puesto en 1 mientras que el último byte del número tiene su MSB puesto en 0. El número final se obtiene concatenando los 7-bits más livianos de cada uno de los bytes.

Ejemplo:

{
    0x93, // 1 001 0011
    0xF8, // 1 111 1000
    0x1C, // 0 001 1100
    ...
}

En este caso el número ocupa 3 bytes dado que el tercer byte tiene su MSB en 0. El número resultante va a ser la concatenación de los 7-bits más livianos de cada byte: 001 0011 111 1000 001 1100 o, lo que es lo mismo, 0 0100 1111 1100 0001 1100 o, lo que es lo mismo, 0x4FC1C o, lo que es lo mismo, 326684.

(En la sección 1.1 del Standard MIDI-File Format Spec. 1.1 que se encuentra en la sección de referencias hay 12 ejemplos de números y su codificación.)

Luego del delta de tiempo viene el evento, que es una secuencia: evento + mensaje + (metaevento).

El evento consiste en un byte y ya fue especificado en el EJ4.

Luego del evento sigue el mensaje del mismo, que es una secuencia de n bytes que representa los parámetros de ese evento en particular. La cantidad de bytes de cada tipo de evento fue especificada en el EJ4.

Cada mensaje dependerá del evento, pero por ejemplo para los eventos de nota el mensaje tendrá dos bytes, donde el primer byte indica la nota y la octava con el formato desarrollado en el EJ4 y el segundo byte será la velocidad con la que se aprieta la tecla, como un número entre 0 y 127.

Los metaeventos son eventos que no entran dentro de los 7 eventos estándar y en ese caso los dos bytes representan el primero de qué metaevento se trata (hay una lista de más de 100 en la bibliografía de referencia) y el segundo cuánto miden esos datos. En el caso de nuestra implementación, como no vamos a procesar metaeventos, podemos utilizar esta longitud para descartar esa información.

Entonces dijimos que para cada evento va a haber una secuencia que será o: tiempo + evento + mensaje, o tiempo + evento + mensaje + metaevento, en el caso de que evento sea de tipo metaevento. En principio esto es así, pero hay una particularidad adicional.

Antes que nada, ya sabemos del EJ4 que todos los eventos tienen su MSB en 1, del mismo modo todos los mensajes tienen su MSB en 0 (como ya habíamos visto para la nota y la velocidad).

Como suele ser frecuente repetir reiteradas veces el mismo evento sobre un determinado canal, existe una variante de evento que es: tiempo + mensaje, o tiempo + mensaje + metaevento. En esta forma ¿cuál es el evento y el canal?, es el mismo del evento anterior. ¿Cómo detectamos si estamos ante esta forma o ante la otra?, simplemente asumamos que se trata de un evento, si la validación fallara entonces no estábamos leyendo la especificación del evento, sino que ya estamos leyendo su mensaje.

../_images/20201_ej5_2.png

Imagen: 4 eventos de una pista.

Evento 14:

Tiempo de 1 byte. Evento genérico. Mensaje de 3 bytes.

Evento 15:

Tiempo de 2 bytes. Evento nota encendida en canal 0. Mensaje de 2 bytes que representan el primero la nota y el segundo la velocidad.

Evento 16:

Tiempo de 1 byte. Evento faltante, entonces se repiten valores del evento 15: nota encendida en canal cero. Mensaje de 2 bytes que representan el primero la nota y el segundo la velocidad.

Evento 17:

Tiempo de 3 bytes. Metaevento en el canal 16. Mensaje de 2 bytes que el primero representa el tipo de metaevento y el segundo la longitud. Longitud bytes con los parámetros del metaevento.

Trabajo

Endianness

Escribir una función uint8_t leer_uint8_t(FILE *f); que reciba un descriptor de archivo f y que devuelva el resultado de leer del archivo un entero sin signo de 8 bits.

Escribir una función uint16_t leer_uint16_big_endian(FILE *f); que reciba un archivo f y que devuelva el resultado de leer del mismo un entero sin signo de 16 bits en formato big-endian.

Escribir una función uint32_t leer_uint32_big_endian(FILE *f); similar a la anterior pero que lea un entero sin signo de 32 bits.

MIDI

Escribir una función bool leer_encabezado(FILE *f, formato_t *formato, uint16_t *numero_pistas, uint16_t *pulsos_negra); que lea del archivo f un encabezado y devuelva por la interfaz el formato, el número de pistas numero_pistas y la cantidad de pulsos por negra pulsos_negra. Si el encabezado es correcto debe devolverse true, en caso contrario false.

Escribir una función bool leer_pista(FILE *f, uint32_t *tamagno); que lea del archivo f el encabezado de una pista y devuelva por la interfaz el tamaño tamagno de la misma. Si la pista es correcta debe devolverse true.

Escribir una función bool leer_tiempo(FILE *f, uint32_t *tiempo); que lea del archivo f el delta de tiempo tiempo de un evento. Debe devolver true si el tiempo es correcto.

Escribir una función bool leer_evento(FILE *f, evento_t *evento, char *canal, int *longitud, uint8_t mensaje[]); que lea del archivo f un evento y su mensaje. La secuencia para realizar esta operación es leer un byte y llamar a decodificar_evento(). Si el evento era válido entonces hay que leer tantos bytes como diga la longitud del evento y guardarlos en el mensaje. En caso de que el evento sea inválido, entonces el byte que se leyó no correspondía al evento sino que era el primer byte del mensaje, en ese caso se leerán longitud - 1 bytes adicionales y se guardarán todos ellos en mensaje. Los parámetros evento, canal y longitud son de entrada/salida. En el caso de que se lea un evento nuevo se modificarán (releer la documentación de decodificar_evento()), en caso contrario se podrá usar el valor que ya tenían las variables que corresponde al último evento leído. La función debe devolver true si todo funcionó correctamente.

Escribir una función void descartar_metaevento(FILE *f, uint8_t tamagno); que ya habiendo sido decodificado del archivo f un metaevento ignore los tamagno bytes que corresponden a la información del mismo.

Programa

Se provee un programa de pruebas main.c que implementa la lógica de alto nivel de procesar un archivo MIDI utilizando las funciones que se desarrollan en este trabajo.

Se entregan además archivos MIDI de ejemplo y salidas de ejemplo de qué hace este programa al procesar esos archivos.

Como en el EJ4 se dejó libertad al alumno al respecto de cómo nombrar las etiquetas de los tipos enumerativos y como traducir dichas etiquetas en cadenas entonces habrá que modificar el archivo main.c para ajustar las etiquetas a las del alumno, y la salida de las pruebas no será idéntica a la salida provista.

Sólo se permite modificar el archivo main.c para modificar las claves de los enumerativos y para incluir los encabezados propios de la modularización del alumno. Todo el resto de la entrega debe hacerse en fuentes independientes desarrollados por el alumno.

Material

El material provisto se descarga de acá: archivos_20201_ej5.tar.gz

Entrega

Deberá entregarse:

  1. El código fuente de los módulos desarrollados en este ejercicion y en el EJ4.

  2. El código fuente del main.c modificado.

  3. El archivo Makefile para compilar el proyecto.

La entrega se realiza a través del sistema de entregas.

El ejercicio es de entrega individual.

Referencias

Endianness:

https://es.wikipedia.org/wiki/Endianness

Formato MIDI:

http://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf

http://faydoc.tripod.com/formats/mid.htm