0

I'm getting periodic signals (beats per minute) from a Transmitter and now want to call methods in a fraction of the period, e.g. send 1/1, 1/2, 1/4, 1/8, 1/16,.. notes.

My solution for this is to create a thread, do a busy wait and then execute the methods. The problem here is that listening to the signal, processing it and sending it back creates a delay of a few milliseconds (depending on the system).

So now I want to determine the delay between the incoming signal and the periodic signal of the thread and if the delay is != 0, stop the current thread and start a new thread after "bpm - delay" milliseconds. How can this be done ?

Illustration:

transmitter signal: |----|----|----|----|

******runner signal : |----|----|----|----|

delay runner signal by "onePeriod - delay" milliseconds:

transmitter signal: |----|----|----|----|

***"runner signal :**** |----|----|----|----|

Both signals are now in sync.

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() {

    }
David Koelle
  • 19,986
  • 23
  • 89
  • 126
user
  • 191
  • 11
  • could you please explain how starting a new thread would help? – Andrei Nikolaenko Feb 13 '15 at 18:59
  • Since the new thread would be delayed, it should now be in sync with the incoming signal ( the delay would be compensated) – user Feb 13 '15 at 19:37
  • What I understood from your code, your runnable doesn't actually listen to a signal, it just uses the calculated period value which can be changed externally any time. So it is not clear what kind of a delay you're speaking about. Delay between what and what? Anyway if there is a delay, your runner can use it to adjust the period (wait longer or shorter). There's no need for a new thread. – Andrei Nikolaenko Feb 13 '15 at 19:39
  • I've added a simple illustration. Hope this helps to understand my problem. – user Feb 13 '15 at 20:09
  • That is exactly what is unclear. If transmitter signals come at strictly equal intervals, then why do you need to average them, and why do you need a thread? You can just call your methods immediately after receiving the signal. If signals come at various intervals, the average value will never be in sync with them no matter what you do. – Andrei Nikolaenko Feb 13 '15 at 20:18
  • You named it. I try to move the runner output forward in time. Scheduling isn't possible. My only information are the timestamps. – user Feb 13 '15 at 20:19
  • Does the output form of the MIDI require it to be without scheduling? You can convert from one type of Midi to another. In any event, real-time processing with Java is hard, because delays due to GC and thread switching are very audible and disruptive. – Phil Freihofner Feb 13 '15 at 20:22
  • The transmitter signals vary sometimes by 1 ms, but overall it's the same (I'm receiving the bpm of the transmitter). If I call the methods immediately I've got the same problem. E.g. If I record some 1/1 notes of the transmitter in a DAW and send the bpm to my program, which immediately sends a 1/1 note after having received a signal, the two notes don't match. So it takes some time till the transmitter has sent it's signal to my program and the program has sent a signal to the DAW. But this time period is constant. – user Feb 13 '15 at 20:34
  • Didn't answer my intended question: Can receiving unit accept and use Midi with scheduling info? Also, am curious: why does it matter if the runner is exactly 1 period or not. Could it be a half period, for example? I don't understand the end goal. – Phil Freihofner Feb 13 '15 at 20:48
  • I had to think a little bit about that, but this is not possible. What I want to do is sending quantised messages dependent on the bpm of the transmitter. So in my runner I would add a counter, check if counter "mod x" = 0 for the 1/1, 1/2, 1/4 notes and then send the previously stored messages at the given time. – user Feb 13 '15 at 21:29

2 Answers2

1

One option would be to implment a Phase-Locked Loop (PLL).

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

Basically, you'll need two threads: One thread sits in a loop waiting for the input beats, and each time it gets a beat, it records the time of arrival.

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

The other thread sits in a loop that goes sixteen times as fast:

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);
}

I'll leave it to you to write the estimate_phase_correction() function. A linear function of the given error with the right coefficient may be all you need. If you get it right, the 16x loop should "lock in" so that every sixteenth semiquaver happens exactly on the beat.


Improvements:

  • have the beat loop compute the tempo.
  • Base the starting guess for the semiquaver period on the current tempo.
  • Notice significant (i.e. abrupt) tempo changes and re-set the semiquaver loop as needed.
Solomon Slow
  • 19,347
  • 4
  • 31
  • 47
  • Cool! But I think you will eventually need to also use output that has scheduling info added, due to lack of real-time guarantees in Java. The disruptions to timing caused by GC, thread-switching, etc., can be quite audible. For example, in the 16x's loop, increment the schedule point by the current increment. – Phil Freihofner Feb 13 '15 at 20:44
  • @PhilFreihofner you may be right. I've never attempted to use Java in a hard real-time application. I'm an old C programmer at heart. I'm also well aware of how noticeable even the smallest glitches in audio timing can be. – Solomon Slow Feb 13 '15 at 20:54
  • That's basically what I did there. If you set the variable "smallestNote" to 1.0/16, the thread will wait a 1/16 note. But I don't know how to implement the estimate_phase_correction function and how to measure the phase_error properly. E.g. if I measure the elapsed time between the incoming beat and the firing of the runner, it's not constant. Like Phil Freihofner said, this might be caused by java itself. – user Feb 14 '15 at 03:04
0

In general, when I work with sound (usually sampled, not MIDI), I find it more accurate to use frame counts than elapsed time. With elapsed time there are too many unknowns (thread slicing, garbage collection, etc.). Latencies may vary, but 44100 frames (if that is the format) is always 1 sec.

With MIDI, doesn't every event have a field with the time that event is supposed to occur? I've seen readouts with both beats/measures and elapsed-time. I would use that info rather real-time time stamps when figuring any sort of positioning onto an existing Midi stream.

If this is something where the incoming is ASAP/real time, but you want to pass it through quantised, can you put scheduling info on the out-going Midi even if the incoming doesn't have it? Then you'd have a solid reference points for the positioning.

Reference on Real-Time. Low Latency Audio Processing in Java: https://diuf.unifr.ch/main/pai/sites/diuf.unifr.ch.main.pai/files/publications/2007_Juillerat_Mueller_Schubiger-Banz_Real_Time.pdf

Phil Freihofner
  • 6,203
  • 1
  • 13
  • 35
  • It's in real time. Unfortunately the only information about the MIDI-messages I've got are the timestamps. – user Feb 13 '15 at 20:13
  • It's coming in, in real time, but can it go out as events with time stamps (with a small latency built in). – Phil Freihofner Feb 13 '15 at 20:19