Anuncios Google

Curso aplicado de GRRLIB - 8º Entrega

Tutoriales Avanzados Homebrewes

Hola de nuevo amigos. Con los conocimientos que hemos adquirido hasta el momento ya somos capaces de hacer un programa en 2D con aspecto profesional. Pero os habréis dado cuenta de que nos falta algo.

MANEJO DEL SISTEMA DE ARCHIVOS, SONIDOS Y MÚSICA

Por un lado sería perfecto poder leer y escribir en la SD para guardar el partidas, configuraciones de programas etc. Por otro lado creo que echaréis de menos algo imprescindible sobre todo en un juego. Estoy hablando de los sonidos y la música.

En esta entrega intentare llenar esos dos huecos, adentrándonos en la biblioteca de acceso al sistema de archivos y explicando como poder reproducir sonidos para hacer los efectos y música en los formatos mp3 y ogg.

En ambos casos se trata de bibliotecas estándar de devkitpro y ajenas a GRRLIB, pero que resultan imprescindibles si queremos darle un buen acabado a nuestro programa.

Finalmente aplicaremos todo esto en la realización de un visualizador de fotos y música.

ACCESO AL SISTEMA DE ARCHIVOS 


En Wii hay cuatro posibles lugares donde pueden Leer y almacenarse archivos:

  • La tarjeta SD.
  • Un dispositivo USB
  • El DVD (solo lectura)
  • La memoria NAND

Las bibliotecas estándar de Devkitpro sólo soportan la SD, el puerto USB 0 (donde conectamos el HD), y el DVD aunque este último sólo en modo lectura.

En el caso del DVD deberemos tener en cuenta que si tenemos lector maldito sólo podremos acceder a los archivos correspondientes a los discos originales.

En cuanto a la memoria NAND existe una biblioteca específica que permite el acceso a su sistema de archivos llamada libifsf de Joseph Jordan pero queda fuera del alcance de esta entrega.

A la hora de nombrar los archivos seguiremos el estándar habitual de C que se basa en UNIX y que tiene algunas diferencias con los sistemas Windows.

Como en el caso de Windows los archivos se guardan en una estructura jerarquica de carpetas, pero a diferencia del caso de Windows a la hora de separar carpetas utilizaremos el símbolo "/".

En nuestro caso, y como veremos a continuación, en lugar de "C:", "D:", etc tendremos un nombre específico para cada uno de los lugares donde podemos encontrar archivos.

Abajo podéis ver los nombres que debemos usar para cada uno de los dispositivos:

  • SD:/ - La tarjeta SD
  • USB:/ - El puerto USB 0.
  • DVD:/ - La unidad de DVD

Se pueden utilizar tanto rutas relativas como absolutas y también se acepta el uso de "." directorio actual y ".." directorio anterior.

Si omitimos el nombre de la unidad nos referiremos a la unidad en la que está situado nuestro programa y lo mismo vale para el nombre de carpeta.

Si comenzamos por "/" el programa interpretaré el nombre de archivo comenzando desde la carpeta raíz.

Podríamos poner como ejemplo algunos nombres válidos del acceso al archivo "ejemplo.png" situado en la carpeta apps/wiitriis de la SD estando situados dentro de la carpeta apps:

  • SD:/apps/wiitriis/ejemplo.png
  • /apps/wiitriis/ejemplo.png
  • ../apps/wiitriis/ejemplo.png
  • wiitriis/ejemplo.png

FUNCIONES DE ACCESO A ARCHIVOS EN C

En C existe una gran cantidad de funciones que permiten acceder al sistema de archivo, aunque nos podríamos arreglar con un pequeño conjunto para la mayoría de nuestras necesidades.

Nosotros vamos a centrar nuestra atención en dos tipos de funciones; Las funciones de acceso a carpetas y las de lectura y escritura de archivos.

En la mayoría de los casos, el manejo de ficheros exige la apertura previa del fichero que se desea utilizar. Dicho fichero se gestionará mediante una variable de tipo FILE que es asignada en el momento en el que abrimos el fichero.

A partir de ahí y hasta que cerramos el fichero deberemos utilizar dicha variable en todas las operaciones relativas a ese fichero.

Las dos funciones que nos permiten abrir y cerrar un fichero son respectivamente fopen y fclose y tienen la siguiente sintaxis:

FILE * fopen ( const char * filename, const char * mode );
Parametros:
        filename: Nombre de archivo a abrir
        mode: modo de apertura del archivo
devuelve el manejador del archivo
 
int fclose ( FILE * stream );
Parametros:
        stream: variable de fichero que hemos abierto previamente con fopen.

La apertura de ficheros puede efectuarse de varias formas; cada una de estas "formas" establece ciertas condiciones sobre el fichero y el modo en que podrá ser utilizado a partir de ese momento. Por ejemplo: puede establecerse que el fichero solo será utilizado para lectura, o que se creará nuevo si no existe otro de igual nombre; o que si existe otro de igual nombre, será borrado y sustituido por otro vacío; que las operaciones de lectura/escritura serán binarias o de texto.

La tabla adjunta muestra las formas de apertura establecidas por la función fopen:

Modo Descripción

"w" Crear para escritura. Si el fichero existe con anterioridad será sobreescrito. 

"w+" Crear para lectura/escritura. Si el fichero existe con anterioridad será sobreescrito.

"wb" Crear para escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito.

"w+b" Crear para lectura/escritura en modo binario. Si el fichero existe con anterioridad será sobreescrito.

"a" Abrir en modo escritura para añadir al final. Si el fichero no existe es creado. 

"ab" Abrir en modo escritura binaria para añadir al final. Si el fichero no existe, es creado.

"r" Abrir para lectura. Si el fichero no existe se produce un error. 

"r+" Abrir un fichero existente para lectura y escritura. Si el fichero no existe se produce un error. 

"rb" Abrir para lectura en modo binario. Si el fichero no existe se produce un error.

"r+b" Abrir un fichero existente para lectura y escritura en modo binario. Si el fichero no existe se produce un error.

Existen muchas funciones de lectura y escritura sobre archivos en C, aunque nos podemos arreglar con un subconjunto reducido de estas.

Seguidamente os listaré algunas de las principales funciones de lectura/escritura sobre archivos.

int getc ( FILE * stream );
Lee un caracter del archivo "stream"
 
char * fgets ( char * str, int num, FILE * stream );
Lee una cadena de caracteres en "str" del archivo "stream"
 
int fscanf ( FILE * stream, const char * format, ... );
Funciona exactamente igual que scanf pero leyendo desde un archivo.
 
int fputc ( int character, FILE * stream );
Escribe el caracte "character" en el archivo "stream"
 
int fputs ( const char * str, FILE * stream );
Escribe la cadena de caracteres "str" en el archivo "stream"
 
int fprintf ( FILE * stream, const char * format, ... );
funciona exactamente igual que printf pero en lugar de sacar el 
resultado por pantalla lo escribe en el fichero "stream"
 
 
int feof ( FILE * stream );
Devuelve un valor distinto de cero si hemos llegado al final del fichero.

Un ejemplo de su utilización podría ser el siguiente:

#include <stdio.h>
int main ()
{
  FILE * pFile;
  long n = 0;
  pFile = fopen ("myfile.txt","rb");
  if (pFile==NULL) perror ("Error opening file");
  else
  {
    while (!feof(pFile)) {
      fgetc (pFile);
      n++;
      }
    fclose (pFile);
    printf ("Total number of bytes: %d\n", n-1);
  }
  return 0;
}

En el que recorremos todo el fichero leyéndolo carácter a carácter contando el número total de caracteres que contiene.

Para profundizar en este tema os recomiendo de nuevo un curso especializado de C aunque veremos ejemplos de utilización de las funciones anteriores en el programa que desarrollaremos en la presente entrega.

GESTION DE SONIDOS EN FORMATO RAW - BIBLIOTECA ASNDLIB

La biblioteca ASNDLIB es la que deberemos utilizar a la hora de reproducir sonidos cortos del tipo del que se produce cuando pulsamos un botón, o el que produce un personaje al moverse. Dicha biblioteca gestiona sonido en formato RAW sin comprimir, por lo que para poder utilizarla deberemos convertir los sonidos y enlazarlos con el programa.

Lo primero que debemos saber, es que el sistema de sonido de Wii permite hasta 32 voces simultáneas que pueden ser gestionadas desde esta biblioteca. Normalmente la primera de las voces (la número 0) es la que usan otras bibliotecas como la "MP3Player" o "Oggplayer", por lo que la evitaremos para otros usos.

Para poder usarla necesitaremos incluirla al principio del programa mediante la línea:

#include "asndlib.h"

Existen bastantes funciones dentro de dicha biblioteca, pero nosotros podremos arreglarnos con 3 funciones que listo a continuación:

void ASND_Init();
void ASND_Pause(s32 paused);
s32 ASND_SetVoice(s32 voice, s32 format, s32 pitch,s32 delay, void *snd, s32 size_snd, s32 volume_l, s32 volume_r, ASNDVoiceCallback callback);

La función ASND_Init(), somo su nombre indica, inicializa la biblioteca ASNDLib por lo que es necesario llamarla una sola vez al principio del programa.

Después de inicializarla la librería permanece en estado de pausa si no le indicamos lo contrario, por lo que deberemos llamar a la función ASND_Pause(1), que pausa los sonidos si le pasamos un "0" y la reanuda si pasamos un valor diferente de "0".

Finalmente, para reproducir el sonido debemos llamar a la función SetVoice, en la cual debemos indicar todos los parámetros de formato en los que tenemos almacenado el sonido.

La función SetVoice tiene el formato siguiente:

s32 ASND_SetVoice(s32 voice, s32 format, s32 pitch,s32 delay, void *snd, s32 size_snd, s32 volume_l, s32 volume_r, ASNDVoiceCallback callback);
 
voice: número de voz a ser utilizada
format: formato de la muestra utilizada. Puede tener uno de los siguientes valores:
 
#define VOICE_MONO_8BIT       0
#define VOICE_MONO_16BIT      1
#define VOICE_MONO_16BIT_BE   1
#define VOICE_STEREO_8BIT     2
#define VOICE_STEREO_16BIT    3
#define VOICE_STEREO_16BIT_BE 3
#define VOICE_MONO_8BIT_U     4
#define VOICE_MONO_16BIT_LE   5
#define VOICE_STEREO_8BIT_U   6
#define VOICE_STEREO_16BIT_LE 7
 
pitch: frecuencia de la muestra
 
delay: Si queremos añadir una pausa en milisegundos antes de hacer sonar la muestra
 
snd: Aquí debemos pasar la variable que contiene la muestra
 
siize_snd: tamaño de la muestra en bytes.
 
volume_l, volume_r: volumen del altavoz izquierdo y derecho respectivamente.
 
callback: Este parámetro es opcional y se usa para que el programa llame automática mente a una función cuando se termina de reproducir el sonido.

La función devuelve SND_OK si se ejecuta con éxito y SND_INVALID en caso contrario.

Yo normalmente utilizo normalmente el formato de sonido 8000Hz mono y para generarlos utilizo el programa Audacity que es gratuito.

Seguidamente voy a explicar algunas funciones más que podrían sernos útiles aunque no las utilizaremos en el programa de ejemplo.

s32 ASND_SetInfiniteVoice(s32 voice, s32 format, s32 pitch,s32 delay, void *snd, s32 size_snd, s32 volume_l, s32 volume_r);

La función ASND_SetInfiniteVoice se utiliza exactamente de la misma manera que ASND_SetVoice pero reproduce la voz indefinidamente.

Si queremos que pare la voz anterior deberemos utilizar ASND_StopVoice:

s32 ASND_StopVoice(s32 voice);

Y si simplemente queremos hacer una pausa utilizaremos ASND_PauseVoice:

32 ASND_PauseVoice(s32 voice, s32 pause);

En ocasiones puede ser complicado encontrar una voz que no estamos utilizando ya. Para ello existe la función ASND_GetFirstUnusedVoice:

s32 ASND_GetFirstUnusedVoice();

Que como su nombre indica nos devuelve la primera voz sin utilizar.

Para modificar el volumen podemos utilizar ASND_ChangeVolumeVoice:

s32 ASND_ChangeVolumeVoice(s32 voice, s32 volume_l, s32 volume_r);

Al final del programa deberíamos llamar a la función ASND_End.

void ASND_End();

OBTENCION DE LOS ARCHIVOS DE SONIDO MEDIANTE AUDACITY

Para la creación de los sonidos utilizaremos el programa Audacity que nos permitirá crear los sonidos en el formato que necesitamos.

