# MIDI Recording Tempo ist immer 120bpm



## tomiondrums (25. Jul 2009)

Hi!
Ich hab grad ein riesiges Problem und hoffe, ihr könnt mir da helfen.
Das Programm, an dem ich gerade schreibe, soll einen MIDI-Track auf einem externen MIDI-Gerät wiedergeben und gleichzeitig (zeitlich synchron) von diesem Gerät etwas aufnehmen. Man muß sich das ganze wie einen MIDI-Recorder vorstellen, der ein etwas komplexeres Metronom bzw eine Begleitautomatik enthält.

Der komplette Code ist ein bischen groß, aber das folgende ist prinzipiell das, was ich gemacht hab:

```
//set up the devices and the sequencer
	midi_sequencer = (Sequencer)MidiSystem.getMidiDevice( midi_sequencer_info );
	midi_out_device = MidiSystem.getMidiDevice( midi_out_device_info );
	midi_in_device = MidiSystem.getMidiDevice( midi_in_device_info );

	// build the sequence for recording and for playback
	Sequence seq = new Sequence( Sequence.PPQ, ticks_per_quarter );
	// add a track with playback data
	fillTrack( seq.createTrack() );
	
	// add a track into which the recorded data will be transferred		    
	Track track_record = seq.createTrack();
			    
	// assign the sequence to the sequencer and record-enable the record_track
	midi_sequencer.setSequence( seq );
	midi_sequencer.recordEnable( track_record, -1 );

	// open the devices and the sequencer
	midi_out_device.open();
	midi_sequencer.open();
	midi_in_device.open();
	// set transmitters and receivers from the sequencer
	midi_sequencer.getTransmitter().setReceiver( midi_out_device.getReceiver() );
	midi_in_device.getTransmitter().setReceiver( midi_sequencer.getReceiver() );

	// set the tempo (shouldn't this method be responsible for the playback
	// tempo as well as for the recording tempo?)
	midi_sequencer.setTempoInBPM( 80 );

	// do it - start recording AND playback
	midi_sequencer.startRecording();
	while( midi_sequencer.isRunning() ){ // wait for playback to finish and then also stop recording
		Thread.sleep( 25 );
	} // play back has finished
	midi_sequencer.stopRecording();

	// close sequencer and devices
	midi_in_device.close();
	midi_sequencer.close();
	midi_out_device.close();



	// print the recorded data
	for( int i = 0; i < track_record.size(); i++ ){
		MidiEvent ev = track_record.get( i );
		if( ev.getMessage() instanceof ShortMessage ){
			ShortMessage msg = (ShortMessage)ev.getMessage();
			if( msg.getCommand() == ShortMessage.NOTE_ON  && msg.getData2() > 0 ){
				System.out.println( ev.getTick() + " " + msg.getData1() + " " + msg.getData2() );
			}
		}
	}
```
Wenn man's laufen lässt, schaut auf den ersten Blick alls super aus. Das Abspielen läuft und es passiert auch tatsächlich in dem Tempo, das ich vorher mit setTempoInBPM(...) angegeben hab. Nach dem erwartungsgemäßen Beenden (ohne Exceptions etc. und nach der erwarteten Zeit) des Abspielens sind auch tatsächlich MidiEvents im record_track drin. Wenn man dann aber genauer hinschaut, fällt einem das Problem auf:
Die Tick-Informationen der MIDI-Messages im record_track basieren allesamt auf einem Tempo von 120bpm, ganz egal, was man vorher mit setTempoInBPM(...) einstellt.

Als Sequencerimplementierung wird der "com.sun.media.sound.RealTimeSequencer" verwendet.
Ich hab auch schonmal versucht, die SlaveSyncModes zu wechseln, aber das einzige, was der RealTimeSequencer kann ist "No Timing".

Ich hab keine Ahnung, ob das Problem auf einem Programmierfehler meinerseits beruht oder auf einem Bug im RealTimeSequencer (oder einfach das von mir verwendeten MIDI-Keyboard spinnt).

Was kann ich da machen ums ans Laufen zu kriegen? Was mach ich evtl. falsch?

Schon jetzt vielen Dank!
 Tom


----------



