scruby 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +53 -0
- data/README.rdoc +65 -0
- data/Rakefile +10 -0
- data/TODO.markdown +3 -0
- data/examples/example.rb +73 -0
- data/lib/scruby/buffer.rb +153 -0
- data/lib/scruby/bus.rb +67 -0
- data/lib/scruby/control_name.rb +29 -0
- data/lib/scruby/core_ext/array.rb +44 -0
- data/lib/scruby/core_ext/delegator_array.rb +44 -0
- data/lib/scruby/core_ext/fixnum.rb +8 -0
- data/lib/scruby/core_ext/numeric.rb +25 -0
- data/lib/scruby/core_ext/object.rb +23 -0
- data/lib/scruby/core_ext/proc.rb +11 -0
- data/lib/scruby/core_ext/string.rb +5 -0
- data/lib/scruby/core_ext/symbol.rb +5 -0
- data/lib/scruby/core_ext/typed_array.rb +54 -0
- data/lib/scruby/env.rb +93 -0
- data/lib/scruby/group.rb +24 -0
- data/lib/scruby/node.rb +102 -0
- data/lib/scruby/server.rb +182 -0
- data/lib/scruby/synth.rb +50 -0
- data/lib/scruby/synthdef.rb +109 -0
- data/lib/scruby/ticker.rb +92 -0
- data/lib/scruby/ugens/buffer_read_write.rb +98 -0
- data/lib/scruby/ugens/demand.rb +9 -0
- data/lib/scruby/ugens/disk_in_out.rb +33 -0
- data/lib/scruby/ugens/env_gen.rb +38 -0
- data/lib/scruby/ugens/in_out.rb +46 -0
- data/lib/scruby/ugens/multi_out.rb +53 -0
- data/lib/scruby/ugens/operation_indices.yaml +92 -0
- data/lib/scruby/ugens/operation_ugens.rb +63 -0
- data/lib/scruby/ugens/panner.rb +137 -0
- data/lib/scruby/ugens/ugen.rb +173 -0
- data/lib/scruby/ugens/ugen_defs.yaml +3123 -0
- data/lib/scruby/ugens/ugen_operations.rb +57 -0
- data/lib/scruby/ugens/ugens.rb +95 -0
- data/lib/scruby/version.rb +3 -0
- data/lib/scruby.rb +65 -0
- data/scruby.gemspec +27 -0
- data/spec/buffer_read_write_spec.rb +333 -0
- data/spec/buffer_spec.rb +199 -0
- data/spec/bus_spec.rb +184 -0
- data/spec/core_ext/core_ext_spec.rb +120 -0
- data/spec/core_ext/delegator_array_spec.rb +144 -0
- data/spec/core_ext/typed_array_spec.rb +95 -0
- data/spec/demand_spec.rb +81 -0
- data/spec/disk_in_out_spec.rb +138 -0
- data/spec/env_gen_spec.rb +23 -0
- data/spec/env_spec.rb +73 -0
- data/spec/group_spec.rb +71 -0
- data/spec/helper.rb +20 -0
- data/spec/in_out_spec.rb +127 -0
- data/spec/integration_spec.rb +88 -0
- data/spec/multiout_ugen_spec.rb +86 -0
- data/spec/node_spec.rb +112 -0
- data/spec/operation_ugens_spec.rb +196 -0
- data/spec/panner_spec.rb +271 -0
- data/spec/server.rb +12 -0
- data/spec/server_spec.rb +198 -0
- data/spec/synth_spec.rb +103 -0
- data/spec/synthdef_spec.rb +267 -0
- data/spec/ugen_operations_spec.rb +100 -0
- data/spec/ugen_spec.rb +356 -0
- data/spec/ugens_spec.rb +65 -0
- 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
|
data/lib/scruby/group.rb
ADDED
@@ -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
|
data/lib/scruby/node.rb
ADDED
@@ -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
|
data/lib/scruby/synth.rb
ADDED
@@ -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
|