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