我正在开发一个小型桌面应用程序,它从声音输入端口获取单声道旋律,并使用音高跟踪技术将其转换为midi消息,以转储到midi端口或存储到midi文件。
当我使用Receiver
中的RealTimeSequencer
将此midi消息录制到midi文件时,会出现实际问题。
程序在另一个线程中生成一个便利类(TSMidiMessage
),它包含一个MidiMessage(主要是ShortMessages
)和一个以System.currentMilliSecs()
值创建的时间戳,并且它被放入与此主题共享的BlockingQueue
。因此,当Receiver
得到一个MidiMessage
和一个以毫秒表示的时间戳时,现在很清楚或很好地记录了如何在Sequencer
内完成此时间戳到midi delta时间的转换
另外,使用十六进制编辑器查看midi文件时,midi事件的所有时间戳都是0x00,因此它解释了为什么在导入到乐谱编辑器时它没有显示任何音符。
下面是相关代码的redux版本:
public class MidiListener implements Runnable {
private BlockingQueue<TSMidiMessage[]> messageQueue = new ArrayBlockingQueue<TSMidiMessage[]>(10);
private volatile boolean run=false;
public volatile boolean start=false;
public static long startTime;
public void run() {
run = true;
TSMidiMessage[] messageBlock = null;
Sequencer sequencer = null;
Sequence sequence = null;
Track pista1 = null;
Receiver receiver = null;
try{
log.debug("Starting sequencer");
sequencer = MidiSystem.getSequencer();
receiver = sequencer.getReceiver();
// almacenar mensaje midi
log.debug("Starting sequence");
sequence = new Sequence(Sequence.PPQ, cuantizeStringToPPQ(cuantizeMode));
pista1 = sequence.createTrack();
//**** General MIDI sysex -- turn on General MIDI sound set ****
// http://www.automatic-pilot.com/midifile.html
log.trace("Loading General MIDI");
byte[] b = {(byte)0xF0, 0x7E, 0x7F, 0x09, 0x01, (byte)0xF7};
SysexMessage sm = new SysexMessage();
sm.setMessage(b, 6);
MidiEvent me = new MidiEvent(sm,(long)0);
pista1.add(me);
log.trace("Add tempo message");
me = new MidiEvent(midiTools.forgeTempoMessage(dataHolder.bpm), 0L);
pista1.add(me);
log.trace("Add signature message");
me = new MidiEvent(midiTools.forgeSignatureMessage(dataHolder.highMeasureNibble, dataHolder.lowMeasureNibble), 0L);
pista1.add(me);
//**** set omni on ****
ShortMessage mm = new ShortMessage();
mm.setMessage(0xB0, 0x7D,0x00);
me = new MidiEvent(mm,(long)0);
pista1.add(me);
//**** set poly on ****
mm = new ShortMessage();
mm.setMessage(0xB0, 0x7F,0x00);
me = new MidiEvent(mm,(long)0);
pista1.add(me);
mm = new ShortMessage();
mm.setMessage(0xC0, 0x00, 0x00);
me = new MidiEvent(mm,(long)0);
pista1.add(me);
sequencer.setSequence(sequence);
sequencer.recordEnable(pista1, 0);
sequencer.setTempoInBPM(dataHolder.bpm);
sequencer.open();
while(!this.start){
// hold for a moment
}
log.debug("Start recording");
startTime = System.currentTimeMillis();
sequencer.startRecording();
}catch (MidiUnavailableException e) {
log.error("Midi device error", e);
} catch (InvalidMidiDataException e) {
log.error("Invalid midi data", e);
}
while(run){
try {
messageBlock = messageQueue.take();
// Message load
log.debug("Message load");
if(messageBlock[0] == null)
throw new InterruptedException("Thread interrupted");
for(int i=0; i < messageBlock.length; i++){
receiver.send(messageBlock[i].getMessage(), messageBlock[i].getTimeStamp());
}
} catch (InterruptedException e1) {
log.debug("Thread interrupted");
this.run = false;
}
}
log.debug("Saving midi file");
log.trace("Sequence "+sequence);
log.trace("Length "+sequence.getMicrosecondLength()+"ms");
MetaMessage endOfTrack = new MetaMessage();
try {
endOfTrack.setMessage(0x2F, new byte[]{}, 0);
receiver.send(endOfTrack, System.currentTimeMillis());
} catch (InvalidMidiDataException e1) {
log.error(e1);
}
sequencer.stopRecording();
sequencer.recordDisable(pista1);
sequencer.close();
try {
midiTools.renderMidiFile(sequence, mainControl.selectedFile);
} catch (IOException e) {
log.error("Error while saving midi file", e);
}
}
}
这里是一个小生成的midi文件的十六进制转储:
0000000: 4d54 6864 0000 0006 0001 0001 0004 4d54 MThd..........MT
0000010: 726b 0000 0268 00f0 057e 7f09 01f7 00ff rk...h...~......
0000020: 5103 0b71 b000 ff58 0406 0324 0c00 b07d Q..q...X...$...}
0000030: 0000 7f00 00c0 0083 b4b0 5990 4c5d 0080 ..........Y.L]..
0000040: 4c00 0090 4e44 0080 4e00 0090 5064 0080 L...ND..N...Pd..
0000050: 5000 0090 4b68 0080 4b00 0090 506c 0080 P...Kh..K...Pl..
0000060: 5000 0090 4d4f 0080 4d00 0090 582d 0080 P...MO..M...X-..
0000070: 5800 0090 473b 0080 4700 0090 4c39 0080 X...G;..G...L9..
0000080: 4c00 0090 5734 0080 5700 0090 4d2c 0080 L...W4..W...M,..
0000090: 4d00 0090 5731 0080 5700 0090 4b2e 0080 M...W1..W...K...
00000a0: 4b00 0090 5832 0080 5800 0090 4d2d 0080 K...X2..X...M-..
00000b0: 4d00 0090 4e30 0080 4e00 0090 4c2f 0080 M...N0..N...L/..
00000c0: 4c00 0090 4c3c 0080 4c00 0090 4f5d 0080 L...L<..L...O]..
00000d0: 4f00 0090 4a76 0080 4a00 0090 5264 0080 O...Jv..J...Rd..
00000e0: 5200 0090 505b 0080 5000 0090 4c60 0080 R...P[..P...L`..
00000f0: 4c00 0090 5059 0080 5000 0090 4d57 0080 L...PY..P...MW..
0000100: 4d00 0090 505c 0080 5000 0090 4e5a 0080 M...P\..P...NZ..
0000110: 4e00 0090 4d6a 0080 4d00 0090 4b63 0080 N...Mj..M...Kc..
0000120: 4b00 0090 5059 0080 5000 0090 4e5d 0080 K...PY..P...N]..
0000130: 4e00 0090 4d53 0080 4d00 0090 4c52 0080 N...MS..M...LR..
0000140: 4c00 0090 4b41 0080 4b00 0090 4c44 0080 L...KA..K...LD..
0000150: 4c00 0090 4a3f 0080 4a00 0090 4c4b 0080 L...J?..J...LK..
0000160: 4c00 0090 4e4e 0080 4e00 0090 4b5a 0080 L...NN..N...KZ..
0000170: 4b00 0090 4c52 0080 4c00 0090 4e5a 0080 K...LR..L...NZ..
0000180: 4e00 0090 504b 0080 5000 0090 4f56 0080 N...PK..P...OV..
0000190: 4f00 0090 5953 0080 5900 0090 4c55 0080 O...YS..Y...LU..
00001a0: 4c00 0090 4a55 0080 4a00 0090 4e54 0080 L...JU..J...NT..
00001b0: 4e00 0090 4b4f 0080 4b00 0090 4a4c 0080 N...KO..K...JL..
00001c0: 4a00 0090 4c4b 0080 4c00 0090 4b49 0080 J...LK..L...KI..
00001d0: 4b00 0090 4e3d 0080 4e00 0090 4f4b 0080 K...N=..N...OK..
00001e0: 4f00 0090 4e52 0080 4e00 0090 4d4b 0080 O...NR..N...MK..
00001f0: 4d00 0090 4b49 0080 4b00 0090 4f3c 0080 M...KI..K...O<..
0000200: 4f00 0090 4d42 0080 4d00 0090 4b47 0080 O...MB..M...KG..
0000210: 4b00 0090 4f41 0080 4f00 0090 4b4c 0080 K...OA..O...KL..
0000220: 4b00 0090 4d44 0080 4d00 0090 4e3e 0080 K...MD..M...N>..
0000230: 4e00 0090 4c44 0080 4c00 0090 5042 0080 N...LD..L...PB..
0000240: 5000 0090 4b3b 0080 4b00 0090 4c3c 0080 P...K;..K...L<..
0000250: 4c00 0090 4b3e 0080 4b00 0090 4c3b 0080 L...K>..K...L;..
0000260: 4c00 0090 4b3a 0080 4b00 0090 4e35 0080 L...K:..K...N5..
0000270: 4e00 0090 4922 0080 4900 00ff 2f00 N...I"..I.../.
可能有必要提一下,我正在使用较小的(但我猜这些是正确的)滴答分辨率值(以PPQ表示),因为我预计它将有助于量化midi事件,蚂蚁这可能是其中之一解释这个问题。我的另一个想法是,发送给Receiver的时间戳数据与其内部工作不一致,因为它必须以其他方式表达,而不是以纪元时间戳表示。
谢谢=)
编辑1:阅读Java Sound Api Progrramer指南(第10章“发送给设备的消息的时间戳”)后,我发现了这一点:
可以选择伴随之间发送的消息的时间戳 Java Sound API中的设备与时序有很大不同 标准MIDI文件中的值。 MIDI文件中的计时值是 通常基于音乐概念,如节拍和节奏,以及每个 事件的时间测量自上次事件以来经过的时间。在 对比,发送到设备接收器的消息上的时间戳 object始终以微秒为单位测量绝对时间。的具体而言, 它测量自设备以来经过的微秒数 拥有接收器已经打开。
所以我发现我发送的时间戳没有以预期的格式表示,但我做了一个快速测试,将这些时间戳替换为-1值,这意味着它应该忽略时间戳值,并且结果相同,事件中没有增量时间戳。
编辑2:当我还在使用它时,我将那些纪元时间戳转换为具有微秒分辨率的累积时间戳格式,期望来自Receiver
。但这不会使它工作(但改进代码=))。此外,我试图将PP分辨率更改为更高的值,96,它应该足够好,如Java声音指南中所指定的那样。
答案 0 :(得分:1)
好的,所以我会回答我自己的问题,因为我找到了解决问题的方法。
如果它无法正常工作,为什么要使用Receiver
以正确的时间获取MidiMessages
?我们可以绕过这个并手动伪造MidiEvents
,使用累积时间戳(以微秒为单位)并将其转换为以滴答表示的累积时间戳,与PPQ参数成比例。
private long microsecondTickToPPQTick(long msTick, Sequencer seq){
long ret = msTick / 1000;
double rawValue = ret / this.tickSize;
double valueA = (rawValue - Math.floor(rawValue));
double valueB = ((Math.floor(rawValue)+1) - rawValue);
double min = Math.min(valueA, valueB);
if(min == valueA){
ret =(long) Math.floor(rawValue);
}else{
ret =(long) (Math.floor(rawValue)+1);
}
log.info("MidiEvent's timestamp: "+ret);
return ret;
}
此方法还会将音符量化为所选PPQ,从而解决后续音符。您可以使用以下公式找到刻度尺寸(以毫秒为单位):
tickSize = (60.0/bpm)/(double)ppq;
然后你得到正确的滴答测量,并将MidiEvent添加到序列中。
// startTime holds the value for the epoch time when the sequencer started recording.
long microTS = (messageBlock[i].getTimeStamp() - startTime)*1000;
long tick = microsecondTickToPPQTick(microTS, sequencer);
MidiEvent me = new MidiEvent(messageBlock[i].getMessage(), tick);
track1.add(me);