TITLE:: MeterSync
summary:: Synchronize barlines of SuperCollider LinkClock peers
categories:: Scheduling>Clocks
related:: Classes/LinkClock

DESCRIPTION::

This class implements additional network messaging, beyond the Ableton Link protocol, to ensure that SuperCollider link::Classes/LinkClock:: peers follow the same meter (beatsPerBar) and barlines as other LinkClock peers.

This class, or the associated methods link::Classes/LinkClock#-enableMeterSync:: and link::Classes/LinkClock#-disableMeterSync::, should be used for performances where all linked machines should follow the same meter emphasis::and:: the meter will change during the performance.

If the meter will not change (typical use case being 4/4 time throughout), then it is necessary only to set code::myLinkClock.quantum = beatsPerBar:: on all peers. (See link::Classes/LinkClock#-quantum::.)

In theory, MeterSync may be used with any SuperCollider clock that answers the methods code::beatsPerBar::, code::beatInBar:: and code::baseBarBeats::. It is currently tested only with link::Classes/LinkClock::. It is not meaningful to use with link::Classes/TempoClock::, because TempoClock does nothing to synchronize its beats with any other machines.

note:: This class considers only the barline positions of SuperCollider LinkClock peers! Other Link-capable applications do not implement this protocol. ::


Subsection:: Background

SuperCollider defines emphasis::meter:: using two numbers:

definitionlist::
## link::Classes/TempoClock#-beatsPerBar:: || How many beats comprise one bar (4 = 4/4 time, 3 = 3/4 time).
## link::Classes/TempoClock#-baseBarBeat:: || The beat value at which the meter last changed.
::

For example, if a clock runs four bars of 4/4 time, and then switches to 3/4 time, the barlines are emphasis::not:: multiples of 3. The meter changed at beat 16, so the new barlines will be code::16 + (3 * n):: (code::n:: is an integer). At this point, then, beatsPerBar = 3 and baseBarBeat = 16.

Ableton Link maintains a emphasis::quantum::, which is analogous to link::Classes/TempoClock#-beatsPerBar::. If all peers are following the same meter and have the same quantum, then Link itself will synchronize the barlines. (Thus, in the normal use case of a consistent meter throughout, it is not necessary to use code::MeterSync:: at all.)

However, when changing the Link quantum, Link does not guarantee that beat values will increment consistently. Setting the quantum is meant to be done before the performance begins, not during.

SuperCollider can change its code::beatsPerBar:: independently of the Link quantum. But, in that case, barlines across peers may not be synchronized.

code::MeterSync:: resolves this by querying other SuperCollider Link peers for their current metrical position. If the local machine differs from the other peers, then the local machine adjusts its code::baseBarBeat:: to be in metrical sync with the others. (Beats proceed as normal; only the barline position is adjusted.)


CLASSMETHODS::

METHOD:: new
Returns a new instance, associated with a specific clock object.

ARGUMENT:: clock
The clock which this instance controls.

ARGUMENT:: id
Optional. An integer, uniquely identifying this instance. If not provided, a random integer will be chosen.

ARGUMENT:: ports
Optional. An array of port numbers to which sync messages will be sent. (sclang instances normally use port 57120, but if that port is in use, they will try 57121 and so on, successively. The default here is to try all of 57120-57127.)


INSTANCEMETHODS::

METHOD:: free
Release this instance, disabling metric sync for the associated clock.


METHOD:: resyncMeter
Adjust the clock's barline position to match other SuperCollider peers on the network.

ARGUMENT:: round
Optional. Force a specific subdivision: round = 1 assumes quarter-notes as the metric base; round = 0.5 assumes eighth-notes (e.g. 5/8 time). If not provided, it will be calculated based on the incoming beatsPerBar values from other peers.

ARGUMENT:: verbose
Boolean. If false, suppress information messages.

returns:: The instance.


METHOD:: queryMeter
Query metrical information from other peers, and collect the results. Normally, you should not need to call this method. It is provided in case you want to verify sync, or implement a custom behavior.

ARGUMENT:: action
A function to evaluate, after the results have been obtained. Results are passed to this function as an argument: a List of Events containing:

list::
## id: The remote peer's ID (integer).
## beats: The remote clock's beats, at the time of answering.
## beatsPerBar: The remote clock's meter.
## baseBarBeat: The remote clock's metrical base.
## beatInBar: The remote clock's metrical position within the bar.
## queriedAtBeat: The emphasis::querying:: clock's query time, in beats.
## syncMeter: Boolean. If true, the remote clock is syncing barlines. If false, the remote clock has an answering MeterSync object, but it is disabled.
::

ARGUMENT:: timeout
Float. A number of seconds to wait for replies. (It will always wait for the entire timeout period. LinkClock knows its link::Classes/LinkClock#-numPeers::, but only SuperCollider peers will answer queries, and there is no way to know how many of the peers are SuperCollider.)

returns:: The instance. Replies are asynchronous; use the code::action:: function to respond.

METHOD:: id
Get the integer ID uniquely identifying this instance. The ID is set at initialization time (link::Classes/LinkClock#-enableMeterSync:: or link::#*new::). To change it, free the current instance and create a new one.

METHOD:: ports
Get the array of ports to which sync messages will be sent. The ports are set at initialization time (link::Classes/LinkClock#-enableMeterSync:: or link::#*new::). To change them, free the current instance and create a new one.

returns:: An array of integer port numbers.

METHOD:: adjustMeterBase
A convenience method to adjust the clock's baseBarBeat, given the local metrical position and the remote metrical position. Normally, you do not need to call this method. It is provided in case you do your own analysis of link::Classes/MeterSync#-queryMeter:: results and you want to adjust barlines in your own way.

ARGUMENT:: localBeats
The local clock's beats. Usually this should be derived from code::result[index][\queriedAtBeat]:: (where code::result:: comes from link::Classes/MeterSync#-queryMeter::). Strongly recommended to use this instead of code::clock.beats::, because the query time does not add the timeout.

ARGUMENT:: remoteBeats
The local clock's beats. Usually this should be derived from code::result[index][\beats]::.

ARGUMENT:: round
Optional. Force a specific subdivision: round = 1 (the default) assumes quarter-notes as the metric base; round = 0.5 assumes eighth-notes (e.g. 5/8 time).

returns:: The instance.

code::
// You *may* do something like this optionally,
// if something went wrong.
// Normally you should just call 'resyncMeter'
// and it's all done for you.

l = LinkClock.new.latency_(s.latency);
m = MeterSync(l);

(
m.queryMeter({ |result|
	result.postln;
	m.adjustMeterBase(result[0][\queriedAtBeat], result[0][\beatInBar]);
});
)

l.stop;  // cleans up 'm' automatically
::

METHOD:: enabled
Enable or disable meter sync. If disabled, this instance will no longer respond to meter changes from peers. It will still answer queryMeter, with code::'syncMeter': false:: in the reply (so you can filter the queryMeter results accordingly).

ARGUMENT:: bool
True = enabled, false = disabled.

returns:: Boolean.

METHOD:: clock
Answers with the clock being controlled by this instance.

note:: You cannot change the associated clock object. You should instead link::Classes/MeterSync#-free:: this instance, and create a new instance for the other clock. ::


METHOD:: repeats
Get or set the number of repeats for OSC messaging.

UDP does not detect failure to deliver messages; therefore, messages may be lost. If that happens, LinkClock peers may fail to synchronize properly and link::#-resyncMeter:: may be unable to recover. To prevent this, sync messages are sent 'repeats' times. The default is 4.

METHOD:: delta
Get or set the number of seconds between OSC messaging repeats.

private:: prBroadcast, update, init, prSetMeterAtBeat, prGetMeter

EXAMPLES::

code::
l = LinkClock.new.latency_(s.latency).enableMeterSync;

// If meter gets off, try to resync:
l.getMeterSync.resyncMeter;

// What are other clocks' timebases?
l.getMeterSync.queryMeter({ |result|
	result.do { |row| row.postln };
});

// I want a different ID and/or ports: free, then recreate
l.disableMeterSync;
l.enableMeterSync(1000, (57120..57135));
::