Preparando el programa:

Lo primero que debéis saber es que el formato que utilizaremos en el programa es RAW de 8 bits con signo ya que es el tipo de sonidos soportado por las librerías ASND y para que el tamaño del ejecutable final no sea más grande de lo necesario, utilizaremos una frecuencia de muestreo de 8000Hz,.

Para configurar estos parámetros seguiremos los siguientes pasos dentro del programa.

Seleccionaremos “Editar/Preferencias”.

Clic en la imagen para ver en grande.

Y en la pestaña formato de archivo seleccionaremos como formato de

exportación “Otro…”

Clic en la imagen para ver en grande.

Tras lo cual nos aparecerá una ventana que nos

permitirá seleccionar una serie de formatos adicionales. Nosotros

seleccionaremos “RAW Signed 8 bits PCM”.

Además de esto seleccionaremos en la pestaña calidad la frecuencia de muestro predeterminada con 8000Hz para hacerla coincidir con el formato que utilizaremos después en la exportación.

Clic en la imagen para ver en grande.

Una vez seleccionadas las opciones anteriores pasaremos a grabar o importar los sonidos necesarios.

Para grabar los sonidos podremos hacerlo con el botón de grabar de la pantalla principal, mientras que si queremos importarlos desde otros formatos deberemos abrir el sonido desde el menú archivo.

Una vez tengamos grabado y modificado nuestro sonido deberemos exportarlo a formato RAW. Para ello seleccionaremos “Exportar RAW” en el menú de archivo. Repetiremos el proceso para todos los sonidos necesarios.


Traducción de los sonidos a C

Ahora que ya tenemos los sonidos en formato RAW se nos plantea el problema de cómo utilizarlos en nuestro programa.

Para ello utilizaremos una utilidad que viene con el devkitpro que se llama raw2c.exe (situada en “\devkitpro\devkitppc\bin”.

Esta utilidad convierte un fichero en formato RAW a un fichero “.c” con todos los bytes del archivo asignados a un array con el mismo nombre que el archivo origen. De esta manera, podremos disponer de nuestro sonido accediendo directamente a dicho array. El formato de utilización es muy sencillo, solo deberemos pasarle el nombre del archivo a traducir y él generará dos archivos, uno con extensión “.c” y otro con extensión “.h” para que podamos incluirlo en nuestro programa.

Ejemplo:

  • Raw2c uno.raw

BIBLIOTECA DE REPRODUCCIÓN DE ARCHIVOS MP3 (MP3Player)

La biblioteca MP3Player es extremadamente simple de utilizar y nos permite mediante unas pocas funciones la reproducción de archivos en formato MP3.

Utiliza la voz número cero de la biblioteca ASNDlib, por lo que no deberíamos usar esa voz en nuestro programa para otros propósitos sino queremos crear interferencias.

Como la mayoría de las bibliotecas necesita inicializarse, para ello disponemos de la función MP3Player_Init.

void MP3Player_Init();

Y al terminar nuestro programa habrá que llamar a la función MP3Player_Stop para liberar los recursos utilizados por la biblioteca.

MP3Player nos suministra la función MP3Player_PlayBuffer para reproducir un sonido:

s32 MP3Player_PlayBuffer(const void *buffer,s32 len,void (*filterfunc)(struct mad_stream *,struct mad_frame *));

La función espera tres parámetros, el primero es un buffer en memoria con el mp3, el segundo es el tamaño de dicho buffer y el tercero es una posible función de filtro que es opcional y que nosotros no utilizaremos.

Como la función espera que le pasemos una variable con el archivo MP3 ya cargado en memoria, deberemos cargarlo previamente por ejemplo con GRRLIB_LoadFile.

Un ejemplo de utilización podría ser el siguiente.

#include <mp3player.h>
 
void *buf_mp3;
s32 mp3_size;
 
int main(int argc, char **argv) {
	ASND_Init();
	ASND_Pause(0);	
	MP3Player_Init();
	while(1){
		WPAD_ScanPads();  // Leer mandos
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME)  break;
		mp3_size = GRRLIB_LoadFile (nombre, (unsigned char**) &buf_mp3);
		MP3Player_PlayBuffer(buf_mp3, mp3_size, NULL);
	}
	MP3Player_Stop();
}

BIBLIOTECA DE REPRODUCCION DE ARCHIVOS OGG (oggplayer)

Los archivos OGG son archivos de música de formato libre (sin licencia) y aunque no está tan extendido como MP3 (que si es de pago), el formato OGG es muy eficiente y está implementado en numerosas plataformas.

Hasta ahora, todas las bibliotecas que hemos utilizado eran parte de las bibliotecas estándar de devkitpro o de GRRLIB. En el caso de los archivos OGG, tenemos que echar mano de una biblioteca que escribió Hermes y que, aunque en algún momento estuvo incluida en devkitpro fue eliminada del paquete.

Es por ello que necesitaremos algunas acciones adicionales para su instalación.

Lo primero que debemos hacer es copiar la carpeta tremor y todo su contenido (incluida en el pack del curso) en:

C:\devkitPro\libogc\include

Una vez hecho esto deberemos copiar los archivos libvorbisidec.a y libvorbisidec.la en la carpeta:

C:\devkitPro\libogc\lib\wii

Por último copiaremos los archivos oggplayer.c y oggplayer.h en nuestra carpeta source.

Uilización de la biblioteca

La utilización de oggplayer aún más sencilla que mp3player y contiene las siguientes funciones:

int PlayOgg(const void *buffer, s32 len, int time_pos, int mode);
void StopOgg();
void PauseOgg(int pause);
int StatusOgg();
void SetVolumeOgg(int volume);
s32 GetTimeOgg();
void SetTimeOgg(s32 time_pos);

Aunque las funciones necesarias son únicamente PlayOgg y StopOgg.

Al igual que MP3Player, utiliza el canal 0 de ASNDlib por lo que deberemos tener precaución de no reproducir un archivo ogg simultaneamente que un archivo mp3.

A diferencia de MP3Player no es necesario llamar a funciones para inicializar o parar la biblioteca.

La función PlayOgg sirve para reproducir un archivo ogg previamente cargado en memoria. De nuevo aquí podríamos usar GRRLIB_LoadFile para cargar el fichero ogg.

El format de PlayOgg es el siguiente:

int PlayOgg(const void *buffer, s32 len, int time_pos, int mode);
 
buffer: variable que contiene el archivo ogg
len: tamaño del archivo
time_pos: tiempo inicial en milisegundos por el que comenzará la reproducción
mode: uno de los siguientes valores
 
OGG_ONE_TIME - Reproduce el archivo una sola vez
OGG_INFINITE_TIME - Reproduce el archivo indefinidamente
 
Devuelve -1 en caso de error y 0 si termina con éxito. 

Un ejemplo de reproducción de un archivo ogg sería el siguiente:

#include <oggplayer.h>
 
void *buf_mp3;
s32 mp3_size;
 
int main(int argc, char **argv) {
	ASND_Init();
	ASND_Pause(0);	
	while(1){
		WPAD_ScanPads();  // Leer mandos
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME)  break;
		mp3_size = GRRLIB_LoadFile (nombre, (unsigned char**) &buf_mp3);
		PlayOgg(buf_mp3, mp3_size, 0, OGG_INFINITE_TIME);
	}
}

ALGUNOS CAMBIOS EN EL ARCHIVO MAKEFILE

Hasta el momento hemos utilizado los archivos makefile que he suministrado con el curso sin hacer ningún cambio en ellos, pero en esta entrega utilizamos ciertas bibliotecas que requieren hacer algunos cambios respecto al makefile que utilizábamos hasta ahora para que podamos compilar nuestro programa.

¿Que es un archivo makefile?

Los makefiles son los ficheros de texto que utiliza make para llevar la gestión de la compilación de programas. Se podrían entender como los guiones de la película que quiere hacer make, o la base de datos que informa sobre las dependencias entre las diferentes partes de un proyecto.

Reglas

Todos los Makefiles están ordenados en forma de reglas, especificando qué es lo que hay que hacer para obtener un módulo en concreto. El formato de cada una de esas reglas es el siguiente:

objetivo: dependencias
        comandos

En “objetivo” definimos el módulo o programa que queremos crear, después de los dos puntos y en la misma línea podemos definir qué otros módulos o programas son necesarios para conseguir el “objetivo”. Por último, en la línea siguiente y sucesivas indicamos los comandos necesarios para llevar esto a cabo.

Un ejemplo de regla prodría ser la siguiente:

#---------------------------------------------------------------------------------
# This rule links in binary data with the .jpg extension
#---------------------------------------------------------------------------------
%.jpg.o	:	%.jpg
#---------------------------------------------------------------------------------
	@echo $(notdir $<)
	$(bin2o)

Comentarios

Los comentarios en un archivo makefile comienzan con el caracter "#". Todas las líneas que comiencen por dicho caracter serán ignoradas por make. En el ejemplo anterior podíamos ver algunos comentarios de ejemplo.

Como en todo, cuanto más comentado esté el archivo mejor.

Variables

Es muy habitual que existan variables en los ficheros makefile, para facilitar su portabilidad a diferentes plataformas y entornos. La forma de definir una variable es muy sencilla, basta con indicar el nombre de la variable (típicamente en mayúsculas) y su valor, de la siguiente forma:

LIBS	:= -lgrrlib
LIBS	+= -lasnd -lvorbisidec -lmad
LIBS	+= -lfreetype
LIBS	+= -lpngu -lpng -ljpeg -lz -lfat
LIBS	+= -lwiiuse
LIBS	+= -lbte -logc -lm

Como vemos en el ejemplo utilizamos ":=" como operador de asignación y "+=" para añadir texto a dicha variable.

Cuando queramos acceder al contenido de esa variable, lo haremos así:

($LIBS)

Además de las variables definidas en el propio Makefile, es posible hacer uso de las variables de entorno, accesibles desde el intérprete de comandos. Esto puede dar pie a formulaciones de este estilo:

TARGET		:=	$(notdir $(CURDIR))

Reglas virtuales

Es relativamente habitual que además de las reglas normales, los ficheros makefile puedan contener reglas virtuales, que no generen un fichero en concreto, sino que sirvan para realizar una determinada acción dentro de nuestro proyecto software. Normalmente estas reglas suelen tener un objetivo, pero ninguna dependencia.

run:
	wiiload $(TARGET).dol

Estructura de nuestro particular Makefile

Hay algunos aspectos de nuestro makefile que puede ser conv eniente conocer si queremos programar para Wii. Seguidamente os doy algunos pequeños apuntes de dichos aspectos.

Una de las primeras cosas que nos encontramos al principio de nuestro archivo es la línea:

include $(DEVKITPPC)/wii_rules

Dicha linea indica a make que incluya todas las reglas por defecto para la programación para Wii.

Si en lugar de un programa para Wii estuvieramos programando para gamecube deberíamos sustituir wii_rules por gamecube_rules

El siguiente conjunto de líneas asignará las variables que gestionarán las carpetas en donde make buscará los archivos y generará los archivos intermedios y los ejecutables:

#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET		:=	$(notdir $(CURDIR))
BUILD		:=	build
SOURCES		:=	source
DATA		:=	data  
INCLUDES	:=

Una variable muy importante es la variable LIBS, que le indicará a make qué bibliotecas deberá enlazar en el ejecutable,

LIBS	:= -lgrrlib
LIBS	+= -lasnd -lvorbisidec -lmad
LIBS	+= -lfreetype
LIBS	+= -lpngu -lpng -ljpeg -lz -lfat
LIBS	+= -lwiiuse
LIBS	+= -lbte -logc -lm

Las reglas build, clean y run, le indicarán a make como deberá compilar, borrar los archivos intermedios o ejecutar un programa recien compilado.

Un poco más abajo nos encontraremos las reglas que le indican a make como enlazar los datos que guardamos en la carpeta data.

#---------------------------------------------------------------------------------
# This rule links in binary data with the .jpg extension
#---------------------------------------------------------------------------------
%.jpg.o	:	%.jpg
#---------------------------------------------------------------------------------
	@echo $(notdir $<)
	$(bin2o)

¿Y entonces qué cambios debemos hacer en nuestro archivo makefile?

Como os dije antes, para el usar las bibliotecas de la presente entrega tenemos que hacer unos pequeños cambios en el archivo makefile. Dichos cambios se centran únicamente en la variable LIBS que ahora debe incluir tambien las nuevas bibliotecas que estamos usando.

El nombre de cada una de las bibliotecas debe ir precedido de "-l".

Hay que tener en cuenta que el orden en el que aparecen las bibliotecas en LIBS es importante ya que unas pueden hacer uso de las otras y hay que indicar en primer lugar las que no tienen dependencias de las demás.

Nuestro cambio respecto del anterior makefile consistirá únicamente en añadir las bibliotecas asnd, vorbisidec y mad justo después de la biblioteca grrlib.

LIBS	:= -lgrrlib
LIBS	+= -lasnd -lvorbisidec -lmad

UN PROGRAMA DE EJEMPLO - VISUALIZADOR DE FOTOS


Y por fin, después de haberos expuesto toda la teoría, llega el momento de aplicar lo estudiado en un caso práctico.

Siguiendo la tónica de los anteriores tutoriales, he buscado un ejemplo que nos permita repasar lo estudiado hasta el momento y por supuesto practicar con lo nuevo que hemos aprendido en el presente capitulo.

Por ese motivo he seleccionado un visualizador de música y fotos. En dicho programa necesitaremos aplicar lo siguiente:

  • Acceder a la lista de archivos mediante las funciones de directorio para buscar los archivos de imagenes y música.
  • Leer y escribir de un archivo de texto para guardar la posición de la última utilización del programa.
  • Reproducción de un sonido raw correspondiente a la pulsación de los botones de la pantalla.
  • Reproducción de música MP3 y OGG.
  • Repaso de las funciones de manejo de tiles y texturas para la interface gráfica y la visualización de archivos de imágenes.
  • Manejo del IR y de los botones del Wiimote.

El programa no es una utilidad completa debido a las limitaciones del uso de texturas, que no permiten imágenes que no tengan resoluciones múltiplos de 4, y además dichas imágenes deben ser de una resolución inferior a 1024x1024, pero cumple el propósito pedagógico para el que está escrito.

Manejo del programa

Como hemos dicho antes, el programa permitirá la visualización de archivos de imagen y la reproducción de archivos de música. Para ello dispondremos de una ventana en el centro de la pantalla destinada a la visualización de imágenes. En dicha ventana aparecerán tambien sendas imágenes preasigandas para carpetas y archivos de música.

Dispondremos de una serie de botones de comando repartidos alrededor de la ventana de visualización y de una línea que nos indicará en todo momento el archivo que se está visualizando en este momento.

Las flechas verdes de la parte inferior nos permitirán movernos por los diferentes archivos de la carpeta activa.

Para cambiar la carpeta activa, solo deberemos buscar con las flechas la subcarpeta deseada y pinchar sobre ella.

Para cambiar de dispositivo tenemos disponibles los iconos USB, SD y DVD a la derecha de la pantalla.

Pinchando sobre la carpeta con la flecha del margen izquierdo retrocederemos a la carpeta anterior.

Según nos desplazamos por los diferentes archivos, las imagenes serán visualizadas automáticamente, mientras que los archivos de música precisarán de pulsar en mario para que sean reproducidos.

Si el archivo actual es una imagen podremos utilizar los siguientes comandos:

  • Lupa: pulsando A sobre la imagen haremos un zoom de un 33% y con B alejaremos la imagen en la misma proporción.
  • Mano: Con la mano podremos mover la ventana visible de la imagen.
  • Las flechas curvadas de abajo nos permitirán girar la imagen a izquierda y derecha. 
  • Pulsando sobre Mario obtendremos una presentación de todas las imagenes de la carpeta actual.

Si el archivo actual es un archivo de música, podremos reproducirlo pinchando sobre Mario o sobre la imagen del archivo.

Por último pulsando la puerta de arriba a la izquierda o con la tecla Home saldremos del programa.

Explorando el código

Lo primero que hacemos, como siempre, es incluir las bibliotecas necesarias. En este caso hemos añadido la biblioteca ogc/lwp_watchdog que incluye la función gettime necesaria para controlar el tiempo de espera durante la presentación de diapositivas.

Además añadimos las bibliotecas asndlib, mp3player, oggplayer para la gestión de sonidos y música.

#include <grrlib.h>
#include <stdlib.h>
#include <ogc/lwp_watchdog.h>			   // Para gettime y ticks_to_millisecs
#include "asndlib.h"
#include <mp3player.h>
#include "oggplayer.h"
#include "click.h"
#include <wiiuse/wpad.h>
#include "botones_png.h"
#include "pointers_png.h"
#include "marco_png.h"
#include "carpeta_png.h"
#include "musica_png.h"
#include "error_png.h"
#include <dirent.h>
#include "arial_black_12_png.h"

"click.h" contiene el sonido que utilizaremos al pulsar cualquier botón. Además necesitaremos todas las texturas del entorno y la fuente bitmap.

Detrás de la inclusión de las bibliotecas declaramos algunas constantes y variables que utilizaremos en el programa:

// Colores RGBA
#define GRRLIB_BLACK   0x000000FF
#define GRRLIB_MAROON  0x800000FF
#define GRRLIB_GREEN   0x008000FF
#define GRRLIB_OLIVE   0x808000FF
#define GRRLIB_NAVY    0x000080FF
#define GRRLIB_PURPLE  0x800080FF
#define GRRLIB_TEAL    0x008080FF
#define GRRLIB_GRAY    0x808080FF
#define GRRLIB_SILVER  0xC0C0C0FF
#define GRRLIB_RED     0xFF0000FF
#define GRRLIB_LIME    0x00FF00FF
#define GRRLIB_YELLOW  0xFFFF00FF
#define GRRLIB_BLUE    0x0000FFFF
#define GRRLIB_FUCHSIA 0xFF00FFFF
#define GRRLIB_AQUA    0x00FFFFFF
#define GRRLIB_WHITE   0xFFFFFFFF
#define GRRLIB_LBLUE   0x5050FFFF
 
#define MAXARCHIVOS 500
#define MAXBOTONES 12
#define WBOTON 80
#define HBOTON 100
#define HPOINTER 44
#define WPOINTER 32
#define MAXCOLA 10
#define ZSTEP 1.3
 
#define TIPO_CARPETA 0
#define TIPO_IMAGEN  1
#define TIPO_MUSICA  2
 
int resx = 640; int resy=528;
float sy = 1.0;
float sfx=1.0;
float sfy=1.0;
int stx=0;
int sty=0;
int pcx=0;
int pcy=0;
int angulo = 0;
 
ir_t ir;
int archivo_actual = 0;
int num_archivos = 0;
bool leido = 0;
float zoom = 1;
 
void *buf_mp3;
s32 mp3_size;
bool sonando = false;
 
s32 voz;
 
 
GRRLIB_texImg* tex_font;
GRRLIB_texImg* tex_botones;
GRRLIB_texImg* tex_marco;
GRRLIB_texImg* tex_carpeta;
GRRLIB_texImg* tex_musica;
GRRLIB_texImg* tex_error;
GRRLIB_texImg* tex_pointers;
GRRLIB_texImg* tex_foto;
 
DIR *pdir;
struct dirent *pent;
struct stat statbuf;
int cursoractivo = 1;
 
char lista[MAXARCHIVOS][300];
u8 tipos[MAXARCHIVOS];
 
char DirActivo[150];
char ext[30];
char nombre[500];
int i;

Tras lo cual encontraremos algunas funciones que serán usadas desde el main, sin embargo vamos a saltar directamente al main para facilitar la comprensión de la estructura del programa.

Entrando en el main lo primero que debemos hacer es inicializar las bibliotecas que lo requieren, que en este caso son GRRLIB, ASNDlib, y MP3Player.

int main(int argc, char **argv) {
 
 
	// Inicializar gráficos
	GRRLIB_Init();
 
	ASND_Init();
	ASND_Pause(0);	
 
	MP3Player_Init();
 
	Leeconfiguracion();

Lo siguiente que haremos será llamar a la función Leeconfiguracion que leerá el archivo "photoviewer.ini" situado en la misma carpeta donde se encuentre el ejecutable.

Veamos el contenido de dicha función:

void Leeconfiguracion(){
	FILE* f = fopen("photoviewer.ini", "r");
	if (f != NULL) {
		fscanf(f, "carpeta: %s\n", DirActivo);
		fscanf(f, "archivo: %d\n", & archivo_actual);
		fclose(f);
	} else {
		strcpy(DirActivo,"sd:/");
		archivo_actual = 0;
	}
 
}

Básicamente la función abre el archivo "photoviewer.ini" en modo solo lectura leyendo, mediante la función fscanf las líneas donde habíamos guardado la carpeta y el archivo activo de la sesión anterior.

A continuación obtenemos automáticamente la resolución horizontal y vertical. Con objeto de que nuestro programa tenga el mismo aspecto independientemente de la resolución de nuestra Wii, diseñaremos todas las texturas para una resolución vertical de 528 píxeles y calcularemos una variable de escalado vertical "sy" que resultará de dividir la resolución vertical de nuestra Wii por 528.

    resx = rmode->fbWidth;
    resy = rmode->efbHeight;
    sy = resy/528.0;
 

Para asegurarnos que la voz que usamos está libre usaremos la función ASND_GetFirstUnusedVoice y guardaremos el resultado en la variable voz.

voz = ASND_GetFirstUnusedVoice();

Inicializamos todo lo relacionado con los Wiimotes:

	// Inicializar wiimotes
	WPAD_Init();
	WPAD_SetDataFormat(WPAD_CHAN_ALL, WPAD_FMT_BTNS_ACC_IR);
	WPAD_SetVRes(WPAD_CHAN_ALL, resx, resy);

Y cargamos las texturas del entorno gráfico y las fuentes bitmap. En el caso de los botones dispondremos de dos filas de botones que corresponderan con el aspecto del botón sin pulsar y pulsado respectivamente.

Para facilitar la ubicación en pantalla de los botones y los iconos de carpeta, musica y fichero erroneo utilizaremos la función GRRLIB_SetMidHandle para que las coordenadas referidas a dichos elementos tomen como referencia el centro de la textura en lugar de la esquina superior izquierda.

Por último, antes de entrar en el while, leeremos por primera vez la lista de archivos de la carpeta actual. Si es la primera vez que usamos el programa la carpeta actual será la carpeta raiz de la SD, en caso contrario la que hayamos leido del fichero phoviewer.ini.

Veamos el contenido de la función leecarpeta:

void leeCarpeta(){
 
	char ext[30];
	struct dirent *pent;
	char nc[500];
 
	pdir=opendir(DirActivo);
	if (!pdir){
		GRRLIB_Printf (10, 450, tex_font, GRRLIB_WHITE, 1, "fallo abriendo la carpeta");
	}
 
	for(i=0; i<MAXARCHIVOS;i++)
		lista[i][0]=0;
	i=0;
	while ((pent=readdir(pdir))!=NULL) {
		getext(pent->d_name, ext);
		nc[0]=0;
		strcat(nc,DirActivo);
		strcat(nc,pent->d_name);
		stat(nc,&statbuf);
	    if(strcmp(".", pent->d_name) == 0 || strcmp("..", pent->d_name) == 0)
	        continue;
		if (  // es una imagen
			(strcmp(ext,".png")==0) ||
//			(strcmp(ext,".jpg")==0) ||
			(strcmp(ext,".bmp")==0)
			){
			strcpy(lista[i],pent->d_name);
			tipos[i] = TIPO_IMAGEN;
			i++;
		} else if (  // es un archivo de musica
			(strcmp(ext,".ogg")==0) ||
			(strcmp(ext,".mp3")==0)) {
			tipos[i] = TIPO_MUSICA;
			strcpy(lista[i],pent->d_name);
			i++;
		} else if (S_ISDIR(statbuf.st_mode)) { // es una subcarpeta
			tipos[i] = TIPO_CARPETA;
			strcpy(lista[i],pent->d_name);
			i++;
		} 
 
	}
	num_archivos = i;
	archivo_actual = 0;
}

El objetivo de dicha función es leer el contenido de la carpeta activa (guardada en DirActivo) y guardarlo en el array lista, de tal manera que pueda ser recorrido hacia adelante y hacia atrás con facilidad.

Para ello lo primero que hacemos es abrir la carpeta con opendir, borrar el contenido del array y ya dentro del while leer repetidamente los archivos de la carpeta actual con readdir.

Como véis en el código opendir devuelve una variable de fichero que después usaremos cuando llamamos a readdir. Esta a su vez nos devolverá una estructura tipo dirent con la información del archivo. El programa repetirá el bucle tantas veces como archivos existan en la carpeta, momento en el cual readdir devolverá NULL.

Dentro del while y para cada archivo, llamaremos a una función (getext) que hemos creado al efecto, que extrae la extensión del archivo para comprobar de que tipo de archivo se trata.

Si se trata de un archivo tipo imagen lo añadiremos al array lista y asignaremos "TIPO_IMAGEN" a la entrada correspondiente del array tipos, y lo mismo haremos para los archivos de música, asignando "TIPO_MUSICA" al array tipos.

También deberemos comprobar si es una carpeta, asignando esta vez TIPO_CARPETA al array tipos.

Cualquier otro tipo de archivo no será añadido a la lista.

Como siempre, dentro del while leeremos los wiimotes.

Seguidamente guardaremos en la variable ext la extensión del archivo actual (el que se está visualizando), y en la variable nombre el nombre completo incluida la ruta, es decir el dispositivo y toda la lista de carpetas y subcarpetas hasta llegar al archivo.

getext(lista[archivo_actual],ext);
 
nombre[0]=0;
strcat(nombre,DirActivo);
strcat(nombre,lista[archivo_actual]);

La siguiente lista de condiciones tiene como objetivo determinar la imagen que se visualizará dependiendo del tipo de archivo:

if (tipos[archivo_actual]==TIPO_CARPETA){
	cursoractivo = 0;
	GRRLIB_DrawImg(320,210*sy,tex_carpeta,0,0.5,sy*0.5,GRRLIB_WHITE);
	if (GRRLIB_PtInRect(65,20,475,365*sy,ir.x,ir.y*sy) && 
		(WPAD_ButtonsDown(0) & WPAD_BUTTON_A)){
		strcat(DirActivo, lista[archivo_actual]);
		strcat(DirActivo, "/");
		leeCarpeta();
	}
} else if (tipos[archivo_actual]==TIPO_IMAGEN){
	if(!leido) {
		angulo = 0;
		cursoractivo = 1;
		leeimagen(archivo_actual);
		leido = true;
	}
	if (tex_foto != NULL){
		GRRLIB_SetMidHandle(tex_foto, true);
		if ((angulo % 180) == 0){
			sfx = 480.0/tex_foto->w;
			sfy = 365.0/tex_foto->h;
		} else {
			sfy = 480.0/tex_foto->h;
			sfx = 365.0/tex_foto->w;
		}
	}
	if (tex_foto != NULL)
		GRRLIB_DrawImg(320-stx,(210-sty)*sy,tex_foto,angulo,sfx*zoom,sy*sfy*zoom,GRRLIB_WHITE);
	else
		GRRLIB_DrawImg(320,210*sy,tex_error,0,1,sy*1.0,GRRLIB_WHITE);
} else if (tipos[archivo_actual]==TIPO_MUSICA) {
		GRRLIB_DrawImg(320,210*sy,tex_musica,0,0.5,sy*0.5,GRRLIB_WHITE);
}

Para el caso de que se trate de una carpeta o un archivo de música dibujaremos los iconos que tenemos preparados para cada caso. En el caso de que se trate de una imagen, leeremos la imagen y la visualizaremos.

Para el caso de las carpetas, tambien comprobaremos si se ha pulsado el botón A sobre la ventana de visualización, en cuyo caso cambiaremos a dicha carpeta y volveremos a leer el contenido de la nueva carpeta activa.

Como hemos dicho antes, para las imagenes debemos cargar la imagen correspondiente al archivo actual. Para realizar dicha acción crearemos la función leeimagen:

void leeimagen(int i){
	if (tex_foto!=NULL)
		GRRLIB_FreeTexture(tex_foto);
 
	nombre[0]=0;
	strcat(nombre,DirActivo);
	strcat(nombre,lista[i]);
	tex_foto = GRRLIB_LoadTextureFromFile(nombre);
	GRRLIB_SetMidHandle(tex_foto,true);
}

La función leeimagen es muy sencilla. Al inicio libera la memoria de la variable de textura en el caso de que ya hubiera sido utilizada (!= NULL), y posteriormente carga la imagen usando GRRLIB_LoadTextureFromFile. Utilizamos la variable booleana leido para evitar leer la imagen de disco más de una vez. Cuando leemos una imagen ponemos la variable leido a true y no volvemos a leer de disco hasta que dicha variable sea false.

Aunque teóricamente debe funcionar bien con jpegs (siempre que su resolución sea divisible por 4), tengo que decir que me he encontrado con algunos archivos jpg hacen que la función GRRLIB_LoadTectureFromFile se cuelgue. Según he leido en algunos foros, puede tener que ver con los valores de terminación del archivo.

Una vez leida la imagen pasaremos a calcular el escalado necesario para ajustar la imagen, tenga el tamaño que tenga, a la ventana de visualización. Tendremos que tener en cuenta que la imagen puede haber sido girada por lo que al escalar deberán intercambiarse los ejes.

if (tex_foto != NULL){
	GRRLIB_SetMidHandle(tex_foto, true);
	if ((angulo % 180) == 0){
		sfx = 480.0/tex_foto->w;
		sfy = 365.0/tex_foto->h;
	} else {
		sfy = 480.0/tex_foto->h;
		sfx = 365.0/tex_foto->w;
	}
}

Una vez calculado el escalado, podemos pasar a visualizar la imagen recien leida, mostrando en caso de error la imagen cargada en tex_error:

if (tex_foto != NULL)
	GRRLIB_DrawImg(320-stx,(210-sty)*sy,tex_foto,angulo,sfx*zoom,sy*sfy*zoom,GRRLIB_WHITE);
else
	GRRLIB_DrawImg(320,210*sy,tex_error,0,1,sy*1.0,GRRLIB_WHITE);

En el caso de que se trate de un archivo de música, simplemente deberemos mostrar la imagen guardada en tex_musica:

} else if (tipos[archivo_actual]==TIPO_MUSICA) {
	GRRLIB_DrawImg(320,210*sy,tex_musica,0,0.5,sy*0.5,GRRLIB_WHITE);
}

Inmediatamente después e independientemente del tipo de archivo mostraremos el nombre del archivo actual y la imagen que hemos incluido como marco.

char dispname[50];
gendispname(nombre, dispname);
GRRLIB_Printf(640/2-strlen(dispname)/2*16,390, tex_font, GRRLIB_YELLOW, 1, dispname);
 
GRRLIB_DrawImg(0,0,tex_marco,0,1,sy,GRRLIB_WHITE);

La función gendispname la hemos diseñado para que se encargue de calcular el nombre que deberemos visualizar, teniendo en cuenta que si el nombre es más largo de lo que cabe en la pantalla, visualizaremos el principio y el final del nombre e insertaremos unos puntos suspensivos en el centro.

void gendispname(char* name, char* dispname){
	int i;
	int len = strlen(name);
	if (len < 30) strcpy(dispname, nombre);
	else {
		dispname[12]=dispname[16]=' ';
		dispname[13]=dispname[14]=dispname[15]='.';
		for (i=0; i<12; i++){
			dispname[i]=name[i];
			dispname[17+i]=name[len-12+i];
		}
		dispname[29]=0;
	}
}

Una vez hemos terminado con la sección de código que se encarga de visualizar las diferentes texturas, pasamos a la gestión de los botones.

Para dicha gestión crearemos una función genérica que gestiona la visualización de los botones y vigila su pulsación:

bool GraphicButton(u32 xb, u32 yb, u32 ntile, int w, int h){
	int tile = ntile;
	if ((cursoractivo != 2) && (GRRLIB_PtInRect(xb-w/2,yb*sy-h*sy/2,w,h*sy,ir.x,ir.y*sy))){
		GRRLIB_Rectangle(xb-w/2,yb*sy-h*sy/2,w,h*sy,GRRLIB_GREEN,false);
		if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_A) tile = ntile+MAXBOTONES;
		if (WPAD_ButtonsUp(0) & WPAD_BUTTON_A) return true;
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) snd_click();
	}
	GRRLIB_DrawTile(xb,yb*sy,tex_botones,0,1,sy,GRRLIB_WHITE,tile);
	return false;
}

La función GraphicButton recibe como parámetros la posición del botón en la pantalla, el número de tile dentro del tileset de botones y la anchura y altura del rectangulo que deberemos dibujar cuando pasemos el puntero por encima.

La función mostrará el botón en estado de reposo (ntile) cuando no esté siendo pulsado y en su estado pulsado (ntile+MAXBOTONES) en el caso de que lo estemos pulsando.

Para ello comprobaremos si el puntero está sobre el botón con la función GRRLIB_PtInRect, en cuyo caso dibujaremos un rectángulo de color verde alrededor del botón, a excepción de que estemos arrastrando la imagen (cursoractivo == 2).

Dentro de la condición anterior comprobaremos lo siguiente:

  1. Si el botón A acaba de ser pulsado (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) reproducimos el sonido click
  2. Si el botón A continúa manteniendose pulsado (WPAD_ButtonsHeld(0) & WPAD_BUTTON_A) dibujamos el botón en estado pulsado (ntile+botones).
  3. Si soltamos el botón A (WPAD_ButtonsUp(0) & WPAD_BUTTON_A) devolvemos true para que pueda ejecutarse la acción asociada al botón. 

Para ejecutar el sonido hemos creado la función snd_click que simplemente utiliza ASND_SetVoice para reproducir la muestra de sonido que hemos enlazado en el programa en la variable click.

void snd_click(void){
   ASND_SetVoice( voz, VOICE_MONO_8BIT, 8000, 0, click, click_size, 255, 255, NULL);
}

Volviendo al main lo primero que hacemos es implementar las acciones asociadas a la pulsación de Mario, que son la presentación de imagenes en el caso de que el archivo actual sea una imagen, y la reproducción del archivo en el caso de que sea un archivo de música:

if (GraphicButton(320,475*sy,2,80,100)){ // Mario
	if (tipos[archivo_actual]==TIPO_IMAGEN) Presentacion();
	if (tipos[archivo_actual]==TIPO_MUSICA) {
		if (!sonando) Escuchar_musica();
		else if (sonando) Parar_musica();
	}
}

Veamos primero la función Presentación:

void Presentacion(){
	int i = archivo_actual;
	leeimagen(i);
	time_t timer = init_timer(2000);
	while(1){
		WPAD_ScanPads();  // Leer mandos
 
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_B) break; //Si se pulsa B salimos
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) break; //Si se pulsa Home salimos
 
		if (timer_finished(timer) || WPAD_ButtonsDown(0) & WPAD_BUTTON_A){
			i = siguienteimagen(i);
			leeimagen(i);
			timer = init_timer(2000);
		}
		float sfx = 640.0/tex_foto->w;
		float sfy = 528.0/tex_foto->h;
		GRRLIB_DrawImg(320.0,528.0/2.0*sy,tex_foto,0,sfx,sy*sfy,GRRLIB_WHITE);
		GRRLIB_Printf(640.0/2.0-strlen(lista[i])/2.0*16.0,0, tex_font, GRRLIB_YELLOW, 1, lista[i]);
		GRRLIB_Render();  // Refrescamos la pantalla
    }
	leeimagen(archivo_actual);
}

La función va mostrando todos los archivos de imagen de la carpeta actual, a intervalos de 2 segundos, hasta que pulsemos B o Home.

Si pulsamos A pasamos directamente al siguiente archivo.

Se han implementado las funciones init_timertimer_finished que permiten gestionar temporizadores sin tener que parar la ejecución del programa. Dichas funciones se basan en la utilización de la función gettime de la biblioteca lwp_watchdog:

time_t getCurrentMilliseconds() {
	return ticks_to_millisecs(gettime());
}
 
time_t init_timer(time_t ms){
	time_t act = getCurrentMilliseconds();
	return act+ms;
}
 
bool timer_finished(time_t timer){
	time_t act = getCurrentMilliseconds();
	if (act >= timer) return true;
	else return false;
}

Volvamos de nuevo al main para ver la gestión del botón de Mario cuando estamos ante un archivo de música. En este caso llamamos a las funciones Escuchar_musica la primera vez y Parar_musica si lo volvemos a pulsar:

void Escuchar_musica(){
	mp3_size = GRRLIB_LoadFile (nombre, (unsigned char**) &buf_mp3);
	sonando=true;
	if (strcmp(ext,".ogg")==0){
		PauseOgg(0);
		PlayOgg(buf_mp3, mp3_size, 0, OGG_INFINITE_TIME);
	}
	if (strcmp(ext,".mp3")==0){
		MP3Player_PlayBuffer(buf_mp3, mp3_size, NULL);
	}
}
 
void Parar_musica(){
	if (strcmp(ext,".ogg")==0){
		StopOgg();
	}
	if (strcmp(ext,".mp3")==0){
		if (MP3Player_IsPlaying()) MP3Player_Stop();
	}
	sonando = false;
	if (buf_mp3!=NULL) free(buf_mp3);
}

En la función Escuchar_musica cargamos el archivo en memoria con GRRLIB_LoadFile y después utilizamos PlayOggMP3Player_PlayBuffer para la reproducción del archivo.

En la función Parar_musica utilizamos StopOggMP3Player_Stop para parar la música y después liberamos el buffer donde teníamos el archivo con free.

De nuevo en el main las siguientes líneas hacen que, para los archivos de música, tenga el mismo efecto pulsar sobre la pantalla que sobre el botón de mario:

if (InPicture() && (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)) {
	if (tipos[archivo_actual]==TIPO_MUSICA) {
		if (!sonando) Escuchar_musica();
		else if (sonando) Parar_musica();
	}
}

Como habréis podido observar hemos definido una nueva función InPicture que devielve true cuando el puntero del wiimote está dentro de los límites de la ventana de visualización.

bool InPicture(){
	return GRRLIB_PtInRect(65,20,475,365*sy,ir.x,ir.y*sy);
}

Inmediatamente después nos encontraremos con la gestión de las flechas de adelante y atrás, que mostraran el archivo siguiente y el anterior respectivamente:

if (GraphicButton(220,475*sy,1,80,100)){ // Izquierda
	archivo_actual = archivoanterior(archivo_actual);
	resetparams();
}
if (GraphicButton(420,475*sy,3,80,100)){ // Derecha
	archivo_actual = archivosiguiente(archivo_actual);
	resetparams();
}

Segidamente los botones que nos permitirán girar la imagen a la derecha o a la izquierda:

if (GraphicButton(120,475*sy,8,80,100)){ // girar izquierda
	angulo-= 90;
}
if (GraphicButton(520,475*sy,9,80,100)){ // girar derecha
	angulo+= 90;
}

Las siguientes líneas ponen el puntero en modo lupa:

if (GraphicButton(40,50*sy,7,80,60)){ // lupa
	cursoractivo = 3;
}

Comprobamos después si se ha pulsado el botón de ir a la carpeta anterior

if (GraphicButton(40,160*sy,10,60,80)){ //  anterior
	resetparams();
	extrae_carpeta();
	leeCarpeta();
}

Si pulsamos el botón de la mano ponemos el curosr en modo mover imagen

if (GraphicButton(40,270*sy,11,60,80)){ //  mover
	cursoractivo = 1;
}

Si pulsamos la puerta salimos

if (GraphicButton(600,50*sy,4,60,80)) break; // puerta

Y los últimos botones que nos quedan por gestionar son los de cambio de dispositivo. Esto botones nos permitirán visualizar los archivos de la SD, el USB o el DVD.

if (GraphicButton(595,160*sy,0,60,50)){ // DVD
	strcpy(DirActivo, "dvd:/");
	leeCarpeta();
	resetparams();
}
if (GraphicButton(595,270*sy,5,60,50)){ // SD
	strcpy(DirActivo, "sd:/");
	leeCarpeta();
	resetparams();
}
if (GraphicButton(595,380*sy,6,60,50)){ // USB
	strcpy(DirActivo, "usb:/");
	leeCarpeta();
	resetparams();
}

Seguidamente pasamos a gestionar las pulsaciones de los botones del wiimote según el cursor activo.

Empezamos con el modo mover imagen (cursoractivo == 1). Tenemos el cursor en modo mano abierta.

Si pulsamos A sobre la imagen memorizamos la posición del puntero y pasamos a modo moviendo (cursoractivo==2) que se traduce en una mano cerrada.

A partir de ese momento caculamos la posición de la imagen teniendo en cuenta la posición en donde pulsamos A.

// estamos en modo mover imagen
if (cursoractivo==1){
	if (InPicture())
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A){
			cursoractivo=2;
			pcx = ir.x+stx; // memorizamos la posición del cursor cuando pulsamos A
			pcy = ir.y+sty ;
		}
}
// estamos moviendo una imagen
if (cursoractivo==2){
	if (InPicture()) {
		if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_A){
			stx= pcx-ir.x; // calculamos lo que hemos movido el cursor desde
			sty= pcy-ir.y; // que hemos pulsado A
		} else cursoractivo = 1;
	}
}
 

Si el cursor activo es igual a 3 nos encontramos en modo lupa. En este modo si pulsamos A ampliamos la imagen en un factor igual a ZSTEP.

if (cursoractivo==3)
	if (InPicture()){
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)
			zoom*=ZSTEP;
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_B)
			zoom/=ZSTEP;
	}
 

Por último dibujamos el cursor activo que forzaremos a la tradicional mano de Wii en el caso de que estemos situados fuera de los límites de la fotografía.

if (InPicture())
	GRRLIB_DrawTile(ir.x,ir.y*sy,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,cursoractivo);
else
	GRRLIB_DrawTile(ir.x,ir.y*sy,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,0);

Ya saliendo del bucle y terminando el programa, liberamos la memoria utilizada por las texturas y guardamos la configuración:

	Guardaconfiguracion();
 
	GRRLIB_FreeTexture(tex_botones);
	GRRLIB_FreeTexture(tex_marco);
	GRRLIB_FreeTexture(tex_pointers);
	GRRLIB_FreeTexture(tex_foto);
	GRRLIB_FreeTexture(tex_carpeta);
	GRRLIB_FreeTexture(tex_musica);
	GRRLIB_FreeTexture(tex_error);
 
    GRRLIB_Exit(); // Liberamos memoria
 
    exit(0);  // Usar exit para salir del program (no usar return desde el main)

La función Guardaconfiguracion es my similar a LeeConfiguración que ya vimos al principio, solo que esta vez abriremos el archivo en modo escritura "w" y escribiremos la información de la carpeta y el archivo actual en el archivo photoviewer.ini.

void Guardaconfiguracion(){
	FILE* f = fopen("photoviewer.ini", "w");
	if (f != NULL) {
		fprintf(f, "carpeta: %s\n", DirActivo);
		fprintf(f, "archivo: %d\n", archivo_actual);
		fclose(f);
	}
}

Al final el programa completo quedaría como sigue:

#include <grrlib.h>
#include <stdlib.h>
#include <ogc/lwp_watchdog.h>			   // Para gettime y ticks_to_millisecs
#include "asndlib.h"
#include <mp3player.h>
#include "oggplayer.h"
#include "click.h"
 
 
#include <wiiuse/wpad.h>
#include "botones_png.h"
#include "pointers_png.h"
#include "marco_png.h"
#include "carpeta_png.h"
#include "musica_png.h"
#include "error_png.h"
#include <dirent.h>
#include "arial_black_12_png.h"
 
// Colores RGBA
#define GRRLIB_BLACK   0x000000FF
#define GRRLIB_MAROON  0x800000FF
#define GRRLIB_GREEN   0x008000FF
#define GRRLIB_OLIVE   0x808000FF
#define GRRLIB_NAVY    0x000080FF
#define GRRLIB_PURPLE  0x800080FF
#define GRRLIB_TEAL    0x008080FF
#define GRRLIB_GRAY    0x808080FF
#define GRRLIB_SILVER  0xC0C0C0FF
#define GRRLIB_RED     0xFF0000FF
#define GRRLIB_LIME    0x00FF00FF
#define GRRLIB_YELLOW  0xFFFF00FF
#define GRRLIB_BLUE    0x0000FFFF
#define GRRLIB_FUCHSIA 0xFF00FFFF
#define GRRLIB_AQUA    0x00FFFFFF
#define GRRLIB_WHITE   0xFFFFFFFF
#define GRRLIB_LBLUE   0x5050FFFF
 
#define MAXARCHIVOS 500
#define MAXBOTONES 12
#define WBOTON 80
#define HBOTON 100
#define HPOINTER 44
#define WPOINTER 32
#define MAXCOLA 10
#define ZSTEP 1.3
 
#define TIPO_CARPETA 0
#define TIPO_IMAGEN  1
#define TIPO_MUSICA  2
 
int resx = 640; int resy=528;
float sy = 1.0;
float sfx=1.0;
float sfy=1.0;
int stx=0;
int sty=0;
int pcx=0;
int pcy=0;
int angulo = 0;
 
ir_t ir;
int archivo_actual = 0;
int num_archivos = 0;
bool leido = 0;
float zoom = 1;
 
void *buf_mp3;
s32 mp3_size;
bool sonando = false;
 
s32 voz;
 
 
GRRLIB_texImg* tex_font;
GRRLIB_texImg* tex_botones;
GRRLIB_texImg* tex_marco;
GRRLIB_texImg* tex_carpeta;
GRRLIB_texImg* tex_musica;
GRRLIB_texImg* tex_error;
GRRLIB_texImg* tex_pointers;
GRRLIB_texImg* tex_foto;
 
DIR *pdir;
struct dirent *pent;
struct stat statbuf;
int cursoractivo = 1;
 
char lista[MAXARCHIVOS][300];
u8 tipos[MAXARCHIVOS];
 
char DirActivo[150];
char ext[30];
char nombre[500];
int i;
 
void snd_click(void){
   ASND_SetVoice( voz, VOICE_MONO_8BIT, 8000, 0, click, click_size, 255, 255, NULL);
}
 
 
void getext(char* name, char* ext){
	int pos = 0;
	char* tmp;
	bool fin = false;
 
	// busca el primer punto empezando desde el final
	ext[0]=0;
	pos = strlen(name)-1;
	while (!fin){
		if (name[pos]=='.'){
			tmp=name+pos;
			strcpy(ext,tmp);
			fin = true;
		} else if (pos==0)
			fin = true;
		else
			pos--;
	}
 
	// convierte a minusculas
	for (pos=0; pos<strlen(ext); pos++){
		if ((ext[pos]>='A') && (ext[pos]<='Z'))
			ext[pos]+='a'-'A';
	}
 
 
}
 
 
void leeCarpeta(){
 
	char ext[30];
	struct dirent *pent;
	char nc[500];
 
	pdir=opendir(DirActivo);
	if (!pdir){
		GRRLIB_Printf (10, 450, tex_font, GRRLIB_WHITE, 1, "fallo abriendo la carpeta");
	}
 
	for(i=0; i<MAXARCHIVOS;i++)
		lista[i][0]=0;
	i=0;
	while ((pent=readdir(pdir))!=NULL) {
		getext(pent->d_name, ext);
		nc[0]=0;
		strcat(nc,DirActivo);
		strcat(nc,pent->d_name);
		stat(nc,&statbuf);
	    if(strcmp(".", pent->d_name) == 0 || strcmp("..", pent->d_name) == 0)
	        continue;
		if (  // es una imagen
			(strcmp(ext,".png")==0) ||
			(strcmp(ext,".jpg")==0) ||
			(strcmp(ext,".bmp")==0)
			){
			strcpy(lista[i],pent->d_name);
			tipos[i] = TIPO_IMAGEN;
			i++;
		} else if (  // es un archivo de musica
			(strcmp(ext,".ogg")==0) ||
			(strcmp(ext,".mp3")==0)) {
			tipos[i] = TIPO_MUSICA;
			strcpy(lista[i],pent->d_name);
			i++;
		} else if (S_ISDIR(statbuf.st_mode)) { // es una subcarpeta
			tipos[i] = TIPO_CARPETA;
			strcpy(lista[i],pent->d_name);
			i++;
		} 
 
	}
	num_archivos = i;
	archivo_actual = 0;
}
 
void extrae_carpeta(){
	i = strlen(DirActivo)-2;
	while ((DirActivo[i] != '/') && (DirActivo[i] != ':')){
		i--;
	}
	if (DirActivo[i]==':') i++;
	DirActivo[i+1]=0;
}
 
bool GraphicButton(u32 xb, u32 yb, u32 ntile, int w, int h){
	int tile = ntile;
	if ((cursoractivo != 2) && (GRRLIB_PtInRect(xb-w/2,yb*sy-h*sy/2,w,h*sy,ir.x,ir.y*sy))){
		GRRLIB_Rectangle(xb-w/2,yb*sy-h*sy/2,w,h*sy,GRRLIB_GREEN,false);
		if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_A) tile = ntile+MAXBOTONES;
		if (WPAD_ButtonsUp(0) & WPAD_BUTTON_A) return true;
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) snd_click();
	}
	GRRLIB_DrawTile(xb,yb*sy,tex_botones,0,1,sy,GRRLIB_WHITE,tile);
	return false;
}
 
