scruby 0.2.7

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.
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