1

我正在开发一个小型桌面应用程序,它从声音输入端口获取单声道旋律,并使用音高跟踪技术将其转换为 midi 消息,以便转储到 midi 端口或存储到 midi 文件。

当我使用Receiver来自RealTimeSequencer.

该程序在另一个线程中生成一个便利类TSMidiMessage(因此,当获取一个和一个时间戳,以毫秒为单位表示时,现在很清楚或有据可查,这个时间戳到 midi delta 时间的转换是如何在.ShortMessagesSystem.currentMilliSecs()BlockingQueueReceiverMidiMessageSequencer

此外,使用十六进制编辑器查看 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 事件,这可能是对此的解释之一问题。我的另一个想法是发送到接收器的时间戳数据与其内部工作不一致,因为它必须以其他方式而不是纪元时间戳表示。

谢谢你=)


编辑 1:阅读 Java Sound Api Progrramer 指南(第 10 章,“发送到设备的消息的时间戳”段落)后,我发现了这一点:

Java Sound API 中设备之间发送的消息可选地伴随的时间戳与标准 MIDI 文件中的时间值完全不同。MIDI 文件中的时序值通常基于节拍和速度等音乐概念,并且每个事件的时序测量自上一个事件以来经过的时间。相比之下,发送到设备接收器对象的消息上的时间戳总是以微秒为单位测量绝对时间。具体来说,它测量自拥有接收器的设备打开以来经过的微秒数

所以我发现我发送的时间戳没有以预期的格式表示,但我做了一个快速测试,将这些时间戳替换为 -1 值,这意味着它应该忽略时间戳值,结果是同样,事件中没有增量时间戳。


编辑 2:当我仍在使用它时,我将那些纪元时间戳转换为具有微秒分辨率的累积时间戳格式,预期来自Receiver. 但这并不能使它起作用(但改进了代码=))。此外,我尝试将 PP 分辨率更改为更高的值,96,这应该足够好,正如 Java 声音指南中所指定的那样。

4

1 回答 1

1

好的,所以我会回答我自己的问题,因为我找到了解决问题的方法。

如果它不能正常工作,为什么要使用 aReceiver来获得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);
于 2012-06-25T19:51:44.707 回答