Emulador de GB – 15: Audio (Parte 1)

Después de mucho tiempo sin poder avanzar con el emulador por fin tengo tiempo y decidí de una vez por todas emular el sonido. Es algo que venía pasando siempre para mas adelante por 3 motivos:

  • Quería tener la mayoría de los features implementados para tener mas confianza de que errores al emular el sonido no sean producto de otras fallas
  • Siempre hay mas cosas para agregar (ej: soportar juegos de game boy color, agregar features del emulador, optimizar y emprolijar el código)
  • Y lo mas importante, NO TENÍA IDEA sobre el tema. Siento que es el tema mas complejo de entender de todo el emulador

Antes de empezar a hacer cosas me leí varias veces la documentación (que es muy poca y pensada para quien entiende del tema), busqué información en blogs e incluso busqué documentación de otras consolas que tienen hardware y funcionamiento parecido. Para agregarle misterio y complejidad al tema, la mayoría de la gente escribiendo sobre desarrollo de emuladores NO implementan el sonido, y los pocos que lo hacen no hablan mucho del tema.

Lo que me hizo entender como empezar con esto es principalmente este video de OneLoneCoder. Si les interesa el contenido de este blog y entienden inglés les va a interesar esa serie completa, y si se enganchan miren mas videos porque toca temas RE interesantes y avanzados y es de lo más didáctico.

Al final voy a poner muchos links con información sobre el tema, pero voy a tratar de explicar en detalle todo lo que pueda en estos posts, asumiendo que quien lee esto, al igual que yo, no sabe por donde empezar y no sabe que significan la mayoría de los términos. En la facultad aprendí algunas cosas sobre el sonido desde el punto de vista físico y matemático, y conozco algunas cosas de sonido digital, pero no sabía como usar ese conocimiento en el emulador.

Lo que sigue probablemente tenga errores conceptuales porque no soy ni de cerca experto en el tema, así que cualquier corrección es bienvenida.

Onda

Forma

Los sonidos son vibraciones del aire que nuestro oido y cerebro interpretan como notas diferentes. Lo mas simple es empezar hablando de un sonido que sea un tono constante, y para esto el aire tiene que vibrar como una onda sinusoidal:

Las ondas tienen 2 componentes principales: La frecuencia, que se expresa en “Hz” (hertz) y la amplitud que en principio no tiene unidad de medida. La frecuencia es cuantas veces en 1 segundo se repite un ciclo completo.

Diferentes frecuencias producen diferentes tonos, y diferentes amplitudes producen diferentes volumenes. Las frecuencias bajas dan la sensación de que el volumen también bajó, pero lo importante es que para un mismo tono el volumen se cambia modificando la amplitud. En esta página se puede ver como a mayor frecuencia el tono es mas alto.

También es importante el concepto del período de una onda, que es el tiempo que tarda en completarse un ciclo. Es decir, el período es 1/frecuencia. No tiene mucha ciencia y probablemente no dije nada nuevo porque es un concepto muy usado, pero lo aclaro porque lo voy a usar.

Tipos

Las ondas sinusoidales generan tonos puntuales (440Hz producen un “LA”, 493.88Hz un “SI”), pero no son las únicas formas de ondas posibles. En principio una onda puede tener cualquier forma que se repita con cierta frecuencia y va a generar sonido, pero si no es sinusoidal genera una combinación de tonos. Es complejo el tema y creo que no tiene sentido que intente explicarlo, pero la idea es que cualquier onda de cualquier forma se puede generar sumando ondas sinusoidales de diferentes frecuencias y por eso se escuchan diferentes tonos al mismo tiempo. Si les interesa leer sobre esto al final dejo un link sobre Series de Fourier. Lo importante es que existen otras formas de ondas y que algunas son bastante usadas entonces tienen nombre propio: Pulso (o Rectangular), Triangular y Sierra.

En este link se puede escuchar como cambia el sonido para una misma frecuencia usando las distintas formas de onda:
Y en este link hay una visualización y escuchación (?) de como se relacionan una onda cuadrada con la sinusoidal usando la serie de Fourier (a la izquierda hay links para Triangular y Sierra).

Duty Cycle

