gamelan 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +27 -0
- data/lib/gamelan.rb +15 -0
- data/lib/gamelan/queue.rb +56 -0
- data/lib/gamelan/scheduler.rb +28 -0
- data/lib/gamelan/task.rb +25 -0
- data/lib/gamelan/timer.rb +62 -0
- metadata +71 -0
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= Gamelan
|
2
|
+
|
3
|
+
Gamelan is a good-enough soft real-time event scheduler especially for music
|
4
|
+
applications. It exposes a simple API for executing Ruby code at a required
|
5
|
+
time. Uses include sending MIDI or OSC messages to external applications or
|
6
|
+
hardware.
|
7
|
+
|
8
|
+
Gamelan also makes life easier by supporting logical time. Logical time is
|
9
|
+
reflected in the scheduler's phase. The unit in logical time is the beat, and
|
10
|
+
the Scheduler's phase will increment by 1.0 with every beat.
|
11
|
+
|
12
|
+
Logical time varies with real time according to the tempo, which is specified
|
13
|
+
in bpm. For example, the Scheduler's phase will increment by 2.0 for every
|
14
|
+
second that elapses when using the default tempo of 120bpm. Applications are
|
15
|
+
free to alter the tempo at any time, including from within tasks.
|
16
|
+
|
17
|
+
= Notes
|
18
|
+
|
19
|
+
The author admits that Ruby is not at all friendly to realtime applications.
|
20
|
+
No guarantees are made about the scheduler's performance. It will not drift
|
21
|
+
(it will always stay in sync with the system clock), but jitter is inevitable.
|
22
|
+
This is minimized by using a hybrid spinlock implementation to wait between
|
23
|
+
dispatches, and by using a reasonably efficient priority queue to store Tasks.
|
24
|
+
|
25
|
+
The design is an elaboration of Topher Cyll's Timer implementation from his
|
26
|
+
book, <em>Practical Ruby Projects</em>, and the Priority Queue implementation
|
27
|
+
comes from Brian Amberg.
|
data/lib/gamelan.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Gamelan is a good-enough soft real-time event scheduler,
|
2
|
+
# written in Ruby, especially for music applications.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008 Jeremy Voorhis <jvoorhis@gmail.com>
|
5
|
+
#
|
6
|
+
# This code released under the terms of the MIT license.
|
7
|
+
|
8
|
+
def jruby?
|
9
|
+
defined?(JRUBY_VERSION)
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'gamelan/scheduler'
|
14
|
+
require 'gamelan/task'
|
15
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Gamelan
|
2
|
+
|
3
|
+
if jruby?
|
4
|
+
|
5
|
+
class Queue
|
6
|
+
include Java
|
7
|
+
include_package 'java.util.concurrent'
|
8
|
+
|
9
|
+
def initialize(sched)
|
10
|
+
@scheduler = sched
|
11
|
+
comparator = lambda { |a,b| a.delay <=> b.delay }
|
12
|
+
@queue = PriorityBlockingQueue.new(10000, &comparator)
|
13
|
+
@lock = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def push(task)
|
17
|
+
@lock.synchronize { @queue.add(task) }
|
18
|
+
end
|
19
|
+
alias << push
|
20
|
+
|
21
|
+
def pop
|
22
|
+
@lock.synchronize { @queue.remove }
|
23
|
+
end
|
24
|
+
|
25
|
+
def ready?
|
26
|
+
@queue.peek && @queue.peek.delay < @scheduler.phase
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
else
|
31
|
+
|
32
|
+
require 'priority_queue/c_priority_queue'
|
33
|
+
require 'priority_queue'
|
34
|
+
|
35
|
+
class Queue
|
36
|
+
def initialize(sched)
|
37
|
+
@scheduler = sched
|
38
|
+
@queue = ::PriorityQueue.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def push(task)
|
42
|
+
@queue.push(task, task.delay)
|
43
|
+
end
|
44
|
+
alias << push
|
45
|
+
|
46
|
+
def pop
|
47
|
+
@queue.delete_min[0]
|
48
|
+
end
|
49
|
+
|
50
|
+
def ready?
|
51
|
+
@queue.min && @queue.min[1] < @scheduler.phase
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'gamelan/timer'
|
2
|
+
require 'gamelan/queue'
|
3
|
+
|
4
|
+
module Gamelan
|
5
|
+
# The scheduler allows the user to schedule tasks, represented by +Gamelan::Task+.
|
6
|
+
class Scheduler < Timer
|
7
|
+
|
8
|
+
# Construct a new scheduler. +Scheduler#run+ must be called explicitly once a Scheduler is created. Accepts two options, +:tempo+ and +:rate+.
|
9
|
+
# [+:tempo+] The tempo's scheduler, in bpm. For example, at +:tempo => 120+, the scheduler's logical +phase+ will advance by 2.0 every 60 seconds.
|
10
|
+
# [+:rate+] Frequency in Hz at which the scheduler will attempt to run ready tasks. For example, The scheduler will poll for tasks 100 times in one
|
11
|
+
# second when +:rate+ is 100.
|
12
|
+
def initialize(options = {})
|
13
|
+
super
|
14
|
+
@queue = Gamelan::Queue.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Schedule a task to be performed at +delay+ beats.
|
18
|
+
def at(delay, *params, &task)
|
19
|
+
@queue << Task.new(self, delay.to_f, *params, &task)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# Run all ready tasks.
|
24
|
+
def dispatch
|
25
|
+
@queue.pop.run while @queue.ready?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/gamelan/task.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Gamelan
|
4
|
+
|
5
|
+
# Tasks run by the Scheduler. A task is a combination of a block to be
|
6
|
+
# run, a delay, in beats, that specifies when to run the Task, and an
|
7
|
+
# optional list of args. A reference to the Scheduler is also stored, so it
|
8
|
+
# can be manipulatd by tasks.
|
9
|
+
class Task
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@scheduler, :at, :phase, :rate, :time
|
12
|
+
attr_reader :delay, :args, :scheduler
|
13
|
+
|
14
|
+
# Construct a Task with a Scheduler reference, a delay in beats, an
|
15
|
+
# optional list of args, and a block.
|
16
|
+
def initialize(sched, delay, *args, &block)
|
17
|
+
@scheduler, @delay, @proc, @args = sched, delay, block, args
|
18
|
+
end
|
19
|
+
|
20
|
+
# The scheduler will invoke Task#run is called with the Task's +delay+ at
|
21
|
+
# the scheduled time. Any optional +args+, if given, will follow.
|
22
|
+
# are yielded to the block.
|
23
|
+
def run; @proc[@delay, *@args] end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Gamelan
|
2
|
+
# The Timer is responsible for executing code at a fixed rate. Users must
|
3
|
+
# subclass +Gamelan::Timer+. See +Gamelan::Scheduler+ for an example.
|
4
|
+
class Timer
|
5
|
+
attr_reader :phase, :rate, :time
|
6
|
+
|
7
|
+
# Construct a new timer. +Timer#run+ must be called explicitly once a Timer
|
8
|
+
# is created. Accepts two options, +:tempo+ and +:rate+.
|
9
|
+
# [+:tempo+] The timer's tempo, in bpm. For example, at +:tempo => 120+,
|
10
|
+
# the timer's logical +phase+ will advance by 2.0 every 60 seconds.
|
11
|
+
# [+:rate+] Frequency in Hz at which the scheduler will dispatch.
|
12
|
+
def initialize(options = {})
|
13
|
+
self.tempo = options.fetch(:tempo, 120)
|
14
|
+
@rate = 1.0 / options.fetch(:rate, 1000)
|
15
|
+
@sleep_for = rate / 10.0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Initialize the scheduler's clock, and begin executing tasks.
|
19
|
+
def run
|
20
|
+
return if @running
|
21
|
+
@running = true
|
22
|
+
@thread = Thread.new do
|
23
|
+
@phase = 0.0
|
24
|
+
@origin = @time = Time.now.to_f
|
25
|
+
loop { dispatch; advance }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Halt the scheduler. Note that the scheduler may be restarted, but is
|
30
|
+
# not resumable.
|
31
|
+
def stop
|
32
|
+
@running = false
|
33
|
+
@thread.kill
|
34
|
+
end
|
35
|
+
|
36
|
+
# Current tempo, in bpm.
|
37
|
+
def tempo
|
38
|
+
@tempo * 60.0
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the tempo in bpm.
|
42
|
+
def tempo=(bpm)
|
43
|
+
@tempo = bpm / 60.0
|
44
|
+
end
|
45
|
+
|
46
|
+
def join; @thread.join end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Advances the internal clock time and spins until it is reached.
|
50
|
+
def advance
|
51
|
+
@time += @rate
|
52
|
+
@phase += (@time - @origin) * @tempo
|
53
|
+
@origin = @time
|
54
|
+
sleep(@sleep_for) until Time.now.to_f >= @time
|
55
|
+
end
|
56
|
+
|
57
|
+
# Run all ready tasks.
|
58
|
+
def dispatch
|
59
|
+
raise NotImplementedError, "subclass responsibility"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gamelan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.3"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Voorhis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-21 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: PriorityQueue
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.2
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: jvoorhis@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- lib/gamelan/queue.rb
|
35
|
+
- lib/gamelan/scheduler.rb
|
36
|
+
- lib/gamelan/task.rb
|
37
|
+
- lib/gamelan/timer.rb
|
38
|
+
- lib/gamelan.rb
|
39
|
+
- README.rdoc
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/jvoorhis/gamelan
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --title
|
45
|
+
- Gamelan
|
46
|
+
- --main
|
47
|
+
- README.rdoc
|
48
|
+
- --line-numbers
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project: gamelan
|
66
|
+
rubygems_version: 1.2.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: Gamelan is a good-enough soft real-time event scheduler, written in Ruby, especially for music applications.
|
70
|
+
test_files: []
|
71
|
+
|