1

私は、サウンド入力ポートからモノフォニック メロディーを取得する小さなデスクトップ アプリケーションを開発しています。ピッチ トラッキング技術を使用して、それを MIDI メッセージに変換し、MIDI ポートにダンプするか、MIDI ファイルに保存します。

実際の問題は、この midi メッセージを midi ファイルに記録するときに発生しReceiverますRealTimeSequencer

プログラムは、別のスレッドで、TSMidiMessageMidiMessage (ほとんどの場合) と値ShortMessagesを取得して作成されたタイムスタンプを保持する便利なクラス ( ) を生成し、このスレッドと共有されます。そのため、がミリ秒単位で表される とタイムスタンプを取得すると、このタイムスタンプから MIDI デルタ時間への変換が 内でどのように行われるかが明確になりました。System.currentMilliSecs()BlockingQueueReceiverMidiMessageSequencer

また、16 進エディタで 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 ファイルの 16 進ダンプです。

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.../.

midiイベントを量子化するのに役立つと期待しているので、ティック解像度(PPQで表される)に小さな(ただし、それらは正しいと思います)値を使用していることに言及することは関連があるかもしれません。問題。私のもう 1 つの考えは、Receiver に送信されるタイムスタンプ データは、エポック タイムスタンプではなく別の方法で表現する必要があるため、その内部作業と一貫性がないということです。

ありがとうございます=)


編集 1: Java Sound Api プログラマーズ ガイド (第 10 章、「デバイスに送信されたメッセージのタイム スタンプ」) を読んだ後、次のことがわかりました。

Java Sound API でデバイス間で送信されるメッセージにオプションで付随できるタイムスタンプは、標準 MIDI ファイルのタイミング値とはかなり異なります。多くの場合、MIDI ファイルのタイミング値はビートやテンポなどの音楽概念に基づいており、各イベントのタイミングは前のイベントからの経過時間を測定します。対照的に、デバイスの Receiver オブジェクトに送信されるメッセージのタイム スタンプは、常に絶対時間をマイクロ秒単位で測定します。具体的には、レシーバーを所有するデバイスが開かれてから経過したマイクロ秒数を測定します。

そこで、送信していたタイムスタンプが期待される形式で表現されていないことがわかりましたが、タイムスタンプ値を無視する必要があることを意味する -1 値にタイムスタンプを置き換えて、簡単なテストを行いました。結果は次のとおりです。同じで、イベントにデルタ タイムスタンプはありません。


編集2:私はまだそれを扱っているので、これらのエポックタイムスタンプを、Receiver. しかし、これは機能しません (ただし、コードは改善されます =))。また、PP 解像度をより高い値 96 に変更しようとしましたが、Java サウンド ガイドでも指定されているように、十分に機能するはずです。

4

1 に答える 1

1

わかりました。問題の回避策を見つけたので、自分の質問に答えます。

正しく機能しない場合は、なぜaを使用して正しいタイミングで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);
于 2012-06-25T19:51:44.707 に答える