Una característica mas que tienen las ondas pulso es el “Duty Cycle” o “Cíclo de trabajo”, aunque creo que todo el mundo le dice Duty Cycle también en español así que lo voy a llamar así todo el tiempo. Define que porcentaje de un ciclo de la onda el pulso está arriba y, por lo tanto, que porcentaje está abajo. Se define como un porcentaje o una proporción, y si la onda tiene un duty cycle del 50% también se la conoce como onda cuadrada.

En Wikipedia hay un gif mostrando como cambia la forma de la onda con distintos Duty Cycles.

No pude encontrar ningúna para probar y escuchar el efecto de distintos duty cycles, así que dejo un link a un video pero CUIDADO CON EL VOLUMEN, VAN A ESCUCHAR TONOS MUY FUERTES Y MUY AGUDOS: https://youtu.be/72dI7dB3ZvQ?t=864

Cambiar el Duty Cycle es equivalente a cambiar las frecuencias de las ondas sinusoidales de la serie de Fourier, así que le resultado es que se escuchan diferentes tonos, aunque la frecuencia de la onda pulso no cambie.

GameBoy

Canales

El hardware de gameboy tiene 4 canales de audio así que puede generar 4 sonidos diferentes al mismo tiempo, que después se mezclan para producir un sonido final. El concepto es análogo a tener varias pistas para distintos instrumentos en una canción, que se reproducen al mismo tiempo y se combinan en un sonido final.

Cada canal tiene sus características y permiten generar sonidos diferentes, pero por ahora sólo voy a hablar de los primeros 2 porque son casi idénticos. El canal 2 tiene un feature menos, pero no voy a cubrir esa diferencia todavía, así que implementar el 2 es literalmente copiar lo que se haga para el 1.

Toda la explicación anterior de los tipos de onda fue porque en el GameBoy los primeros 2 canales generan ondas pulso, así que el juego va a estar configurando la frecuencia, el volumen y el duty cycle de cada canal. Cambiando la configuración de cada canal en intervalos muy cortos de tiempo se genera música.

Al igual que con los otros componentes, hay un componente de hardware que genera el sonido de los 4 canales, los combina y manda el resultado a los parlantes, y la forma de configurar el componente es escribiendo valores en posiciones de memoria específicas. Son un montón porque cada canal usa 4 o 5 registros, y además hay otros registros generales que afectan como se mezclan entre si y a que parlante va cada canal, porque soporta sonido stereo.

Duty cycle

Antes di una explicación mas teórica de que significa esto, pero ahora veamos cómo se define en la consola.

En el GameBoy no se puede definir un porcentaje explícitamente, el hardware solo está preparado para trabajar con 4 valores de duty cycle: 12.5%, 25%, 50% y 75%. Como son 4 opciones, los juegos en relaidad escriben un número de 0 a 3 en binario en los bits 6 y 7 de un byte de memoria específico (0xFF11 para el canal 1 y 0xFF16 para el 2), y cada valor corresponde a un porcentaje.

En código es algo así:

switch (ValorDe0xFF11 >> 6) {
    case 0x00: channel1.DutyCycle = 0.125f; break;
    case 0x01: channel1.DutyCycle = 0.25f; break;
    case 0x02: channel1.DutyCycle = 0.5f; break;
    case 0x03: channel1.DutyCycle = 0.75f; break;
}

Frecuencia

Las frecuencias dije que se definen en Hz (o KHz o MHz), pero por como está diseñado el hardware no se definen así en el GameBoy.

La consola soporta frecuencias dentro de un rango grande, pero lo que el juego escribe en la memoria es un valor que se usa en un cálculo para obtener la frecuencia final. Lo que permite esto es configurar con valores chicos (entre 0 y 2047) frecuencias dentro de un rango grande (64Hz – 131072Hz).

El cálculo es:

frecuencia = 131072 / (2048 – x) Hz, siendo “x” el valor que define el juego

A partir de ahora voy a hablar de frecuencia para el valor final en Hz, y “frecuenciaX” para el valor “x” que define el juego, aunque no sea realmente una frecuencia.