## Spacerat (26. Jul 2009)

"setTempoInBPM()" endet in nativen (JNI) Code. Das bedeutet, dass nur schwer nachvollziehbar ist, was da vor sich geht. Möglicherweise unterstüzt das die Hardware nicht (kaum vorstellbar) oder sie wurde nicht korrekt konfiguriert.
Jetzt bin ich musikalisch nicht so bewandert... was bedeutet den "setTempoInMPQ()" (Microsekunden pro Viertelnote). Die Methode endet zwar auch irgendwann in nativem Code wird aber unter gewissen Umständen von "setTempoInBPM()" alternativ aufgerufen.


----------



## tomiondrums (26. Jul 2009)

Hi Spacerat,
wie du schon richtig sagst, setzt setTempoInMPQ() genauso das Tempo, halt bezogen auf eine andere Umrechnungsbasis (=60000000/bpm-wert). Die Idee, diese besagte Methode zu verwenden hatte ich auch schon, an meinem Problem hats allerdings nix geändert. Daß die Hardware das Problem ist, glaube ich eher weniger, aber das müsste man ja rauskriegen können, indem man mit einem anderen java-basierte MIDI-Recording Programm (kennt hier jemand soetwas?) mal ein paar Versuche macht. Höchst merkwürdig kommt es mir vor, daß der Sequencer nicht wirklich Sync-Modes unterstützt (also außer Internal-Clock und sowas halt). Ich frage mich, ob man ihm die nachträglich irgendwie beibringen kann.
Alternativ - ich bin nämlich schon ziemlich fertig wegen dem Problem, weils bei dem Projekt nämlich um meine Bachelorarbeit geht und die bald fertig sein muß - gäbs dann noch die Möglichkeit die Jungens von Sun zu fragen, ob die nicht irgendwie eine Idee haben, was da falsch läuft. Kommt man an die überhaupt irgendwie ran und wenn ja, wie?

danke


----------



## Spacerat (26. Jul 2009)

tomiondrums hat gesagt.:


> ...gäbs dann noch die Möglichkeit die Jungens von Sun zu fragen, ob die nicht irgendwie eine Idee haben, was da falsch läuft. Kommt man an die überhaupt irgendwie ran und wenn ja, wie?


Was sollte denn das bringen? Die sind ja selbst kaum in der Lage das Sound-API ausreichend zu dokumentieren. Außerdem scheint für das Sound-API einzig und allein (bzw. überwiegend) ein gewisser oder eine gewisse Kara Kytle verantwortlich zu sein. Und wenn der oder die nicht mehr bei Sun beschäftigt ist... selbst Google spuckt darüber nicht viel aus.
...eine Idee hätte ich aber noch. Aus deinem Quelltext geht nicht hervor, welche Geräte-Info (midi_sequencer_info) du verwendest. Mit sicherheit einen funktionierenden aus der Liste eines anderen Threads. Hast du da schon mal andere Geräte ausprobiert? Vllt. hast du mit dem verwendeten Eintrag ja ausgerechnet 'ne Software-Emulation oder ähnliches getroffen, die "setTempoInXXX()" kaum oder gar nicht unterstützt oder nicht an die eigentliche Hardware weiterleitet.


----------



## tomiondrums (26. Jul 2009)

Hey Spacerat,
ich hab mir eine Klasse geschrieben, die alle für mein Programm relevanten Devices bzw. Sequencer auflisten kann.
Die schaut wie folgt aus:

```
public class ConnectionExplorer{
	private Map<String, MidiDevice.Info> in_connections = new HashMap<String, MidiDevice.Info>();
	private Map<String, MidiDevice.Info> out_connections = new HashMap<String, MidiDevice.Info>();
	private Map<String, MidiDevice.Info> sequencers = new HashMap<String, MidiDevice.Info>();
	
	private static ConnectionExplorer instance = null;
	public static ConnectionExplorer getInstance(){
		if( instance == null ){
			instance = new ConnectionExplorer();
		}
		return( instance );
	}
	
	private ConnectionExplorer(){
		MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
		for( int i = 0; i < infos.length; i++ ){
			try{
				MidiDevice device = MidiSystem.getMidiDevice( infos[i] );
				if( device.getMaxTransmitters() != 0 ){ // allows input
					in_connections.put( infos[i].getName(), infos[i] );
				}
				if( device.getMaxReceivers() != 0 ){ // allows output
					out_connections.put( infos[i].getName(), infos[i] );
				}
				if( MidiSystem.getMidiDevice( infos[i] ) instanceof Sequencer ){ // is a sequencer device
					 sequencers.put( infos[i].getName(), infos[i] );
				}else{
					//System.out.println( "Device " + infos[i] + " is not a sequencer device" );
				}
				in_connections.entrySet().removeAll( sequencers.entrySet() );
				out_connections.entrySet().removeAll( sequencers.entrySet() );
			}catch( MidiUnavailableException e ){}
		}
	}
	
	public int countInConnections(){
		return( in_connections.size() );
	}
	
	public int countOutConnections(){
		return( out_connections.size() );
	}
	
	public Iterable<MidiDevice.Info> getInConnections(){
		return( in_connections.values() );
	}
	
	public Iterable<MidiDevice.Info> getOutConnections(){
		return( out_connections.values() );
	}
	
	public Iterable<MidiDevice.Info> getSequencers(){
		return( sequencers.values() );
	}

	
	//public MidiDevice.Info getInConnection( String name ) throws ConnectionNotAvailableException{
	//	MidiDevice.Info in_connection = in_connections.get( name );
	//	if( in_connection != null ){
	//		return( in_connection );
	//	}else{
	//		throw( new ConnectionNotAvailableException( name ) );
	//	}
	//}
	
	//public MidiDevice.Info getOutConnection( String name ) throws ConnectionNotAvailableException{
	//	MidiDevice.Info out_connection = out_connections.get( name );
	//	if( out_connection != null ){
	//		return( out_connections.get( name ) );
	//	}else{
	//		throw( new ConnectionNotAvailableException( name ) );
	//	}
	//}
	
	//public MidiDevice.Info getSequencer( String name ) throws ConnectionNotAvailableException{
	//	MidiDevice.Info sequencer_conn = sequencers.get( name );
	//	if( sequencer_conn != null ){
	//		return( sequencer_conn );
	//	}else{
	//		throw( new ConnectionNotAvailableException( name ) );
	//	}
	//}
}
```

