Fitxers MIDI (SMF). Format

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Format dels fitxes midi

MIDI standard specs: For GM1 and GM2 specs see

For GS download a Roland Sound Canvas manual from

and see the appendix lists. For XG try http://web.archive.org/web/20060926124939/http://www.yamaha.co.uk/xg/reading/pdf/xg_spec.pdf

També:

Editors Hexadecimals

GUI: ghex

Amb un n'hi haurà prou: ghex

Ghex is hex editor for GNOME. GHex allows the user to load data from any file, view and edit it in either hex or ascii.

$ sudo apt-get install ghex
$ hex2

Obro un fitxer midi file i així puc estudiar el format midi

CLI

Amb xxd puc fer un bolcat ràpid d'un fitxer binari a hexadecimal:

$ xxd /home/joan/projectes/llucanes_vilarmau/1.midi 
0000000: 4d54 6864 0000 0006 0001 0002 0180 4d54  MThd..........MT
0000010: 726b 0000 0053 00ff 030d 636f 6e74 726f  rk...S....contro
0000020: 6c20 7472 6163 6b00 ff01 0963 7265 6174  l track....creat
0000030: 6f72 3a20 00ff 011e 474e 5520 4c69 6c79  or: ....GNU Lily
...

També es pot fer en sentit invers, la sortida hexadecimal convertir-la de nou a binari:

$ xxd /home/joan/projectes/llucanes_vilarmau/1.midi | xxd -r > /home/joan/projectes/llucanes_vilarmau/1b.midi
$ xxd /home/joan/projectes/llucanes_vilarmau/1b.midi

Efectivament els dos fitxer són idèntics: (no dóna cap sortida, bona senyal)

$ cmp /home/joan/projectes/llucanes_vilarmau/1.midi /home/joan/projectes/llucanes_vilarmau/1b.midi 

Estudi d'un fitxer midi

Treballo amb el fitxer 1.midi (folklore Lluçanès). Obro el fitxer amb el hex2, i miro la teoria de http://www.sonicspot.com/guide/midifiles.html. Es tracta d'anar poc a poc i identificar les parts.

Els events midi els puc tenir fent:

$ python midi.py -p -d -i /home/joan/projectes/llucanes_vilarmau/1.midi > 1.txt

S'entén, excepte que el càlcul del delta time és un lio

Càlcul del delta time

S'entén, excepte que el càlcul del delta time és un lio. En el fitxer 1.txt tinc el valor absolut del temps, mentre que en el fitxer midi sempre hi ha el temps respecte l'event anterior. A més, el problema ve donat per les unitats, i que en el càlcul del temps real entren en joc dos factors:

Efectivament, en el fitxer lilypond vaig ficar:

\tempo 4=83

amb aquests dos paràmetres es pot fer el càlcul real de cadascun dels events, i això és el que anem a fer ara.

Per parsejar (mirar a dins) d'un fitxer midi tenim dues possibilitats (entre d'altres):

Considerem el fitxer projectes/llucanes_vilarmau/1.midi, i ens fixem en el primer event on el deltatime<>0. Es correspon al track1, a la primera note on. Amb el midi.py obtenim:

    <MidiEvent NOTE_ON, t=384, track=1, channel=2, pitch=73, velocity=90>
    <MidiEvent DeltaTime, t=192, track=1, channel=None>
    <MidiEvent NOTE_ON, t=576, track=1, channel=2, pitch=73, velocity=0>
    <MidiEvent DeltaTime, t=0, track=1, channel=None>

Amb el midiscv obtenim:

2, 384, Note_on_c, 1, 73, 90
2, 576, Note_on_c, 1, 73, 0

Veig que la informació és la mateixa, tot i que està posada d'una altra manera. En el primer cas considero el temps absolut en què passarè el event (t=384), i el que durarà el event (és el temps fins que ens trobem amb el note off). En el segon cas, diguem que més simple, només és el temps absolut en què hi ha els events (evidentment, 576-384=192).

nota. hi ha altres diferències entre la informació parsejada. En el cas de midi.py, veiem que el track=1 i channel=2. En el cas de midicsv, el track=2 (1a columna) i el channel=1 (4a columna). No li hem de donar més importància. En el midicsv fa servir dos tracks per la capçalera (i el midi.py només 1); quant al canal, suposo que és el fet de considerar 0-15 o 1-16.