Pero 2047 no se puede representar con 1 byte que es la unidad de memoria que maneja la consola, así que el valor de la frecuencia se define en mas de un paso. Para representar el 2047 se necesitan 11 bits, los juegos tienen que escribir los 8 bits mas bajos de la frecuenciaX en una posición de memoria (0xFF13 para el canal 1, 0xFF18 para el 2) y los 3 bits mas altos en los primeros 3 bits de la posición siguiente (0xFF14 para el canal 1, 0xFF19 para el 2)

En código es algo así:

channel1.FrequencyX = 0; // FrequencyX es un entero de 16 bits
channel1.FrequencyX = ValorDe0xFF13; // ValorDe0xFF13 es un entero de 8 bits
channel1.FrequencyX = ((ValorDe0xFF14 & 0x07) << 8) + channel1.FrequencyX;
channel1.Frequency = 131072 / (2048 - channel1.FrequencyX);
channel1.Period = 1 / channel1.Frequency;

Estoy copiando código pero modificándolo un poco para que sea mas claro, en el emulador tuve que hacer algunas cosas diferentes por cuestiones que no vienen al caso, pero si comparan van a notar que no es exctamente igual.

Volumen

Además de un volumen general, cada canal puede tener un volumen propio. Pueden configurar un volumen variable también, pero por el momento lo voy a saltear para no hacer tan largo este post.

El volumen se define en los 4 bits mas altos de un registro (0xFF12 para el canal 1, 0xFF17 para el 2):

channel1.Volume = ValorDe0xFF12 >> 4;

Es decir que el volumen puede valer entre 0 y 15, sin unidad.

Habilitar

Todos los canales suenan mientras estén habilitados, así que el juego tiene que habilitarlos escribiendo un “1” en el bit 7 de posiciones puntuales de memoria (0xFF14 para el canal 1, 0xFF24 para el 2). En general lo que hacen los juegos es tener el canal deshabilitado, setean la frecuencia, duty cycle y volumen, y después escriben ese 1 para habilitar el canal y que empiece a sonar. En otro post voy a hablar sobre como se frena (no es poniendo un “0” en el mismo bit), pero por ahora quiero implementar lo mínimo.

Emulación

Los canales son mas compejos que lo que conté hasta ahora, pero con lo que expliqué ya se debería poder escuchar algo.

Hasta ahora todo lo que hice fue reinterpretar los valores que el juego escribe en la memoria para tener la información lista de una forma mas útil. Lo que sigue es emular el hardware de sonido, ¿pero que significa esto? Esta es una de las cosas que mas me frenaban para arrancar con esto, no me quedaba claro que hace el juego, que hace la consola (o sea, el emulador), y como se traduce eso en sonido que la PC pueda reproducir.

El juego

Al igual que con los sprites que no están guardados en archivos, tampoco los sonidos están guardados en archivos. El juego simplemente copia valores de la memoria ROM a ciertos lugares de la memoria del sistema en momentos bien sincronizados y con esos valores cambian los tonos que se escuchan. La información de cuando y como modificar los registros tampoco está en un formato compatible con hardware y software actual.

La PC
Actualmente el hardware de sonido funciona de una forma mucho mas estandarizada, así que necesito sonidos en un formato que SFML pueda interpretar. Hay varias clases para manejar esto de las que voy a hablar mas adelante.

La consola
El hardware de audio entonces tiene que simular el paso del tiempo, según las frecuencias y duty cycles ver a que punto de cada onda corresponde ese instante, y combinarlos para obtiener un volumen actual. NO genera tonos, los tonos son lo que interpreta nuestro oido según que tan rápido cambia el resultado.

El código que voy a escribir no replica exactamente el hardware original, por ahora prefiero hacer algo que simplemente genere un resultado equivalente para poder entender mejor el tema, y capaz en el futuro voy a intentar una solución que simule todo el funcionamiento interno del hardware.

Muestras

El sonido digital (cualquier sonido generado o reproducido en una computadora) es una secuencia de valores discretos. Es análogo a como un video en realidad son 60 imágenes distintas y no los infinitos estados intermedios. De la misma forma que cada imagen en un video es un “cuadro”, para los sonidos cada valor de la secuencia es una “muestras” o “sample”. Son valores enteros, y en el caso de SFML y supongo que en general, son enteros de 16 bits con signo. O sea, un valor entre −32,768 y 32,767.

De la misma forma que al aumentar los cuadros por segundo de un video la calidad del movimiento aumenta, al aumentar las muestras por segundo de un sonido la calidad también aumenta. Esta cantidad de muestras por segundo se llama frecuencia de muestreo (o “sample rate”), y se expresa en Hz.

Distintas tecnologías de audio digital usan distintos valores de sample rate y por eso tienen distinta calidad de sonido. Por ejemplo, los CDs usan 44100 Hz y los DVDs 48000 Hz. El hardware de cualquier PC actual permite reproducir audio con distintos sample rates, así que en principio hay que elegir un valor. Muchos emuladores permiten configurarlo y así el usuario puede ganar velocidad aunque empeore la calidad de sonido, pero por ahora yo voy a trabajar con un valor fijo de 44100 muestras por segundo.

Ese número es otro de los motivos por los que venía evitando ponerme a trabajar en esto: un segundo de audio son 44100 muestras de sonido, ¿cómo se que estoy generándolos bien? ¿cómo lo debugueo? ¿cómo se si un sample que vale “5822” está bien? ¿puedo saber cual es el valor siguiente? Con la parte visual es mas simple porque son sólo 60 cuadros en un segundo y se puede pausar y ver que la imagen actual tiene sentido, pero con el sonido no se puede “pausar” de la misma forma. Encima tampoco es que puedo elegir otro número mucho mas bajo, 8000 Hz es lo mínimo escuchable para un humano: https://github.com/audiojs/sample-rate

En un post futuro voy a hablar de una tool que me hice para entender todo esto un poco mas.

Retomando… decidí que voy a necesitar generar 44100 muestras de sonido por segundo, es decir, 1 muestra cada 0.00002267 segundos. Pero no puedo simplemente ejecutar el programa y cada 0.00002267 segundos reales generar una muestra, tengo que sincronizar esto con la velocidad del emulador de alguna manera.

Sincronización

La CPU tiene un clock de 1.048576 MHz, o sea que por cada clock de CPU emulado, pasaron 1 / (1.048576 * 1000000) segundos emulados, o 0.00000095374 segundos.

Tengo que generar 1 muestra cada 0.00002267 segundos emulados, así que cada vez que emulo una instrucción voy acumulando en un contador el tiempo equivalente, y cada vez que ese valor supere los 0.00002267 segundos calculo una muestra y vuelvo el acumulador para atrás. Calculando la proporción debería estar calculando una muestra mas o menos cada 23.7 ciclos de CPU.

De esta forma al emular 1 segundo de tiempo (1048500 ciclos) generé 44100 muestras, o 1 segundo de audio.

En el loop de main cada vez que se ejecuta una función tengo el dato de cuantos ciclos se emularon, así que tengo que agregar ahí el update del componente Audio. El componente va a actualizarse y si pasó suficiente tiempo va a generar una muestra y devolver true, si no false.

En la clase Audio:

// en Audio.h
float deltaTimePerEmulatedCycle = 1 / (1.0485f * 1000000);
float sampleTime = 1 / 44100.0f;
float elapsedTime = 0.0f;
u8 sample = 0;

// en Audio.cpp
bool Audio::Step(u8 cycles) {
    elapsedTime += deltaTimePerEmulatedCycle * cycles;
    if (elapsedTime >= sampleTime) {
        elapsedTime -= sampleTime;
        sample = GetSample();
        return true;
    }
    return false;
}

Y en main.cpp

int main() {
  ...
  while (gameWindow.IsOpen()) {
    ...
    u8 lastOpCycles = cpu.lastOpCycles;
    ...
    
    if (audio.Step(lastOpCycles)) {
      // acá tengo disponible una nueva muestra en audio.sample
    }
    ...
  }
}

En un rato voy a hablar de GetSample y de qué hacer con la muestra generada, pero todavía faltan entender un par de cosas. Lo importante es que con esa estructura ya tengo una forma de sincronizar la CPU y el audio, y de generar un sample cada 0.00002267 emulados.

Es importante tener en cuenta que para que todo esto se escuche bien el emulador tiene que funcionar a la misma velocidad que el hardware real, y una forma fácil de medir esto es asegurarse que funcione a 60 cuadros por segundo. Idealmente tiene que andar a mayor velocidad y con algún mecanismo limitarlo. Para lograr esto tuve que hacer varias cosas, pero va a ser un tema de otro post.

La consecuencia de que funcione a menos de 60FPS es que tarda mas de 1 segundo en generar 44100 muestras, por lo que empiezan a notarse microcortes en el audio. La consecuencia de que funcione a mas de 60FPS es que el sonido se genera más rápido que lo que se reproduce y se termina pisando a si mismo.

Sonido en SFML

SFML tiene una clase Sound que está pensada para reproducir sonidos cortos (cuando se aprieta un botón, cuando se dispara un arma, el ruido de un animal, etc). Esta clase puede reproducir un sonido desde un archivo, pero también puede reproducir muestras de un SoundBuffer (otra clase de SFML). Mientras hacía esto el emulador ya andaba a mas de 60 FPS compilado en modo release, pero todavía no tenía ningún mecanismo para limitar la velocidad a 60 FPS constantes. Lo que se me ocurrió fue que podía emular hasta tener 44100 muestras (1 segundo de audio) y reproducirlo. Para esto no importa mucho si el emulador anda mas rápido o mas lento de lo esperado, puede tardar todo el tiempo necesario para generar un segundo completo y ese segundo se reproduce bien. El siguiente segundo de audio no va a sonar justo después del anterior, pero al menos en 1 segundo de audio se puede comprobar si las cosas suenan como deberían.

Entonces lo que hice fue algo así:

if (audio.Step(lastOpCycles)) {
  // samples es un array de sf::Int16 de 44100 elementos
  samples[sampleIndex] = audio.sample;
  sampleIndex++;

  if (sampleIndex == 44100) {
    // actualizar el SoundBuffer
    // actualizar la instancia de Sound con el SoundBuffer actualizado
    // reproducir el sonido
  }
}

Con SFML además de poder usar un SoundBuffer para la instancia de Sound que se quiere reproducir, también se puede guardar en un archivo de audio normal (ej, un .ogg), y así es posible guardar el segundo emulado y reproducirlo y analizarlo por fuera del emulador con un programa como Audacity. Tener un segundo completo de audio para escuchar es mucho mas útil que tener un par de muestras, porque podés escuchar algo reconocible. No es suficiente para confirmar que se está generando 100% bien, pero da una buena idea. No voy a mostrar esto porque no aporta tanto y no tiene mucha ciencia (es literalmente una función), pero me sirvió así que lo quería contar.

Después de implementar esto (y GetSample, ya voy a llegar a eso) pude escuchar por primera vez algo! Y algo RECONOCIBLE:

Parece una pavada, pero de verdad no lo podía creer cuando sonó bien. Mi primer intento no había sido muy bueno, ojo con el volumen que tiene sonidos muy agudos:

Mas allá de las ventajas de emular de a 1 segundo esto tiene un problema muy claro: un delay de 1 segundo en el sonido, porque estoy esperando todo un segundo de emulación para reproducirlo. Esto es relativamente fácil de arreglar: en lugar de generar todo un segundo de muestras genero un solo frame y lo reproduzco, y genero otro y lo reproduzco. De esta forma el audio solo tiene un delay de 1 frame que es imperceptible. Si el buffer tiene un solo frame ya no tiene mucho utilidad guardarlo como un .ogg, pero bueno, después de varias pruebas y ajustes emulando 1 segundo completo ya tenía un poco mas de confianza de que las cosas estaban funcionando mejor.

Pero hacer esto hizo mas notorios los problema del emulador no funcionando a 60 FPS constantes, si un frame es mas corto de lo esperado los frames de audio se pisan, y si es mas largo hay microcortes. Cuando emulaba de a 1 segundo esto se escuchaba cada tanto, pero ahora se escuchaba mal entre cada frame. Era muy feo de escuchar.

Por ahora no voy a hablar sobre como solucioné esto, perdí mucho tiempo tratando de solucionar problemas que no tenían solución porque estaba haciendo cosas mal pero quiero explicar los problemas y la forma correcta de arreglarlos en detalle en otro post.

GetSample

Ahora si, ¿Que significa generar una muestra del sonido? En primer lugar hay que generar una muestra para cada canal activo, después hay que combinarlas y por último hay que transformar ese numero en un número normal para muestras de audio.

