Search code examples
trackingphasesupercollider

SuperCollider: automatic phase and frequency alignment of oscillators


Anyone has an idea for automatic phase and frequency alignment?

To explain: assume, you have an Impulse

in = Impulse.ar(Rand(2, 5), Rand(0, 1));

now I'd like to manipulate the frequency of another Impulse such that it adapts its phase and frequency to match the input. Any suggestions, even for a google search are highly appreciated.

[question asked on behalf of a colleague]


Solution

  • I don't agree that this, as frame is a tough problem. Impulses are simple to track - that's why, for example, old rotary dial phones used pulse trains.

    Here's some code that generates an impulse at a random frequency, then resynthesises another impulse at the same frequency. It also outputs a pitch estimate.

    (
    var left, right, master, slave, periodestimatebus, secretfrequency;
    s = Server.default;
    left = Bus.new(\audio, 0,1);
    right = Bus.new(\audio, 1,1);
    periodestimatebus = Bus.control(s,1);
    //choose our secret frequency here for later comparison:
    secretfrequency = rrand(2.0,5.0);
    
    //generate impulse with secret frequency at some arbitrary phase
    master = {Impulse.ar(secretfrequency, Rand(0, 1));}.play(s, left);
    
    slave = {
        var masterin, clockcount, clockoffset, syncedclock, periodestimate, tracking;
        masterin = In.ar(left);
        //This 1 Hz LFSaw is the "clock" against which we measure stuff
        clockcount = LFSaw.ar(1, 0, 0.5, 0.5);
        clockoffset = Latch.ar(clockcount, Delay1.ar(masterin));
        syncedclock = (clockcount - clockoffset).frac;
        //syncedclock is a version of the clock hard-reset (one sample after) every impulse trigger
        periodestimate = Latch.ar(syncedclock, masterin);
        //sanity-check our f impulse
        Out.kr(periodestimatebus, periodestimate);
        //there is no phase estimate per se - what would we measure it against? -
        //but we can resynthesise a new impulse up to a 1 sample delay from the matched clock.
        tracking = (Slope.ar(syncedclock)>0);
    }.play(master, right, 0, addAction: \addAfter);
    
    //Let's see how we performed
    {
        periodestimatebus.get({|periodestimate|
            ["actual/estimated frequency", secretfrequency, periodestimate.reciprocal].postln;
        });
    }.defer(1);
    )
    

    Notes to this code:

    The periodestimate is generated by tricksy use of Delay1 to make sure that it samples the value of the clock just before it is reset. As such it is off by one sample.

    The current implementation will produce a good period estimate with varying frequencies, down to 1Hz at least. Any lower and you'd need to change the clockcount clock to have a different frequency and tweak the arithmetic.

    Many improvements are possible. For example, if you wish to track varying frequencies you might want to tweak it a little bit so that the resynthesized signal does not click too often as it underestimates the signal.