Anem a considerar aquest event de track:

<MidiEvent NOTE_ON, t=384, track=1, channel=2, pitch=73, velocity=90>

De http://253.ccarh.org/handout/smf/ la informació que em dóna el event és:

Track Event
A track event consists of a delta time since the last event, and one of three types of events.

 
   track_event = <v_time> + <midi_event> | <meta_event> | <sysex_event>
 

<v_time>
    a variable length value specifying the elapsed time (delta time) from the previous event to this event. 
<midi_event>
    any MIDI channel message such as note-on or note-off. Running status is used in the same manner as it is used between MIDI devices. 
<meta_event>
    an SMF meta event. 
<sysex_event>
    an SMF system exclusive event

que com que en el meu cas és un midi event:

 track_event = <v_time> + <midi_event>

Obro el fitxer 1.midi amb el ghex2, i he de localitzar els bytes que es corresponen a aquesta informació (veure més avall):

83 00 91 49 5A 81 40 91 49 00 00 C1 ...

aquí allò important és adonar-se que el v_time és un valor de longitud variables, i he de saber com es tracta les cadenes de longitud variable:

Variable Length Values
Several different values in SMF events are expressed as variable length quantities (e.g. delta time values). A variable length value uses a minimum number of bytes to hold the value, and in most circumstances this leads to some degree of data compresssion.

A variable length value uses the low order 7 bits of a byte to represent the value or part of the value. The high order bit is an "escape" or "continuation" bit. All but the last byte of a variable length value have the high order bit set. The last byte has the high order bit cleared. The bytes always appear most significant byte first.

Here are some examples:

   Variable length              Real value
   0x7F                         127 (0x7F)
   0x81 0x7F                    255 (0xFF)
   0x82 0x80 0x00               32768 (0x8000)

La idea és que sí el primer bit és un 1, vol dir que la cadena continua. Per fer el càlcul només s'ha de considerar els 7 bits restants.

Hem de localitzar la part que ens interessa. Està després de:

<MidiEvent KEY_SIGNATURE, t=0, track=1, channel=None, data='\x03\x00'>

i de fet no sé fins on arriba, perquè el deltatime és de longitud variable. La informació comença en aquesta ristra de bytes:

83 00 91 49 5A 81 40 91 49 00 00 C1 ...

per tant el primer que ens trobem és vtime (delta time), que és una cadena de longitud variable. El primer byte comença per 8 i el segon per 0, i per tant són dos bytes:

83 -> 1 0000011
00 -> 0 0000000
ajuntant-los:
00000110000000 -> 1*256+1*128 = 384

ja tinc el valor del delta_time: 384.

i ara ve el midi event, que és un midi channel message

ja tinc el missatge, i ja sé on acaba i on comença el següent.

Ara sé que el primer missatge cés delta_time=384. Com que és el primer event, això no vol dir que sigui un offset, sinó que en realitat és el valor del time division in ticks per beat que ja havia calculat més amunt (aquest valor el midicsv el fica a la capçalera, i el midi.py no; però tant se val perquè de fet és el primer delta time <>0).

El que dura la primera nota, i que ens dóna una idea de la velocitat de la cançó, és el valor de 576-384=192. El 576 ja el sabem calcular, i el 192 ticks per beat és el que dura la primera nota. Mirant la partitura veig que és una corxea, i com que el tempo és de 83 bpm la negra, vol dir que són negres cada minut, per tant són 83/60=1.383 negres/segon, o el que és el mateix, la negra dura 0,723 segons i la corxea dura 0,361 segons.

per tant ja sé que els meus 192 ticks per beat es corresponen a 0,723 segons

Què significa que el meu time division=384 ticks per beat? He d'entendre que un beat és una negra, i cada negra la divideixo en 384 ticks (diguem que és la resolució que tinc). Per tant, 192 ticks (que és la meitat), és la durada d'una corxea. Lo ràpid que sonarà aquesta corxea dependrà del tempo, en el meu cas 83bpm.

Resumint, interessa que en el fitxer midi tot el tema de durada dels events es guardi en ticks, que és independent del temps real. Quan li aplico el temp és quan aquests ticks cobren una dimensió real i temporal, i és això el que sigui fàcil canviar el tempo d'un midi file (no s'han de recalcular tots els delta_times. Quan es processa el midi file és quan tindrà conseqüències en el temps).