bool InPicture(){
	return GRRLIB_PtInRect(65,20,475,365*sy,ir.x,ir.y*sy);
}
 
time_t getCurrentMilliseconds() {
	return ticks_to_millisecs(gettime());
}
 
time_t init_timer(time_t ms){
	time_t act = getCurrentMilliseconds();
	return act+ms;
}
 
bool timer_finished(time_t timer){
	time_t act = getCurrentMilliseconds();
	if (act >= timer) return true;
	else return false;
}
 
 
 
 
int archivoanterior(int i){
	if (i > 0) 
		i--;
	else i = num_archivos-1;
	return i;
}
 
int archivosiguiente(int i){
	if (i < num_archivos-1) 
		i++;
	else i = 0;
	return i;
}
 
int siguienteimagen(int i){
	int j = archivosiguiente(i);
	while ((j != i) && (tipos[j]!=TIPO_IMAGEN)){
		j = archivosiguiente(j);
	}
	return j;
}
 
void leeimagen(int i){
	if (tex_foto!=NULL)
		GRRLIB_FreeTexture(tex_foto);
 
	nombre[0]=0;
	strcat(nombre,DirActivo);
	strcat(nombre,lista[i]);
	tex_foto = GRRLIB_LoadTextureFromFile(nombre);
	GRRLIB_SetMidHandle(tex_foto,true);
}
 
void Presentacion(){
	int i = archivo_actual;
	leeimagen(i);
	time_t timer = init_timer(2000);
	while(1){
		WPAD_ScanPads();  // Leer mandos
 
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_B) break; //Si se pulsa B salimos
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) break; //Si se pulsa Home salimos
 
		if (timer_finished(timer) || WPAD_ButtonsDown(0) & WPAD_BUTTON_A){
			i = siguienteimagen(i);
			leeimagen(i);
			timer = init_timer(2000);
		}
		float sfx = 640.0/tex_foto->w;
		float sfy = 528.0/tex_foto->h;
		GRRLIB_DrawImg(320.0,528.0/2.0*sy,tex_foto,0,sfx,sy*sfy,GRRLIB_WHITE);
		GRRLIB_Printf(640.0/2.0-strlen(lista[i])/2.0*16.0,0, tex_font, GRRLIB_YELLOW, 1, lista[i]);
		GRRLIB_Render();  // Refrescamos la pantalla
    }
	leeimagen(archivo_actual);
}
 
void Escuchar_musica(){
	mp3_size = GRRLIB_LoadFile (nombre, (unsigned char**) &buf_mp3);
	sonando=true;
	if (strcmp(ext,".ogg")==0){
		PauseOgg(0);
		PlayOgg(buf_mp3, mp3_size, 0, OGG_INFINITE_TIME);
	}
	if (strcmp(ext,".mp3")==0){
		MP3Player_PlayBuffer(buf_mp3, mp3_size, NULL);
	}
}
 
void Parar_musica(){
	if (strcmp(ext,".ogg")==0){
		StopOgg();
	}
	if (strcmp(ext,".mp3")==0){
		if (MP3Player_IsPlaying()) MP3Player_Stop();
	}
	sonando = false;
	if (buf_mp3!=NULL) free(buf_mp3);
}
 
void resetparams(){
	leido = false;
	zoom = 1;
	stx = 0;
	sty = 0;
	if ((tipos[archivo_actual]==TIPO_MUSICA) && sonando) Parar_musica();
}
 
void Leeconfiguracion(){
	FILE* f = fopen("photoviewer.ini", "r");
	if (f != NULL) {
		fscanf(f, "carpeta: %s\n", DirActivo);
		fscanf(f, "archivo: %d\n", & archivo_actual);
		fclose(f);
	} else {
		strcpy(DirActivo,"sd:/");
		archivo_actual = 0;
	}
	if (	feof(f)) archivo_actual = 0;
}
 
void Guardaconfiguracion(){
	FILE* f = fopen("photoviewer.ini", "w");
	if (f != NULL) {
		fprintf(f, "carpeta: %s\n", DirActivo);
		fprintf(f, "archivo: %d\n", archivo_actual);
		fclose(f);
	}
}
 
void gendispname(char* name, char* dispname){
	int i;
	int len = strlen(name);
	if (len < 30) strcpy(dispname, nombre);
	else {
		dispname[12]=dispname[16]=' ';
		dispname[13]=dispname[14]=dispname[15]='.';
		for (i=0; i<12; i++){
			dispname[i]=name[i];
			dispname[17+i]=name[len-12+i];
		}
		dispname[29]=0;
	}
}
 
int main(int argc, char **argv) {
 
 
	// Inicializar gráficos
	GRRLIB_Init();
 
	ASND_Init();
	ASND_Pause(0);	
 
	MP3Player_Init();
 
	Leeconfiguracion();
 
    resx = rmode->fbWidth;
    resy = rmode->efbHeight;
	sy = resy/528;
 
	voz = ASND_GetFirstUnusedVoice();
 
	// Inicializar wiimotes
	WPAD_Init();
	WPAD_SetDataFormat(WPAD_CHAN_ALL, WPAD_FMT_BTNS_ACC_IR);
	WPAD_SetVRes(WPAD_CHAN_ALL, resx, resy);
 
	tex_font = GRRLIB_LoadTexture(arial_black_12_png);
	GRRLIB_InitTileSet (tex_font, 16, 23, 32);
 
	tex_botones = GRRLIB_LoadTexture(botones_png);
	tex_pointers = GRRLIB_LoadTexture(pointers_png);
	tex_marco = GRRLIB_LoadTexture(marco_png);
	tex_carpeta = GRRLIB_LoadTexture(carpeta_png);
	tex_musica = GRRLIB_LoadTexture(musica_png);
	tex_error = GRRLIB_LoadTexture(error_png);
	GRRLIB_InitTileSet (tex_botones, WBOTON, HBOTON, 0);
	GRRLIB_InitTileSet (tex_pointers, WPOINTER, HPOINTER, 0);
 
	GRRLIB_SetMidHandle(tex_botones, true);
	GRRLIB_SetMidHandle(tex_carpeta, true);
	GRRLIB_SetMidHandle(tex_musica, true);
	GRRLIB_SetMidHandle(tex_error, true);
 
	leeCarpeta();
 
 
	while(1){
 
 
		WPAD_ScanPads();  // Leer mandos
		WPAD_IR(0, &ir);
 
		getext(lista[archivo_actual],ext);
 
		nombre[0]=0;
		strcat(nombre,DirActivo);
		strcat(nombre,lista[archivo_actual]);
 
		if (num_archivos > 0) {
 
			if (tipos[archivo_actual]==TIPO_CARPETA){
				cursoractivo = 0;
				GRRLIB_DrawImg(320,210*sy,tex_carpeta,0,0.5,sy*0.5,GRRLIB_WHITE);
				if (GRRLIB_PtInRect(65,20,475,365*sy,ir.x,ir.y*sy) && 
					(WPAD_ButtonsDown(0) & WPAD_BUTTON_A)){
					strcat(DirActivo, lista[archivo_actual]);
					strcat(DirActivo, "/");
					leeCarpeta();
				}
			} else if (tipos[archivo_actual]==TIPO_IMAGEN){
				if(!leido) {
					angulo = 0;
					cursoractivo = 1;
	//				if (tex_foto!=NULL)
	//					GRRLIB_FreeTexture(tex_foto);
					leeimagen(archivo_actual);
	//				tex_foto = GRRLIB_LoadTextureFromFile(nombre);
					leido = true;
				}
				if (tex_foto != NULL){
					GRRLIB_SetMidHandle(tex_foto, true);
					if ((angulo % 180) == 0){
						sfx = 480.0/tex_foto->w;
						sfy = 365.0/tex_foto->h;
					} else {
						sfy = 480.0/tex_foto->h;
						sfx = 365.0/tex_foto->w;
					}
				}
				if (tex_foto != NULL)
					GRRLIB_DrawImg(320-stx,(210-sty)*sy,tex_foto,angulo,sfx*zoom,sy*sfy*zoom,GRRLIB_WHITE);
				else
					GRRLIB_DrawImg(320,210*sy,tex_error,0,1,sy*1.0,GRRLIB_WHITE);
			} else if (tipos[archivo_actual]==TIPO_MUSICA) {
					GRRLIB_DrawImg(320,210*sy,tex_musica,0,0.5,sy*0.5,GRRLIB_WHITE);
			}
		}
		char dispname[50];
		gendispname(nombre, dispname);
		GRRLIB_Printf(640/2-strlen(dispname)/2*16,390, tex_font, GRRLIB_YELLOW, 1, dispname);
 
		GRRLIB_DrawImg(0,0,tex_marco,0,1,sy,GRRLIB_WHITE);
 
		if (GraphicButton(320,475*sy,2,80,100)){ // Mario
			if ((num_archivos > 0) && tipos[archivo_actual]==TIPO_IMAGEN) Presentacion();
			if ((num_archivos > 0) && tipos[archivo_actual]==TIPO_MUSICA) {
				if (!sonando) Escuchar_musica();
				else if (sonando) Parar_musica();
			}
		}
		if (InPicture() && (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)) {
			if ((num_archivos > 0) && tipos[archivo_actual]==TIPO_MUSICA) {
				if (!sonando) Escuchar_musica();
				else if (sonando) Parar_musica();
			}
		}
		if (GraphicButton(220,475*sy,1,80,100)){ // Izquierda
			archivo_actual = archivoanterior(archivo_actual);
			resetparams();
		}
		if (GraphicButton(420,475*sy,3,80,100)){ // Derecha
			archivo_actual = archivosiguiente(archivo_actual);
			resetparams();
		}
		if (GraphicButton(120,475*sy,8,80,100)){ // girar izquierda
			angulo-= 90;
		}
		if (GraphicButton(520,475*sy,9,80,100)){ // girar derecha
			angulo+= 90;
		}
 
		if (GraphicButton(40,50*sy,7,80,60)){ // lupa
			cursoractivo = 3;
		}
 
		if (GraphicButton(40,160*sy,10,60,80)){ //  anterior
			resetparams();
			extrae_carpeta();
			leeCarpeta();
		}
 
		if (GraphicButton(40,270*sy,11,60,80)){ //  mover
			cursoractivo = 1;
		}
 
		if (GraphicButton(600,50*sy,4,60,80)) break; // puerta
 
		if (GraphicButton(595,160*sy,0,60,50)){ // DVD
			strcpy(DirActivo, "dvd:/");
			leeCarpeta();
			resetparams();
		}
		if (GraphicButton(595,270*sy,5,60,50)){ // SD
			strcpy(DirActivo, "sd:/");
			leeCarpeta();
			resetparams();
		}
		if (GraphicButton(595,380*sy,6,60,50)){ // USB
			strcpy(DirActivo, "usb:/");
			leeCarpeta();
			resetparams();
		}
 
         // Si pulsamos home salimos del bucle
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME)  break;
 
		if (WPAD_ButtonsDown(0) & WPAD_BUTTON_1)
			GRRLIB_ScrShot("CAP8.png");
 
		// estamos en modo mover imagen
		if (cursoractivo==1){
			if (InPicture())
				if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A){
					cursoractivo=2;
					pcx = ir.x+stx; // memorizamos la posición del cursor cuando pulsamos A
					pcy = ir.y+sty ;
				}
		}
		// estamos moviendo una imagen
		if (cursoractivo==2){
			if (InPicture()) {
				if (WPAD_ButtonsHeld(0) & WPAD_BUTTON_A){
					stx= pcx-ir.x; // calculamos lo que hemos movido el cursor desde
					sty= pcy-ir.y; // que hemos pulsado A
				} else cursoractivo = 1;
			}
		}
 
		if (cursoractivo==3)
			if (InPicture()){
				if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)
					zoom*=ZSTEP;
				if (WPAD_ButtonsDown(0) & WPAD_BUTTON_B)
					zoom/=ZSTEP;
			}
 
		if (InPicture())
			GRRLIB_DrawTile(ir.x,ir.y*sy,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,cursoractivo);
		else
			GRRLIB_DrawTile(ir.x,ir.y*sy,tex_pointers,ir.angle,1,1, GRRLIB_WHITE,0);
		GRRLIB_Render();  // Refrescamos la pantalla
    }
 
	Guardaconfiguracion();
 
	GRRLIB_FreeTexture(tex_botones);
	GRRLIB_FreeTexture(tex_marco);
	GRRLIB_FreeTexture(tex_pointers);
	GRRLIB_FreeTexture(tex_foto);
	GRRLIB_FreeTexture(tex_carpeta);
	GRRLIB_FreeTexture(tex_musica);
	GRRLIB_FreeTexture(tex_error);
 
    GRRLIB_Exit(); // Liberamos memoria
 
    exit(0);  // Usar exit para salir del program (no usar return desde el main)
}