Mit der Methode public Iterable<MidiDevice.Info> getSequencers() kannst du dir (wenn ich's nicht falsch gemacht hab) alle auf dem System verfügbaren Sequencer(-Implementierungen) anzeigen lassen. (Ich verwende die Methode um im Konfigurationsdialog von meinem Programm dem Benutzer alle seine am System verfügbaren Sequencer in einer ComboBox zur Auswahl zu stellen.)

Auf meinem System (Linux x86 Gentoo mit ALSA-Libs Version 1.0.17a und Kernel 2.6.30.1 (ich verwende den USB-MIDI Treiber) und Sun Java JDK 1.6.0_14) listet mir das eigentlich ausschließlich den "Real Time Sequencer" auf. Vielleicht bin da ja falsch aufgeklärt, aber meines Erachtens ist das ein Software-Sequencer (Klasse "com.sun.media.sound.RealTimeSequencer"). Ob es in meinem System auch Hardware-Sequencer gibt und wie ich die verwenden kann, davon hab ich keine Ahnung, aber vielleicht könnt Ihr mich da ja schlauer machen.

Ich sollte vielleicht noch Erwähnen, daß es da ja noch das Tritonus-Projekt gibt, welches auch eine Sequencer-Implementierung zur Verfügung stellt, die ich zwischenzeitlich auch mal installiert und probiert hab (D.h. ich hab sie halt mal anstelle des "Real Time Sequencer" verwendet). Dummerweise ist die schon uralt und kann auf meinem System überhaupt nicht recorden. Das Abspielen damit geht einigermaßen. 

Welche Sequencer stellt euch euer System eigentlich unter Java zur Verfügung?


----------



## Spacerat (27. Jul 2009)

Standardmässig gibt es nur den RealTimeSequencer. Wichtig dabei ist aber, das er pro Instanz ein anderes im System vorhandenes Gerät anspricht. Das tut er über JNI (also nativer Code). Ich hatte jedoch wenig Gelegenheit (mangelnde Hardware und Zeit) mich einschlägig mit MIDI in Java zu befassen. Ich hab' deswegen auch noch keine Möglichkeit gefunden, wann der RealTimeSequencer konkret auf Hardware zugreift oder nur eine Softwareemulation dahinter steckt. Das Verhalten des RealTimeSequencers ist jedenfalls nie von vorneherein eindeutig. Es kommt halt drauf an, welche GeräteInfo man verwendet hat (Im Zweifelsfall halt ausprobieren, bei welcher Auswahl sich der Sequencer wie erwartet verhält). Eigene Implementationen können über das sog. Serviceproviderinterface (SPI) eingebunden werden. Das ist aber 'ne Schweinearbeit und alles andere als trivial. Ich hab' mir auf diese Art ein Dolby5.1-AudioDevice für Java (basierend auf JOAL) gebastelt.


----------



## tomiondrums (27. Jul 2009)

Ist es dann überhaupt möglich, den Real Time Sequencer in seiner Gerätewahl zu beeinflussen? Wo hast du diese Infos her?


----------



## Spacerat (27. Jul 2009)

Das wenigste erfährt man per JavaDoc im Quellcode des JDK. Einiges mehr durch intensives googlen. Vor allem wenn's um das SPI geht, muß man sich davon lösen, das es ausschliesslich für das Sound-API verwendet wird, sondern z.B. auch für ImageIO. Den Rest muss man sich selbst zusammen reimen oder im Zweifelsfall sogar klassenweise decompilieren. Durch debuggen, mit Breakpoints an den richtigen Stellen, erfährt man dann welche Klassen wann, wo verwendet werden. Alles in allem also ein langwieriges Unterfangen.
Auf diese Weise ist mir z.B. auch kein weiterer Sequencer in den Standard-Paketen aufgefallen (bedeutet im Kontext, dass es durchaus nicht der einzige sein muß). Den RealTimeSequencer kann man in der Wahl der Geräte leider nicht beeinflussen. Dafür gibt es aber, wie gesagt, pro im System vorhandenem Midi-Gerät eine Instanz davon. Im Klartext: Jeder Eintrag im Geträte-Info-Array stellt eine Instanz des RealTimeSequencers dar.
Unter Linux gibt es, soweit ich weis, noch die Möglichkeit im "/dev"-Verzeichnis das Standard-Midi-Device ("/dev/midi -> /dev/midi0") zu verlinken.
@Edit: ...ganz Vergessen... Es gibt noch einen Standard-Java-Sequencer ("Java Sound Sequencer"), welcher, soweit ich das feststellen kann, nur dann verwendet wird, wenn im System überhaupt kein Midi-Gerät gefunden wird. Midi wird dann über "javax.sound.sampled" emuliert.


----------



## tomiondrums (27. Jul 2009)

Hey Leutz,
ich hab mir grad einen sehr schönen Workaround für das Problem gefunden. Dazu hab ich mir mal den Aufbau von MIDI-Dateien angeschaut und bemerkt, daß dort i.d.R. am Anfang ein 'SET_TEMPO' MetaEvent im 0. Track enhalten ist (meist auf Tick 0). Da der Sequencer MIDI-Dateien mit einer vollkommen korrekten Tempo-Sync abgespielt hat und nur bei meinen selbstgebauten Tracks das Recording-Tempo immer auf 120bpm liegengeblieben ist, bin ich jetzt nach langem Suchen auf die Idee gekommen, diese ominöse MetaMessage (die zu senden ja eigentlich voll in die Zuständigkeit des Sequencers fällt) selber zu bauen und in den Track einzureihen. Die Message-Type ist übrigens 0x51. Von der setTempoInBPM- oder setTempoInMPQ-Methode sollte man dann keinen Gebrauch mehr machen müssen. (Außerdem - und das finde ich ziemlich cool - sind dadurch präsize getimte Tempoänderungen zu jedem beliebigen Tick möglich)

An alle (speziell an Spacerat), die sich über mein Problem auch den Kopf zerbrochen haben, trotzdem vielen herzlichen Dank!


----------