Los dos pasos del final los hice muy simples por ahora: sumo las muestras de cada canal y multiplico el valor por otro número grande. Este número grande es medio aleatorio y es cuestión de probar hasta encontrar alguno que funcione bien, porque en realidad define el volumen así que no hay un valor correcto. Lo único que tengo que tener en cuenta es que no se pase del rango [−32,768, 32,767]:

s16 GetSample() {
  s16 mix = GetChannel1Sample() + GetChannel2Sample();
  return mix * 1000;
}

Para generar la muestra de cada canal primero tienen que estar habilitados. Cada canal tiene su propio contador de tiempo que se reinicia cuando se habiltita, entonces de acuerdo a cuanto tiempo lleva habilitado se puede saber que lugar de la onda se está reproduciendo.

Por ejemplo, supongamos que se configuró el canal 1 con una frecuencia de 64Hz, un duty cycle de 25% y un volumen de 15, y se lo activa. 64 Hz es un período de 0.015625 segundos, de los cuales el 25% (0.00390625 segundos) la señal está en 1 y el resto (0.01171875 segundos) está en -1.

Las muestras se toman cada 0.00002267 segundos, así que durante un ciclo completo se generan 172 muestras valiendo 1 y 516 valiendo -1. Después empieza otro ciclo y se repiten mas o menos las mismas cantidades.

En algún momento el canal se deshabilita y siempre que esté deshabilitado se genera un 0.

Para implementar esto hay que llevar la cuenta del tiempo que estuvo sonando el canal.

Al habilitar el canal:

bool enabled = ((ValorDe0xFF14 & 0x80) != 0);
if (enabled) {
  channel1.Enabled = true;
  channel1.ElapsedTime = 0.0f;
}

Al actualizar el componente de Audio:

bool Audio::Step(u8 cycles) {
  float delta = deltaTimePerEmulatedCycle * cycles;
  elapsedTime += delta;

  if (channel1.Enabled)
    channel1.ElapsedTime += delta;

  if (elapsedTime >= sampleTime) {
    elapsedTime -= sampleTime;
    sample = GetSample();
    return true;
  }
  return false;
}

Y GetChannel1Sample queda:

s16 GetChannel1Sample() {
  if (!channel1.Enabled)
    return 0;

  float temp = fmod(channel1.ElapsedTime, channel1.Period);
  temp /= channel1.Period;

  if (temp <= channel1.DutyCycle)
    return channel1.Volume;
  else
    return -channel1.Volume;
}

Como la onda se repite se puede calcular el módulo (con floats, por eso fmod en lugar de %) para tener un valor entre 0 y el período. Después al dividirlo por el período se obtiene un valor entre 0 y 1, y solo queda compararlo con el duty cycle para saber que valor tiene la onda en ese momento.

Por ahora todo lo que conté aplica a los canales 1 y 2, así que podemos usar el mismo código.

En este video están sonando los dos canales y el emulador no funciona a 60FPS así que se notan los cortes raros. También está reproduciendose de a 1 segundo de audio en vez de a 1 frame, así se que nota el delay:

Se puede escuchar como los sonidos son reconocibles, mas allá de todos los problemas que ya describí antes.

Una forma rápida de comprobar si lo que se escucha al menos tiene sentido es usar el emulador BGB como hice en otros casos. Por un lado se puede chequear que los valores de memoria coincidan, pero además con las teclas F5, F6, F7 y F8 se pueden apagar y prender cada canal. Apagando los canales 3 y 4 se puede escuchar como es el sonido real.

Conclusión

Por ahora voy a terminar es post acá porque ya es mucha información junta.

Lo único que quiero remarcar es que trabajar con sonido puede parece muy complejo al principio, pero no es tan difícil. Los conceptos son medio complejos y mas abstractos que en otros temas, pero yendo de a poco y viendo muchos ejemplos creo que se puede entender bastante sin necesidad de conocer sobre física o matemática avanzada.

En el próximo voy a hablar sobre como sincronizar bien el audio y reproducir todo a 60FPS constantes, y en el siguiente voy a retomar con mas features de los canales 1 y 2 y a agregar el canal 3.

Links

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Crea tu sitio web con WordPress.com
Empieza ahora
A %d blogueros les gusta esto: