// Version 1.1: 5 de junio de 2013
// corregido un bug debido a considerar la pista de control como una pista normal
// El error se puso de manifiesto al tratar de leer un midi de voces con musescore
// Gracias a Angel Criado por avisarme de este error.

#define SI 1
#define NO 0
#define ISOLISTA 71
#define IACOMP 4
#define MAXTRACKS 24

#include <stdio.h>
#include <stdlib.h>
#include <string.h>



struct track {
	char 		myl[8];         /*marca y longitud*/
	int  		longitud;       /*longitud de la pista sin contar los 8 bytes iniciales*/
	unsigned char 	*datos;         /*la pista propiamente dicha*/
	char 		*nombrepista;
	int  		posinstrumento; /*posicion en datos donde va el instrumento*/
	int  		posvolumen;     /*posicion en datos donde va el volumen*/
	int  		pospanning;     /*posicion en datos donde va el panning*/
	unsigned char 	instrumento;    /*instrumento original de la pista*/
	unsigned char 	volumen;        /*volumen original*/
	int  		enabled;        /*flag que indica si se creara un fichero
				          con esta pista destacada*/
};

struct opciones {
	int           cambiaracomp;           /*flag que indica si hay que cambiar
				                los instrumentos de las pistas
				                de acompaniamiento*/
	unsigned char acomp;                  /*el instrumento de acompaniamiento*/
	unsigned char instrumento[MAXTRACKS]; /*instrumentos de cada pista
						destacada*/
};



/******************************************************************/
/*la organizacion de los 4 bytes de un int en memoria
 * va en orden contrario a como se guarda en el fichero, por eso
 * es necesario dar la vuelta byte a byte a la informacion leida*/
int vuelveint (char *s) {
	int resultado, i;
	unsigned char n;

	resultado=0;
	for (i=0; i<4; i++) {
		n=s[i];
		resultado=resultado*256+(int)n;
	}
	return resultado;
}



/*************************************************************/
/*leer abre el fichero MIDI y lo guarda en memoria
 * la estructura track contiene dos campos principales
 * myl, donde va la marca y longitud y 
 * datos donde va todo lo demas*/
leer (struct track *midi, char *nombrefichero) {

	FILE *p;
	int l,i;
	int ntracks;
	
	if ((p=fopen(nombrefichero,"r"))==NULL) {
		printf ("Error al abrir el fichero %s\n",nombrefichero);
		exit (1);
		}
	/*en primer lugar hay que leer la cabecera
	 * entre otros datos, aqui esta la informacion sobre cuantas pistas
	 * hay en el fichero*/
	fread (midi[0].myl, 8, 1, p);
	if (midi[0].myl[0]!='M' || 
	    midi[0].myl[1]!='T' ||
	    midi[0].myl[2]!='h' ||
	    midi[0].myl[3]!='d') {
		printf ("El fichero %s no es MIDI\n", nombrefichero);
		exit (2);
	}
	l=vuelveint(midi[0].myl+4);
	midi[0].longitud=l;
	midi[0].datos=malloc(l);
	fread(midi[0].datos, 1, l, p);
	if (midi[0].datos[0]!=0 ||
	    midi[0].datos[1]!=1) {
		printf("El fichero MIDI %s no es de formato 1\n", nombrefichero);
		exit(3);
	}
	if (midi[0].datos[2]!=0 ||
	    midi[0].datos[3]>=MAXTRACKS) {
		printf("El fichero MIDI %s tiene demasiadas pistas\n", nombrefichero);
		exit(7);
	}
	ntracks=(int)midi[0].datos[3];
	/*ya sabemos cuantas pistas hay, ahora las guardamos una a una*/
	for (i=1; i<=ntracks; i++) {
		fread (midi[i].myl, 8,1, p);
		if (midi[i].myl[0]!='M' || 
	    	    midi[i].myl[1]!='T' ||
	    	    midi[i].myl[2]!='r' ||
	    	    midi[i].myl[3]!='k') {
			printf ("Pista %d invalida\n", i);
			exit (4);
		}
		l=vuelveint(midi[i].myl+4);
		midi[i].longitud=l;
		midi[i].datos=malloc(l);
		fread(midi[i].datos, 1, l, p);
	}
	fclose (p);
}




