gamelan 0.3
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.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
|
+
|