maca-Scruby 0.0.8
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/README.rdoc +26 -0
- data/Rakefile +10 -0
- data/Scruby.gemspec +36 -0
- data/bin/live_session.rb +12 -0
- data/changes +1 -0
- data/lib/live/session.rb +144 -0
- data/lib/scruby.rb +60 -0
- data/lib/scruby/audio/control_name.rb +29 -0
- data/lib/scruby/audio/env.rb +97 -0
- data/lib/scruby/audio/node.rb +20 -0
- data/lib/scruby/audio/server.rb +112 -0
- data/lib/scruby/audio/synth.rb +15 -0
- data/lib/scruby/audio/synthdef.rb +114 -0
- data/lib/scruby/audio/ugens/env_gen.rb +18 -0
- data/lib/scruby/audio/ugens/in_out.rb +43 -0
- data/lib/scruby/audio/ugens/multi_out_ugens.rb +48 -0
- data/lib/scruby/audio/ugens/operation_indices.yaml +92 -0
- data/lib/scruby/audio/ugens/operation_ugens.rb +64 -0
- data/lib/scruby/audio/ugens/ugen.rb +154 -0
- data/lib/scruby/audio/ugens/ugen_defs.yaml +3421 -0
- data/lib/scruby/audio/ugens/ugen_operations.rb +44 -0
- data/lib/scruby/audio/ugens/ugens.rb +34 -0
- data/lib/scruby/control/metro.rb +6 -0
- data/lib/scruby/extensions.rb +109 -0
- data/lib/scruby/typed_array.rb +64 -0
- data/spec/audio/env_gen_specs.rb +25 -0
- data/spec/audio/in_out_spec.rb +107 -0
- data/spec/audio/integration_spec.rb +106 -0
- data/spec/audio/lib_spec.rb +14 -0
- data/spec/audio/multiout_ugen_spec.rb +112 -0
- data/spec/audio/node_spec.rb +60 -0
- data/spec/audio/operation_ugens_spec.rb +189 -0
- data/spec/audio/server_spec.rb +68 -0
- data/spec/audio/synth_spec.rb +46 -0
- data/spec/audio/synthdef_spec.rb +275 -0
- data/spec/audio/ugen_operations_spec.rb +146 -0
- data/spec/audio/ugen_spec.rb +333 -0
- data/spec/audio/ugens_spec.rb +61 -0
- data/spec/env_spec.rb +64 -0
- data/spec/extensions_spec.rb +133 -0
- data/spec/helper.rb +11 -0
- data/spec/typed_array_spec.rb +95 -0
- metadata +129 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Scruby
|
4
|
+
module Audio
|
5
|
+
class UDPSender < OSC::UDPServer #:nodoc:
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
alias :udp_send :send
|
9
|
+
def send( command, host, port, *args )
|
10
|
+
udp_send( OSC::Message.new( command, type_tag(args), *args ), 0, host, port )
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_message( message, host, port )
|
14
|
+
udp_send( message, 0, host, port )
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_tag(*args)
|
18
|
+
args = *args
|
19
|
+
args.collect{ |msg| OSC::Packet.tag( msg ) }.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
$UDP_Sender = UDPSender.instance
|
23
|
+
|
24
|
+
class Server
|
25
|
+
attr_reader :host, :port
|
26
|
+
@@sc_path = '/Applications/SuperCollider/scsynth'
|
27
|
+
@@servers = []
|
28
|
+
|
29
|
+
# Initializes and registers a new Server instance and sets the host and port for it.
|
30
|
+
# The server is a Ruby representation of scsynth which can be a local binary or a remote
|
31
|
+
# server already running.
|
32
|
+
# Server class keeps an array with all the instantiated servers
|
33
|
+
def initialize( host = 'localhost', port = 57111)
|
34
|
+
@host, @port = host, port
|
35
|
+
@@servers << self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Boots the local binary of the scsynth forking a process, it will rise a SCError if the scsynth
|
39
|
+
# binary is not found in /Applications/SuperCollider/scsynt (default Mac OS path) or given path.
|
40
|
+
# The default path can be overriden using Server.scsynt_path=('path')
|
41
|
+
def boot
|
42
|
+
raise SCError.new('Scsynth not found in the given path') unless File.exists?( @@sc_path )
|
43
|
+
Thread.new do
|
44
|
+
path = @@sc_path.scan(/[^\/]+/)
|
45
|
+
@server_pipe = IO.popen( "cd /#{ path[0..-2].join('/') }; ./#{ path.last } -u #{ @port }" )
|
46
|
+
loop { p Special.new(@server_pipe.gets.chomp) }
|
47
|
+
end unless @server_pipe
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sends the /quit OSC signal to the scsynth server and kills the forked process if the scsynth
|
51
|
+
# server is running locally
|
52
|
+
def stop
|
53
|
+
send '/quit'
|
54
|
+
Process.kill 'KILL', @server_pipe.pid if @server_pipe
|
55
|
+
@server_pipe = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sends an OSC command to the scsyth server.
|
59
|
+
# E.g. <tt>server.send('/dumpOSC', 1)</tt>
|
60
|
+
def send( command, *args )
|
61
|
+
return $UDP_Sender.send( command, @host, @port, *args )
|
62
|
+
end
|
63
|
+
|
64
|
+
# Encodes and sends a SynthDef to the scsynth server
|
65
|
+
def send_synth_def( synth_def )
|
66
|
+
*blob = OSC::Blob.new( synth_def.encode ), 0
|
67
|
+
def_message = OSC::Message.new( '/d_recv', $UDP_Sender.type_tag( blob ), *blob )
|
68
|
+
send_message( OSC::Bundle.new( nil, def_message ) )
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def send_message( message ) #:nodoc:
|
73
|
+
$UDP_Sender.send_message( message, @host, @port )
|
74
|
+
end
|
75
|
+
|
76
|
+
public
|
77
|
+
class << self
|
78
|
+
|
79
|
+
# Specify the scsynth binary path
|
80
|
+
def sc_path=( path )
|
81
|
+
@@sc_path = path
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the scsynth path
|
85
|
+
def sc_path
|
86
|
+
@@sc_path
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns an array with all the registered servers
|
90
|
+
def all
|
91
|
+
@@servers
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return a server corresponding to the specified index of the registered servers array
|
95
|
+
def [](index)
|
96
|
+
@@servers[index]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Set a server to the specified index of the registered servers array
|
100
|
+
def []=(index)
|
101
|
+
@@servers[index]
|
102
|
+
@@servers.uniq!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
class SCError < StandardError
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Scruby
|
2
|
+
class Synth < Node
|
3
|
+
|
4
|
+
def initialize( name, opts = {} )
|
5
|
+
super( name, opts.delete(:servers) )
|
6
|
+
@servers.each{ |s| s.send '/s_new', *([@name, self.id, 0, 1] + opts.to_a.flatten) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def set( args = {} )
|
10
|
+
@servers.each{ |s| s.send '/n_set', *([self.id] + args.to_a.flatten) }
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Scruby
|
2
|
+
module Audio
|
3
|
+
class SynthDef
|
4
|
+
attr_reader :name, :children, :constants, :control_names
|
5
|
+
# Creates a new SynthDef instance
|
6
|
+
# An "ugen graph" block should be passed:
|
7
|
+
#
|
8
|
+
# SynthDef.new('simple') do |rate|
|
9
|
+
# Out.ar( 0, SinOsc.ar(rate) )
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Default values and rates for the block can be passed with the <tt>:values => []</tt> and <tt>:rates => []</tt> options:
|
13
|
+
# E.g.
|
14
|
+
# SynthDef.new( :am, :values => [1, 1000, 10, 1] ) do |gate, portadora, moduladora, amp|
|
15
|
+
# modulacion = SinOsc.kr( moduladora, 0, 0.5, 0.5 )
|
16
|
+
# sig = SinOsc.ar( portadora, 0, modulacion )
|
17
|
+
# env = EnvGen.kr( Env.asr(2,1,2), gate, :doneAction => 2 )
|
18
|
+
# Out.ar( 0, sig*env*amp )
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# is equivalent to the Sclang SynthDef
|
22
|
+
# SynthDef(\am, {|gate=1, portadora=1000, moduladora=10, amp=1|
|
23
|
+
# var modulacion, sig, env;
|
24
|
+
# modulacion = SinOsc.kr(moduladora, 0, 0.5, 0.5);
|
25
|
+
# sig = SinOsc.ar(portadora, 0, modulacion);
|
26
|
+
# env = EnvGen.kr(Env.asr(2,1,2), gate, doneAction:2);
|
27
|
+
# Out.ar(0, sig*env*amp);
|
28
|
+
# }).send(s)
|
29
|
+
#
|
30
|
+
def initialize( name, options = {}, &block )
|
31
|
+
@name, @children = name.to_s, []
|
32
|
+
|
33
|
+
values = options.delete( :values ) || []
|
34
|
+
rates = options.delete( :rates ) || []
|
35
|
+
block = block || Proc.new{}
|
36
|
+
|
37
|
+
@control_names = collect_control_names( block, values, rates )
|
38
|
+
build_ugen_graph( block, @control_names )
|
39
|
+
@constants = collect_constants( @children )
|
40
|
+
|
41
|
+
@variants = [] #stub!!!
|
42
|
+
|
43
|
+
warn( 'A SynthDef without a block is useless' ) unless block_given?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a string representing the encoded SynthDef in a way scsynth can interpret and generate.
|
47
|
+
# This method is called by a server instance when sending the synthdef via OSC.
|
48
|
+
#
|
49
|
+
# For complex synthdefs the encoded synthdef can vary a little bit from what SClang would generate
|
50
|
+
# but the results will be interpreted in the same way
|
51
|
+
def encode
|
52
|
+
controls = @control_names.reject { |cn| cn.non_control? }
|
53
|
+
encoded_controls = [controls.size].pack('n') + controls.collect{ |c| c.name.encode + [c.index].pack('n') }.to_s
|
54
|
+
|
55
|
+
init_stream + name.encode + constants.encode_floats + values.flatten.encode_floats + encoded_controls +
|
56
|
+
[children.size].pack('n') + children.collect{ |u| u.encode }.join('') +
|
57
|
+
[@variants.size].pack('n') #stub!!!
|
58
|
+
end
|
59
|
+
|
60
|
+
def init_stream(file_version = 1, number_of_synths = 1) #:nodoc:
|
61
|
+
'SCgf' + [file_version].pack('N') + [number_of_synths].pack('n')
|
62
|
+
end
|
63
|
+
|
64
|
+
def values #:nodoc:
|
65
|
+
@control_names.collect{ |control| control.value }
|
66
|
+
end
|
67
|
+
|
68
|
+
alias :send_msg :send
|
69
|
+
# Sends itself to the given servers. One or more servers or an array of servers can be passed.
|
70
|
+
# If no arguments are given the synthdef gets sent to all instantiated servers
|
71
|
+
# E.g.
|
72
|
+
# s = Server.new('localhost', 5114)
|
73
|
+
# s.boot
|
74
|
+
# r = Server.new('127.1.1.2', 5114)
|
75
|
+
#
|
76
|
+
# SynthDef.new('sdef'){ Out.ar(0, SinOsc.ar(220)) }.send(s)
|
77
|
+
# # this synthdef is only sent to s
|
78
|
+
#
|
79
|
+
# SynthDef.new('sdef2'){ Out.ar(1, SinOsc.ar(222)) }.send
|
80
|
+
# # this synthdef is sent to both s and r
|
81
|
+
#
|
82
|
+
def send( *servers )
|
83
|
+
servers = *servers
|
84
|
+
(servers ? servers.to_array : Server.all).each{ |s| s.send_synth_def( self ) }
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def collect_control_names( function, values, rates ) #:nodoc:
|
90
|
+
return [] if (names = function.argument_names).empty?
|
91
|
+
names.zip( values, rates ).collect_with_index{ |array, index| ControlName.new *(array << index) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_controls( control_names ) #:nodoc:
|
95
|
+
# control_names.select{ |c| c.rate == :noncontrol }.sort_by{ |c| c.control_name.index } +
|
96
|
+
[:scalar, :trigger, :control].collect do |rate|
|
97
|
+
same_rate_array = control_names.select{ |control| control.rate == rate }
|
98
|
+
Control.and_proxies_from( same_rate_array ) unless same_rate_array.empty?
|
99
|
+
end.flatten.compact.sort_by{ |proxy| proxy.control_name.index }
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_ugen_graph( function, control_names ) #:nodoc:
|
103
|
+
Ugen.synthdef = self
|
104
|
+
function.call *build_controls( control_names)
|
105
|
+
Ugen.synthdef = nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def collect_constants( children ) #:nodoc:
|
109
|
+
children.send( :collect_constants ).flatten.compact.uniq
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Scruby
|
2
|
+
module Audio
|
3
|
+
module Ugens
|
4
|
+
class EnvGen < Ugen
|
5
|
+
class << self
|
6
|
+
def ar( envelope, gate = 1, levelScale = 1, levelBias = 0, timeScale = 1, doneAction = 0 )
|
7
|
+
new(:audio, gate, levelScale, levelBias, timeScale, doneAction, *envelope.to_array)
|
8
|
+
end
|
9
|
+
|
10
|
+
def kr( envelope, gate = 1, levelScale = 1, levelBias = 0, timeScale = 1, doneAction = 0 )
|
11
|
+
new(:control, gate, levelScale, levelBias, timeScale, doneAction, *envelope.to_array)
|
12
|
+
end
|
13
|
+
named_args_for :ar, :kr
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Scruby
|
2
|
+
module Audio
|
3
|
+
module Ugens
|
4
|
+
|
5
|
+
class In < MultiOutUgen
|
6
|
+
def initialize( rate, channels, bus ) #:nodoc:
|
7
|
+
super rate, *(0...channels).map{ |i| OutputProxy.new rate, self, i }
|
8
|
+
@inputs = [bus]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.ar( bus, channels = 1 )
|
12
|
+
new :audio, channels, bus
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.kr( bus, num_channels = 1 )
|
16
|
+
new :control, channels, bus
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Out < Ugen
|
21
|
+
def initialize(*args) #:nodoc:
|
22
|
+
super
|
23
|
+
@channels = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.ar ( bus, *inputs )
|
27
|
+
inputs = *inputs
|
28
|
+
new :audio, bus, *inputs; 0.0 #Out has no output
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.kr ( bus, *inputs )
|
32
|
+
inputs = *inputs
|
33
|
+
new :control, bus, *inputs; 0.0 #Out has no output
|
34
|
+
end
|
35
|
+
|
36
|
+
def output_specs
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Scruby
|
2
|
+
module Audio
|
3
|
+
module Ugens
|
4
|
+
|
5
|
+
class OutputProxy < Ugen
|
6
|
+
attr_reader :source, :control_name, :output_index
|
7
|
+
|
8
|
+
def initialize( rate, source, output_index, name = nil )
|
9
|
+
super rate
|
10
|
+
@source, @control_name, @output_index = source, name, output_index
|
11
|
+
end
|
12
|
+
|
13
|
+
def index
|
14
|
+
@source.index
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_to_synthdef; end
|
18
|
+
end
|
19
|
+
|
20
|
+
class MultiOutUgen < Ugen
|
21
|
+
def initialize( rate, *channels )
|
22
|
+
super rate
|
23
|
+
@channels = channels
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.new( rate, *args )
|
27
|
+
super( rate, *args ).channels #returns the channels but gets instantiated
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def output_specs
|
32
|
+
channels.collect{ |output| output.send :output_specs }.flatten
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Control < MultiOutUgen #:nodoc:
|
37
|
+
def initialize( rate, *names )
|
38
|
+
super rate, *names.collect_with_index{|n, i| OutputProxy.new rate, self, i, n }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.and_proxies_from( names )
|
42
|
+
Control.new( names.first.rate, *names )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
binary:
|
3
|
+
!ruby/symbol +: 0
|
4
|
+
!ruby/symbol -: 1
|
5
|
+
!ruby/symbol *: 2
|
6
|
+
!ruby/symbol div: 3
|
7
|
+
!ruby/symbol /: 4
|
8
|
+
!ruby/symbol mod: 5
|
9
|
+
!ruby/symbol <=: 10
|
10
|
+
!ruby/symbol >=: 11
|
11
|
+
!ruby/symbol minimum: 12
|
12
|
+
!ruby/symbol maximum: 13 #can't be called max because there is an array method calle maximum
|
13
|
+
!ruby/symbol lcm: 17
|
14
|
+
!ruby/symbol gcd: 18
|
15
|
+
!ruby/symbol round: 19
|
16
|
+
!ruby/symbol roundUp: 20
|
17
|
+
!ruby/symbol trunc: 21
|
18
|
+
!ruby/symbol atan2: 22
|
19
|
+
!ruby/symbol hypot: 23
|
20
|
+
!ruby/symbol hypotApx: 24
|
21
|
+
!ruby/symbol pow: 25
|
22
|
+
!ruby/symbol leftShift: 26
|
23
|
+
!ruby/symbol rightShift: 27
|
24
|
+
!ruby/symbol unsignedRightShift: 28
|
25
|
+
!ruby/symbol ring1: 30
|
26
|
+
!ruby/symbol ring2: 31
|
27
|
+
!ruby/symbol ring3: 32
|
28
|
+
!ruby/symbol ring4: 33
|
29
|
+
!ruby/symbol difsqr: 34
|
30
|
+
!ruby/symbol sumsqr: 35
|
31
|
+
!ruby/symbol sqrsum: 36
|
32
|
+
!ruby/symbol sqrdif: 37
|
33
|
+
!ruby/symbol absdif: 38
|
34
|
+
!ruby/symbol thresh: 39
|
35
|
+
!ruby/symbol amclip: 40
|
36
|
+
!ruby/symbol scaleneg: 41
|
37
|
+
!ruby/symbol clip2: 42
|
38
|
+
!ruby/symbol excess: 43
|
39
|
+
!ruby/symbol fold2: 44
|
40
|
+
!ruby/symbol wrap2: 45
|
41
|
+
!ruby/symbol rrand: 47
|
42
|
+
!ruby/symbol exprand: 48
|
43
|
+
|
44
|
+
unary:
|
45
|
+
!ruby/symbol neg: 0
|
46
|
+
!ruby/symbol bitNot: 4
|
47
|
+
!ruby/symbol abs: 5
|
48
|
+
!ruby/symbol asFloat: 6
|
49
|
+
!ruby/symbol ceil: 8
|
50
|
+
!ruby/symbol floor: 9
|
51
|
+
!ruby/symbol frac: 10
|
52
|
+
!ruby/symbol sign: 11
|
53
|
+
!ruby/symbol squared: 12
|
54
|
+
!ruby/symbol cubed: 13
|
55
|
+
!ruby/symbol sqrt: 14
|
56
|
+
!ruby/symbol exp: 15
|
57
|
+
!ruby/symbol reciprocal: 16
|
58
|
+
!ruby/symbol midicps: 17
|
59
|
+
!ruby/symbol cpsmidi: 18
|
60
|
+
!ruby/symbol midiratio: 19
|
61
|
+
!ruby/symbol ratiomidi: 20
|
62
|
+
!ruby/symbol dbamp: 21
|
63
|
+
!ruby/symbol ampdb: 22
|
64
|
+
!ruby/symbol octcps: 23
|
65
|
+
!ruby/symbol cpsoct: 24
|
66
|
+
!ruby/symbol log: 25
|
67
|
+
!ruby/symbol log2: 26
|
68
|
+
!ruby/symbol log10: 27
|
69
|
+
!ruby/symbol sin: 28
|
70
|
+
!ruby/symbol cos: 29
|
71
|
+
!ruby/symbol tam: 30
|
72
|
+
!ruby/symbol asin: 31
|
73
|
+
!ruby/symbol acos: 32
|
74
|
+
!ruby/symbol atan: 33
|
75
|
+
!ruby/symbol sinh: 34
|
76
|
+
!ruby/symbol cosh: 35
|
77
|
+
!ruby/symbol tanh: 36
|
78
|
+
!ruby/symbol rand: 37
|
79
|
+
!ruby/symbol rand2: 38
|
80
|
+
!ruby/symbol linrand: 39
|
81
|
+
!ruby/symbol bilinrand: 40
|
82
|
+
!ruby/symbol sum3rand: 41
|
83
|
+
!ruby/symbol distort: 42
|
84
|
+
!ruby/symbol softclip: 43
|
85
|
+
!ruby/symbol coin: 44
|
86
|
+
!ruby/symbol rectWindow: 48
|
87
|
+
!ruby/symbol hanWindow: 49
|
88
|
+
!ruby/symbol welWindow: 50
|
89
|
+
!ruby/symbol triWindow: 51
|
90
|
+
!ruby/symbol ramp: 52
|
91
|
+
!ruby/symbol scurve: 53
|
92
|
+
|