scruby 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +53 -0
  5. data/README.rdoc +65 -0
  6. data/Rakefile +10 -0
  7. data/TODO.markdown +3 -0
  8. data/examples/example.rb +73 -0
  9. data/lib/scruby/buffer.rb +153 -0
  10. data/lib/scruby/bus.rb +67 -0
  11. data/lib/scruby/control_name.rb +29 -0
  12. data/lib/scruby/core_ext/array.rb +44 -0
  13. data/lib/scruby/core_ext/delegator_array.rb +44 -0
  14. data/lib/scruby/core_ext/fixnum.rb +8 -0
  15. data/lib/scruby/core_ext/numeric.rb +25 -0
  16. data/lib/scruby/core_ext/object.rb +23 -0
  17. data/lib/scruby/core_ext/proc.rb +11 -0
  18. data/lib/scruby/core_ext/string.rb +5 -0
  19. data/lib/scruby/core_ext/symbol.rb +5 -0
  20. data/lib/scruby/core_ext/typed_array.rb +54 -0
  21. data/lib/scruby/env.rb +93 -0
  22. data/lib/scruby/group.rb +24 -0
  23. data/lib/scruby/node.rb +102 -0
  24. data/lib/scruby/server.rb +182 -0
  25. data/lib/scruby/synth.rb +50 -0
  26. data/lib/scruby/synthdef.rb +109 -0
  27. data/lib/scruby/ticker.rb +92 -0
  28. data/lib/scruby/ugens/buffer_read_write.rb +98 -0
  29. data/lib/scruby/ugens/demand.rb +9 -0
  30. data/lib/scruby/ugens/disk_in_out.rb +33 -0
  31. data/lib/scruby/ugens/env_gen.rb +38 -0
  32. data/lib/scruby/ugens/in_out.rb +46 -0
  33. data/lib/scruby/ugens/multi_out.rb +53 -0
  34. data/lib/scruby/ugens/operation_indices.yaml +92 -0
  35. data/lib/scruby/ugens/operation_ugens.rb +63 -0
  36. data/lib/scruby/ugens/panner.rb +137 -0
  37. data/lib/scruby/ugens/ugen.rb +173 -0
  38. data/lib/scruby/ugens/ugen_defs.yaml +3123 -0
  39. data/lib/scruby/ugens/ugen_operations.rb +57 -0
  40. data/lib/scruby/ugens/ugens.rb +95 -0
  41. data/lib/scruby/version.rb +3 -0
  42. data/lib/scruby.rb +65 -0
  43. data/scruby.gemspec +27 -0
  44. data/spec/buffer_read_write_spec.rb +333 -0
  45. data/spec/buffer_spec.rb +199 -0
  46. data/spec/bus_spec.rb +184 -0
  47. data/spec/core_ext/core_ext_spec.rb +120 -0
  48. data/spec/core_ext/delegator_array_spec.rb +144 -0
  49. data/spec/core_ext/typed_array_spec.rb +95 -0
  50. data/spec/demand_spec.rb +81 -0
  51. data/spec/disk_in_out_spec.rb +138 -0
  52. data/spec/env_gen_spec.rb +23 -0
  53. data/spec/env_spec.rb +73 -0
  54. data/spec/group_spec.rb +71 -0
  55. data/spec/helper.rb +20 -0
  56. data/spec/in_out_spec.rb +127 -0
  57. data/spec/integration_spec.rb +88 -0
  58. data/spec/multiout_ugen_spec.rb +86 -0
  59. data/spec/node_spec.rb +112 -0
  60. data/spec/operation_ugens_spec.rb +196 -0
  61. data/spec/panner_spec.rb +271 -0
  62. data/spec/server.rb +12 -0
  63. data/spec/server_spec.rb +198 -0
  64. data/spec/synth_spec.rb +103 -0
  65. data/spec/synthdef_spec.rb +267 -0
  66. data/spec/ugen_operations_spec.rb +100 -0
  67. data/spec/ugen_spec.rb +356 -0
  68. data/spec/ugens_spec.rb +65 -0
  69. metadata +207 -0