4.62963
Tu voto: Ninguno Votos totales: 4.6 (27 votos)

Anuncios Google

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de martinezcanto

RIZAR EL RIZO??

Buenas

 

No se si me meto en aguas movedizas e intento RIZAR EL RIZO...

 

Mi intencion es crear un fichero que contendra los numeros de los cartones, he estado leyendo algo pero no veo que estructura debe tener exactamente el fichero, ya que me dice que debe encontrarse un cambio de linea "/n" o el final del fichero EOF...

Por que esto, lo que pretendo que el programa vaya comprobando en este fichero el carton que se esta acercando a la linea o al bingo...

 

Asi que algunas preguntillas...

- con que programa creo el fichero... es suficiente con el bloc de notas??

- debo crear un fichero por carton, o lo puedo integrar todo en uno??

- el cambio de linea y final del programa entiendo que lo genera el propio programa

- donde debo poner este fichero, en data??

- lo debo tener el la SD para poder enviar el programa a la wii y que funcione??

 

 

Gracias y saludos

 

 

 

 

Imagen de wilco2009

No acabo de entender lo que

No acabo de entender lo que quieres hacer.

Los ficheros suelen servir para guardar información de estado, o datos que se converven incluso cuando cerramos el programa y que posteriormente hemos de utilizar.

Un ejemplo es la configuración del programa, o un archivo con records.

El guardar los cartones podría ser una posibilidad para conservar un set de cartones que pudieran ser modificados editando el archivo de text con el notepad, o bien para guardar una partida a medias y continuar en otro momento, pero para trabajar con los cartones, primero debes leerlos en memoria en una estructura interna (por ejemplo un array) y luego recorrerla.

No parece muy lógico estar leyendo el archivo una y otra vez para comprobar si tienes línea o bingo.

Crear un fichero o varios es irrelevante y depende más de un tema de orden. Yo crearía uno sólo pero no tienes necesariamente que hacerlo así.

Si quieres leer el archivo en tiempo de ejecución lo tienes que guardar en la SD, en la carpeta que desees. Luego debes poner la ruta correcta que te permita acceder a él.

Poner el archivo en la carpeta data de tu ordenador, sólo tendría lógica para enlazar el contenido del archivo en tiempo de compilación y convertirlo en un array "hardcoded" en el programa.

Quizás sea esto último lo que quieras hacer, si lo que quieres es tener un listado de cartones metidos manualmente.

Una alternativa lógica podría ser declarar un array y generar los números aleatoriamente añadiendo algunas restricciones.

Espero que te haya servido de alguna ayuda.


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de martinezcanto

QUIZAS NO ME

Buenas

Quizas no me haya explicado del todo en mis intenciones, y como siempre, wilco agradezco tu interes...

 

Lo que pretendo es suministrar con el programa una serie de cartones FIJOS, para que los que jueguen los utilicen...

Estos cartones serian los que estarian guardados en el fichero que comento, y el programa, debe comprobar que carton se esta acercando a la linea o al bingo, de tal manera que se puedan comprobar AUTOMATICAMENTE, eso si, siempre que alguien pulse linea o bingo en el programa (ya lo tengo preparado para ello) y meta el carton premiado..pienso poner un teclado para meter ese dato ( el carton que alguien ha cantado)

 

Por eso, lo de saber leer el fichero que contiene los cartones...

 

SEGURO que aun no me explico bien. No quiero guardar datos sino leer.... ten en cuenta que el carton lo tendran los jugadores, no es que aparezcan en la pantalla para cada uno ( quizas para la version 5000 me lo piense...jejejje)...

 

Como siempre, alguna indicacion nos daras, si me he explicado bien... GRACIAS MAESTRO

 

 

 

Imagen de martinezcanto

CAMBIANDO

Buenas

 

Estoy cambiando de opinion, ya que para que voy a hacer que el programa este leyendo constantemente el fichero... que lo haga cuando llegue el momento.

 

Asi que ahora con otro planteamiento tengo unas dudas, ya que no encuentro la funcion correcta para lo que busco...

 

LO primero, creare un fichero de texto, en la que cada linea contenga la informacion de los 15 numeros del carton teniendo en cuenta que el carton tiene realmente 27 cuadraditos (espacios vacios para cuando no haya numeros) y todos los numero de dos caracteres .... algo asi por ejemplo

0611    46  62  88    2037  51  7783  1821  4153  71  (fin de linea carton 1)

03  29    59  7986  10  3547  6076      2533  5863  85(fin de linea carton 2)

09    3048    7589  1723    5261  810115  3649  65    (fin de linea carton 3)

 

etc etc

 

Bien, he visto las funciones de leer los ficheros, pero no encuento ninguna que se pueda establecer que lea una linea concreta del fichero, quiero decir, supongamos que el carton premiado es el tres, y este dato lo he leido con el teclado del programa...

 

Que funcion existe para que al abrir el fichero pueda ir directamente a leer la fila 3 y cargar los datos de la misma en una array del carton?? sin tener que leer la totalidad del fichero empezando por la primera fila.

 

He estado buscando por internete, ademas de las buenas indicaciones de este tutorial, pero no encuentro nada...

Tu ayuda, como siempre, me dara ese empujoncito...

 

Gracias

 

 

 

Imagen de wilco2009

Puedes utilizar la función

Puedes utilizar la función fseek para posicionarte en un lugar concreto del fichero

fseek(fichero, posicion, origen).

que situa el puntero en la posición: posicion + origen.

Puedes utilizar algunos valores predefinidos para el parámetro "origen" que te serán utiles:

 

SEEK_SET o 0 : principio del fichero.

SEEK_CUR o 1 : posición actual.

SEEK_END o 2 : final del fichero.

 

Ejemplo:

 

fseek (f, 0, SEEK-SET); colocará el puntero al principio del fichero.
 
fseek (f, 3*sizeof(int), SEEK-CUR);  colocará el puntero 3 posiciones más allá de la posición actual del puntero.

 

Como verás esto está pensado para archivos en los que lees siempre cantidades fijas, como por ejemplo una estructura de datos.

En el caso de un archivo de texto es un poco complicado calcular la posición a la que quieres ir si las líneas de texto tienen una longitud variable.

En tu caso parece que has diseñado el archivo teniendo líneas de la misma longitud, por lo que podrías hacer uso de la función de la siguiente manera:

Si suponemos una longitud de línea de 30 caracteres (15 numeros *2 carateres/numero), para ir al cartón número 5 tendrías que escribir:

fseek(pFile, 30*5, SEEK-SET)


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de martinezcanto

Siempre eres de gran ayuda

Buenas

 

Siempre eres de gran ayuda, he probado la funcion que me propones y es lo que estaba buscando, pero tengo algun problemilla mas..

 

he creado esto

char cartonlinea[3][9][3]; para guardar los numeros del carton

char caracter[2]; esto para guardar la lectura una vez situado el puntero

 ya en el while

for (cfila=0;cfila<3;cfila++){
    for (dfila=0;dfila<9;dfila++){
    fgets (caracter ,2, pFile);//para leer de dos en dos caracteres
    strcpy (cartonlinea[cfila][dfila],caracter);
    }
    }

y por ultimo esto

GRRLIB_PrintfTTF(50,30, textos, cartonlinea[0][5], 30, GRRLIB_WHITE2);// el 0 y cinco del cartonlinea para probar

 

El caso es que consigo que se vea en pantalla lo que tiene la posicion del cartonlinea, pero solo me saca un caracter. La funcion fgets deberia leer dos caracteres si no estoy equivocado... pero porque se guarda solo uno en el array? si le he definido 3 uniddes de longitud.

 

Esperando el empujon... saludos

 

 

 

Imagen de martinezcanto

impulsivo

Buenas

A veces soy impulsivo y no me fijo en las cosas, ya he solucionado lo que planteaba con la siguiente modificacion

 

for (cfila=0;cfila<3;cfila++){
    for (dfila=0;dfila<9;dfila++){
    fgets (cartonlinea[cfila][dfila], ,2, pFile);//para leer de dos en dos caracteres
   ...

 

Ya me carga los caracteres de 2 en 2 y probando veo que los guarda bien , por lo menos en la primera fila del fichero...

 

tengo problemas con el resto, pero seguire probando antes de pedir ayuda...

 

Saludos

 

Imagen de wilco2009

Me alegro, y disculpa si no

Me alegro, y disculpa si no te contesto pero estoy bastante inactivo últimamente.

Imagen de martinezcanto

LAMENTO CORREGIRTE

Buenas

 

Lamento corregirte, tu pusiste esto

 

Si suponemos una longitud de línea de 30 caracteres (15 numeros *2
carateres/numero), para ir al cartón número 5 tendrías que escribir:

fseek(pFile, 30*5;SEEK_NET)

 

Pero no has tenido en cuenta el caracter final de linea \n, y que la primera linea es la 0, por lo que para que funcione hay que poner

fseek(pFile, 32*4;SEEK_NET)

 

Siempre aprendiendo de ti, saludos

xxxxxxxxxxxxxxxxxxxx

Editado: Crear hilos y comentarios duplicados incumple las Normas de la comunidad, si necesita cambiar su comentario puede usar la opcion de Editar, antes de volver a postear por favor revíse las Normas.| CarlosSakura
xxxxxxxxxxxxxxxxxxxx


Imagen de martinezcanto

ESTOY EN ELLO

buenas

 

Estoy en ello , en el tema de los ficheros, he hecho un programilla muy simple, pero creo que me falla en lo principal, abrir el fichero...

 

Donde esta el fallo, al principio lo puse direccionado al disco duro de mi ordenador, despues he grabado el fichero el la SD, pero lo que tengo claro es que no me abre el fichero,ya que todo lo que esta despues del ELSE no lo hace...

 

FILE * pFile;
pFile = fopen ("SD:/apps/Bingo/cartones.txt","r");

if (pFile==NULL) perror ("Error opening file");
  else
  {

GRRLIB_PrintfTTF(50,0, textos, "fichero abierto", 30, GRRLIB_WHITE2);

    }
   
   
   

  fclose (pFile);
}

GRRLIB_Render();
    }
 
    GRRLIB_Exit();
 

 
 
    exit(0);
}

 

Seguire peleandome, pero algo no hago bien

 

Saludos y gracias

 

Imagen de wilco2009

El archivo, por descontado,

El archivo, por descontado, lo debes colocar en la tarjeta SD, ya que en tiempo de ejecución no puedes tener acceso a tu disco duro.

Prueba a poner "sd:" en minúsculas, no estoy seguro de si es obligatorio.

Por lo demás lo veo bien, por lo que si no abre el archivo es que la ruta no debe ser la correcta.


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de martinezcanto

Es importante

Buenas

Efectivamente es importante el utilizar las minusculas en este caso... cambiado y todo oK

 

Gracias

 

Imagen de martinezcanto

Uso de RAW2c.exe

Buenas

Ahora me he puesto a trabajar con sonidos, ya que mi programilla va creciendo.

Quiero añadir sonidos, ya los he convertido a formato RAW, pero aqui mis dudas

No entiendo mucho el uso del conversor, como ejecutarlo..Ademas si convierte el archivo donde lo guarda..

He arrastrado mi fichero raw encima del icono del conversor, parece que hace algo, pero no encuentro el fichero guardado.

 

Otra pregunta...LOs ficheros de sonido una vez convertidos para c se guardan en la carpeta source o en la data?

Como siempre, gracias maestro

 

Imagen de wilco2009

El .c y el .h los guarda en

El .c y el .h los guarda en la carpeta desde donde llamas al raw2c.

Para no tener problemas podrías copiar el raw2c.exe y el archivo de sonido todo en una misma carpeta y desde una ventana de consola de comandos (ecribe cmd desde "Inicio\ejecutar"), primero cambia a la carpeta que hayas creado y después ejecut el programa pasandole como parametro el nombre del archivo de musica.

Es decir:

cd "nombre de carpeta"

raw2c "nombre del archivo"

Una vez generado el .c y el .h los debes copiar a tu carpeta source.


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de martinezcanto

lo pongo en practica

Buenas

Lo pongo en practica, seguro que funciona, como siempre siguiendo tus indicaciones

 

saludos, gracias

Imagen de martinezcanto

EL PESAITO DEL ARRAY

Buenas

Al final me conoceras como el pesaito del array, pero todo mi programa va en torno a numeros aleatorios..

