mattly-schuschein-client 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mkdn +31 -0
- data/Rakefile +5 -0
- data/lib/schuschein.rb +39 -0
- data/lib/schuschein/schedule.rb +30 -0
- data/spec/schedule_spec.rb +76 -0
- data/spec/schuschein_spec.rb +68 -0
- data/spec/spec_helper.rb +15 -0
- metadata +68 -0
data/README.mkdn
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Schuschein Client
|
2
|
+
|
3
|
+
by Matthew Lyon <matt@flowerpowered.com>
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
Schuschein is a protocol for synchronizing events across multiple processes and possibly multiple computers on a very rapid timeline. It is intended to be used in music sequencing.
|
8
|
+
|
9
|
+
The name comes from the "Glass Clock of Bad Schuschein" in Terry Pratchett's [Thief of Time](http://wiki.lspace.org/wiki/Glass_Clock) novel, after a clock that ticks at the speed of the universe, thereby destroying time.
|
10
|
+
|
11
|
+
## FEATURES
|
12
|
+
|
13
|
+
- Stores blocks in a queue to be called at a specified tick time in the future
|
14
|
+
- Receives Time Packets from Schuschein Server (not yet released), pulls scheduled processes out and calls them.
|
15
|
+
|
16
|
+
# SYNOPSIS
|
17
|
+
|
18
|
+
scheduler = Schuschein.new 5000
|
19
|
+
process = lambda {|tick, sch| puts "the time is now: #{tick}" }
|
20
|
+
[480, 960, 1440, 1920].each {|time| scheduler.queue.push time, process}
|
21
|
+
a.listen
|
22
|
+
sleep 30
|
23
|
+
# start the Schuschein server at position 0
|
24
|
+
# => the time is now: 480
|
25
|
+
# => the time is now: 960
|
26
|
+
# => the time is now: 1440
|
27
|
+
# => the time is now: 1920
|
28
|
+
|
29
|
+
## REQUIREMENTS
|
30
|
+
|
31
|
+
- [Datagrammer](http://github.com/mattly/datagrammer)
|
data/Rakefile
ADDED
data/lib/schuschein.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'datagrammer'
|
2
|
+
|
3
|
+
alias :L :lambda
|
4
|
+
|
5
|
+
$:.unshift File.dirname(__FILE__)
|
6
|
+
require 'schuschein/schedule'
|
7
|
+
class Schuschein
|
8
|
+
|
9
|
+
def initialize(port=33333, address="0.0.0.0")
|
10
|
+
@queue = Schuschein::Schedule.new
|
11
|
+
@listener = Datagrammer.new(port, :address => address)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :position, :now, :queue, :listener
|
15
|
+
|
16
|
+
def listen
|
17
|
+
@listener.listen do |dg, msg|
|
18
|
+
send(msg.shift.sub(/^\//,''), *msg)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def tick(bar, beat, pulse, absolute)
|
23
|
+
@position = bar, beat, pulse
|
24
|
+
@now = absolute
|
25
|
+
@queue.shift_to(@now).each {|time, proc| proc.call(time, self) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def tempo(rate=nil)
|
29
|
+
return @tempo unless rate
|
30
|
+
self.tempo = rate
|
31
|
+
end
|
32
|
+
|
33
|
+
def tempo=(rate=nil)
|
34
|
+
return unless rate.kind_of?(Numeric)
|
35
|
+
@tempo = rate
|
36
|
+
@listener.speak('/tempo', rate)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Schuschein
|
2
|
+
class Schedule
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@contents = []
|
6
|
+
@time = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :contents, :time
|
10
|
+
|
11
|
+
def at(position, &block)
|
12
|
+
@contents.push [position.to_i, block]
|
13
|
+
end
|
14
|
+
|
15
|
+
def in(delta, &block)
|
16
|
+
at @time + delta.to_i.abs, &block
|
17
|
+
end
|
18
|
+
|
19
|
+
def next(multiple, &block)
|
20
|
+
at @time + multiple - @time % multiple, &block
|
21
|
+
end
|
22
|
+
|
23
|
+
def shift_to(position)
|
24
|
+
@time = position
|
25
|
+
ready, @contents = @contents.partition {|pos, proc| pos <= position }
|
26
|
+
ready
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Schuschein::Schedule do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@schedule = Schuschein::Schedule.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has an empty queue" do
|
10
|
+
@schedule.contents.should be_empty
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a zero time value" do
|
14
|
+
@schedule.time.should be_zero
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "queueing" do
|
18
|
+
before do
|
19
|
+
@event = L{ puts "hi" }
|
20
|
+
end
|
21
|
+
|
22
|
+
it "queues things to a specific time with at" do
|
23
|
+
@schedule.at(100, &@event)
|
24
|
+
@schedule.contents.should include([100, @event])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "queues things to an arbitrary future time with in" do
|
28
|
+
@schedule.time = 100
|
29
|
+
@schedule.in(100, &@event)
|
30
|
+
@schedule.contents.should include([200, @event])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "queues things to the next multiple of time with next" do
|
34
|
+
@schedule.time = 500
|
35
|
+
@schedule.next(480, &@event)
|
36
|
+
@schedule.contents.should include([960, @event])
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "dequeueing" do
|
42
|
+
before do
|
43
|
+
@time = 100
|
44
|
+
@event = L{ puts "hi"}
|
45
|
+
@schedule.at @time, &@event
|
46
|
+
end
|
47
|
+
|
48
|
+
it "doesn't return events after the given time" do
|
49
|
+
@schedule.shift_to(99).should be_empty
|
50
|
+
end
|
51
|
+
|
52
|
+
it "doesn't remove items after the given time" do
|
53
|
+
@schedule.shift_to(99)
|
54
|
+
@schedule.contents.should == [[@time, @event]]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns events for the specified time" do
|
58
|
+
@schedule.shift_to(100).should == [[@time, @event]]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns events before the specified time" do
|
62
|
+
@schedule.shift_to(101).should == [[@time, @event]]
|
63
|
+
end
|
64
|
+
|
65
|
+
it "removes events when returning them" do
|
66
|
+
@schedule.shift_to(100)
|
67
|
+
@schedule.contents.should be_empty
|
68
|
+
end
|
69
|
+
|
70
|
+
it "sets the internal time for reference" do
|
71
|
+
@schedule.shift_to(100)
|
72
|
+
@schedule.time.should == 100
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Schuschein do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@s = Schuschein.new
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
@s.listener.socket.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have a schedule object for its queue" do
|
14
|
+
@s.queue.should be_kind_of(Schuschein::Schedule)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have a datagrammer for its listener" do
|
18
|
+
@s.listener.should be_kind_of(Datagrammer)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "responding to tick messages" do
|
22
|
+
|
23
|
+
def tick
|
24
|
+
@s.tick(4,1,1,7680)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sets the position" do
|
28
|
+
tick
|
29
|
+
@s.position.should == [4,1,1]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "shifts the queue to the absolute time" do
|
33
|
+
@s.queue.should_receive(:shift_to).with(7680).and_return([[7679, proc { "" }]])
|
34
|
+
tick
|
35
|
+
end
|
36
|
+
|
37
|
+
it "calls the procs returned from the queue" do
|
38
|
+
var = 0
|
39
|
+
@s.queue.stub!(:shift_to).and_return([[7679, proc {|t,s| var = t }]])
|
40
|
+
tick
|
41
|
+
var.should == 7679
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "tempo" do
|
47
|
+
it "returns the tempo on #tempo without arguments" do
|
48
|
+
@s.instance_variable_set(:@tempo, 120)
|
49
|
+
@s.tempo.should == 120
|
50
|
+
end
|
51
|
+
|
52
|
+
it "stores the tempo value on #tempo(arg)" do
|
53
|
+
@s.tempo(96)
|
54
|
+
@s.tempo.should == 96
|
55
|
+
end
|
56
|
+
|
57
|
+
it "responds by calling #tempo= on #tempo(arg)" do
|
58
|
+
@s.should_receive(:tempo=).with(96)
|
59
|
+
@s.tempo(96)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "tells the listener to speak the new tempo on #tempo=" do
|
63
|
+
@s.listener.should_receive(:speak).with("/tempo", 96)
|
64
|
+
@s.tempo = 96
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mattly-schuschein-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Lyon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-25 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mattly-datagrammer
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.1
|
23
|
+
version:
|
24
|
+
description: client for the schuschein distributed scheduler
|
25
|
+
email: matt@flowerpowered.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- README.mkdn
|
34
|
+
- Rakefile
|
35
|
+
- spec/schedule_spec.rb
|
36
|
+
- spec/schuschein_spec.rb
|
37
|
+
- spec/spec_helper.rb
|
38
|
+
- lib/schuschein
|
39
|
+
- lib/schuschein/schedule.rb
|
40
|
+
- lib/schuschein.rb
|
41
|
+
has_rdoc: false
|
42
|
+
homepage: http://github.com/mattly/schuschein-client
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.8.6
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: client for the schuschein distributed scheduler
|
67
|
+
test_files: []
|
68
|
+
|