@@ -0,0 +1,54 @@
1
+
2
+ # Typed array is a kind of Array that only accepts elements of a given Class, it will raise a TypeError if an element of
3
+ # diferent Class is passed to the operation methods or if an Array containing objects of a diferent class is concatenated.
4
+ class TypedArray < Array
5
+ attr_reader :type
6
+
7
+ # +Type+ a Class or an instance, on the operation methods it will match the argument against the Class or instance's Class
8
+ def initialize type, elements = []
9
+ @type = type.instance_of?(Class) ? type : type.class
10
+ check_array_passed elements
11
+ check_types_for_array elements
12
+ super elements
13
+ end
14
+
15
+ # alias :old_plus :+ #:nodoc:
16
+ #
17
+ # def + array
18
+ # self.class.new @type, self.old_plus( array )
19
+ # end
20
+
21
+ def concat array
22
+ check_array_passed array
23
+ check_types_for_array array
24
+ super
25
+ end
26
+
27
+ def << e
28
+ check_type_for_obj e
29
+ super
30
+ end
31
+
32
+ def []= index, e
33
+ check_type_for_obj e
34
+ super
35
+ end
36
+
37
+ def push e
38
+ check_type_for_obj e
39
+ super
40
+ end
41
+
42
+ private
43
+ def check_array_passed obj
44
+ raise TypeError.new( "#{obj} is not Array" ) unless obj.instance_of? Array
45
+ end
46
+
47
+ def check_types_for_array array
48
+ raise TypeError.new("All elements of #{array} should be instance of #{@type}") unless array.reject{ |e| e.instance_of? @type }.empty?
49
+ end
50
+
51
+ def check_type_for_obj obj
52
+ raise TypeError.new("#{obj} is not instance of #{@type}") unless obj.instance_of? @type
53
+ end
54
+ end
data/lib/scruby/env.rb ADDED
@@ -0,0 +1,93 @@
1
+ module Scruby
2
+ class Env
3
+ attr_accessor :levels, :times, :curves, :release_node, :array
4
+ SHAPE_NAMES = {
5
+ :step => 0,
6
+ :lin => 1,
7
+ :linear => 1,
8
+ :exp => 2,
9
+ :exponential => 2,
10
+ :sin => 3,
11
+ :sine => 3,
12
+ :wel => 4,
13
+ :welch => 4,
14
+ :sqr => 6,
15
+ :squared => 6,
16
+ :cub => 7,
17
+ :cubed => 7
18
+ }
19
+
20
+ def initialize levels, times, curves = :lin, release_node = nil, loop_node = nil
21
+ #times should be one less than levels size
22
+ # raise( ArgumentError, 'levels and times must be array')
23
+ @levels, @times, @curves, @release_node, @loop_node = levels, times, curves.to_array, release_node, loop_node
24
+ raise ArgumentError, "levels and times should be array" unless levels.kind_of?(Array) and times.kind_of?(Array)
25
+ end
26
+
27
+ class << self
28
+ def triangle dur = 1, level = 1
29
+ dur = dur * 0.5
30
+ new [0, level, 0], [dur, dur]
31
+ end
32
+
33
+ def sine dur = 1, level = 1
34
+ dur = dur * 0.5
35
+ new [0, level, 0], [dur, dur], :sine
36
+ end
37
+
38
+ def perc attackTime = 0.01, releaseTime = 1, level = 1, curve = -4
39
+ new [0, level, 0], [attackTime, releaseTime], curve
40
+ end
41
+
42
+ def linen attackTime = 0.01, sustainTime = 1, releaseTime = 1, level = 1, curve = :lin
43
+ new [0, level, level, 0], [attackTime, sustainTime, releaseTime], curve
44
+ end
45
+
46
+ def cutoff releaseTime = 0.1, level = 1, curve = :lin
47
+ new [level, 0], [releaseTime], curve, 0
48
+ end
49
+
50
+ def dadsr delayTime = 0.1, attackTime = 0.01, decayTime = 0.3, sustainLevel = 0.5, releaseTime = 1, peakLevel = 1, curve = -4, bias = 0
51
+ new [0, 0, peakLevel, peakLevel * sustainLevel, 0].collect{ |e| e + bias }, [delayTime, attackTime, decayTime, releaseTime], curve, 3
52
+ end
53
+
54
+ def adsr attackTime = 0.01, decayTime = 0.3, sustainLevel = 0.5, releaseTime = 1, peakLevel = 1, curve = -4, bias = 0
55
+ new [0, peakLevel, peakLevel * sustainLevel, 0].collect{ |e| e + bias }, [attackTime, decayTime, releaseTime], curve, 2
56
+ end
57
+
58
+ def asr attackTime = 0.01, sustainLevel = 1, releaseTime = 1, curve = -4
59
+ new [0, sustainLevel, 0], [attackTime, releaseTime], curve, 1
60
+ end
61
+
62
+ named_arguments_for :triangle, :sine, :perc, :linen, :cutoff, :dadsr, :adsr, :asr
63
+ end
64
+
65
+ def to_array
66
+ contents = levels[0], times.size, release_node, loop_node
67
+ contents + levels[1..-1].wrap_and_zip( times, shape_numbers, curve_values ).flatten
68
+ end
69
+
70
+ def shape_numbers
71
+ curves.collect do |curve|
72
+ Ugens::Ugen.valid_input?( curve ) ? 5 : SHAPE_NAMES[curve]
73
+ end
74
+ end
75
+
76
+ def curve_values
77
+ curves.collect do |curve|
78
+ Ugens::Ugen.valid_input?( curve ) ? curve : 0
79
+ end
80
+ end
81
+
82
+ def release_node
83
+ @release_node ||= -99
84
+ end
85
+
86
+ def loop_node
87
+ @loop_node ||= -99
88
+ end
89
+
90
+ def collect_constants #:nodoc:
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ module Scruby
2
+ class Group < Node
3
+
4
+ def free_all
5
+ send '/g_freeAll', self.id
6
+ self
7
+ end
8
+
9
+ def deep_free
10
+ send '/g_deepFree', self.id
11
+ self
12
+ end
13
+
14
+ def dump_tree post = false
15
+ send '/g_dumpTree', self.id, post
16
+ self
17
+ end
18
+
19
+ class << self
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,102 @@
1
+ module Scruby
2
+ class Node
3
+ @@base_id = 999
4
+ attr_reader :servers, :group, :id
5
+
6
+ ACTIONS = [:head, :tail, :before, :after, :replace]
7
+
8
+ def initialize *args
9
+ args.flatten!
10
+ args.compact!
11
+ args.each{ |s| raise TypeError.new("#{s} should be instance of Server") unless Server === s }
12
+ @id = args.pop if args.last.is_a? Integer
13
+ @servers = args.empty? ? Server.all : args
14
+ @id ||= @@base_id += 1
15
+ end
16
+
17
+ def set args = {}
18
+ send '/n_set', self.id, *args.to_a.flatten
19
+ self
20
+ end
21
+
22
+ def free
23
+ send '/n_free', self.id
24
+ @group, @playing, @running = nil, false, false
25
+ self
26
+ end
27
+
28
+ def run run = true
29
+ send '/n_run', self.id, run
30
+ self
31
+ end
32
+
33
+ # Map controls in this Node to read from control or audio rate Buses. Controls are defined in a SynthDef as args or instances of
34
+ # Control or its subclasses. They are specified here using symbols, strings, or indices, and are listed in pairs with Bus objects.
35
+ # The number of sequential controls mapped corresponds to the Bus' number of channels. If this Node is a Group this will map all
36
+ # Nodes within the Group. Note that with mapMsg if you mix audio and control rate busses you will get an Array of two messages
37
+ # rather than a single message. Integer bus indices are assumed to refer to control buses. To map a control to an audio bus, you
38
+ # must use a Bus object.
39
+ def map args
40
+ control, audio, content = ['/n_mapn', self.id], ['/n_mapan', self.id], []
41
+ args = args.to_a.each do |param, bus|
42
+ raise ArgumentError, "`#{ control }` is not a Bus" unless bus.kind_of? Bus
43
+ array = audio if bus.rate == :audio
44
+ array = control if bus.rate == :control
45
+ array.push param, bus.index, bus.channels if array
46
+ end
47
+ content << control unless control.empty?
48
+ content << audio unless audio.empty?
49
+ send_bundle nil, *content
50
+ self
51
+ end
52
+
53
+ # mapn
54
+ def trace
55
+ send '/n_trace', self.id
56
+ self
57
+ end
58
+
59
+ def move_before node
60
+ @group = node.group
61
+ send '/n_before', self.id, node.id
62
+ self
63
+ end
64
+
65
+ def move_after node
66
+ @group = node.group
67
+ send '/n_after', self.id, node.id
68
+ self
69
+ end
70
+
71
+ # def move_to_head group
72
+ # @group = node.group
73
+ # @server.each{ |s| s.send '/n_after', self.id, node.id }
74
+ # end
75
+ #
76
+ # def move_to_tail group
77
+ # @group = node.group
78
+ # @server.each{ |s| s.send '/n_after', self.id, node.id }
79
+ # end
80
+
81
+ def playing?; @playing || false; end
82
+ alias :running? :playing?
83
+
84
+ # Reset the node count
85
+ def self.reset!
86
+ @@base_id = 2000
87
+ end
88
+
89
+ # Sends a bundle to all registered +servers+ for this node
90
+ def send_bundle timestamp, *messages
91
+ bundle = Bundle.new( timestamp, *messages.map{ |message| Message.new *message } )
92
+ @servers.each{ |s| s.send bundle }
93
+ end
94
+
95
+ # Sends a message to all registered +servers+ for this node
96
+ def send command, *args
97
+ message = Message.new command, *args
98
+ @servers.each{ |s| s.send message }
99
+ self
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,182 @@
1
+ require 'singleton'
2
+
3
+ module Scruby
4
+ include OSC
5
+
6
+ TrueClass.send :include, OSC::OSCArgument
7
+ TrueClass.send(:define_method, :to_osc_type){ 1 }
8
+
9
+ FalseClass.send :include, OSC::OSCArgument
10
+ FalseClass.send(:define_method, :to_osc_type){ 0 }
11
+
12
+ Hash.send :include, OSC::OSCArgument
13
+ Hash.send :define_method, :to_osc_type do
14
+ self.to_a.collect{ |pair| pair.collect{ |a| OSC.coerce_argument a } }
15
+ end
16
+
17
+ Array.send(:include, OSC::OSCArgument)
18
+ Array.send( :define_method, :to_osc_type) do
19
+ Blob.new Message.new(*self).encode
20
+ end
21
+
22
+ class Server
23
+ attr_reader :host, :port, :path, :buffers, :control_buses, :audio_buses
24
+ DEFAULTS = { :buffers => 1024, :control_buses => 4096, :audio_buses => 128, :audio_outputs => 8, :audio_inputs => 8,
25
+ :host => 'localhost', :port => 57111, :path => '/Applications/SuperCollider/scsynth'
26
+ }
27
+
28
+ # Initializes and registers a new Server instance and sets the host and port for it.
29
+ # The server is a Ruby representation of scsynth which can be a local binary or a remote
30
+ # server already running.
31
+ # Server class keeps an array with all the instantiated servers
32
+ # Options:
33
+ # +host+:
34
+ # defaults to 'localhost'
35
+ # +port+:
36
+ # TCP port defaults to 57111
37
+ # +control_buses+
38
+ # Number of buses for routing control data defaults to 4096, indices start at 0.
39
+ # +audio_buses+
40
+ # Number of audio Bus channels for hardware output and input and internal routing, defaults to 128
41
+ # +audio_outputs+
42
+ # Reserved +buses+ for hardware output, indices available are 0 to +audio_outputs+ - 1 defaults to 8.
43
+ # +audio_inputs+
44
+ # Reserved +buses+ for hardware input, +audio_outputs+ to (+audio_outputs+ + +audio_inputs+ - 1), defaults to 8.
45
+ # +buffers+
46
+ # Number of available sample buffers defaults to 1024
47
+ def initialize opts = {}
48
+ @opts = DEFAULTS.dup.merge opts
49
+ @buffers = []
50
+ @control_buses = []
51
+ @audio_buses = []
52
+ @client = Client.new port, host
53
+ Bus.audio self, @opts[:audio_outputs] # register hardware buses
54
+ Bus.audio self, @opts[:audio_inputs]
55
+ self.class.all << self
56
+ end
57
+
58
+ def host; @opts[:host]; end
59
+ def port; @opts[:port]; end
60
+ def path; @opts[:path]; end
61
+
62
+ # Boots the local binary of the scsynth forking a process, it will rise a SCError if the scsynth
63
+ # binary is not found in /Applications/SuperCollider/scsynth (default Mac OS path) or given path.
64
+ # The default path can be overriden using Server.scsynt_path=('path')
65
+ def boot
66
+ raise SCError.new('Scsynth not found in the given path') unless File.exists? path
67
+ if running?
68
+ warn "Server on port #{ port } allready running"
69
+ return self
70
+ end
71
+
72
+ ready = false
73
+ timeout = Time.now + 2
74
+ @thread = Thread.new do
75
+ IO.popen "cd #{ File.dirname path }; ./#{ File.basename path } -u #{ port }" do |pipe|
76
+ loop do
77
+ if response = pipe.gets
78
+ puts response
79
+ ready = true if response.match /ready/
80
+ end
81
+ end
82
+ end
83
+ end
84
+ sleep 0.01 until ready or !@thread.alive? or Time.now > timeout
85
+ sleep 0.01 # just to be shure
86
+ send "/g_new", 1 # default group
87
+ self
88
+ end
89
+
90
+ def running?
91
+ @thread and @thread.alive? ? true : false
92
+ end
93
+
94
+ def stop
95
+ send "/g_freeAll", 0
96
+ send "/clearSched"
97
+ send "/g_new", 1
98
+ end
99
+ alias :panic :stop
100
+
101
+ # Sends the /quit OSC signal to the scsynth
102
+ def quit
103
+ Server.all.delete self
104
+ send '/quit'
105
+ end
106
+
107
+ # Sends an OSC command or +Message+ to the scsyth server.
108
+ # E.g. +server.send('/dumpOSC', 1)+
109
+ def send message, *args
110
+ message = Message.new message, *args unless Message === message or Bundle === message
111
+ @client.send message
112
+ end
113
+
114
+ def send_bundle timestamp = nil, *messages
115
+ send Bundle.new( timestamp, *messages.map{ |message| Message.new *message } )
116
+ end
117
+
118
+ # Encodes and sends a SynthDef to the scsynth server
119
+ def send_synth_def synth_def
120
+ send Bundle.new( nil, Message.new('/d_recv', Blob.new(synth_def.encode), 0) )
121
+ end
122
+
123
+ # Allocates either buffer or bus indices, should be consecutive
124
+ def allocate kind, *elements
125
+ collection = instance_variable_get "@#{kind}"
126
+ elements.flatten!
127
+
128
+ max_size = @opts[kind]
129
+ if collection.compact.size + elements.size > max_size
130
+ raise SCError, "No more indices available -- free some #{ kind } before allocating more."
131
+ end
132
+
133
+ return collection.concat(elements) unless collection.index nil # just concat arrays if no nil item
134
+
135
+ indices = []
136
+ collection.each_with_index do |item, index| # find n number of consecutive nil indices
137
+ break if indices.size >= elements.size
138
+ if item.nil?
139
+ indices << index
140
+ else
141
+ indices.clear
142
+ end
143
+ end
144
+
145
+ case
146
+ when indices.size >= elements.size
147
+ collection[indices.first, elements.size] = elements
148
+ when collection.size + elements.size <= max_size
149
+ collection.concat elements
150
+ else
151
+ raise SCError, "No block of #{ elements.size } consecutive #{ kind } indices is available."
152
+ end
153
+ end
154
+
155
+ @@servers = []
156
+ class << self
157
+ # Returns an array with all the registered servers
158
+ def all
159
+ @@servers
160
+ end
161
+
162
+ # Clear the servers array
163
+ def clear
164
+ @@servers.clear
165
+ end
166
+
167
+ # Return a server corresponding to the specified index of the registered servers array
168
+ def [] index
169
+ @@servers[index]
170
+ end
171
+
172
+ # Set a server to the specified index of the registered servers array
173
+ def []= index
174
+ @@servers[index]
175
+ @@servers.uniq!
176
+ end
177
+ end
178
+ end
179
+
180
+ class SCError < StandardError
181
+ end
182
+ end
@@ -0,0 +1,50 @@
1
+ module Scruby
2
+ class Synth < Node
3
+ attr_reader :name
4
+
5
+ def initialize name, servers
6
+ super servers
7
+ @name = name.to_s
8
+ end
9
+
10
+ class << self
11
+ def new name, args = {}, target = nil, action = :head
12
+ case target
13
+ when nil
14
+ target_id, servers = 1, nil
15
+ when Group
16
+ target_id, servers = group.id, target.servers
17
+ when Node
18
+ target_id, servers = 1, target.servers
19
+ else
20
+ raise TypeError.new("expected #{ target } to kind of Node or nil")
21
+ end
22
+
23
+ synth = super name, servers
24
+ synth.send '/s_new', synth.name, synth.id, Node::ACTIONS.index(action), target_id, args
25
+ synth
26
+ end
27
+
28
+ def after target, name, args = {}
29
+ new name, args, target, :after
30
+ end
31
+
32
+ def before target, name, args = {}
33
+ new name, args, target, :before
34
+ end
35
+
36
+ def head target, name, args = {}
37
+ new name, args, target, :head
38
+ end
39
+
40
+ def tail target, name, args = {}
41
+ new name, args, target, :tail
42
+ end
43
+
44
+ def replace target, name, args = {}
45
+ new name, args, target, :replace
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,109 @@
1
+ module Scruby
2
+ class SynthDef
3
+ attr_reader :name, :children, :constants, :control_names
4
+ # Creates a new SynthDef instance
5
+ # An "ugen graph" block should be passed:
6
+ #
7
+ # SynthDef.new('simple') do |rate|
8
+ # Out.ar( 0, SinOsc.ar(rate) )
9
+ # end
10
+ #
11
+ # Default values and rates for the block can be passed with the <tt>:values => []</tt> and <tt>:rates => []</tt> options:
12
+ # E.g.
13
+ # SynthDef.new( :am, :values => [1, 1000, 10, 1] ) do |gate, portadora, moduladora, amp|
14
+ # modulacion = SinOsc.kr( moduladora, 0, 0.5, 0.5 )
15
+ # sig = SinOsc.ar( portadora, 0, modulacion )
16
+ # env = EnvGen.kr( Env.asr(2,1,2), gate, :doneAction => 2 )
17
+ # Out.ar( 0, sig*env*amp )
18
+ # end
19
+ #
20
+ # is equivalent to the Sclang SynthDef
21
+ # SynthDef(\am, {|gate=1, portadora=1000, moduladora=10, amp=1|
22
+ # var modulacion, sig, env;
23
+ # modulacion = SinOsc.kr(moduladora, 0, 0.5, 0.5);
24
+ # sig = SinOsc.ar(portadora, 0, modulacion);
25
+ # env = EnvGen.kr(Env.asr(2,1,2), gate, doneAction:2);
26
+ # Out.ar(0, sig*env*amp);
27
+ # }).send(s)
28
+ #
29
+ def initialize name, options = {}, &block
30
+ @name, @children = name.to_s, []
31
+ raise( ArgumentError.new('An UGen graph (block) must be passed') ) unless block_given?
32
+
33
+ values = options.delete( :values ) || []
34
+ rates = options.delete( :rates ) || []
35
+
36
+ @control_names = collect_control_names block, values, rates
37
+ build_ugen_graph block, @control_names
38
+ @constants = collect_constants @children
39
+
40
+ @variants = [] #stub!!!
41
+ end
42
+
43
+ # Returns a string representing the encoded SynthDef in a way scsynth can interpret and generate.
44
+ # This method is called by a server instance when sending the synthdef via OSC.
45
+ #
46
+ # For complex synthdefs the encoded synthdef can vary a little bit from what SClang would generate
47
+ # but the results will be interpreted in the same way
48
+ def encode
49
+ controls = @control_names.reject { |cn| cn.non_control? }
50
+ encoded_controls = [controls.size].pack('n') + controls.collect{ |c| c.name.encode + [c.index].pack('n') }.join
51
+
52
+ init_stream + name.encode + constants.encode_floats + values.flatten.encode_floats + encoded_controls +
53
+ [children.size].pack('n') + children.collect{ |u| u.encode }.join +
54
+ [@variants.size].pack('n') #stub!!!
55
+ end
56
+
57
+ def init_stream file_version = 1, number_of_synths = 1 #:nodoc:
58
+ 'SCgf' + [file_version].pack('N') + [number_of_synths].pack('n')
59
+ end
60
+
61
+ def values #:nodoc:
62
+ @control_names.collect{ |control| control.value }
63
+ end
64
+
65
+ alias :send_msg :send
66
+ # Sends itself to the given servers. One or more servers or an array of servers can be passed.
67
+ # If no arguments are given the synthdef gets sent to all instantiated servers
68
+ # E.g.
69
+ # s = Server.new('localhost', 5114)
70
+ # s.boot
71
+ # r = Server.new('127.1.1.2', 5114)
72
+ #
73
+ # SynthDef.new('sdef'){ Out.ar(0, SinOsc.ar(220)) }.send(s)
74
+ # # this synthdef is only sent to s
75
+ #
76
+ # SynthDef.new('sdef2'){ Out.ar(1, SinOsc.ar(222)) }.send
77
+ # # this synthdef is sent to both s and r
78
+ #
79
+ def send *servers
80
+ servers.peel!
81
+ (servers.empty? ? Server.all : servers).each{ |s| s.send_synth_def( self ) }
82
+ self
83
+ end
84
+
85
+ private
86
+ def collect_control_names function, values, rates
87
+ names = function.arguments
88
+ names.zip( values, rates ).collect_with_index{ |array, index| ControlName.new *(array << index) }
89
+ end
90
+
91
+ def build_controls control_names
92
+ # control_names.select{ |c| c.rate == :noncontrol }.sort_by{ |c| c.control_name.index } +
93
+ [:scalar, :trigger, :control].collect do |rate|
94
+ same_rate_array = control_names.select{ |control| control.rate == rate }
95
+ Control.and_proxies_from( same_rate_array ) unless same_rate_array.empty?
96
+ end.flatten.compact.sort_by{ |proxy| proxy.control_name.index }
97
+ end
98
+
99
+ def build_ugen_graph function, control_names
100
+ Ugen.synthdef = self
101
+ function.call *build_controls(control_names)
102
+ Ugen.synthdef = nil
103
+ end
104
+
105
+ def collect_constants children
106
+ children.send( :collect_constants ).flatten.compact.uniq
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,92 @@
1
+ module Scruby
2
+ # Thread.new do
3
+ # EventMachine.run do
4
+ # EM.set_quantum 5
5
+ # EM.error_handler do |e|
6
+ # puts e
7
+ # end
8
+ # end
9
+ # end
10
+
11
+ # A timer will call a given block periodically. The period is specified in beats per minute.
12
+ class Ticker
13
+ attr_reader :start, :tick, :interval
14
+ attr_accessor :tempo, :resolution, :size, :loop
15
+
16
+ def initialize tempo = 120, size = 16, resolution = 1, loop = true, &block
17
+ @tempo , @resolution, @size, @loop = tempo , resolution, size, loop
18
+ @interval = 60.0 / @tempo
19
+ @tick = 0
20
+ @block = block
21
+ end
22
+
23
+ named_args_for :initialize
24
+
25
+ def block &block
26
+ @block = block
27
+ end
28
+
29
+ def run
30
+ return self if @timer
31
+ @start = Time.now
32
+ @timer = EventMachine::PeriodicTimer.new @interval * 0.01 do
33
+ if @next.nil? or Time.now >= @next
34
+ dispatch
35
+ @tick += @resolution
36
+ next_time
37
+ end
38
+ end
39
+ self
40
+ end
41
+
42
+ def index
43
+ return @tick unless @size
44
+ tick = @tick % @size
45
+ if tick == 0 and @tick > 0 and !@loop
46
+ stop
47
+ nil
48
+ else
49
+ tick
50
+ end
51
+ end
52
+
53
+ def next_time
54
+ @next = @start + @tick * @interval
55
+ end
56
+
57
+ def stop
58
+ @timer.cancel if @timer
59
+ @timer = nil
60
+ @next = nil
61
+ @tick = 0
62
+ self
63
+ end
64
+
65
+ def running?
66
+ not @timer.nil?
67
+ end
68
+
69
+ def dispatch
70
+ @block.call index if @block
71
+ end
72
+ end
73
+
74
+ class Scheduler < Ticker
75
+ def initialize opts = {}
76
+ super
77
+ @queue = []
78
+ end
79
+
80
+ def dispatch
81
+ if blocks = @queue[index]
82
+ blocks.each{ |b| b.call }
83
+ end
84
+ end
85
+
86
+ def at tick, &proc
87
+ @queue[tick] ||= []
88
+ @queue[tick].push proc
89
+ end
90
+ end
91
+
92
+ end