Ya he creado array para los colores, la textura, el texto...y ahora me toca el sonido. Pretendo que el sonido vaya en funcion del numero aleatorio salido.

he creado un array

s32 canta[10];

he metido los archivos

canta[0]=canta0; (canta 0 lo he convertido con raw2c y los ficheros los tengo en source

canta[1]=canta1; etc etc

Tambien he creado la funcion snd_cantar(igual que la click del ejemplo), la variable voz tambien esta definida y el valor lo obtine aplicando ASND_GetFirstUnusedVoice();

void snd_cantar(void){
   ASND_SetVoice( voz, VOICE_MONO_8BIT, 8000, 0, cantanumero, cantanumero_size,255, 255, NULL);
}
 

despues he creado una variable para relacionar la voz a mostrar en funcion del numero aleatorio (supongamos el 8)

cantanumero=canta[8];

Y por ultimo durante el programa se llama a la funcion snd_cantar;

 he iniciado la libraria ASND_init();, no la he pausado.

El program se compila, pero no se oye el sonido... Es problema de los array? realmente se usa s32 para indicar que es un sonido? el tamaña del fichero lo encuentra automaticamente (cantanumero_size)?

Quizas demasiadas dudas, y supongo que muchas relacionadas con c.

De antemano, gracias

 

 

Imagen de wilco2009

Es necesario llamar a

Es necesario llamar a "ASND_Pause(0)" ya que el sonido comienza con pausa como si hubieras puesto "ASND_Pause(1)".

Los sonidos son arrays de char, tal y como puedes ver en el archivo que te ha creado el raw2c. Si quieres un ejemplo de como manejar arrays de sonidos puedes recurrir a la Parte 4 (Balanceboard) de mi curso "Profundizando en los mandos de la Wii" en donde hago uso de un array de sonidos para implementar una cola FIFO de sonidos. En dicho programa se hace uso de la balanceboard para leer el peso en Kg en voz alta. 


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de martinezcanto

Dejas sin palabras

Buenas

Me dejas sin palabras, eres todo un experto, y para mi y supongo que otros un MAESTRO..

He leido el tutorial, aplicando cosas, y tachan tachan....FUNCIONA...

Ahora, ya es cosa mia, darle el toque personal...

 

GRACIAS

Imagen de ateno_3

Error con GRRLIB

 

Cuando intento compilara el siguiente code:

 

 

 

#include <grrlib.h>
#include <stdlib.h>
#include <wiiuse/wpad.h>
#include "default_ttf.h"
 
#define Wpad1 0
#define Wpad2 1
#define Wpad3 2
#define Wpad4 3
#define WpadAll -1
 
#define ButtonA WPAD_BUTTON_A
#define ButtonB WPAD_BUTTON_B
#define Button1 WPAD_BUTTON_1
#define Button2 WPAD_BUTTON_2
#define ButtonMinus WPAD_BUTTON_MINUS
#define ButtonPlus WPAD_BUTTON_PLUS
#define ButtonRight WPAD_BUTTON_RIGHT
#define ButtonLeft WPAD_BUTTON_LEFT
#define ButtonUp WPAD_BUTTON_UP
#define ButtonDown WPAD_BUTTON_DOWN
#define ButtonHome WPAD_BUTTON_HOME
#define NunchukZ WPAD_NUNCHUK_BUTTON_Z
#define NunchukC WPAD_NUNCHUK_BUTTON_C
 
 
 
	u32 ProcBoton(u32 press, u32 boton, char* text, GRRLIB_ttfFont* font){
		if(press&boton){
			GRRLIB_PrintfTTF(20,60,font, text, 11, 0xFFFFFFFF);
			return boton;
			}
		return 0;
		}
 
	int main(int argc, char* argv[]){
		GRRLIB_Init();
		WPAD_Init();
		u32 pressed;
		GRRLIB_ttfFont* font=GRRLIB_LoadFont(default_ttf, (s32)default_ttf_size);
		while(1){
			WPAD_ScanPads();
			pressed=WPAD_ButtonsDown(Wpad1);
			ProcBoton(pressed, ButtonA, "Has presionado el boton A", font);
			ProcBoton(pressed, ButtonB, "Has presionado el boton B", font);
			ProcBoton(pressed, Button1, "Has presionado el boton 1", font);
			ProcBoton(pressed, Button2, "Has presionado el boton 2", font);
			ProcBoton(pressed, ButtonMinus, "Has presionado el boton -", font);
			ProcBoton(pressed, ButtonPlus, "Has presionado el boton +", font);
			ProcBoton(pressed, ButtonLeft, "Has presionado el boton ->", font);
			ProcBoton(pressed, ButtonRight, "Has presionado el boton <-", font);
			ProcBoton(pressed, ButtonUp, "Has presionado el boton ^", font);
			ProcBoton(pressed, ButtonDown, "Has presionado el boton v", font);
			ProcBoton(pressed, NunchukC, "Has presionado el boton C", font);
			ProcBoton(pressed, NunchukZ, "Has presionado el boton Z", font);
			if(ProcBoton(pressed, ButtonHome, "Has presionado el boton HOME", font)!=0) goto out;
			GRRLIB_Render();
			}
		out:
		GRRLIB_FreeFont(font);
		GRRLIB_Exit();
		exit(0);
		return 0;
		}

 

 

 

 

Me sale esto:

 

 


Programo en C, C++, C# y ASM ( Sintaxis INTEL y AT&T )

Proyectos:

- UnlockMii 5.0 ----> UnlockMii 5.1: ( EN CONSTRUCCION -> 2% )

- MiiOS ( EN CONSTRUCCION -> 0,01% )

El mejor antivirus ==> Avast!

Imagen de wilco2009

Debes poner GRRLIB_LoadTTF y

Debes poner GRRLIB_LoadTTF y GRRLIB_FreeTTF.

Imagen de ateno_3

Gracias

Gracias Wilco2009, ya compila.

Ahora tengo un problema:

El codigo sigue siendo el anterior con el nombre de las funciones corregidas pero al ejecutarlo y presionar botones aparece y desaparece rapidamente el texto que pongo con GRRLIB_PrintTTF.

 


Programo en C, C++, C# y ASM ( Sintaxis INTEL y AT&T )

Proyectos:

- UnlockMii 5.0 ----> UnlockMii 5.1: ( EN CONSTRUCCION -> 2% )

- MiiOS ( EN CONSTRUCCION -> 0,01% )

El mejor antivirus ==> Avast!

Imagen de wilco2009

Tal y como has planteado el

Tal y como has planteado el programa deberías usar WPAD_ButtonsHeld en lugar de WPAD_Buttonsdown.

WPAD_ButtonsDown solo devuelve "true" una sola vez después de pulsar el botón. Con WPAD_ButtonsHeld te sigue devolviendo "true" hasta que sueltas el botón.

Ten en cuenta que la pantalla se redibuja por completo a cada vuelta del bucle while.


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de ateno_3

Cargar fuente de archivo

Gracias, ya funciona. Me gustaria hacerte una pregunta:

¿Se puede cargar una fuente desde el programa en ejecucion, leyendolo y almacenandolo en una variable, o tiene que ser en el momento de ejecucion?


Programo en C, C++, C# y ASM ( Sintaxis INTEL y AT&T )

Proyectos:

- UnlockMii 5.0 ----> UnlockMii 5.1: ( EN CONSTRUCCION -> 2% )

- MiiOS ( EN CONSTRUCCION -> 0,01% )

El mejor antivirus ==> Avast!

Imagen de wilco2009

Claro que se puede. Se debe

Claro que se puede.

Se debe utilizar la función GRRLIB_LoadFile, a la que debes pasar el nombre del archivo y te devuelve un puntero a la memoria donde lo ha cargado.

No es necesario que reserves la memoria ya que la función lo hace sola.

Si tienes dudas te aconsejo que leas los tutoriales. Concretamente esa función se utiliza en la octava entrega para cargar archivos de música.

En dicha entrega también tienes algunas notas de como se estructuran los directorios en el sistema de archivos de Wii.


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de ateno_3

Gracias

Gracias.

Imagen de chango1611

Buen trabajo

Excelente tus tutoriales, aunque note que en el archivo adjunto no andan los archivos que se necesitan para agregar la librería que maneja los .ogg Si puedes montarlos te lo agradecería :)

 

Edit: Ya lo consegí por mi cuenta usando google, aunque igual sería bueno que lo colocaras. Ahorita estoy teniendo un error del que el programa no me corre, simplemente se queda en negro, no se cual será la razón, pero creo que se debe al manejo de archivos.

Imagen de wilco2009

Ya lo he comentado con un

Ya lo he comentado con un editor para que lo suba.

Si no encuentras el problema pregunta lo que necesites

 

Imagen de chango1611

Bien, Gracias por estar

Bien, Gracias por estar atento ^^

Hice codigos más directos tanto para escuchar sonidos como para ver archivos y ambos me funcionaron perfectamente. Supongo que debe ser un problema con que no tenga conectado un usb o algo por el estilo? Bueno cualquiera que sea el caso, logré hacer que funcionara lo que enseñastes en esta entrega que es lo que importa :)

Imagen de wilco2009

Encontrado el problema. El

Encontrado el problema.

El problema está en el escalado que se hace con la variable sy. Como C convierte todo en entero cuando alguno de los operando es entero la operación sy=resy/528 da cero!!!.

basta con cambiar lo anterior por sy = (float) resy / 528.0;


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de chango1611

Buen trabajo

Excelente tus tutoriales, aunque note que en el archivo adjunto no andan los archivos que se necesitan para agregar la librería que maneja los .ogg Si puedes montarlos te lo agradecería :)

Imagen de BYoshi

Librería GRRLIB

Wilco, no sé como lo haces, pero ésta entrega me dejó boquiabierto xD, en verdad Wilco, cómo aprendistes mucho del GRRLIB, porque hacer un programa de éstos son bien pero bien difíciles, sobre todo la gran cantidad de seudocódigos que se utiiza para compilar la aplicación.

Wilco, te deseo ¡FELIZ NAVIDAD Y PRÓSPERO AÑO NUEVO!

Un saludo.


Miiverse ID: BrianRC07 | Mii y Mario Kart 8: BRKart 07

¿Tenés la Wii U y te gustaría jugar backups de Wii?, entra aquí.

Imagen de wilco2009

gracias y feliz navidad a ti

gracias y feliz navidad a ti tambien LMM

Sin duda la mejor

Sin duda la mejor entrega!!buen trabajo y muchas felicidades!

Imagen de wilco2009

Gracias dirfinyu.

Gracias dirfinyu.

Imagen de EquipoR

Wilco, no sé como te las apañas

El tutorial está muy bien hecho y completo. Qué me recomiendas para hacer un homebrew simple: ¿DS o Wii?

Imagen de wilco2009

En DS no tengo experiencia

En DS no tengo experiencia aunque me consta que es muy similar.

Yo particularmente prefiero Wii porque lo conosco, aunque no descarto probar a programar para DS. Probablemente cuando tengamos homebrew para 3DS me apuntaré.

 


Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11

Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3  - Parte 4 (Balanceboard) - Parte 5 (Miis) 

Homebrew - WiiTriis -  LifemiiWii

Imagen de EquipoR

Respecto a eso de la 3DS

No creo que haya demasiado que sacar como homebrew, ya que no creo que este mundillo llegue tan lejos como para programar el 3D. Además, las demás tarjetas no se motivan por mejorarse y funcionar en 3DS, así que la única esperanza será la Crown 3DS y las copias de las demás tarjetas (como R4, M3, Cyclops...).

PD: Si llega mejor que mejor.


Mi Wii 4.3e tiene el menú darkwii gracias a este post de sebaskull; y la tuya tabién puede tenerlo accediendo aquí.

¿Quieres tu Mario Kart Wii Hackeado para cargar hasta 128 nuevos circuitos más los 32 originales? ¡Entra aquí!

¡No a la SOPA! ¡Queremos CALDO (Cuidemos A Las Descargas Online)!

¿Quieres aprender a hackear todos los contenidos del Mario Kart para disco origina, disco pirata o juego por USB? Si recibo 30 mensajes privados diciéndome que sí haré el post.

Imagen de BYoshi

Firma

Cambia la imagen de la firma, el máximo permitido es 350 (ancho) x 200 (alto). Los moderadores se darán cuenta y te harán una llamada de atención.


Miiverse ID: BrianRC07 | Mii y Mario Kart 8: BRKart 07

¿Tenés la Wii U y te gustaría jugar backups de Wii?, entra aquí.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.