El Time Division pot ser dues coses:

Veure l'explicació a http://www.sonicspot.com/guide/midifiles.html

Més càlcul del delta time (i altres coses)

Treballo amb el fitxer canco_bressol.midi (projectes/jplayfine/midi/canco_bressol.midi)

The most important thing to remember about delta times is that they are relative values, not absolute times. The actual time they represent is determined by a couple factors. The time division (defined in the MIDI header chunk) and the tempo (defined with a track

i aleshores per cada track vénen els midi events, que poden ser MIDI Channel Events, System Exclusive Events and Meta Events. Tots ells comencen per un delta-time de longitud variable. Si després ve un FF é un metaevent. Si ve un F0 és un sysex. En cas contrari és un Channel Event.

Midi Channel Events:
Delta Time      Event   Type Value      MIDI Channel    Parameter 1             Parameter 2
variable-length         4 bits          4 bits                  1 byte                  1 byte

Variable-length values use the lower 7 bits of a byte for data and the top bit to signal a following data byte. If the top bit is set to 1, then another value byte follows

Per tant, agafo el track #2 i calculo els delta-time

00 ff 03 00 -> delta_time=0; metaevent Sequence/Track Name; 00: és la longitud del que ve després, per tant no ve res.
00 c1 28  -> delta_time=0; c1: channel event, program change canal 1; 28: program number
00 ff 04 06 76 69 6f 6c 69 6e -> delta_time=0; metaevent intrument name; 06: data length=6 bytes. "violin"
00 91 3e 5a -> delta_time=0; 91: channel event, note on canal 1; nota 3e; velocity 5a
00 ff 59 02 ff 00 -> delta_time=0; metaevent keysignature: length=2; ff (fent el complement a 1, hauria de ser un -1, que significa un bemoll); 0 indica que és to major
81 40 91 3e 00 -> delta time. 0x81 0x40=1(0000001) 0(1000000) -> 11000000 = 192dec; 91 3e 00: note on nota=3e velocity=0

i així anar fent.

el delay-time acumulat és de 9216 ticks (es veu en els fitxers parsejats). Calcular el delay-time acumulat no és difícil, però sí una mica meticulós. No sé si val la pena fer-ho a jplayfine. A quants compassos es correspon?

-ticks_per_beat = 384
-beats_per_minute = 80
-beats_per_bar = 2

        9216 t 
------------------------ = 12 bar (compassos) (independent dels bpm)
384 t/beat * 2 beat/bar 

Accedir a un SMF des de lenguatge C++

He fet un petit programa per accedir als valors del ticks_per_beat, tempo i signature. També al format type i number of tracks, i detectar el end_of_track i el end_of_file (no hi ha cap metaevent que sigui end_of_file, senzillament és la detecció de tots els metaevents end_of_track). Ho utilitzaré a jplayfine. tempo_signature2.cpp:

//g++ -o tempo_signature tempo_signature2.cpp
//http://ubuntuforums.org/showthread.php?t=857511
//llegir (i escriure) un fitxer binari
//http://en.kioskea.net/faq/978-c-language-handling-64-bit-integers
//bitwise operators: http://www.cprogramming.com/tutorial/bitwise_operators.html
//format MIDI: http://www.sonicspot.com/guide/midifiles.html

#include <fstream>
#include <iostream>
#include "math.h"

int format_type;
int number_tracks;
int number_tracks_detectats=0;
int ticks_per_beat;
int bpm;
int num,den;

int main()
{

	//obrim per llegir
	std::fstream ofs( "./canco_bressol.midi", std::ios::in | std::ios::binary );

	if (ofs)
	{
		//http://www.cplusplus.com/doc/tutorial/variables/
		//char: 			1 byte
		//short int: 	2 bytes
		//int:			4 bytes
		//long long: 	8 bytes
		unsigned char rec; //char és 1 byte
		short int previ_tempo;
		long long mpqn;
		int tempo_i_signature=0;

		//llegim el primer caràcter
		ofs.read( (char *) &rec, sizeof(char) );
		printf("%c %x %d\n",rec,rec,rec); //M 4d 77 (1r caràcter) (01001101 en binari)

		//exemple AND bitwise
		//There are two kinds of AND operators in the C language: the logical AND, and the bitwise AND. 
		//The former is represented by the && operator, and the latter uses the & operator
		printf("%d\n",(rec>>1) & 1); //01001101 >> 1 = 0
		printf("%d\n",(rec>>2) & 1); //01001101 >> 2 = 1
		printf("%d\n",(rec>>3) & 1); //01001101 >> 3 = 1
		printf("%d\n",(rec>>4) & 1); //01001101 >> 4 = 0
		printf("%d\n",(rec>>5) & 1); //01001101 >> 5 = 0
		printf("%d\n",(rec>>6) & 1); //01001101 >> 6 = 1
		printf("%d\n",(rec>>7) & 1); //01001101 >> 7 = 0
		printf("%d\n",(rec>>8) & 1); //01001101 >> 8 = 0

		//ara llegim el caràcter 7 des del primer, és a dir, el caràcter 8
		ofs.seekp( 7 * sizeof(char), std::ofstream::beg );
		ofs.read( (char *) &rec, sizeof(char) );
		printf("%c %x %d\n",rec,rec,rec);

		//llegeixo el format type (bytes 9 i 10, de fet només m'interessa el byte 10: possibles valors 0, 1, 2)
		printf("byte 10: format type\n");
		ofs.seekp( 9 * sizeof(char), std::ofstream::beg );
		ofs.read( (char *) &rec, sizeof(char) );
		printf("%c %x %d\n",rec,rec,rec);
		format_type = (int)rec;

		//llegeixo el number of tracks (bytes 11 i 12, de fet només m'interessa el byte 12, em limito a smf de com a màxim 255 tracks)
		printf("byte 12: number of tracks\n");
		ofs.seekp( 11 * sizeof(char), std::ofstream::beg );
		ofs.read( (char *) &rec, sizeof(char) );
		printf("%c %x %d\n",rec,rec,rec);
		number_tracks = (int)rec;

		//llegeixo la Time Division (bytes 13 i 14)
		printf("byte 13 i 14: time division\n");
		ofs.seekp( 12 * sizeof(char), std::ofstream::beg );
		ofs.read( (char *) &rec, sizeof(char) );
		printf("%c %x %d\n",rec,rec,rec);
		if (!(rec>>7)&1) { //el bit més significatiu és un 0: time division in ticks per beat
			printf("time division in ticks per beat\n");
			//http://www.sonicspot.com/guide/midifiles.html: els ticks per beat es calculen amb els 7 bits del primer bytes més significatiu (el primer bit es descarta), i el segon byte
			ticks_per_beat = (rec & 127)*256;
			ofs.read( (char *) &rec, sizeof(char) );
			printf("%c %x %d\n",rec,rec,rec);
			ticks_per_beat += (int)rec;
			printf("Ticks per beat: %d\n",ticks_per_beat);
		} else {
			printf("time division in frames per second\n");
			printf("ara no m'interessa calcular els frames per second\n");
		}


		// Càlcul del tempo *******************************
		//This meta event sets the sequence tempo in terms of microseconds per quarter-note which is encoded in three bytes. It usually is found in the first track chunk, time-aligned to occur at the same time as a MIDI clock message to promote more accurate synchronization. If no set tempo event is present, 120 beats per minute is assumed. The following formula's can be used to translate the tempo from microseconds per quarter-note to beats per minute and back.
		//MICROSECONDS_PER_MINUTE = 60000000
		//BPM = MICROSECONDS_PER_MINUTE / MPQN
		//MPQN = MICROSECONDS_PER_MINUTE / BPM
		//Meta Event 	Type 	Length 	Microseconds/Quarter-Note
		//255 (0xFF) 	81 (0x51) 	3 	0-8355711
		//per tant, el que hauré de fer és cercar la seqüència FF51, i quan la tingui, mirar els tres bytes següents.
		printf("Càlcul del tempo\n");
		//puc llegir de 2 en 2, però aleshores els 2 bytes estan intercanviats: dóna 21581, que és equivalent a 544d (i no 4d54 que és el que voldria obtenir). Podria fer un swap dels dos bytes...
		//ofs.seekp( 0 * sizeof(char), std::ofstream::beg );
		//ofs.read( (char *) &previ_tempo, sizeof(short int) );
		//printf("%d\n",previ_tempo);
		//... però potser és millor no liar-se i anar byte per byte
		for(;;) {
			ofs.read( (char *) &rec, sizeof(char) );
			if ((int)rec==255) { //he trobat un 0xFF
				ofs.read( (char *) &rec, sizeof(char) );
				if ((int)rec==81) { //he trobat un 0x51: vol dir que ja bé el tempo
					printf("ve el tempo\n");
					ofs.read( (char *) &rec, sizeof(char) ); //aquest valor ha de valer 3, i són els bytes que he de llegir (ja ho sé)
					ofs.read( (char *) &rec, sizeof(char) );
					mpqn=(long long)rec*256*256;
					ofs.read( (char *) &rec, sizeof(char) );
					mpqn+=(long long)rec*256;
					ofs.read( (char *) &rec, sizeof(char) );
					mpqn+=(long long)rec;
					printf("MPQN = %lld\n",mpqn); //199537
					bpm = (int) ((long long)60000000 / mpqn);
					printf("tempo = %d\n",bpm); //300
					tempo_i_signature++;
				}
				if ((int)rec==88) { //he trobat un 0x58: vol dir que ja bé el Time Signature
					printf("ve el time signature\n");
					ofs.read( (char *) &rec, sizeof(char) ); //aquest valor ha de valer 4, i són els bytes que he de llegir (ja ho sé)
					ofs.read( (char *) &rec, sizeof(char) );
					num=(int)rec;
					ofs.read( (char *) &rec, sizeof(char) );
					den = pow(2,rec);
					printf("time signature = %d / %d\n",num,den); //300
					tempo_i_signature++;
				}
			}
			if (tempo_i_signature==2) break;
		}

		// Detectar end_of_track i end_of_file *******************************
		printf("Detectar end_of_track i end_of_file\n");
		for(;;) {
			ofs.read( (char *) &rec, sizeof(char) );
			if ((int)rec==255) { //he trobat un 0xFF
				ofs.read( (char *) &rec, sizeof(char) );
				if ((int)rec==47) { //he trobat un 0x2F: vol dir que ja bé el end_of_track
					number_tracks_detectats++;
					printf("end_of_track #%d\n",number_tracks_detectats);
				}
			}
			if (number_tracks_detectats==number_tracks) break;
		}
		printf("end_of_file\n");
		ofs.close();
	}
	//valors per defecte en el cas de què no s'hagin definit
	if (bpm==0) bpm=120;
	if (num==0) num=4;
	if (den==0) den=4;
	return 0;
}

libsmf

És la llibreria que utilitza jack-smf-player per llegir els SMF files. jack-smf-utils-1.0/libsmf. La última versió està disponible a:

Descarrego de sourceforge la versió libsmf-1.3.tar.gz, que és una versió més nova que la que ve amb jack-smf-player.

$ ./configure
$ make
$ sudo make install
...
Libraries have been installed in:
   /usr/local/lib
...

Per tant ara ja puc incloure smf.h en un projecte.

Per compilar una aplicació es necessita glib (http://developer.gnome.org/glib/), que acostuma a estar instal.lat:

La GLib proporciona els blocs bàsics de creació d'aplicacions per a les biblioteques i programes desenvolupats en el llenguatge C. Ofereix el nucli del sistema d'objectes del GNOME, la implementació del bucle principal i un gran nombre de funcions d'utilitat per al tractament de cadenes i d'estructures de dades.

Ús de la API:

$ g++ -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -o prova_libsmf prova_libsmf.cpp -lglib-2.0 -lsmf
compila bé però
./prova_libsmf: error while loading shared libraries: libsmf.so.0: cannot open shared object file: No such file or directory

llegir amb calma

// g++ -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -o prova_libsmf prova_libsmf.cpp -lglib-2.0 -lsmf
// g++ -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -o prova_libsmf prova_libsmf.cpp -lsmf

//per no tenir el problema de error while loading shared libraries:
//http://www.network-theory.co.uk/docs/gccintro/gccintro_25.html
// LD_LIBRARY_PATH=/usr/local/lib
// export LD_LIBRARY_PATH

using namespace std;
#include <stdio.h>
#include <stdlib.h>

#include <smf.h>

smf_t		*smf = NULL;
smf_event_t *event;

int main(int argc, char *argv[]) {
	char *file_name=(char *)"../../midi/canco_bressol.midi";
	smf = smf_load(file_name);

	if (smf == NULL) {
		printf("Loading SMF file failed.");
		exit(-1);
	}

	while ((event = smf_get_next_event(smf)) != NULL) {
		printf("%d\n",event->track->track_number);
	}


}

A partir d'aquí es poden fer coses interessants...

smf_parser

smf_parser v1.01

smf_parser és un parsejador de fitxers midi que he fet jo mateix per utilitzar-lo en el jplayfine. La idea és que en el jplayfine, abans que res, analitza el SMF file per extreure'n informació important, com ara ticks_per_beat, núm de compassos,... En la v1.01 és una aplicació standalong, però la idea és que en la v1.02 serà un include (smf_parser.h), que és el que s'inclourà en el jplayfine. De moment està poc testejat, però en els midi files simples testejats funciona bé.

La sortida és similar a:

smfparser v1.01
Created by Joan Quintana Compte (joanillo)
Licensed under GPL v.3

format type: 1
number of tracks: 3
time division in ticks per beat
Ticks per beat: 384
track #1
track size: 83
Channel Event. t= 0 (0) 
Meta Event. Type: 0x3 (3dec), Sequence/Track Name (13 bytes): control track
Channel Event. t= 0 (0) 
Meta Event. Type: 0x1 (1dec), Text Event (9 bytes): creator: 
Channel Event. t= 0 (0) 
Meta Event. Type: 0x1 (1dec), Text Event (30 bytes): GNU LilyPond 2.12.3           
Channel Event. t= 0 (0) 
Meta Event. Type: 0x58 (88dec), Time Signature (4 bytes): 2 / 4
Channel Event. t= 0 (0) 
Meta Event. Type: 0x51 (81dec), Set Tempo (3 bytes): 80 bpm
Channel Event. t= 0 (0) 
Meta Event. Type: 0x2f (47dec), End of Track (0 bytes): 
track #2
track size: 324
Channel Event. t= 0 (0) 
Meta Event. Type: 0x3 (3dec), Sequence/Track Name (0 bytes): 
Channel Event. t= 0 (0) Program Change ch=1 par1=40 par2=0
Meta Event. Type: 0x4 (4dec), Instrument Name (6 bytes): violin
Channel Event. t= 0 (0) Note On ch=1 par1=62 par2=90
Channel Event. t= 0 (0) 
Meta Event. Type: 0x59 (89dec), Key Signature (2 bytes): -1 (number of sharps or flats); 0 (0: major, 1: minor)
Channel Event. t= 192 (192) Note On ch=1 par1=62 par2=0
Channel Event. t= 0 (192) Note On ch=1 par1=64 par2=90
...
Channel Event. t= 0 (8832) Note On ch=1 par1=62 par2=90
Channel Event. t= 384 (9216) Note On ch=1 par1=62 par2=0
Channel Event. t= 0 (9216) 
Meta Event. Type: 0x2f (47dec), End of Track (0 bytes): 
track #3
track size: 203
Channel Event. t= 0 (0) 
Meta Event. Type: 0x3 (3dec), Sequence/Track Name (0 bytes): 
Channel Event. t= 0 (0) Note On ch=2 par1=50 par2=90
Channel Event. t= 0 (0) Note On ch=2 par1=53 par2=90
Channel Event. t= 0 (0) 
Meta Event. Type: 0x59 (89dec), Key Signature (2 bytes): -1 (number of sharps or flats); 0 (0: major, 1: minor)
Channel Event. t= 768 (768) Note On ch=2 par1=50 par2=0
Channel Event. t= 0 (768) Note On ch=2 par1=53 par2=0
Channel Event. t= 0 (768) Note On ch=2 par1=48 par2=90
...
Channel Event. t= 384 (9216) Note On ch=2 par1=50 par2=0
Channel Event. t= 0 (9216) Note On ch=2 par1=53 par2=0
Channel Event. t= 0 (9216) 
End of file

ABSTRACT: 
-----
Number of tracks: 3
ticks_per_beat: 384
Time Signature: 2 / 4
Tempo: 80
Number of bars: 12.000000


creat per Joan Quintana Compte, novembre 2011

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