/*************************************************************************/
/*copianombre coloca en un campo de la estructura track el nombre de la pista*/
char * copianombre(char *data,int l){
	int i;
	char * name;

	name=malloc(l+1);
	for (i=0;i<l;i++) 
		name[i]=data[i];
	name[l]=0;
	return name;
}



/******************************************************************/
/*analizar toma cada pista guardada en memoria y busca
 * las cadenas de texto, los controladores de panning y volumen
 * y las asignaciones de instrumento y guarda los datos que encuentra 
 * diferentes campos de la estructura track. 
 * Ademas cambia algunos valores (panning e instrumento) para convertirlos
 * en pistas de acompaniamiento*/
analizar (struct track *midi, struct opciones options) {
	int ntracks;
	int i;
	unsigned char tempo;
	unsigned char instruccion;
	int fin;
	int contador;
	unsigned char tipocadena;
	unsigned char l;
	unsigned char oldinstruccion;
	unsigned char controlador;
	int pp, pv, pi;
	
	ntracks=(int)midi[0].datos[3];
	for (i=2; i<=ntracks; i++) { // empezamos en 2 para no considerar la pista de control
		midi[i].nombrepista=NULL;
		midi[i].posinstrumento=0;
		midi[i].posvolumen=0;
		midi[i].pospanning=0;
		fin=NO;
		contador=0;
		oldinstruccion=0;
		while (fin==NO) {
			tempo=midi[i].datos[contador++];
			if (tempo!=0) fin=SI;
			else {
				instruccion=midi[i].datos[contador++];
				if ((instruccion & 0x80)==0) {
					instruccion=oldinstruccion;
					contador--;
				}
				oldinstruccion=instruccion;
				instruccion=instruccion & 0xF0;
				switch (instruccion) {
				case 0:
					printf("Instruccion erronea o no soportada\n");
					exit(6);
					break;
				case 0xF0:	
					tipocadena=midi[i].datos[contador++];
					l=midi[i].datos[contador++];
					if (tipocadena==3)
						midi[i].nombrepista=copianombre(midi[i].datos+contador,l);
					if (tipocadena==0x2F)
							fin=SI;
					contador+=l;
					break;
				case 0xB0:
					controlador=midi[i].datos[contador++];
					if (controlador==7) {
						midi[i].posvolumen=contador;
						midi[i].volumen=midi[i].datos[contador];
					}
					if (controlador==0x0a) {
						midi[i].pospanning=contador;
					}
					contador++;
					break;
				case 0xC0:
					midi[i].posinstrumento=contador;
					midi[i].instrumento=midi[i].datos[contador];
					contador++;
					break;
				default:
					fin=SI;
					break;
					
				}
			}
		}
		/*vamos a ver si la pista cumple los requisitos para
		 * poder ser destacada (enabled)
		 * y vamos a colocar el panning a 0 y el instrumento
		 * de acompaniamiento si la opcion esta activada*/
		pi=midi[i].posinstrumento;
		pv=midi[i].posvolumen;
		pp=midi[i].pospanning;	
		midi[i].enabled=NO;
		midi[i].datos[pv]=64;  	        /* rebajar el volumen*/	
		if (pi !=0 && pv !=0 && pp  !=0 && midi[i].nombrepista !=NULL) 
			if (midi[i].nombrepista[0]!=0)
				midi[i].enabled=SI;
	 	if (pp!=0)
			midi[i].datos[pp]=0;
		if (pi!=0 && options.cambiaracomp==SI)
			midi[i].datos[pi]=options.acomp;
		
		
	}


}



/*****************************************************************/
/*cambiar toma una pista y cambia sus parametros de instrumento,
 * panning y volumen a los correspondientes a una pista destacada*/
cambiar(struct track pista, unsigned char ins) {
	int pi, pv, pp;

	pi=pista.posinstrumento;
	pv=pista.posvolumen;
	pp=pista.pospanning;
	
	pista.datos[pi]=ins;
	pista.datos[pv]=127;
	pista.datos[pp]=127;
	
}



/*********************************************************************/
/*descambiar toma una pista y vuelve a colocar los valores de volumen, 
 * instrumento y panning a los correspondientes a una pista de acompaniamiento*/
descambiar(struct track pista, struct opciones options) {
	int pi, pv, pp;
	unsigned int auxi, auxv, auxp;

	pi=pista.posinstrumento;
	pv=pista.posvolumen;
	pp=pista.pospanning;
	
	if (options.cambiaracomp==SI)
		pista.datos[pi]=options.acomp;
	else
		pista.datos[pi]=pista.instrumento;
	pista.datos[pv]=64;
//	pista.datos[pv]=pista.volumen;
	pista.datos[pp]=0;
}




/******************************************************************/
/*crearsufijos toma una cadena que es el nombre de un archivo midi
 * de la forma fichero.mid y un sufijo, y devuelve otra cadena del tipo
 * fichero-sufijo.mid*/
char * creasufijos(char * sufijo, char *nombre) {
	char *resultado;
	int l1,l2;

	l1=strlen(sufijo);
	l2=strlen(nombre);
	if (strcmp(nombre+l2-4, ".mid")!=0 &&
	    strcmp(nombre+l2-4, ".MID")!=0 &&
	    strcmp(nombre+l2-4, ".Mid")!=0){
		printf("La extension ha de ser .mid\n");
		exit(8);
	}
	resultado=malloc(l1+l2+20);  //Lo logico seria +1, 
	                             //+20 es un workaround por culpa de un bug no localizado
	strcpy(resultado, nombre);
	resultado[l2-4]=0;
	strcat(resultado, "-");
	strcat(resultado, sufijo);
	strcat(resultado, ".mid");
	return resultado;
}
	


/********************************************************************/
/*escribir crea varios ficheros midis cada uno de los cuales tiene una pista
 * destacada*/
escribir (struct track *midi, char *nombrefichero, struct opciones options) {
	FILE *p;
	int i,j,k,l;
	int ntracks;
	char *s;
	
	
	
	midi[0].enabled=NO;
	ntracks=(int)midi[0].datos[3];
	k=0;
	for (j=2; j<=ntracks; j++) { // empezamos en 2 para no considerar la pista de control
		if (midi[j].enabled==SI){
			s=creasufijos(midi[j].nombrepista, nombrefichero);
			if ((p=fopen(s, "w"))==NULL) {
				printf("Error al abrir el fichero %s\n",s);
				exit(5);
			}
			cambiar(midi[j],options.instrumento[k]);
			k++;
			for (i=0; i<=ntracks; i++) {
				fwrite(midi[i].myl, 1, 8, p);
				fwrite(midi[i].datos, 1, midi[i].longitud,p);
			}
			descambiar (midi[j], options);
			printf("%s\n",s);
			fclose(p);
		}
	}
			
}
			



/****************************************************************/
/*leernumero convierte una cadena de texto que representa un numero
 * en decimal en ese mismo valor decimal.
 * Si la cadena no contiene un numero, devuelve el valor -1*/
int leernumero(char *s) {

	int resultado;
	int i,l,a;

	l=strlen(s);
	resultado =0;
	for (i=0; i<l; i++) {
		if ((s[i]<'0') || (s[i]>'9')) return -1;
		a=(int)(s[i]-(int)'0');
		resultado=resultado*10+a;
	}
	return resultado;
}





/****************************************************************/
/*leeropciones analiza la linea de comandos y rellena la estructura
 * opciones con los datos que obtiene.
 * Las opciones posibles son -a que indica que hay que cambiar el instrumento
 * de las pistas de acompaniamiento por un instrumento por defecto, o por el
 * instrumento especificado a continuacion de -a.
 * Cualquier otro numero en la linea de comandos se interpreta como un 
 * instrumento para dar a las pistas destacadas, por orden. Si se suministran
 * menos instrumentos que pistas, se usara el ultimo instrumento suministrado*/
