livecode 0.0.8 → 0.1.0

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/.gitignore CHANGED
@@ -19,3 +19,4 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ testing/*
@@ -1,6 +1,12 @@
1
1
  = Livecode: Ruby toolkit for TextMate/OSX
2
2
 
3
- More to come.
3
+ <em>Live coding (sometimes known as 'interactive programming', 'on-the-fly programming', 'just in time programming') is the name given to the process of writing software in realtime as part of a performance. Historically, similar techniques were used to produce early computer art, but recently it has been explored as a more rigorous alternative to laptop DJs who, live coders often feel, lack the charisma and pizzazz of musicians performing live.</em>
4
+
5
+ <em>Generally, this practice stages a more general approach: one of interactive programming, of writing (parts of) programs while they run. Traditionally most computer music programs have tended toward the old write/compile/run model which evolved when computers were much less powerful. This approach has locked out code-level innovation by people whose programming skills are more modest. Some programs have gradually integrated real-time controllers and gesturing (for example, MIDI-driven software synthesis and parameter control). Until recently, however, the musician/composer rarely had the capability of real-time modification of program code itself."</em> - http://en.wikipedia.org/wiki/Livecoding#Live_coding
6
+
7
+ Livecode is a toolkit for livecoding with Ruby using TextMate on OSX. At the core, it's a server/client setup that'll let you run and modify code in realtime.
8
+
9
+ The server and TextMate bundle is functional, I'm currently working on porting over the MIDI code. Watch this space.
4
10
 
5
11
  == Installation
6
12
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.1.0
@@ -0,0 +1,49 @@
1
+ require 'pathname'
2
+
3
+ # = The Livecode library
4
+ #
5
+ # To get started, require and include Livecode:
6
+ #
7
+ # require 'livecode'
8
+ # include Livecode
9
+ #
10
+ # For your main loop, have a look at Timer, Delay or the more sophisticated Clock class.
11
+
12
+ module Livecode
13
+ LIBRARY_PATH = Pathname.new(File.join(File.dirname(__FILE__))).realpath.to_s
14
+ unless self.const_defined?('Loader')
15
+ $:.unshift LIBRARY_PATH
16
+ end
17
+ end
18
+
19
+ # Bootstrap loader
20
+ (bootstrap = %w{livecode/loader livecode/extensions/main}).each{|l| require l}
21
+ self.send(:include, Livecode::Extensions::Main)
22
+ (bootstrap + [__FILE__]).each{|l| Livecode.loader.add_reloadable(l)}
23
+
24
+ # These should all be safe to load multiple times
25
+ %w{
26
+ clock
27
+ clock_recipients
28
+ delay
29
+ extensions/numeric
30
+ extensions/object
31
+ extensions/string
32
+ silenceable
33
+ timer
34
+ }.each{|l| live_require "livecode/#{l}"}
35
+
36
+ # Apply extensions
37
+ Numeric.send(:include, Livecode::Extensions::Numeric)
38
+ Object.send(:include, Livecode::Extensions::Object)
39
+ String.send(:include, Livecode::Extensions::String)
40
+
41
+ class NilClass #:nodoc:
42
+ def chance?; false; end; alias :c? :chance?
43
+ end
44
+ class FalseClass #:nodoc:
45
+ def chance?; false; end; alias :c? :chance?
46
+ end
47
+ class TrueClass #:nodoc:
48
+ def chance?; true; end; alias :c? :chance?
49
+ end
@@ -0,0 +1,192 @@
1
+ module Livecode
2
+
3
+ # = Clock
4
+ #
5
+ # Clock is quite literally the beating pulse of Livecode,
6
+ # providing you with a way to sync up blocks of code.
7
+ #
8
+ # == Creating a clock
9
+ #
10
+ # Clock.new takes two options; :tempo and :resolution.
11
+ # Tempo should be self-explanatory, and is measured in beats
12
+ # per minute (BPM). The default is 120 BPM.
13
+ # Resolution is the number of ticks per beat. The default
14
+ # is 4, which is the equivalent of 16th notes.
15
+ #
16
+ # clock = Clock.new(:tempo => 120, :resolution => 4)
17
+ #
18
+ # == Control
19
+ #
20
+ # clock.start # Starts the clock
21
+ # clock.stop # Stops the clock, resets the tick
22
+ # clock.pause # Pauses the clock, does not reset the tick
23
+ # clock.restart # Restarts the clock
24
+ # clock.running? # Check if the clock is running
25
+ #
26
+ # If you lose track of your variables, you can use
27
+ # <tt>Clock.stop_all</tt> to stop all clocks.
28
+ #
29
+ # == Ticks
30
+ #
31
+ # The clock is based on ticks, which is simply an incrementing counter.
32
+ # <tt>clock.tick</tt> will return the current tick, while
33
+ # <tt>clock.tick_length</tt> will return the length of one tick expressed
34
+ # in seconds.
35
+ #
36
+ # The modulo operator is quite handy for turning ticks into rythmical
37
+ # structures:
38
+ #
39
+ # bassdrum.play if clock.tick % 4 == 0 # Plays the bass drum on every beat
40
+ # snare.play if clock.tick % 8 == 4 # ..and the snare on every other beat
41
+ #
42
+ # == Recipients
43
+ #
44
+ # A recipient is a callback that will be triggered on every tick. Each
45
+ # recipient must have a unique name, and there's a few ways to attach them.
46
+ # The following examples all do the same:
47
+ #
48
+ # clock.recipients.add(:hihat, proc{|clock| hihat.play})
49
+ # clock.recipients[:hihat] = proc{|clock| hihat.play})
50
+ # clock.recipients.hihat = proc{|clock| hihat.play}
51
+ # clock.recipients.hihat{|clock| hihat.play}
52
+ # clock[:hihat] = proc{|clock| hihat.play}
53
+ # clock.hihat = proc{|clock| hihat.play}
54
+ # clock.hihat{|clock| hihat.play}
55
+ #
56
+ # To remove a recipient, simply unset it:
57
+ #
58
+ # clock.hihat = false
59
+ #
60
+ # Furthermore, recipients can be muted, temporarily disabling them:
61
+ #
62
+ # clock.mute(:bassdrum, :snare) # Mutes the bass drum and snare
63
+ # clock.solo(:synth, :bass) # Solos the synth and bass
64
+ # clock.enable_all # Re-enables all recipients
65
+ #
66
+ # Remember: The callbacks are executed sequentially. If your callback takes
67
+ # more than a split second, you should wrap the code in it's own thread to
68
+ # allow for parallel processing.
69
+ #
70
+ # Clock will also try to compensate for the run time of your code in
71
+ # order to stay in sync.
72
+
73
+ class Clock
74
+ class << self
75
+ # Register a new clock.
76
+ def register(clock)
77
+ clocks << clock
78
+ end
79
+
80
+ # Return all registered clocks.
81
+ def clocks
82
+ @@clocks ||= []
83
+ end
84
+
85
+ # Stop all clocks.
86
+ def stop_all
87
+ clocks.each{|clock| clock.stop}
88
+ end
89
+ end
90
+
91
+ attr_accessor :tempo, :resolution
92
+ attr_reader :tick
93
+
94
+ def initialize(options={})
95
+ @tempo = options[:tempo] || 120
96
+ @resolution = options[:resolution] || 4
97
+ @tick = -1
98
+ @thread = nil
99
+ @running = false
100
+ @recipients = ClockRecipients.new
101
+ Clock.register(self)
102
+ end
103
+
104
+ public
105
+
106
+ # Clock recipients
107
+ def recipients; @recipients; end
108
+ alias :r :recipients
109
+
110
+ # Length of a single tick (in seconds).
111
+ def tick_length
112
+ tl = (1/(@tempo.to_f/60))/@resolution
113
+ tl = 0.00000001 if tl <= 0
114
+ tl
115
+ end
116
+ alias :tl :tick_length
117
+
118
+ # Start the clock. Restarts if the clock is already running.
119
+ def start
120
+ @running = true
121
+ stop_thread
122
+ start_thread
123
+ self
124
+ end
125
+ alias :play :start
126
+
127
+ # Stop the clock.
128
+ def stop
129
+ stop_thread
130
+ @tick = -1
131
+ @running = false
132
+ self
133
+ end
134
+
135
+ # Pause the clock.
136
+ def pause
137
+ @running = false
138
+ self
139
+ end
140
+
141
+ # Restart the clock.
142
+ def restart
143
+ @tick = -1
144
+ start
145
+ end
146
+
147
+ # Returns true if the clock is running.
148
+ def running?
149
+ @running ? true : false
150
+ end
151
+
152
+ # Run next tick.
153
+ def tick!
154
+ @tick += 1
155
+ recipients.each do |r|; unless r.silenced?
156
+ if r.kind_of?(Proc)
157
+ r.call(self)
158
+ elsif r.respond_to?(:tick)
159
+ r.tick(self)
160
+ end
161
+ end; end
162
+ end
163
+
164
+ def method_missing(method_name, *args, &block)
165
+ self.recipients.send(method_name, *args, &block)
166
+ end
167
+
168
+ protected
169
+
170
+ # Stop the clock thread.
171
+ def stop_thread
172
+ if @thread
173
+ @thread.exit
174
+ @thread = nil
175
+ end
176
+ end
177
+
178
+ # Start the clock thread.
179
+ def start_thread
180
+ clock = self
181
+ @thread ||= Thread.new do
182
+ next_time = Time.now
183
+ while @running
184
+ clock.tick!
185
+ next_time += tick_length
186
+ sleep_time = next_time - Time.now
187
+ sleep(sleep_time) if sleep_time > 0
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,81 @@
1
+ module Livecode
2
+
3
+ # Recipients hash for the clock, see Clock for documentation.
4
+
5
+ class ClockRecipients
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @collection = {}
10
+ end
11
+
12
+ def each
13
+ @collection.each{|key,r| yield r}
14
+ end
15
+
16
+ def each_with_index
17
+ @collection.each{|key,r| yield key, r}
18
+ end
19
+
20
+ def keys
21
+ @collection.map{|k,o| k}
22
+ end
23
+
24
+ def has_key?(key)
25
+ (@collection.keys.map{|k| k}.include?(key.to_sym)) ? true : false
26
+ end
27
+
28
+ def set(key, obj=nil, &block)
29
+ obj = block if block_given?
30
+ if obj
31
+ @collection[key.to_sym] = Silenceable.apply( obj )
32
+ elsif has_key?(key)
33
+ @collection.delete(key.to_sym)
34
+ end
35
+ end
36
+ alias :add :set
37
+ alias :attach :set
38
+ alias :[]= :set
39
+
40
+ def get(key)
41
+ return nil unless self.has_key?(key)
42
+ @collection[key.to_sym]
43
+ end
44
+ alias :[] :get
45
+
46
+ def method_missing(method_sym, *args, &block)
47
+ method_name = method_sym.to_s
48
+ if method_name =~ /=$/
49
+ self.set(method_name.gsub(/=$/, ''), *args)
50
+ elsif block_given?
51
+ self.set(method_name, *args, &block)
52
+ else
53
+ return nil unless self.has_key?(method_name)
54
+ self.get(method_name)
55
+ end
56
+ end
57
+
58
+ def silence(*keys)
59
+ keys.each{|k| self[k].silenced = true}
60
+ end
61
+ alias :mute :silence
62
+
63
+ def silence_all
64
+ each_with_index{|k,o| self[k].silenced = true}
65
+ end
66
+ alias :mute_all :silence_all
67
+
68
+ def solo(*keys)
69
+ silence_all
70
+ keys.each{|k| self[k].silenced = false}
71
+ end
72
+ alias :play :solo
73
+
74
+ def enable_all
75
+ each_with_index{|k,o| self[k].silenced = false}
76
+ end
77
+ alias :play_all :enable_all
78
+ alias :unsolo :enable_all
79
+
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ module Livecode
2
+
3
+ # = Delay
4
+ #
5
+ # Delay lets you schedule a block for later.
6
+ #
7
+ # Delay.new(1000){puts "One second later"}
8
+ # later = proc{puts "I am a proc"}
9
+ # Delay.new(500, later)
10
+
11
+ class Delay
12
+ attr_accessor :time, :proc
13
+
14
+ # Time is specified in milliseconds
15
+ def initialize(time, proc=nil, &block)
16
+ @time = time || 1
17
+ if proc
18
+ @proc = proc
19
+ elsif block_given?
20
+ @proc = block
21
+ end
22
+ Silenceable.apply(@proc) if @proc
23
+ if @proc && !@proc.silenced?
24
+ @thread = Thread.new do
25
+ sleep @time.to_f / 1000
26
+ @proc.call
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Livecode
2
+ module Extensions
3
+ module Main
4
+ def live_require(file)
5
+ Livecode.loader.load_file(file)
6
+ end
7
+
8
+ def reload!
9
+ Livecode.loader.reload!
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Livecode
2
+ module Extensions
3
+ module Numeric
4
+
5
+ def chance?
6
+ (rand() < self) ? true : false
7
+ end
8
+ alias :c? :chance?
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Livecode
2
+ module Extensions
3
+ module Object
4
+ def help
5
+ puts "No help for #{self.class.to_s}"; nil
6
+ end
7
+ alias :doc :help
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Livecode
2
+ module Extensions
3
+ module String
4
+
5
+ def chance?; true; end
6
+ alias :c? :chance?
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,47 @@
1
+ module Livecode
2
+
3
+ # Handles (re)loading of library files, which is mostly useful for development.
4
+
5
+ class Loader
6
+ attr_accessor :reloadable_files
7
+
8
+ def initialize
9
+ @reloadable_files = []
10
+ @reloading = false
11
+ end
12
+
13
+ def add_reloadable(file)
14
+ file = parse_path(file)
15
+ @reloadable_files << file unless @reloadable_files.include?(file)
16
+ end
17
+
18
+ def load_file(file, force=false)
19
+ file = parse_path(file)
20
+ return false if @reloading && !force && @reloadable_files.include?(file)
21
+ add_reloadable file
22
+ load("#{file}.rb")
23
+ end
24
+
25
+ def reload!
26
+ puts "Reloading: #{@reloadable_files.inspect}"
27
+ @reloading = true
28
+ old_v = $-v; $-v = nil # Temporarily disable warnings
29
+ results = @reloadable_files.map{ |f| load_file( f, true ) }
30
+ $-v = old_v # Re-enable eventual warnings
31
+ @reloading = false
32
+ return results
33
+ end
34
+
35
+ def parse_path(path)
36
+ path.gsub(/\.rb$/, '').gsub(/^\.\//, '')
37
+ end
38
+ end
39
+
40
+ class << self
41
+ attr_accessor :loader
42
+ def loader
43
+ @loader ||= Loader.new
44
+ @loader
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ module Livecode
2
+
3
+ # Silenceable lets you mute procs and blocks when used by Timer and Clock.
4
+ module Silenceable
5
+ class << self
6
+ def apply(obj)
7
+ unless obj.respond_to?(:silenced?)
8
+ class << obj
9
+ include Silenceable
10
+ end
11
+ end
12
+ return obj
13
+ end
14
+ end
15
+ attr_accessor :silenced
16
+ @silenced = false
17
+ def silenced?; @silenced ? true : false; end
18
+ end
19
+ end
@@ -0,0 +1,68 @@
1
+ module Livecode
2
+
3
+ # = Timer
4
+ #
5
+ # Timer lets you loop a block at a certain interval.
6
+ #
7
+ # timer = Timer.new(100) {puts "I'm executed every 100ms!"}
8
+ # timer.stop # Stops the execution
9
+ # timer.start # Starts the timer again
10
+ # timer.time = 200 # Reschedules the timer
11
+ #
12
+ # Proc objects are also supported:
13
+ #
14
+ # timer_proc = proc{puts "I'm a proc"}
15
+ # timer2 = Timer.new(100, timer_proc)
16
+ #
17
+ # Timer will try to compensate for the run time of your code in order to stay in sync.
18
+
19
+ class Timer
20
+ attr_accessor :time, :proc
21
+
22
+ def initialize(time, proc=nil, &block)
23
+ @time = time || 1
24
+ if proc
25
+ @proc = proc
26
+ elsif block_given?
27
+ @proc = block
28
+ end
29
+ @tread = nil
30
+ start
31
+ end
32
+
33
+ # Call the block
34
+ def call
35
+ Silenceable.apply(@proc) if @proc
36
+ if @proc && !@proc.silenced?
37
+ @proc.call
38
+ end
39
+ end
40
+
41
+ # Start the timer
42
+ def start
43
+ stop
44
+ timer = self
45
+ @thread = Thread.new do
46
+ next_time = Time.now
47
+ while true
48
+ timer.call
49
+ next_time += (timer.time.to_f / 1000)
50
+ sleep_time = next_time - Time.now
51
+ sleep(sleep_time) if sleep_time > 0
52
+ end
53
+ end
54
+ end
55
+ alias :run :start
56
+
57
+ # Stop the timer.
58
+ def stop
59
+ if @thread
60
+ @thread.exit
61
+ @thread = nil
62
+ end
63
+ end
64
+ alias :clear :stop
65
+ alias :end :stop
66
+ end
67
+
68
+ end
@@ -7,6 +7,10 @@ module LivecodeServer
7
7
  @__scope_binding ||= Proc.new {}
8
8
  end
9
9
 
10
+ def include(mod)
11
+ self.class.send(:include, mod)
12
+ end
13
+
10
14
  def puts(string)
11
15
  @__server.output string
12
16
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{livecode}
8
- s.version = "0.0.8"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Inge J\303\270rgensen"]
12
- s.date = %q{2009-10-23}
12
+ s.date = %q{2009-10-24}
13
13
  s.default_executable = %q{livecode}
14
14
  s.description = %q{A toolkit for livecoding using Ruby and TextMate on OSX}
15
15
  s.email = %q{inge@elektronaut.no}
@@ -34,6 +34,16 @@ Gem::Specification.new do |s|
34
34
  "extras/textmate/Ruby Livecode.tmbundle/Syntaxes/Ruby Livecode.tmLanguage",
35
35
  "extras/textmate/Ruby Livecode.tmbundle/info.plist",
36
36
  "lib/livecode.rb",
37
+ "lib/livecode/clock.rb",
38
+ "lib/livecode/clock_recipients.rb",
39
+ "lib/livecode/delay.rb",
40
+ "lib/livecode/extensions/main.rb",
41
+ "lib/livecode/extensions/numeric.rb",
42
+ "lib/livecode/extensions/object.rb",
43
+ "lib/livecode/extensions/string.rb",
44
+ "lib/livecode/loader.rb",
45
+ "lib/livecode/silenceable.rb",
46
+ "lib/livecode/timer.rb",
37
47
  "lib/livecode_server.rb",
38
48
  "lib/livecode_server/client.rb",
39
49
  "lib/livecode_server/command.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livecode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Inge J\xC3\xB8rgensen"
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-23 00:00:00 +02:00
12
+ date: 2009-10-24 00:00:00 +02:00
13
13
  default_executable: livecode
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -47,6 +47,16 @@ files:
47
47
  - extras/textmate/Ruby Livecode.tmbundle/Syntaxes/Ruby Livecode.tmLanguage
48
48
  - extras/textmate/Ruby Livecode.tmbundle/info.plist
49
49
  - lib/livecode.rb
50
+ - lib/livecode/clock.rb
51
+ - lib/livecode/clock_recipients.rb
52
+ - lib/livecode/delay.rb
53
+ - lib/livecode/extensions/main.rb
54
+ - lib/livecode/extensions/numeric.rb
55
+ - lib/livecode/extensions/object.rb
56
+ - lib/livecode/extensions/string.rb
57
+ - lib/livecode/loader.rb
58
+ - lib/livecode/silenceable.rb
59
+ - lib/livecode/timer.rb
50
60
  - lib/livecode_server.rb
51
61
  - lib/livecode_server/client.rb
52
62
  - lib/livecode_server/command.rb