Canviar el tempo d'un fitxer midi: change tempo smf

De Wikijoan
Dreceres ràpides: navegació, cerca

Introducció

En el projecte Ajuda_per_als_baixos_del_diatònic:_diatonicbaixosvull visualitzar com es toquen els baixos en l'acordió diatònic. Un dels requisits del programa és que el midi s'ha de poder tocar més o menys ràpid. Per a fer el play-along es fa servir el programa jack-smf-player. Per tant, previ a arrencar el jack-smf-player he de canviar el tempo del fitxer, i això ho faré directament modificant el fitxer midi, reescribint els tres bytes on està definit el tempo.

En aquesta petita utilitat la única cosa que es pretén és repassar el format midi, identificar on està definit el tempo dins el fitxer, i fer una reescritura directa dels bytes.

Desenvolupament

carpeta ~/projectes/change_tempo_smf

Repasso el format midi amb el següent enllaç:

Amb la utilitat ghex es pot visualitzar de forma clara els bytes:

$ ghex /home/joan/projectes/diatonicbaixos/src/midi/modista_balaguer.midi

Llegint la documentació queda clar que el tempo està definit després de les seqüències:

FF 51 03

En principi hi poden haver moltes seqüències. Primer perquè l'event canvi de tempo no és un event global, sinó de pista. Per tant pot haver un canvi de tempo per pista. I després perquè els events canvi de tempo també poden estar en qualsevol posició de la pista. De totes maneres, jo em centro en SMF d'una sola pista i sense canvis del tempo pel mig, és a dir, només canviaré la primera ocurrència de la seqüència. Sempre es podria modificar el codi font.

Per ex:

FF 51 03 0B 07 CB

Després dels tres bytes FF 51 03 (meta event, type i length) trobem els bytes 0B 07 CB. El byte 03 vol dir que segueixen tres bytes. En principi sempre segueixen tres bytes. Si hi hagués un contraexemple s'haurien de llegir els bytes especificats.

Els tres bytes no defineixen el tempo sinó el MPQN (microseconds per quarter notes). Un cop tingui el MPQN, per passar a BPM (beats per minute), senzillament serà una divisió.

Càlcul:

0B 07 CB -> x0b\x07\xcb -> 11*256^2 + 7*256^1 + (12*16+11)*256^0 = 720896 + 1792 + 203 = 722891. 
Aleshores BPM = MICROSECONDS_PER_MINUTE / MPQN = 60000000/722891 = 83 bpm

En la versió 1.0.0 l'aplicatiu queda de la següent manera:

$ ./change_tempo_smf [-q] -t tempo midifile

Amb l'opció -q el programa no retorna cap sortida cap a stdout, que és el que interessa per integrar-ho amb el diatonicbaixos.

El programa (la funció main) retorna 1 si hi ha hagut canvi de tempo, i 0 si no hi ha hagut canvi de tempo. Aquesta serà la manera de detectar des de Bash si hi ha hagut canvi de tempo o no. Amb la variable d'entorn $? podem recuperar l'últim byte que ha retornat el main(), i per tant podrem saber si hi ha hagut canvi de tempo o no:

$ ./change_tempo_smf -q -t 110 ../midi/miley_cyrus-wrecking_ball.mid 
$ echo $?
0
$ ./change_tempo_smf -q -t 120 ../midi/miley_cyrus-wrecking_ball.mid 
$ echo $?
1

Dins del codi trobem:

...
int main(int argc, char *argv[])
{
   ...
   if (tempo_antic != tempo_nou) result = 1;
   ...
   return result;
}
...

Codi

El codi queda molt senzill (no depèn de cap llibreria) i es compila fent:

gcc -Wall change_tempo_smf-1.0.0.c -o change_tempo_smf
// cd /home/joan/projectes/change_tempo_smf/src
// gcc -Wall change_tempo_smf-1.0.0.c -o change_tempo_smf

//versió 1.0.0:	vaig a fer els càlculs per tal de convertir el tempo als tres bytes del MPQN (llegir llegir.txt)
//				opció quiet (-q)
//				si el tempo ha canviat, l'aplicació retorna 1; retorna 0 en cas contrari

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

char SOFTWARE[] = "change_tempo_smf";
char DESCRIPTION[] = "Little utility for changing the tempo to a SMF file (midi file)";
char DATE[] = "January";
char YEAR[] = "2014";
char AUTHOR[] = "Joan Quintana Compte (joanillo)";
char LICENSE[] = "GPL v.3";
char WEB[] = "joanqc@gmail.com - www.joanillo.org";
char VERSION[] = "v1.0.0";

