同步到每分钟节拍/发送量化消息

时间:2015-02-13 18:44:47

标签: java multithreading quantization javax.sound.midi

我从Transmitter获得定期信号(每分钟节拍),现在想要在一段时间内调用方法,例如发送1 / 1,1 / 2,1 / 4,1 / 8,1 / 16,..笔记。

我的解决方案是创建一个线程,执行繁忙的等待,然后执行这些方法。这里的问题是,收听信号,处理信号并将其发送回来会产生几毫秒的延迟(取决于系统)。

所以现在我想确定输入信号和线程的周期信号之间的延迟,如果延迟是!= 0,停止当前线程并在" bpm - delay&#34之后启动一个新线程;毫秒。怎么办呢?

插图:

发射器信号:| ---- | ---- | ---- | ---- |

******跑者信号:| ---- | ---- | ---- | ---- |

延迟转轮信号由" onePeriod - 延迟"毫秒:

发射器信号:| ---- | ---- | ---- | ---- |

***"跑者信号:**** | ---- | ---- | ---- | ---- |

两个信号现在都是同步的。

public class Quantiser implements Receiver{
    private int[] bpmsInMillis = new int[4];
    private int bpmInMillis=0;
    private double smallestNote = 1;
    private long period=0;

    private long fire=0;
    private long prevTimeStamp=0;

    private Runnable runny = new Runnable() {
        @Override
        public void run() {
            while(true){
                fire = System.nanoTime() + period;
                while(System.nanoTime() < fire){} // busy wait
                // Call some methods here.
            }
        }
    };
    private Thread thread = new Thread(runny);


    @Override
    public void send(MidiMessage message, long timeStamp) {

        // Calculate average bpm
        for(int i=0; i<bpmsInMillis.length-1;i++)
            bpmsInMillis[i] = bpmsInMillis[i+1];

        bpmsInMillis[bpmsInMillis.length-1] = (int) ((timeStamp - prevTimeStamp) / 1000);

        bpmInMillis = arithmeticMean(bpmsInMillis);
        prevTimeStamp = timeStamp;

        period = (long) (bpmInMillis * smallestNote * 1000000);

        if(!thread.isAlive()) {
            thread.start();
        }
        /*
        else{
            Calculate delay between signal and thread-signal.
            if(delay != 0){                    
                Delay new thread by "bpm - delay" milliseconds.
                Stop old thread.
                Start new thread.
            }
        */            
    }

    @Override
    public void close() {

    }

2 个答案:

答案 0 :(得分:1)

一种选择是实现锁相环(PLL)。

http://en.wikipedia.org/wiki/Phase-locked_loop

基本上,你需要两个线程:一个线程坐在循环中等待输入节拍,每次获得节拍时,它会记录到达时间。

long time_of_last_beat;
while (true) {
    wait_for_next_beat();
    time_of_last_beat = System.currentTimeMillis();
}

另一个线程处于循环中,速度提高了16倍:

long semiquaver_duration = <starting guess>;
while (true) {
    notify_whoever_cares_that_its_time_for_the_next_semiquaver();
    Thread.sleep(sixteenth_note_duration);
    long phase_error = System.currentTimeMillis() - time_of_last_beat;
    semiquaver_duration += estimate_phase_correction(phase_error);
}

我会留给你写estimate_phase_correction()函数。具有正确系数的给定误差的线性函数可能是您所需要的。如果你做对了,16x循环应该&#34;锁定&#34;所以每十六个半卦都会在节拍上发生。


改进:

  • 让节拍循环计算速度。
  • 将半节奏周期的起始猜测基于当前速度。
  • 注意显着(即突然)速度变化和根据需要重新设置semiquaver循环。

答案 1 :(得分:0)

一般来说,当我处理声音(通常是采样而不是MIDI)时,我发现使用帧计数比使用时间更准确。随着时间的流逝,有太多未知数(线程切片,垃圾收集等)。延迟可能会有所不同,但44100帧(如果是格式)总是1秒。

对于MIDI,不是每个事件都有一个字段,其中包含事件应该发生的时间吗?我看过有节拍/测量和经过时间的读数。在对现有的Midi流进行任何定位时,我会使用该信息而非实时时间戳。

如果这是传送是实时/实时的,但你想通过量化传递它,你是否可以在即将到来的Midi上安排调度信息,即使传入的没有它?然后你就有了一个坚实的定位参考点。

实时参考。 Java中的低延迟音频处理: https://diuf.unifr.ch/main/pai/sites/diuf.unifr.ch.main.pai/files/publications/2007_Juillerat_Mueller_Schubiger-Banz_Real_Time.pdf