Earlab Projects-Performances-Stasi/Net Sync
Projects-Performances-Stasi

Net Sync

Projects > Performances > Stasi > Net Sync

/* IZ 100517
NetSync: Synchronize processes running on different computers in a network via OSC. 

*/

/*
a = SyncSender(Pbind(\event, '/beats'));
a.start;

c = SyncAction('/beats', { | ... args | args.postln })

b = SyncResponder.new;
b.addDependant({ | ... args | args.postln; });
c = SimpleController(b, '/test', { | receiver, message, time |
	format("receiver: %, message: %, time: %", receiver, message, time).postln
});
c.remove

b.deactivate;
b.activate;

a.clock.tempo = 2;

=====


a = SyncSender.new;
a.pattern = Pbind
a.start;

b = SyncResponder.new;
b.addDependant({ | ... args | args.postln; });
c = SimpleController(b, '/test', { | receiver, message, time |
	format("receiver: %, message: %, time: %", receiver, message, time).postln
});

Pcollect(['/test', _], Pseries(1, 1, inf))


*/

SyncSender {
	classvar <defaultSyncMessage = '/sync';
	var <>pattern;	// the pattern that produces the stream
	var <>clients;	// array of NetAddr to which the messages from the pattern are broadcast
	var <clockFunc; 	// the function that makes the clock each time that we start the SyncSender
	var <protoEvent; 	// protoEvent supplied to the pattern, with the clock and the clients
	var <clock;	// clock for running the pattern
	var <stream;		// stream that schedules and broadcasts the messages


	*new { | pattern, clients, clockFunc, protoEvent |
		// lazy initialization of netsync event 
		if (Event.partialEvents.playerEvent.eventTypes[\netsync].isNil) {
			Event.addEventType(\netsync, {
				// we are caching these to avoid accessing ~vars in the ~clients do: loop
				var oscMessage, syncEvent, clock; 
				var tempo, beats, beatsPerBar, baseBarBeat=0.0, baseBar;
				var args;
				oscMessage = ~oscMessage ? defaultSyncMessage;
				syncEvent = ~syncEvent;
				clock = ~clock;
				tempo = clock.tempo;
				beats = clock.beats.round.asInteger;
				beatsPerBar = clock.beatsPerBar;
				baseBarBeat = clock.baseBarBeat;
				baseBar = clock.baseBarBeat;
				args = ~args.(syncEvent, clock);
				~sender.broadcast(oscMessage, syncEvent, beats, tempo, beatsPerBar, baseBarBeat, baseBar, *args);
			});		
		}
		^this.newCopyArgs(pattern, (clients ? [NetAddr.localAddr]), 
			clockFunc ?? {{ TempoClock.new }}, protoEvent).init;
	}

	init {
		protoEvent = protoEvent ?? { (type: \netsync, sender: this, syncEvent: 'beats') };
	}

	broadcast { | ... args |
		clients do: { | client | client.sendMsg(*args) }
	}	

	start {
		/* clock must be made each time we start, since clocks stopped by CMD-. cannot restart */
		clock = clockFunc.(this);
		protoEvent[\clock] = clock;
		if (stream.isNil or: { stream.isPlaying.not }) {
			stream = pattern.play(clock, protoEvent);
		}
	}

	stop {
		if (stream.notNil) {
			stream.stop;
			stream = nil;
		}
	}

	pause {
		if (stream.notNil) {
			stream.pause;
		}	
	}

	reset {
		if (stream.notNil) {
			stream.reset;		
		}
	}

	resume {
		if (stream.notNil) {
			stream.resume;
		}
	}
}

SyncResponder {
	classvar <all;	// stores each responder under its syncMessage, because we should not have duplicate messages
	var <responder;
	var <actions;		// dictionary of lists of actions (FunctionList)
	var <dependants;	// dependants are necessary to update things that depend on the osc message itself

	*new { | syncMessage, activate = true |
		var new;
		syncMessage = syncMessage ? SyncSender.defaultSyncMessage;		if (all.isNil) { all = IdentityDictionary.new };
		new = all[syncMessage];
		if (new.notNil) {
			^new
		}{
			new = super.new.init(syncMessage, activate);
			all[syncMessage] = new;
			^new;	
		};
	}

	init { | syncMessage, activate |
		actions = IdentityDictionary.new;
		responder = OSCresponder(nil, syncMessage ? SyncSender.defaultSyncMessage, { | time, resp, msg |
			dependants do: { | d | d.update(this, *msg); };
			actions[msg[1]].(*msg[2..]);
		});
		if (activate) { this.activate };
	}

	addDependant { | dependant |
		if (dependants.isNil) {
			dependants = Set.new add: dependant
		}{
			dependants.add(dependant)
		}
	}

	removeDependant { | dependant |
		if (dependants.notNil) {
			dependants remove: dependant;
			if (dependants.size == 0) { dependants = nil }
		}
	}

	addAction { | syncEvent, func |
		actions[syncEvent] = actions[syncEvent] addFunc: func;
	}

	removeAction { | syncEvent, func |
		var flist;
		flist = actions[syncEvent];
		if (flist.isNil) { ^this };
		if (func === flist) {
			actions.removeAt(syncEvent)
		}{
			flist.removeFunc(func)		
		} 
	}

	activate { responder.add }

	deactivate { responder.remove }
}

/* add individual actions that respond to a syncEvent tag received by a SyncResponder
Any number of actions can be added to listen to the same syncEvent.
An action can be activated or deactivated by sending it the corresponding messages. 
*/
SyncAction {
	var <syncEvent;
	var <action;
	var <responder;
	*new { | syncEvent, action, syncMessage |
		^this.newCopyArgs(syncEvent, action, SyncResponder(syncMessage ? SyncSender.defaultSyncMessage)).activate;	}
	activate {
		responder.addAction(syncEvent, action);
	}
	deactivate { responder.removeAction(syncEvent, action) }	
}