leeropciones(struct opciones *options, int argc, char *argv[]) {
	int i,k;
	int n;
	int a;
	
	
	/*primero damos valores por defecto*/
	options->cambiaracomp=NO;
	options->acomp=IACOMP;
	for (i=0; i<MAXTRACKS; i++) {
		options->instrumento[i]=ISOLISTA;
	}

	/*y ahora analizamos la linea de comando*/
	if (argc>MAXTRACKS+2) {
		printf("Excesivo numero de instrumentos\n");
		exit (11);
	}
	if (argc>2) {
		k=0;
		a=NO;
		for (i=2; i<argc; i++) {
			n=leernumero(argv[i]);
			if (n>127) {
				printf("Instrumento fuera de rango\n");
				exit (12);
			}
			if (n!=-1) 
				if (a==SI) {
					options->acomp=(unsigned char)n;
					a=NO;
				}
				else
					options->instrumento[k++]=(unsigned char)n;
			else if (strcmp(argv[i], "-a")==0) {
				options->cambiaracomp=SI;
				a=SI;
			}
			else {
				printf("Error de sintaxis\n");
				exit(10);
			}
		}
		/*rellenamos las otras pistas con el ultimo instrumento*/
		if (k>0) {
			for (i=k; i<MAXTRACKS; i++)
				options->instrumento[i]=options->instrumento[k-1];
		}
	}

}




/***************************************************************/
/*breves instrucciones de uso*/
usage() {
	printf("\nsplitmidi 1.0\n");
	printf("instrucciones: splitmidi fichero.mid [n1 n2 n3 ...] [-a [n]]\n\n");
	printf("Los parametros n1, n2, ... son los diferentes instrumentos\n");
	printf("que toman las pistas destacadas. El parametro n es el instrumento\n");
	printf("que toman las demas voces por defecto. Si no se utiliza la opcion -a,\n");
	printf("los instrumentos asignados a las demas voces no se alteran.\n");
	printf("Los parametros entre corchetes son opcionales.\n\n");
	printf("El programa splitmidi programa toma un fichero MIDI con n pistas\n");
	printf("y genera n ficheros MIDI cada uno de los cuales tiene \n");
	printf("una pista destacada sobre las demas.\n");
	printf("Para destacar las voces, cada fichero creado sufre los siguientes cambios:\n");
	printf("1. el instrumento: se le da un instrumento a cada pista destacada mediante\n");
	printf("   la linea de comandos, o bien, se le da un valor por defecto.\n");
	printf("2. el panning: se pone a 127 y el resto de las voces a 0, es decir\n");
	printf("   la voz principal va por un altavoz y el resto por el otro.\n");
	printf("3. el volumen: se pone a 127, el valor maximo, las otras voces no se alteran.\n\n");
	printf("Debido a la limitada funcionalidad del programa se requiere que:\n");
	printf("1. el fichero de entrada sea de formato MIDI 1\n");
	printf("2. el fichero de entrada tenga la extension .mid, .MID, o .Mid\n");
	printf("3. los primeros eventos de cada pista sean metaeventos\n");
	printf("   de texto, controladores, o cambios de instrumento\n");
	printf("4. las voces que se deseen extraer han de tener asignada:\n");
	printf("   nombre de la pista, volumen, panning, e instrumento.\n");
	printf("   En caso contrario, no se creara el respectivo fichero.\n");
	printf("\nCopyright  http://tomasluisdevictoria.org\n");
			
}




/****************************************************************/
int main (int argc, char *argv[]) {
	struct track midi[MAXTRACKS];   
	struct opciones options;	

	if (argc<2) {
		usage();
		exit (9);
	}

	
	leeropciones(&options, argc, argv);
	leer(midi, argv[1]);
	analizar(midi, options);
	escribir(midi, argv[1], options);
	return 0;
}

