Elcrian
Commander
- Registriert
- Feb. 2008
- Beiträge
- 2.493
Hallo,
ich möchte aus bestimmten Gründen ein Midi-File ohne die javax.sound-Library generieren. Dazu habe ich mich gestern erst mit dem Aufbau von Midi-Dateien im Allgemeinen (Header, Footer, Track-Chunks) sowie mit zwei Beispiel-Programmen beschäftigt und es versucht umzusetzen.
Der Sinn ist eigentlich just ein einfaches "Klick" nach einer bestimmten BPM-Geschwindigkeit zu erzeugen.
Ich gebe euch einfach mal den Code rein, habe ihn eigentlich sehr gut auskommentiert.
Mein "Workflow" sollte sein:
Leider sind die Dateien nicht abspielbar. Das ist der Hex-Code der mit einem gewünschten Input von 120BPM raus kommt.
Ich weiß, dass das wohl etwas speziell und komplex ist, das hat aber den Vorteil das mir jemand der sich da auskennt bestimmt einen einfachen Fehler ankreiden können wird.
lg
ich möchte aus bestimmten Gründen ein Midi-File ohne die javax.sound-Library generieren. Dazu habe ich mich gestern erst mit dem Aufbau von Midi-Dateien im Allgemeinen (Header, Footer, Track-Chunks) sowie mit zwei Beispiel-Programmen beschäftigt und es versucht umzusetzen.
Der Sinn ist eigentlich just ein einfaches "Klick" nach einer bestimmten BPM-Geschwindigkeit zu erzeugen.
Ich gebe euch einfach mal den Code rein, habe ihn eigentlich sehr gut auskommentiert.
Mein "Workflow" sollte sein:
- Header
- Track-Chunk (Header/Identifier + NoteOn + NoteOff)
- Footer
- Output
Leider sind die Dateien nicht abspielbar. Das ist der Hex-Code der mit einem gewünschten Input von 120BPM raus kommt.
Code:
4D 54 68 64 00 00 00 06 00 01 00 01 00 00 4D 54 72 6B 00 00 00 14 09 00 3C 78 09 00 3C 78 01 FF 2F 00
Ich weiß, dass das wohl etwas speziell und komplex ist, das hat aber den Vorteil das mir jemand der sich da auskennt bestimmt einen einfachen Fehler ankreiden können wird.

lg
Code:
package rawMidi;
import java.io.DataOutput;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Vector;
/**
* @author: C.H.
* @date: 12/10/11
*
* Translates BPM to midi-file with specified speed
*/
public class RawMidi {
final static int MSPM = 60000000;
// Midi-Event-Values
final static int noteOn = 0x08;
final static int noteOff = 0x09;
final static int metaEvent = 0xFF;
private Vector<int[]> trackStorage = new Vector<int[]>();
// Header Chunk
final static int header[] = new int[]
{
0x4D, 0x54, 0x68, 0x64, // MThd header
0x00, 0x00, 0x00, 0x06, // 0x0000006 chunk-size constant
0x00, 0x01, // format type = 0 -> one track
0x00, 0x01, // track number (value 0-1 -> up to 65,535 tracks)
0x00, // time division (01 -> FPS) in 15 bytes:
0x7F00 // first 7 bits: frames (30), second 8: how many ticks per frame (default TPQ)
/**
* [url=http://www.sonicspot.com/guide/midifiles.html]MIDI File Format - The Sonic Spot[/url]
* The top 7 bits (bit mask 0x7F00) define
* a value for the number of SMPTE frames
* and can be 24, 25, 29 (for 29.97 fps) or 30.
* The remaining byte (bit mask 0x00FF) defines
* how many clock ticks or track delta positions
* there are per frame. So a time division example
* of 0x9978 could be broken down into it's three parts:
* the top bit is one, so it is in SMPTE frames per second format,
* the following 7 bits have a value of 25 (0x19) and the bottom byte has
* a value of 120 (0x78). This means the example plays at 24 frames per second
* SMPTE time and has 120 ticks per frame.
*/
};
// Footer Chunk
final static int footer[] = new int[]
{
0x01, 0xFF, 0x2F, 0x00 // constants
};
/**
* MIDI-Events
*/
private int[] getNoteOn(int delta, int note, int velocity) {
int[] data = new int[4];
data[0] = (byte) delta; // chanel
data[1] = 0x08; // indentifer
data[2] = (byte) note; // note number
data[3] = (byte) velocity; // velocity
return data;
}
private int[] getNoteOff(int delta, int note, int velocity) {
int[] data = new int[4];
data[0] = (byte) delta;
data[1] = 0x09;
data[2] = (byte) note;
data[3] = 0;
return data;
}
private int[] getTempoAsHex(int bpmAsInt) {
int[] data = new int[4];
data[0] = metaEvent;
data[1] = 0x51;
data[2] = 0x03;
data[3] = (byte) parseBPM(bpmAsInt);
return data;
}
/**
* END MIDI-Events
*/
private int parseBPM(int bpm){
if (bpm == 0) return 0;
return MSPM / bpm;
}
private int[] createTrackChunk(int bpmAsInt){
int[] data = new int[16]; // 2 events allowed so far
// track-information
trackStorage.add(getNoteOn(4,60,bpmAsInt));
trackStorage.add(getNoteOff(4,60,bpmAsInt));
// header (constant) -> MTrk
data[0] = 0x4D;
data[1] = 0x54;
data[2] = 0x72;
data[3] = 0x6B;
// Size in Big-Endian as byte (default to reserve array-space)
data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = 0x00;
int size = 0;
for (int p = 0; p < trackStorage.size(); p++) {
// Note on
int[] noteOn = (int[]) trackStorage.get(p);
for (int i = 0; i < noteOn.length; i++) {
data[i+8] = noteOn[i]; // offset 0x09
size++;
}
// Note Off
int[] noteOff = (int[]) trackStorage.get(p);
for (int i = 0; i < noteOff.length; i++) {
data[i+12] = noteOff[i]; // offset 0x13
size++;
}
}
System.out.println("Chunk size: "+size);
// set size / if <= 0xFF
data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = (byte) size+4;
return data;
}
/**
* WRITE
*/
public void writeMetronomeFile(String filename, String dir, int bpm) {
try {
FileOutputStream fos = new FileOutputStream (dir+filename);
fos.write(intArrayToByteArray(header)); // Header-Chunk
fos.write(intArrayToByteArray(createTrackChunk(bpm))); // Track-Chunk
fos.write(intArrayToByteArray(footer)); // Footer-Chunk
System.out.println("File written: "+dir+filename);
} catch (FileNotFoundException e) {
System.err.println(e);
} catch (IOException e) {
System.err.println(e);
}
}
private static byte[] intArrayToByteArray (int[] ints)
{
int l = ints.length;
byte[] out = new byte[ints.length];
for (int i = 0; i < l; i++)
{
out[i] = (byte) ints[i];
}
return out;
}
}