int main(int argc, char *argv[])
{
	char *smffile = (char *)malloc(50*sizeof(char));
	FILE *input;
	int help_mode = 0;
	int quiet_mode = 0;
	int tempo_antic, tempo_nou;
	//unsigned char rec; //char és 1 byte
	unsigned char buffer[3];
	unsigned char bufferwrite[3];
	int residu;
	int trobat = 0;
	double mpqn;
	int result = 0;

	//arguments management
	int k=1;
	for( k=1 ; k < argc ; k++ )
	{
		if( (*(argv[k]+0) == '-') & (*(argv[k]+1) != '-') )
		{ 
			if( *(argv[k]+1) == 'h' )
				{ help_mode = 1; }
			if( *(argv[k]+1) == 'q' )
				{ quiet_mode = 1; }
			if( *(argv[k]+1) == 't' ) //tempo_nou
				{ tempo_nou = atoi(argv[k+1]);}
		} 
	}
	smffile = argv[argc-1];
	if (argc != 4 && argc!=5 ) help_mode = 1;
	if (!strstr (smffile,".mid"))  help_mode = 1;

	if (!quiet_mode) system("clear");
	if (!quiet_mode) printf("\n%s. %s\n",SOFTWARE, VERSION);
	if (!quiet_mode) printf("\nCreated by %s on %s, %s.\n\n",AUTHOR, DATE, YEAR);

	if( help_mode)
	{
		if (quiet_mode) system("clear");
		if (quiet_mode) printf("\n%s. %s\n",SOFTWARE, VERSION);
		if (quiet_mode) printf("\nCreated by %s on %s, %s.\n\n",AUTHOR, DATE, YEAR);
		printf("change_tempo_smf [-q] -t tempo midifile\n\n");
		printf("Little utility for changing the tempo to a SMF file (midi file)\n\n");
		printf("-h: usage information\n");

		return result;
 	}
 	mpqn = 60000000 / (double)tempo_nou;
 	//printf("%f\n",mpqn);
 	bufferwrite[0] = (int)(mpqn / 65536);
 	//printf("byte1: %d\n", bufferwrite[0]);
 	//printf("%c\n",bufferwrite[0] );
 	residu = (int)mpqn % 65536;
 	//printf("residu: %d\n", residu);

 	bufferwrite[1] = (int)(residu / 256);
 	//printf("byte2: %d\n", bufferwrite[1]);
 	//printf("%c\n",bufferwrite[1] );
 	residu = residu % 256;
 	//printf("residu: %d\n", residu);

	bufferwrite[2] = residu;
 	//printf("byte3: %d\n", bufferwrite[2]);

 	input = fopen ( smffile, "r+b" ); //read and writing
	if ( input == NULL )
	{
		puts ( "Cannot open source file" );
		fclose ( input );
	}

	int comptador = 0;
	fseek(input,comptador,SEEK_SET);
	//vaig llegint byte a byte, agafant blocs de 3 bytes
	while(fread(&buffer, 1, 3, input) == 3) //fread retorna el número de caràcters llegits
	{
		//printf("%c %d %X\n", buffer[0], buffer[0], buffer[0]);
		if (trobat == 1) {
			//printf("%c %d %X\n", buffer[0], buffer[0], buffer[0]);
			//printf("%c %d %X\n", buffer[1], buffer[1], buffer[1]);
			//printf("%c %d %X\n", buffer[2], buffer[2], buffer[2]);
			tempo_antic = buffer[0]*65536;
			tempo_antic += buffer[1]*256;
			tempo_antic += buffer[2];
			tempo_antic = (int)(60000000 / tempo_antic);

			fseek(input,comptador+2,SEEK_SET); //reposiciono el cursor per escriure en el lloc correcte
			fwrite(&bufferwrite, 1, 3, input);
			if (!quiet_mode) printf("Tempo changed from %d to %d\n", tempo_antic, tempo_nou);
			if (!quiet_mode) printf("3 bytes changed in position %d: [%.2X %.2X %.2X] -> [%.2X %.2X %.2X]\n", comptador+2, buffer[0], buffer[1], buffer[2], bufferwrite[0], bufferwrite[1], bufferwrite[2]);
			if (tempo_antic != tempo_nou) result = 1;
			break;
		}

		if (buffer[0]==0xFF && buffer[1]==0x51 && buffer[2]==0x3) { //Tempo: FF 51 03. Els següents tres bytes seran el tempo
			trobat = 1;
		}
		comptador++;
		//vaig al següent byte quan estic buscant, i vaig al tempo quan ja he trobat que hi ha el tempo
		(trobat!=1) ? fseek(input,comptador,SEEK_SET) : fseek(input,comptador+2,SEEK_SET);
	}
	fclose ( input );
	//exit(0);
	return result;
}

creat per Joan Quintana Compte, gener 2014

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
Institut Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines