livecode 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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