Permalink
Browse files

refactored module structure, removed Audio module

  • Loading branch information...
1 parent d13a17c commit 54ff5c165484a8067a33f376eccf407f13ba810d @maca committed Jul 27, 2009
View
86 Manifest.txt
@@ -49,21 +49,21 @@ doc/created.rid
doc/files/README_rdoc.html
doc/files/bin/live_session_rb.html
doc/files/lib/live/session_rb.html
-doc/files/lib/scruby/audio/control_name_rb.html
-doc/files/lib/scruby/audio/env_rb.html
-doc/files/lib/scruby/audio/node_rb.html
-doc/files/lib/scruby/audio/server_rb.html
-doc/files/lib/scruby/audio/synth_rb.html
-doc/files/lib/scruby/audio/synthdef_rb.html
-doc/files/lib/scruby/audio/ugens/env_gen_rb.html
-doc/files/lib/scruby/audio/ugens/in_out_rb.html
-doc/files/lib/scruby/audio/ugens/multi_out_ugens_rb.html
-doc/files/lib/scruby/audio/ugens/operation_indices_yaml.html
-doc/files/lib/scruby/audio/ugens/operation_ugens_rb.html
-doc/files/lib/scruby/audio/ugens/ugen_defs_yaml.html
-doc/files/lib/scruby/audio/ugens/ugen_operations_rb.html
-doc/files/lib/scruby/audio/ugens/ugen_rb.html
-doc/files/lib/scruby/audio/ugens/ugens_rb.html
+doc/files/lib/scruby/control_name_rb.html
+doc/files/lib/scruby/env_rb.html
+doc/files/lib/scruby/node_rb.html
+doc/files/lib/scruby/server_rb.html
+doc/files/lib/scruby/synth_rb.html
+doc/files/lib/scruby/synthdef_rb.html
+doc/files/lib/scruby/ugens/env_gen_rb.html
+doc/files/lib/scruby/ugens/in_out_rb.html
+doc/files/lib/scruby/ugens/multi_out_ugens_rb.html
+doc/files/lib/scruby/ugens/operation_indices_yaml.html
+doc/files/lib/scruby/ugens/operation_ugens_rb.html
+doc/files/lib/scruby/ugens/ugen_defs_yaml.html
+doc/files/lib/scruby/ugens/ugen_operations_rb.html
+doc/files/lib/scruby/ugens/ugen_rb.html
+doc/files/lib/scruby/ugens/ugens_rb.html
doc/files/lib/scruby/control/metro_rb.html
doc/files/lib/scruby/extensions_rb.html
doc/files/lib/scruby/typed_array_rb.html
@@ -84,39 +84,39 @@ extras/Ruby Live.tmbundle/Syntaxes/Ruby Live.tmLanguage
extras/Ruby Live.tmbundle/info.plist
lib/live/session.rb
lib/scruby.rb
-lib/scruby/audio/control_name.rb
-lib/scruby/audio/env.rb
-lib/scruby/audio/node.rb
-lib/scruby/audio/server.rb
-lib/scruby/audio/synth.rb
-lib/scruby/audio/synthdef.rb
-lib/scruby/audio/ugens/env_gen.rb
-lib/scruby/audio/ugens/in_out.rb
-lib/scruby/audio/ugens/multi_out_ugens.rb
-lib/scruby/audio/ugens/operation_indices.yaml
-lib/scruby/audio/ugens/operation_ugens.rb
-lib/scruby/audio/ugens/ugen.rb
-lib/scruby/audio/ugens/ugen_defs.yaml
-lib/scruby/audio/ugens/ugen_operations.rb
-lib/scruby/audio/ugens/ugens.rb
+lib/scruby/control_name.rb
+lib/scruby/env.rb
+lib/scruby/node.rb
+lib/scruby/server.rb
+lib/scruby/synth.rb
+lib/scruby/synthdef.rb
+lib/scruby/ugens/env_gen.rb
+lib/scruby/ugens/in_out.rb
+lib/scruby/ugens/multi_out_ugens.rb
+lib/scruby/ugens/operation_indices.yaml
+lib/scruby/ugens/operation_ugens.rb
+lib/scruby/ugens/ugen.rb
+lib/scruby/ugens/ugen_defs.yaml
+lib/scruby/ugens/ugen_operations.rb
+lib/scruby/ugens/ugens.rb
lib/scruby/extensions.rb
lib/scruby/typed_array.rb
script/console
script/destroy
script/generate
-spec/audio/env_gen_specs.rb
-spec/audio/in_out_spec.rb
-spec/audio/integration_spec.rb
-spec/audio/lib_spec.rb
-spec/audio/multiout_ugen_spec.rb
-spec/audio/node_spec.rb
-spec/audio/operation_ugens_spec.rb
-spec/audio/server_spec.rb
-spec/audio/synth_spec.rb
-spec/audio/synthdef_spec.rb
-spec/audio/ugen_operations_spec.rb
-spec/audio/ugen_spec.rb
-spec/audio/ugens_spec.rb
+spec/env_gen_specs.rb
+spec/in_out_spec.rb
+spec/integration_spec.rb
+spec/lib_spec.rb
+spec/multiout_ugen_spec.rb
+spec/node_spec.rb
+spec/operation_ugens_spec.rb
+spec/server_spec.rb
+spec/synth_spec.rb
+spec/synthdef_spec.rb
+spec/ugen_operations_spec.rb
+spec/ugen_spec.rb
+spec/ugens_spec.rb
spec/control/timer_spec.rb
spec/env_spec.rb
spec/extensions_spec.rb
View
31 lib/scruby.rb
@@ -39,29 +39,28 @@ module Scruby
require "scruby/core_ext/symbol"
require "scruby/core_ext/typed_array"
require "scruby/core_ext/delegator_array"
-require "scruby/audio/env"
-require "scruby/audio/control_name"
+require "scruby/env"
+require "scruby/control_name"
-require "scruby/audio/ugens/ugen"
-require "scruby/audio/ugens/ugen_operations"
-require "scruby/audio/ugens/multi_out_ugens"
-require "scruby/audio/ugens/in_out"
+require "scruby/ugens/ugen"
+require "scruby/ugens/ugen_operations"
+require "scruby/ugens/multi_out_ugens"
+require "scruby/ugens/in_out"
-require "scruby/audio/ugens/operation_ugens"
+require "scruby/ugens/operation_ugens"
-require "scruby/audio/ugens/ugens"
-require "scruby/audio/synthdef"
+require "scruby/ugens/ugens"
+require "scruby/synthdef"
-require "scruby/audio/server"
-require "scruby/audio/ugens/env_gen"
+require "scruby/server"
+require "scruby/ugens/env_gen"
-require "scruby/audio/node"
-require "scruby/audio/synth"
-require "scruby/audio/bus"
-require "scruby/audio/buffer"
+require "scruby/node"
+require "scruby/synth"
+require "scruby/bus"
+require "scruby/buffer"
include Scruby
-include Audio
include Ugens
View
BIN lib/scruby/.DS_Store
Binary file not shown.
View
153 lib/scruby/buffer.rb
@@ -0,0 +1,153 @@
+module Scruby
+ def expand_path path
+ path = "~/Scruby/#{ path }" unless path.match %r{^(?:/|~)}
+ File.expand_path path
+ end
+
+ class Buffer
+ # readNoUpdate
+ # loadCollection
+ # sendCollection
+ # streamCollection
+ # loadToFloatArray
+ # getToFloatArray
+ # set
+ # setn
+ # get
+ # getn
+ # fill
+ # normalize
+ # gen
+ # sine1
+ # ...
+ # copy
+ # copyData
+ # query
+ # updateInfo
+ # queryDone
+ # printOn
+ # play
+ # duration
+ # asBufWithValues
+
+ attr_reader :server
+ attr_accessor :path, :frames, :channels, :rate
+
+ def read path, file_start = 0, frames = -1, buff_start = 0, leave_open = false, &message
+ # @on_info = message
+ message ||= ["/b_query", buffnum]
+ @server.send "/b_read", buffnum, expand_path(path), file_start, frames, buff_start, leave_open, message.value(self)
+ self
+ end
+
+ def read_channel path, file_start = 0, frames = -1, buff_start = 0, leave_open = false, channels = [], &message
+ message ||= 0
+ @server.send *(["/b_ReadChannel", buffnum, expand_path(path), start, frames, buff_start, leave_open] + channels << message.value(self))
+ self
+ end
+
+ def close &message
+ message ||= 0
+ @server.send '/b_close', buffnum, message.value(self)
+ self
+ end
+
+ def zero &message
+ message ||= 0
+ @server.send '/b_zero', buffnum, message.value(self)
+ self
+ end
+
+ def cue_sound_file path, start = 0, &message
+ message ||= 0
+ @server.send "/b_read", buffnum, expand_path(path), start, @frames, 0, 1, message.value(self)
+ self
+ end
+
+ def write path = nil, format = 'aiff', sample_format = 'int24', frames = -1, start = 0, leave_open = false, &message
+ message ||= 0
+ path ||= "#{ DateTime.now }.#{ format }"
+ @server.send "/b_write", buffnum, expand_path(path), format, sample_format, frames, start, leave_open, message.value(self)
+ self
+ end
+
+ def initialize server, frames = -1, channels = 1
+ @server, @frames, @channels = server, frames, channels
+ end
+
+ def allocate &message
+ message ||= 0
+ @server.allocate :buffers, self
+ @server.send '/b_alloc', buffnum, frames, channels, message.value(self)
+ self
+ end
+
+ def buffnum
+ @server.buffers.index self
+ end
+ alias :as_ugen_input :buffnum
+ alias :index :buffnum
+ # alias :as_control_input :buffnum
+
+ def free &message
+ message ||= 0
+ @server.send "/b_free", buffnum, message.value(self)
+ @server.buffers.delete self
+ end
+
+ # :nodoc:
+ def allocate_and_read path, start, frames, &message
+ @server.allocate :buffers, self
+ message ||= ["/b_query", buffnum]
+ @server.send "/b_allocRead", buffnum, @path = expand_path(path), start, frames, message.value(self)
+ self
+ end
+
+ def allocate_read_channel path, start, frames, channels, &message
+ @server.allocate :buffers, self
+ message ||= ["/b_query", buffnum]
+ @server.send *(["/b_allocReadChannel", buffnum, expand_path(path), start, frames] + channels << message.value(self))
+ self
+ end
+
+ class << self
+ def allocate server, frames = -1, channels = 1, &message
+ new(server, frames, channels).allocate &message
+ end
+
+ def cue_sound_file server, path, start, channels = 2, buff_size = 32768, &message
+ allocate server, buff_size, channels do |buffer|
+ message ||= 0
+ ['/b_read', buffer.buffnum, expand_path(path), start, buff_size, 0, true, message.value(buffer)]
+ end
+ end
+
+ # Allocate a buffer and immediately read a soundfile into it.
+ def read server, path, start = 0, frames = -1, &message
+ buffer = new server, &message
+ buffer.allocate_and_read expand_path(path), start, frames
+ end
+
+ def read_channel server, path, start = 0, frames = -1, channels = [], &message
+ new(server, frames, channels).allocate_read_channel expand_path(path), start, frames, channels, &message
+ end
+
+ def alloc_consecutive buffers, server, frames = -1, channels = 1, &message
+ buffers = Array.new(buffers){ new server, frames, channels }
+ server.allocate :buffers, buffers
+ message ||= 0
+ buffers.each do |buff|
+ server.send '/b_alloc', buff.buffnum, frames, channels, message.value(buff)
+ end
+ end
+
+ named_arguments_for :allocate, :read, :cue_sound_file, :alloc_consecutive, :read_channel
+
+ # readNoUpdate
+ # loadCollection
+ # sendCollection
+ # loadDialog
+ end
+ end
+end
+
View
67 lib/scruby/bus.rb
@@ -0,0 +1,67 @@
+module Scruby
+
+ class Bus
+ attr_reader :server, :rate, :channels, :main_bus
+
+ def initialize server, rate, channels = 1, main_bus = self, hardware_out = false
+ @server, @rate, @channels, @main_bus, @hardware_out = server, rate, channels, main_bus, hardware_out
+ end
+
+ def index
+ @index ||= @server.__send__("#{ @rate }_buses").index(self)
+ end
+
+ def free
+ @index = nil
+ @server.__send__("#{ @rate }_buses").delete(self)
+ end
+
+ def to_map
+ raise SCError, 'Audio buses cannot be mapped' if rate == :audio
+ "c#{ index }"
+ end
+
+ def audio_out?
+ index < @server.instance_variable_get(:@opts)[:audio_outputs]
+ end
+
+ # Messaging
+ def set *args
+ args.flatten!
+ message_args = []
+ (index...channels).to_a.zip(args) do |chan, val|
+ message_args.push(chan).push(val) if chan and val
+ end
+ if args.size > channels
+ warn "You tried to set #{ args.size } values for bus #{ index } that only has #{ channels } channels, extra values are ignored."
+ end
+ @server.send '/c_set', *message_args
+ end
+
+ def fill value, channels = @channels
+ if channels > @channels
+ warn "You tried to set #{ channels } values for bus #{ index } that only has #{ @channels } channels, extra values are ignored."
+ end
+ @server.send '/c_fill', index, channels.min(@channels), value
+ end
+
+ class << self
+ private :new
+
+ def control server, channels = 1
+ buses = [new(server, :control, channels)]
+ buses.push new(server, :control, channels, buses.first) while buses.size < channels
+ server.allocate :control_buses, buses
+ buses.first
+ end
+
+ def audio server, channels = 1
+ buses = [new(server, :audio, channels)]
+ buses.push new(server, :audio, channels, buses.first) while buses.size < channels
+ server.allocate :audio_buses, buses
+ buses.first
+ end
+ end
+
+ end
+end
View
29 lib/scruby/control_name.rb
@@ -0,0 +1,29 @@
+module Scruby
+ class ControlName #:nodoc:
+ attr_accessor :name, :value, :rate, :index
+ RATES = { 'n_' => :noncontrol, 'i_' => :scalar, 'k_' => :control, 't_' => :trigger }
+
+ def initialize name, value, rate, index
+ @name, @value, @rate, @index = name.to_s, value.to_f, set_rate( name, rate ), index
+ end
+
+ def set_rate name, rate
+ RATES.has_value?( rate ) ? rate : rate_from_name( name )
+ end
+
+ def rate_from_name name
+ RATES[ name.to_s[0..1] ] || :control
+ end
+
+ def non_control?
+ @rate == :noncontrol
+ end
+
+ def == other
+ @name == other.name and
+ @value == other.value and
+ @rate == other.rate and
+ @index == other.index
+ end
+ end
+end
View
93 lib/scruby/env.rb
@@ -0,0 +1,93 @@
+module Scruby
+ class Env
+ attr_accessor :levels, :times, :curves, :release_node, :array
+ SHAPE_NAMES = {
+ :step => 0,
+ :lin => 1,
+ :linear => 1,
+ :exp => 2,
+ :exponential => 2,
+ :sin => 3,
+ :sine => 3,
+ :wel => 4,
+ :welch => 4,
+ :sqr => 6,
+ :squared => 6,
+ :cub => 7,
+ :cubed => 7
+ }
+
+ def initialize levels, times, curves = :lin, release_node = nil, loop_node = nil
+ #times should be one less than levels size
+ # raise( ArgumentError, 'levels and times must be array')
+ @levels, @times, @curves, @release_node, @loop_node = levels, times, curves.to_array, release_node, loop_node
+ raise ArgumentError, "levels and times should be array" unless levels.instance_of?(Array) and times.instance_of?(Array)
+ end
+
+ class << self
+ def triangle dur = 1, level = 1
+ dur = dur * 0.5
+ new [0, level, 0], [dur, dur]
+ end
+
+ def sine dur = 1, level = 1
+ dur = dur * 0.5
+ new [0, level, 0], [dur, dur], :sine
+ end
+
+ def perc attackTime = 0.01, releaseTime = 1, level = 1, curve = -4
+ new [0, level, 0], [attackTime, releaseTime], curve
+ end
+
+ def linen attackTime = 0.01, sustainTime = 1, releaseTime = 1, level = 1, curve = :lin
+ new [0, level, level, 0], [attackTime, sustainTime, releaseTime], curve
+ end
+
+ def cutoff releaseTime = 0.1, level = 1, curve = :lin
+ new [level, 0], [releaseTime], curve, 0
+ end
+
+ def dadsr delayTime = 0.1, attackTime = 0.01, decayTime = 0.3, sustainLevel = 0.5, releaseTime = 1, peakLevel = 1, curve = -4, bias = 0
+ new [0, 0, peakLevel, peakLevel * sustainLevel, 0].collect{ |e| e + bias }, [delayTime, attackTime, decayTime, releaseTime], curve, 3
+ end
+
+ def adsr attackTime = 0.01, decayTime = 0.3, sustainLevel = 0.5, releaseTime = 1, peakLevel = 1, curve = -4, bias = 0
+ new [0, peakLevel, peakLevel * sustainLevel, 0].collect{ |e| e + bias }, [attackTime, decayTime, releaseTime], curve, 2
+ end
+
+ def asr attackTime = 0.01, sustainLevel = 1, releaseTime = 1, curve = -4
+ new [0, sustainLevel, 0], [attackTime, releaseTime], curve, 1
+ end
+
+ named_arguments_for :triangle, :sine, :perc, :linen, :cutoff, :dadsr, :adsr, :asr
+ end
+
+ def to_array
+ contents = levels[0], times.size, release_node, loop_node
+ contents + levels[1..-1].wrap_and_zip( times, shape_numbers, curve_values ).flatten
+ end
+
+ def shape_numbers
+ curves.collect do |curve|
+ Ugens::Ugen.valid_input?( curve ) ? 5 : SHAPE_NAMES[curve]
+ end
+ end
+
+ def curve_values
+ curves.collect do |curve|
+ Ugens::Ugen.valid_input?( curve ) ? curve : 0
+ end
+ end
+
+ def release_node
+ @release_node ||= -99
+ end
+
+ def loop_node
+ @loop_node ||= -99
+ end
+
+ def collect_constants #:nodoc:
+ end
+ end
+end
View
20 lib/scruby/group.rb
@@ -0,0 +1,20 @@
+module Scruby
+ class Group < Node
+
+ def free_all
+ send '/g_freeAll', self.node_id
+ self
+ end
+
+ def deep_free
+ send '/g_deepFree', self.node_id
+ self
+ end
+
+ def dump_tree post = false
+ send '/g_dumpTree', self.node_id, post
+ self
+ end
+
+ end
+end
View
106 lib/scruby/node.rb
@@ -0,0 +1,106 @@
+module Scruby
+ class Node
+ @@base_id = 2000
+ attr_reader :servers, :group, :id
+ alias :node_id :id
+
+ ACTIONS = [:head, :tail, :before, :after, :replace]
+
+ def initialize *args
+ args.flatten!
+ args.compact!
+ @id = args.pop if args.last.is_a? Integer
+ @servers = args.empty? ? Server.all : TypedArray.new( Server, args )
+ @id ||= @@base_id += 1
+ end
+
+ def set args = {}
+ send '/n_set', self.id, *args.to_a.flatten
+ self
+ end
+
+ def free
+ send '/n_free', self.id
+ @group, @playing, @running = nil, false, false
+ self
+ end
+
+ def run run = true
+ send '/n_run', self.id, run
+ self
+ end
+
+ # Map controls in this Node to read from control or audio rate Buses. Controls are defined in a SynthDef as args or instances of
+ # Control or its subclasses. They are specified here using symbols, strings, or indices, and are listed in pairs with Bus objects.
+ # The number of sequential controls mapped corresponds to the Bus' number of channels. If this Node is a Group this will map all
+ # Nodes within the Group. Note that with mapMsg if you mix audio and control rate busses you will get an Array of two messages
+ # rather than a single message. Integer bus indices are assumed to refer to control buses. To map a control to an audio bus, you
+ # must use a Bus object.
+ def map args
+ control, audio, content = ['/n_mapn', self.id], ['/n_mapan', self.id], []
+ args = args.to_a.each do |param, bus|
+ raise ArgumentError, "`#{ control }` is not a Bus" unless bus.kind_of? Bus
+ array = audio if bus.rate == :audio
+ array = control if bus.rate == :control
+ array.push param, bus.index, bus.channels if array
+ end
+ content << control unless control.empty?
+ content << audio unless audio.empty?
+ send_bundle nil, *content
+ self
+ end
+
+ # mapn
+ def trace
+ send '/n_trace', self.id
+ self
+ end
+
+ def move_before node
+ @group = node.group
+ send '/n_before', self.id, node.id
+ self
+ end
+
+ def move_after node
+ @group = node.group
+ send '/n_after', self.id, node.id
+ self
+ end
+
+ # def move_to_head group
+ # @group = node.group
+ # @server.each{ |s| s.send '/n_after', self.id, node.id }
+ # end
+ #
+ # def move_to_tail group
+ # @group = node.group
+ # @server.each{ |s| s.send '/n_after', self.id, node.id }
+ # end
+
+ # def playing?
+ # @playing || false
+ # end
+ #
+ # def running?
+ # @running || false
+ # end
+
+ # Reset the node count
+ def self.reset!
+ @@base_id = 2000
+ end
+
+ # Sends a bundle to all registered +servers+ for this node
+ def send_bundle timestamp, *messages
+ bundle = Bundle.new( timestamp, *messages.map{ |message| Message.new *message } )
+ @servers.each{ |s| s.send bundle }
+ end
+
+ # Sends a message to all registered +servers+ for this node
+ def send command, *args
+ message = Message.new command, *args
+ @servers.each{ |s| s.send message }
+ end
+ end
+end
View
206 lib/scruby/server.rb
@@ -0,0 +1,206 @@
+require 'singleton'
+
+module Scruby
+ include OSC
+
+ class Message < OSC::Message
+ def initialize command, *args
+ args.peel!
+ args.collect! do |arg|
+ case arg
+ when Array
+ Blob.new self.class.new(*arg).encode
+ when true
+ 1
+ when false
+ 0
+ when Symbol
+ arg.to_s
+ else
+ arg
+ end
+ end
+ super command, type_tags(args), *args
+ end
+
+ def type_tags *args
+ args.peel!
+ args.collect{ |arg| OSC::Packet.tag arg }.join
+ end
+ end
+
+ class UDPSender < OSC::UDPServer #:nodoc:
+ include Singleton
+
+ alias :udp_send :send
+ def send command, host, port, *args
+ args = args.collect{ |arg| arg.kind_of?( Symbol ) ? arg.to_s : arg }
+ udp_send Message.new( command, *args ), 0, host, port
+ end
+
+ def send_message message, host, port
+ udp_send message, 0, host, port
+ end
+ end
+ $UDP_Sender = UDPSender.instance
+
+ class Server
+ attr_reader :host, :port, :path, :buffers, :control_buses, :audio_buses
+ DEFAULTS = { :buffers => 1024, :control_buses => 4096, :audio_buses => 128, :audio_outputs => 8, :audio_inputs => 8 }.freeze
+
+ # Initializes and registers a new Server instance and sets the host and port for it.
+ # The server is a Ruby representation of scsynth which can be a local binary or a remote
+ # server already running.
+ # Server class keeps an array with all the instantiated servers
+ # Options:
+ # +host+:
+ # defaults to 'localhost'
+ # +port+:
+ # TCP port defaults to 57111
+ # +control_buses+
+ # Number of buses for routing control data defaults to 4096, indices start at 0.
+ # +audio_buses+
+ # Number of audio Bus channels for hardware output and input and internal routing, defaults to 128
+ # +audio_outputs+
+ # Reserved +buses+ for hardware output, indices available are 0 to +audio_outputs+ - 1 defaults to 8.
+ # +audio_inputs+
+ # Reserved +buses+ for hardware input, +audio_outputs+ to (+audio_outputs+ + +audio_inputs+ - 1), defaults to 8.
+ # +buffers+
+ # Number of available sample buffers defaults to 1024
+ def initialize opts = {}
+ @host = opts.delete(:host) || 'localhost'
+ @port = opts.delete(:port) || 57111
+ @path = opts.delete(:path) || '/Applications/SuperCollider/scsynth'
+ @opts = DEFAULTS.dup.merge opts
+ @buffers = []
+ @control_buses = []
+ @audio_buses = []
+ Bus.audio self, @opts[:audio_outputs] # register hardware buses
+ Bus.audio self, @opts[:audio_inputs]
+ self.class.all << self
+ end
+
+ # Boots the local binary of the scsynth forking a process, it will rise a SCError if the scsynth
+ # binary is not found in /Applications/SuperCollider/scsynth (default Mac OS path) or given path.
+ # The default path can be overriden using Server.scsynt_path=('path')
+ def boot
+ raise SCError.new('Scsynth not found in the given path') unless File.exists? @path
+ if running?
+ warn "Server on port #{ @port } allready running"
+ return self
+ end
+
+ ready = false
+ @thread = Thread.new do
+ IO.popen "cd #{ File.dirname @path }; ./#{ File.basename @path } -u #{ @port }" do |pipe|
+ loop do
+ if response = pipe.gets
+ puts response
+ ready = true if response.match /ready/
+ end
+ end
+ end
+ end
+ sleep 0.01 until ready or !@thread.alive?
+ sleep 0.01 # just to be shure
+ send "/g_new", 1
+ self
+ end
+
+ def running?
+ @thread and @thread.alive? ? true : false
+ end
+
+ def stop
+ send "/g_freeAll", 0
+ send "/clearSched"
+ send "/g_new", 1
+ end
+
+ # Sends the /quit OSC signal to the scsynth
+ def quit
+ Server.all.delete self
+ send '/quit'
+ end
+
+ # Sends an OSC command or +Message+ to the scsyth server.
+ # E.g. +server.send('/dumpOSC', 1)+
+ def send message, *args
+ case message
+ when Bundle, Message
+ $UDP_Sender.send_message message, @host, @port
+ else
+ $UDP_Sender.send message, @host, @port, *args
+ end
+ self
+ end
+
+ def send_bundle timestamp = nil, *messages
+ send Bundle.new( timestamp, *messages.map{ |message| Message.new *message } )
+ end
+
+ # Encodes and sends a SynthDef to the scsynth server
+ def send_synth_def synth_def
+ send Bundle.new( nil, Message.new( '/d_recv', Blob.new(synth_def.encode), 0 ) )
+ end
+
+ # Allocates either buffer or bus indices, should be consecutive
+ def allocate kind, *elements
+ collection = instance_variable_get "@#{kind}"
+ elements.flatten!
+
+ max_size = @opts[kind]
+ if collection.compact.size + elements.size > max_size
+ raise SCError, "No more indices available -- free some #{ kind } before allocating more."
+ end
+
+ return collection.concat(elements) unless collection.index nil # just concat arrays if no nil item
+
+ indices = []
+ collection.each_with_index do |item, index| # find n number of consecutive nil indices
+ break if indices.size >= elements.size
+ if item.nil?
+ indices << index
+ else
+ indices.clear
+ end
+ end
+
+ case
+ when indices.size >= elements.size
+ collection[indices.first, elements.size] = elements
+ when collection.size + elements.size <= max_size
+ collection.concat elements
+ else
+ raise SCError, "No block of #{ elements.size } consecutive #{ kind } indices is available."
+ end
+ end
+
+ @@servers = []
+ class << self
+ # Returns an array with all the registered servers
+ def all
+ @@servers
+ end
+
+ # Clear the servers array
+ def clear
+ @@servers.clear
+ end
+
+ # Return a server corresponding to the specified index of the registered servers array
+ def [] index
+ @@servers[index]
+ end
+
+ # Set a server to the specified index of the registered servers array
+ def []= index
+ @@servers[index]
+ @@servers.uniq!
+ end
+ end
+ end
+
+ class SCError < StandardError
+ end
+end
View
58 lib/scruby/synth.rb
@@ -0,0 +1,58 @@
+module Scruby
+
+ class Synth < Node
+ attr_reader :name
+
+ def initialize name, servers
+ super servers
+ @name = name.to_s
+ end
+
+ protected
+
+
+ class << self
+ def new name, args = {}, target = nil, action = :head
+ servers, target_id = params_from_target target
+ synth = instantiate name, servers
+ synth.send '/s_new', synth.name, synth.id, Node::ACTIONS.index(action), target_id, *args.to_a.flatten
+ synth
+ end
+
+ def paused
+ end
+
+ def after
+ end
+
+ # before
+ # head
+ # tail
+ # replace
+
+ # def as_target obj
+ # case obj
+ # when Server then Group.new obj, 1
+ # when Node then obj
+ # when nil then Group.new
+ # end
+ # end
+
+
+ private
+ def params_from_target target
+ servers = target.servers if target.respond_to? :servers
+ target_id = target if target.is_a? Integer
+ target_id ||= target.respond_to?(:node_id) ? target.node_id : 1
+ [servers, target_id]
+ end
+
+ def instantiate *args
+ obj = allocate
+ obj.__send__ :initialize, *args
+ obj
+ end
+ end
+
+ end
+end
View
109 lib/scruby/synthdef.rb
@@ -0,0 +1,109 @@
+module Scruby
+ class SynthDef
+ attr_reader :name, :children, :constants, :control_names
+ # Creates a new SynthDef instance
+ # An "ugen graph" block should be passed:
+ #
+ # SynthDef.new('simple') do |rate|
+ # Out.ar( 0, SinOsc.ar(rate) )
+ # end
+ #
+ # Default values and rates for the block can be passed with the <tt>:values => []</tt> and <tt>:rates => []</tt> options:
+ # E.g.
+ # SynthDef.new( :am, :values => [1, 1000, 10, 1] ) do |gate, portadora, moduladora, amp|
+ # modulacion = SinOsc.kr( moduladora, 0, 0.5, 0.5 )
+ # sig = SinOsc.ar( portadora, 0, modulacion )
+ # env = EnvGen.kr( Env.asr(2,1,2), gate, :doneAction => 2 )
+ # Out.ar( 0, sig*env*amp )
+ # end
+ #
+ # is equivalent to the Sclang SynthDef
+ # SynthDef(\am, {|gate=1, portadora=1000, moduladora=10, amp=1|
+ # var modulacion, sig, env;
+ # modulacion = SinOsc.kr(moduladora, 0, 0.5, 0.5);
+ # sig = SinOsc.ar(portadora, 0, modulacion);
+ # env = EnvGen.kr(Env.asr(2,1,2), gate, doneAction:2);
+ # Out.ar(0, sig*env*amp);
+ # }).send(s)
+ #
+ def initialize name, options = {}, &block
+ @name, @children = name.to_s, []
+ raise( ArgumentError.new('An UGen graph (block) must be passed') ) unless block_given?
+
+ values = options.delete( :values ) || []
+ rates = options.delete( :rates ) || []
+
+ @control_names = collect_control_names block, values, rates
+ build_ugen_graph block, @control_names
+ @constants = collect_constants @children
+
+ @variants = [] #stub!!!
+ end
+
+ # Returns a string representing the encoded SynthDef in a way scsynth can interpret and generate.
+ # This method is called by a server instance when sending the synthdef via OSC.
+ #
+ # For complex synthdefs the encoded synthdef can vary a little bit from what SClang would generate
+ # but the results will be interpreted in the same way
+ def encode
+ controls = @control_names.reject { |cn| cn.non_control? }
+ encoded_controls = [controls.size].pack('n') + controls.collect{ |c| c.name.encode + [c.index].pack('n') }.join
+
+ init_stream + name.encode + constants.encode_floats + values.flatten.encode_floats + encoded_controls +
+ [children.size].pack('n') + children.collect{ |u| u.encode }.join('') +
+ [@variants.size].pack('n') #stub!!!
+ end
+
+ def init_stream file_version = 1, number_of_synths = 1 #:nodoc:
+ 'SCgf' + [file_version].pack('N') + [number_of_synths].pack('n')
+ end
+
+ def values #:nodoc:
+ @control_names.collect{ |control| control.value }
+ end
+
+ alias :send_msg :send
+ # Sends itself to the given servers. One or more servers or an array of servers can be passed.
+ # If no arguments are given the synthdef gets sent to all instantiated servers
+ # E.g.
+ # s = Server.new('localhost', 5114)
+ # s.boot
+ # r = Server.new('127.1.1.2', 5114)
+ #
+ # SynthDef.new('sdef'){ Out.ar(0, SinOsc.ar(220)) }.send(s)
+ # # this synthdef is only sent to s
+ #
+ # SynthDef.new('sdef2'){ Out.ar(1, SinOsc.ar(222)) }.send
+ # # this synthdef is sent to both s and r
+ #
+ def send *servers
+ servers.peel!
+ (servers.empty? ? Server.all : servers).each{ |s| s.send_synth_def( self ) }
+ self
+ end
+
+ private
+ def collect_control_names function, values, rates
+ names = function.arguments
+ names.zip( values, rates ).collect_with_index{ |array, index| ControlName.new *(array << index) }
+ end
+
+ def build_controls control_names
+ # control_names.select{ |c| c.rate == :noncontrol }.sort_by{ |c| c.control_name.index } +
+ [:scalar, :trigger, :control].collect do |rate|
+ same_rate_array = control_names.select{ |control| control.rate == rate }
+ Control.and_proxies_from( same_rate_array ) unless same_rate_array.empty?
+ end.flatten.compact.sort_by{ |proxy| proxy.control_name.index }
+ end
+
+ def build_ugen_graph function, control_names
+ Ugen.synthdef = self
+ function.call *build_controls(control_names)
+ Ugen.synthdef = nil
+ end
+
+ def collect_constants children
+ children.send( :collect_constants ).flatten.compact.uniq
+ end
+ end
+end
View
38 lib/scruby/ugens/env_gen.rb
@@ -0,0 +1,38 @@
+module Scruby
+ module Ugens
+ # Done Actions:
+ #
+ # * 0 do nothing when the UGen is finished
+ # * 1 pause the enclosing synth, but do not free it
+ # * 2 free the enclosing synth
+ # * 3 free both this synth and the preceding node
+ # * 4 free both this synth and the following node
+ # * 5 free this synth; if the preceding node is a group then do g_freeAll on it, else free it
+ # * 6 free this synth; if the following node is a group then do g_freeAll on it, else free it
+ # * 7 free this synth and all preceding nodes in this group
+ # * 8 free this synth and all following nodes in this group
+ # * 9 free this synth and pause the preceding node
+ # * 10 free this synth and pause the following node
+ # * 11 free this synth and if the preceding node is a group then do g_deepFree on it, else free it
+ # * 12 free this synth and if the following node is a group then do g_deepFree on it, else free it
+ # * 13 free this synth and all other nodes in this group (before and after)
+ # * 14 free the enclosing group and all nodes within it (including this synth)
+ class EnvGen < Ugen
+ class << self
+ # New EnvGen with :audio rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def ar envelope, gate = 1, levelScale = 1, levelBias = 0, timeScale = 1, doneAction = 0
+ new :audio, gate, levelScale, levelBias, timeScale, doneAction, *envelope.to_array
+ end
+ # New EnvGen with :control rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def kr envelope, gate = 1, levelScale = 1, levelBias = 0, timeScale = 1, doneAction = 0
+ new :control, gate, levelScale, levelBias, timeScale, doneAction, *envelope.to_array
+ end
+
+ named_arguments_for :ar, :kr
+ private
+ def new *args; super; end
+ end
+ end
+ end
+
+end
View
62 lib/scruby/ugens/in_out.rb
@@ -0,0 +1,62 @@
+module Scruby
+ module Ugens
+ class In < MultiOutUgen
+ #:nodoc:
+ def initialize rate, channels, bus
+ super rate, *(0...channels).map{ |i| OutputProxy.new rate, self, i }
+ @inputs = [bus]
+ end
+
+ class << self
+ # New In with :audio rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def ar bus, channels = 1
+ new :audio, channels, bus
+ end
+ # New In with :control rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def kr bus, channels = 1
+ new :control, channels, bus
+ end
+
+ def params #:nodoc:
+ {:audio => [[:bus, nil], [:channels, 1], [:mul, 1], [:add, 0]], :control => [[:bus, nil], [:channels, 1], [:mul, 1], [:add, 0]]}
+ end
+
+ private
+ def new *args; super; end
+ end
+ end
+
+ class Out < Ugen
+ # ar and kr should be use for instantiatio
+ def initialize *args
+ super
+ @channels = []
+ end
+
+ #:nodoc:
+ def output_specs; []; end
+
+ class << self
+ # New Out with :audio rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def ar bus, *inputs
+ inputs.peel!
+ new :audio, bus, *inputs; 0.0 #Out has no output
+ end
+
+ # New Out with :control rate, inputs should be valid Ugen inputs or Ugens, arguments can be passed as an options hash or in the given order
+ def kr bus, *inputs
+ inputs.peel!
+ new :control, bus, *inputs; 0.0 #Out has no output
+ end
+
+
+ def params #:nodoc:
+ {:audio => [[:bus,nil], [:inputs, []], [:mul, 1], [:add, 0]], :control => [[:bus,nil], [:inputs, []], [:mul, 1], [:add, 0]]}
+ end
+
+ private
+ def new *args; super; end
+ end
+ end
+ end
+end
View
44 lib/scruby/ugens/multi_out_ugens.rb
@@ -0,0 +1,44 @@
+module Scruby
+ module Ugens
+ class OutputProxy < Ugen #:nodoc:
+ attr_reader :source, :control_name, :output_index
+
+ def initialize rate, source, output_index, name = nil
+ super rate
+ @source, @control_name, @output_index = source, name, output_index
+ end
+
+ def index
+ @source.index
+ end
+
+ def add_to_synthdef; end
+ end
+
+ class MultiOutUgen < Ugen #:nodoc:
+ def initialize rate, *channels
+ super rate
+ @channels = channels
+ end
+
+ def self.new rate, *args
+ super.channels #returns the channels but gets instantiated
+ end
+
+ private
+ def output_specs
+ channels.collect{ |output| output.send :output_specs }.flatten
+ end
+ end
+
+ class Control < MultiOutUgen #:nodoc:
+ def initialize rate, *names
+ super rate, *names.collect_with_index{|n, i| OutputProxy.new rate, self, i, n }
+ end
+
+ def self.and_proxies_from names
+ new names.first.rate, *names
+ end
+ end
+ end
+end
View
92 lib/scruby/ugens/operation_indices.yaml
@@ -0,0 +1,92 @@
+
+binary:
+ !ruby/symbol +: 0
+ !ruby/symbol -: 1
+ !ruby/symbol *: 2
+ !ruby/symbol div: 3
+ !ruby/symbol /: 4
+ !ruby/symbol mod: 5
+ !ruby/symbol <=: 10
+ !ruby/symbol >=: 11
+ !ruby/symbol minimum: 12
+ !ruby/symbol maximum: 13 #can't be called max because there is an array method calle maximum
+ !ruby/symbol lcm: 17
+ !ruby/symbol gcd: 18
+ !ruby/symbol round: 19
+ !ruby/symbol roundUp: 20
+ !ruby/symbol trunc: 21
+ !ruby/symbol atan2: 22
+ !ruby/symbol hypot: 23
+ !ruby/symbol hypotApx: 24
+ !ruby/symbol pow: 25
+ !ruby/symbol leftShift: 26
+ !ruby/symbol rightShift: 27
+ !ruby/symbol unsignedRightShift: 28
+ !ruby/symbol ring1: 30
+ !ruby/symbol ring2: 31
+ !ruby/symbol ring3: 32
+ !ruby/symbol ring4: 33
+ !ruby/symbol difsqr: 34
+ !ruby/symbol sumsqr: 35
+ !ruby/symbol sqrsum: 36
+ !ruby/symbol sqrdif: 37
+ !ruby/symbol absdif: 38
+ !ruby/symbol thresh: 39
+ !ruby/symbol amclip: 40
+ !ruby/symbol scaleneg: 41
+ !ruby/symbol clip2: 42
+ !ruby/symbol excess: 43
+ !ruby/symbol fold2: 44
+ !ruby/symbol wrap2: 45
+ !ruby/symbol rrand: 47
+ !ruby/symbol exprand: 48
+
+unary:
+ !ruby/symbol neg: 0
+ !ruby/symbol bitNot: 4
+ !ruby/symbol abs: 5
+ !ruby/symbol asFloat: 6
+ !ruby/symbol ceil: 8
+ !ruby/symbol floor: 9
+ !ruby/symbol frac: 10
+ !ruby/symbol sign: 11
+ !ruby/symbol squared: 12
+ !ruby/symbol cubed: 13
+ !ruby/symbol sqrt: 14
+ !ruby/symbol exp: 15
+ !ruby/symbol reciprocal: 16
+ !ruby/symbol midicps: 17
+ !ruby/symbol cpsmidi: 18
+ !ruby/symbol midiratio: 19
+ !ruby/symbol ratiomidi: 20
+ !ruby/symbol dbamp: 21
+ !ruby/symbol ampdb: 22
+ !ruby/symbol octcps: 23
+ !ruby/symbol cpsoct: 24
+ !ruby/symbol log: 25
+ !ruby/symbol log2: 26
+ !ruby/symbol log10: 27
+ !ruby/symbol sin: 28
+ !ruby/symbol cos: 29
+ !ruby/symbol tam: 30
+ !ruby/symbol asin: 31
+ !ruby/symbol acos: 32
+ !ruby/symbol atan: 33
+ !ruby/symbol sinh: 34
+ !ruby/symbol cosh: 35
+ !ruby/symbol tanh: 36
+ !ruby/symbol rand: 37
+ !ruby/symbol rand2: 38
+ !ruby/symbol linrand: 39
+ !ruby/symbol bilinrand: 40
+ !ruby/symbol sum3rand: 41
+ !ruby/symbol distort: 42
+ !ruby/symbol softclip: 43
+ !ruby/symbol coin: 44
+ !ruby/symbol rectWindow: 48
+ !ruby/symbol hanWindow: 49
+ !ruby/symbol welWindow: 50
+ !ruby/symbol triWindow: 51
+ !ruby/symbol ramp: 52
+ !ruby/symbol scurve: 53
+
View
63 lib/scruby/ugens/operation_ugens.rb
@@ -0,0 +1,63 @@
+module Scruby
+ module Ugens
+
+ class BasicOpUgen < Ugen #:nodoc:
+ attr_accessor :operator
+
+ class << self
+ def new operator, *inputs
+ obj = super get_rate(inputs), inputs
+ set_operator_for obj, operator
+ obj
+ end
+
+ private
+ #:nodoc:
+ def set_operator_for input, operator
+ input.kind_of?(Array) ? input.each{ |element| set_operator_for element, operator } : input.operator = operator
+ end
+
+ #:nodoc:
+ def get_rate *inputs
+ max_index = inputs.flatten.collect{ |ugen| Ugen::RATES.index ugen.rate }.max
+ Ugen::RATES[max_index]
+ end
+ end
+ end
+
+ class UnaryOpUGen < BasicOpUgen
+ def self.new operator, input
+ super
+ end
+
+ def special_index
+ UgenOperations::UNARY[operator.to_sym]
+ end
+ end
+
+ class BinaryOpUGen < BasicOpUgen
+ def self.new operator, left, right
+ super
+ end
+
+ def special_index
+ UgenOperations::BINARY[operator.to_sym]
+ end
+ end
+
+ class MulAdd < Ugen
+ def self.new input, mul, add
+ no_mul = mul == 1.0
+ minus = mul == -1.0
+ return add if mul == 0
+ return input if no_mul and add == 0
+ return input.neg if minus and add == 0
+ return input * mul if add == 0
+ return add - input if minus
+ return input + add if no_mul
+ super input.rate, input, mul, add
+ end
+ end
+ end
+
+end
View
178 lib/scruby/ugens/ugen.rb
@@ -0,0 +1,178 @@
+module Scruby
+ module Ugens
+ # All ugens inherit from this "abstract" class
+ #
+ # == Creation
+ #
+ # Ugens are usually instantiated inside an "ugen graph" or the block passed when creating a SynthDef
+ # using either the ar, kr, ir or new methods wich will determine the rate.
+ # * ar: audio rate
+ # * kr: control rate
+ # * ir: scalar rate
+ # * new: demand rate
+ #
+ # Not all the ugens provide all the rates
+ #
+ # Two ugens inside an ugen graph:
+ # SynthDef.new('simple'){ Out.ar(0, SinOsc.ar) }
+ # # Out and SinOsc are both ugens
+ #
+ #
+ # == Passing arguments when creating
+ #
+ # Usually when instantiating an ugen the arguments can be passed in order:
+ # Pitch.kr(0, 220, 80, ...)
+ #
+ # Or using a hash where the keys are symbols corresponding to the argument name.
+ # Pitch.kr( :initFreq => 220, :execFreq => 300 )
+ #
+ # Or a combination of both ways:
+ # Pitch.kr(0, 220, :execFreq => 300)
+ #
+ # Arguments not passed in either way will resort to default
+ #
+ #
+ # == Defining ugens
+ #
+ # This named arguments functionality is provided for all the default Ugens but can be provided when defining a new Ugen by calling
+ # <tt>#named_arguments_for</tt> passing a symbol with the name of a defined method:
+ #
+ # class Umaguma < Ugen
+ # class << self
+ # def ar(karma = 200, pitch = 20, rate = 200)
+ # ...
+ # end
+ # named_arguments_for :ar
+ # end
+ #
+ # end
+ #
+ # For more info and limitations on named arguments check the gem: http://github.com/maca/arguments
+ #
+ # Otherwise usage is pretty the same as in SuperCollider
+ #
+ # TODO: Provide a way of getting the argument names and default values
+ class Ugen
+ attr_reader :inputs, :rate, :index, :special_index, :output_index, :channels
+
+ RATES = :scalar, :trigger, :demand, :control, :audio
+ E_RATES = :scalar, :control, :audio, :demand
+ VALID_INPUTS = Numeric, Array, Ugen, Env, ControlName
+ @@synthdef = nil
+
+
+ def initialize rate, *inputs
+ @rate, @inputs = rate, inputs.compact
+ @special_index ||= 0
+ @output_index ||= 0
+ @channels ||= [1]
+ @index = add_to_synthdef || 0
+ end
+
+ # Instantiate a new MulAdd passing self and the multiplication and addition arguments
+ def muladd mul, add
+ MulAdd.new self, mul, add
+ end
+
+ def encode
+ self.class.to_s.split('::').last.encode + [ E_RATES.index(rate) ].pack('w') +
+ [ inputs.size, channels.size, special_index, collect_input_specs ].flatten.pack('n*') +
+ output_specs.pack('w*')
+ end
+
+ private
+ def synthdef #:nodoc:
+ @synthdef ||= Ugen.synthdef
+ end
+
+ def add_to_synthdef #:nodoc:
+ (synthdef.children << self).size - 1 if synthdef
+ end
+
+ def collect_constants #:nodoc:
+ @inputs.send( :collect_constants )
+ end
+
+ def input_specs synthdef #:nodoc:
+ [index, output_index]
+ end
+
+ def collect_input_specs #:nodoc:
+ @inputs.collect{ |i| i.send :input_specs, synthdef }
+ end
+
+ def output_specs #:nodoc:
+ [E_RATES.index(rate)]
+ end
+
+ public
+ def == other
+ self.class == other.class and
+ self.rate == other.rate and
+ self.inputs == other.inputs and
+ self.channels == other.channels
+ end
+
+ class << self
+ #:nodoc:
+ def new rate, *inputs
+
+ if rate.kind_of? Array
+ rate = RATES.slice rate.collect { |rate| # get the highest rate, raise error if rate is not defined
+ rate = rate.to_sym
+ raise ArgumentError.new( "#{rate} not a defined rate") unless RATES.include? rate
+ RATES.index rate
+ }.max
+ else
+ raise ArgumentError.new( "#{rate} not a defined rate") unless RATES.include? rate.to_sym
+ end
+
+ size = 1 # Size of the largest multichannel input (Array)
+ inputs.peel! # First input if input is Array and size is 1
+ inputs.map! do |input|
+ input = input.as_ugen_input if input.respond_to?(:as_ugen_input) # Convert input to prefered form
+ raise ArgumentError.new( "#{ input.inspect } is not a valid ugen input") unless valid_input? input
+ size = input.size if input.size > size if input.kind_of? Array
+ input
+ end
+
+ return instantiate( rate, *inputs.flatten ) unless size > 1 #return an Ugen if no array was passed as an input
+
+ inputs.map! do |input|
+ Array === input ? input.wrap_to!(size) : input = Array.new(size, input)
+ input
+ end
+ output = inputs.transpose
+ output.map! do |new_inputs| new rate, *new_inputs end
+ output.to_da
+ end
+
+
+ def synthdef #:nodoc:
+ @@synthdef
+ end
+
+ def synthdef= synthdef #:nodoc:
+ @@synthdef = synthdef
+ end
+
+ def valid_input? obj
+ case obj
+ when *VALID_INPUTS then true
+ else false
+ end
+ end
+
+ def params
+ {}
+ end
+
+ def instantiate *args
+ obj = allocate
+ obj.__send__ :initialize, *args
+ obj
+ end
+ end
+ end
+ end
+end
View
3,389 lib/scruby/ugens/ugen_defs.yaml
3,389 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
57 lib/scruby/ugens/ugen_operations.rb
@@ -0,0 +1,57 @@
+module Scruby
+ module Ugens
+ # This module enables Ugen operations for Ugens, Numeric and Arrays, when any instance of this classes executes an operation with an Ugen a BinaryUgenOp
+ # is instantiated where both objects are the inputs of the operation, an UnaryUgenOp is instantiated for unary operations
+ # This are the permited operations:
+ #
+ # Binary:
+ # +, -, *, div, /, mod, <=, >=, minimum, maximum, lcm, gcd, round, roundUp, trunc, atan2, hypot, hypotApx, pow, leftShift, rightShift, unsignedRightShift, ring1, ring2, ring3, ring4, difsqr, sumsqr, sqrsum, sqrdif, absdif, thresh, amclip, scaleneg, clip2, excess, fold2, wrap2, rrand and exprand
+ #
+ # Unary:
+ # neg, bitNot, abs, asFloat, ceil, floor, frac, sign, squared, cubed, sqrt, exp, reciprocal, midicps, cpsmidi, midiratio, ratiomidi, dbamp, ampdb, octcps, cpsoct, log, log2, log10, sin, cos, tam, asin, acos, atan, sinh, cosh, tanh, rand, rand2, linrand, bilinrand, sum3rand, distort, softclip, coin, rectWindow, hanWindow, welWindow, triWindow, ramp and scurve
+ #
+ module UgenOperations
+ operation_indices = YAML::load File.open( File.dirname(__FILE__) + "/operation_indices.yaml" )
+ UNARY = operation_indices['unary']
+ BINARY = operation_indices['binary']
+ SAFE_NAMES = { :+ => :plus, :- => :minus, :* => :mult, :/ => :div2, :<= => :less_than_or_eql, :>= => :more_than_or_eql }
+
+ def self.included klass
+ # Define unary operations
+ UNARY.each_key do |op|
+ define_method(op){ UnaryOpUGen.new op, self } unless klass.instance_methods.include? op
+ end
+
+ # Define binary operations
+ ugen_subclass = klass.ancestors.include? Ugen
+ meth_def =
+ if ugen_subclass
+ proc do |safe, op|
+ proc{ |input| BinaryOpUGen.new op, self, input }
+ end
+ else
+ proc do |safe, op|
+ proc do |input|
+ if input.kind_of? Ugen
+ BinaryOpUGen.new op, self, input
+ else
+ __send__ "__original_#{ safe }", input
+ end
+ end
+ end
+ end
+
+ BINARY.each_key do |op|
+ safe = SAFE_NAMES[op]
+ define_method "__ugenop_#{ safe || op }", &meth_def.call(safe || op, op)
+ klass.send :alias_method, "__original_#{ safe || op }", op if safe unless ugen_subclass
+ klass.send :alias_method, op, "__ugenop_#{ safe || op }"
+ end
+ end
+ end
+
+ [Ugen, Fixnum, Float].each{ |k| k.send :include, UgenOperations }
+ end
+end
+
+
View
88 lib/scruby/ugens/ugens.rb
@@ -0,0 +1,88 @@
+module Scruby
+ module Ugens
+
+ #
+ # Default SuperCollider Ugens definitions are stored in the ugen_defs.yml file and are defined as Ruby classes on the fly, the yml format is:
+ #
+ # NewUgen:
+ # :control:
+ # - - :input
+ # -
+ # - - :freq
+ # - 440
+ # :audio:
+ # - - :input
+ # -
+ # - - :freq
+ # - 440
+ #
+ # To define a Ruby class corresponding to an Ugen +name+ should be passed and a hash of +rates+, inputs and default values, default values can be nil
+ #
+ # Ugens.define_ugen( 'NewUgen', {:control => [[:input, nil], [:freq, 440]], :audio => [[:input, nil], [:freq, 440]]} )
+ #
+ # The previous is equivalent as the following ruby code:
+ #
+ # class NewUgen < Ugen
+ # class << self
+ # def kr( input, freq = 440 )
+ # new :control, input, freq
+ # end
+ #
+ # def ar( input, freq = 440)
+ # new :audio, input, freq
+ # end
+ # # Makes possible passing args either in order or as argument hash
+ # named_arguments_for :ar, :kr
+ # end
+ # end
+ #
+ # In future versions Ugen definitions will be loaded from ~/Ugens or ~/.Ugens directories either as yml files or rb files
+ #
+ def self.define_ugen name, rates
+ rate_name = {:audio => :ar, :control => :kr, :scalar => :ir, :demand => :new}
+ rates.delete_if{ |key, value| key == :demand } #I don't know what to do with these
+
+ methods = rates.collect do |rate, args|
+ args.push [:mul, 1], [:add, 0]
+ args.uniq!
+ assigns = []
+ args.each_with_index do |arg, index|
+ key, val = arg
+ assigns << %{
+ #{ key } = opts[:#{ key }] || args[#{ index }] || #{ val }
+ raise( ArgumentError.new("`#{ key }` value must be provided") ) unless #{ key }
+ }
+ end
+
+ args = [":#{ rate }"] + args[0...-2].collect{ |a| a.first }
+ <<-RUBY_EVAL
+ def #{ rate_name[rate] } *args
+ opts = args.last.kind_of?( Hash ) ? args.pop : {}
+ #{ assigns.join("\n") }
+ new( #{ args.join(', ') } ).muladd( mul, add )
+ end
+
+ def params
+ @params
+ end
+ RUBY_EVAL
+ end.join("\n")
+
+ self.class_eval <<-RUBY_EVAL
+ class #{ name } < Ugen
+ @params = #{ rates.inspect }
+ class << self
+ #{ methods }
+ end
+ end
+ RUBY_EVAL
+ # # TODO: Load from ~/Ugens directory
+ end
+
+
+
+ YAML::load( File.open( File.dirname(__FILE__) + "/ugen_defs.yaml" ) ).each_pair do |key, value|
+ self.define_ugen key, value
+ end
+ end
+end
View
BIN spec/.DS_Store
Binary file not shown.
View
197 spec/buffer_spec.rb
@@ -0,0 +1,197 @@
+require File.expand_path(File.dirname(__FILE__)) + "/helper"
+
+require 'date'
+require 'arguments'
+require 'tempfile'
+require 'osc'
+require "scruby/buffer"
+require "scruby/bus"
+require "scruby/server"
+require File.join( File.expand_path(File.dirname(__FILE__)), "server")
+
+include Scruby
+
+
+describe Buffer do
+ describe "messaging" do
+ before :all do
+ @server = Server.new
+ @server.boot
+ @server.send "/dumpOSC", 3
+ sleep 0.05
+ end
+
+ after :all do
+ @server.quit
+ end
+
+ describe 'Buffer.read' do
+ before do
+ @buffer = Buffer.read @server, "sounds/a11wlk01-44_1.aiff"
+ sleep 0.005
+ end
+
+ it "should instantiate and send /b_allocRead message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_allocRead", #{ @buffer.buffnum }, "/.+/Scruby/sounds/a11wlk01-44_1.aiff", 0, -1, DATA\[20\] \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe 'Buffer.allocate' do
+ before do
+ @buffer = Buffer.allocate @server, 44100 * 8.0, 2
+ sleep 0.005
+ end
+
+ it "should call allocate and send /b_alloc message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_alloc", #{ @buffer.buffnum }, 352800, 2, 0 \]}
+ @server.output.should =~ /69 00 00 00 00 00 00 00 00 44 ac 48 00 00 00 02/
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe 'Buffer.cueSoundFile' do
+ before do
+ @buffer = Buffer.cue_sound_file @server, "/sounds/a11wlk01-44_1.aiff", 0, 1
+ sleep 0.005
+ end
+
+ it "should send /b_alloc message and instantiate" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_alloc", #{ @buffer.buffnum }, 32768, 1, DATA\[72\] \]}
+ @server.output.should =~ /6e 64 73 2f 61 31 31 77 6c 6b 30 31 2d 34 34 5f/
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe '#free' do
+ before do
+ @buffer = Buffer.allocate @server, 44100 * 10.0, 2
+ @buffer2 = Buffer.allocate @server, 44100 * 10.0, 2
+ @bnum = @buffer2.buffnum
+ @buffer2.free
+ sleep 0.005
+ end
+
+ it "should remove itself from the server @buffers array and send free message" do
+ @buffer2.buffnum.should be_nil
+ @server.output.should =~ %r{\[ "/b_free", #{ @bnum }, 0 \]}
+ end
+
+ it "should allow passing a completion message"
+
+ end
+
+ describe 'Buffer.alloc_consecutive' do
+ before do
+ @buffers = Buffer.alloc_consecutive 8, @server, 4096, 2
+ sleep 0.005
+ end
+
+ it "should send alloc message for each Buffer and instantiate" do
+ @buffers.should have(8).buffers
+ @buffers.each do |buff|
+ @server.output.should =~ %r{\[ "/b_alloc", #{ buff.buffnum }, 4096, 2, 0 \]}
+ end
+ end
+
+ it "should allow passing a message"
+ end
+
+ describe 'Buffer.read_channel' do
+ before do
+ @buffer = Buffer.read_channel @server, "sounds/SinedPink.aiff", :channels => [0]
+ sleep 0.005
+ end
+
+ it "should allocate and send /b_allocReadChannel message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_allocReadChannel", #{ @buffer.buffnum }, "/.+/Scruby/sounds/SinedPink.aiff", 0, -1, 0, DATA\[20\] \]}
+ end
+ end
+
+ describe '#read' do
+ before do
+ @buffer = Buffer.allocate( @server, 44100 * 10.0, 2 ).read( "sounds/robot.aiff" )
+ sleep 0.005
+ end
+
+ it "should send message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_read", #{ @buffer.buffnum }, "/.+/Scruby/sounds/robot.aiff", 0, -1, 0, 0, DATA\[20\] \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe '#close' do
+ before do
+ @buffer = Buffer.read( @server, "sounds/a11wlk01-44_1.aiff" ).close
+ sleep 0.005
+ end
+
+ it "should send message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_close", #{ @buffer.buffnum }, 0 \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe '#zero' do
+ before do
+ @buffer = Buffer.read( @server, "sounds/a11wlk01-44_1.aiff" ).zero
+ sleep 0.005
+ end
+
+ it "should send message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_zero", #{ @buffer.buffnum }, 0 \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe '#cue_sound_file' do
+ before do
+ @buffer = Buffer.allocate( @server, 44100, 2 ).cue_sound_file( "sounds/robot.aiff" )
+ sleep 0.005
+ end
+
+ it "should send message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_read", #{ @buffer.buffnum }, "/.+/Scruby/sounds/robot.aiff", 0, 44100, 0, 1, 0 \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+
+ describe '#write' do
+ before do
+ @buffer = Buffer.allocate( @server, 44100 * 10.0, 2 ).write(
+ "sounds/test.aiff", "aiff", "int16", 0, 0, true
+ );
+ sleep 0.005
+ end
+
+ it "should send message" do
+ @buffer.should be_a(Buffer)
+ @server.output.should =~ %r{\[ "/b_write", #{ @buffer.buffnum }, "/.+/Scruby/sounds/test.aiff", "aiff", "int16", 0, 0, 1, 0 \]}
+ end
+
+ it "should have a default path" do
+ @server.flush
+ buffer = Buffer.allocate( @server, 44100 * 10.0, 2 ).write( nil, "aiff", "int16", 0, 0, true );
+ sleep 0.005
+ @server.output.should =~ %r{\[ "/b_write", #{ buffer.buffnum }, "/.+/Scruby/\d\d\d\d.+\.aiff", "aiff", "int16", 0, 0, 1, 0 \]}
+ end
+
+ it "should allow passing a completion message"
+ end
+ end
+end
View
184 spec/bus_spec.rb
@@ -0,0 +1,184 @@
+require File.expand_path(File.dirname(__FILE__)) + "/helper"
+
+require 'tempfile'
+require 'osc'
+require "scruby/core_ext/numeric"
+require "scruby/bus"
+require "scruby/server"
+require File.join( File.expand_path(File.dirname(__FILE__)), "server")
+
+include Scruby
+
+
+describe Bus do
+ describe 'instantiation' do
+ before do
+ @server = Server.new
+ @audio = Bus.audio @server
+ @control = Bus.control @server
+ end
+
+ it "should be a bus" do
+ @audio.should be_a(Bus)
+ @control.should be_a(Bus)
+ end
+
+ it "should not instantiate with new" do
+ lambda { Bus.new @server, :control, 1 }.should raise_error(NoMethodError)
+ end
+
+ it "should set server" do
+ @audio.server.should == @server
+ end
+
+ it "should set audio rate" do
+ @audio.rate.should == :audio
+ end
+
+ it "should set control rate" do
+ Bus.control(@server).rate.should == :control
+ end
+
+ it "should allocate in server on instantiation and have index" do
+ @server.audio_buses.should include(@audio)
+ @server.control_buses.should include(@control)
+ end
+
+ it "should have index" do
+ @audio.index.should == 16
+ @control.index.should == 0
+ end
+
+ it "should free and null index" do
+ @audio.free
+ @server.audio_buses.should_not include(@audio)
+ @audio.index.should == nil
+ @control.free
+ @server.audio_buses.should_not include(@control)
+ @control.index.should == nil
+ end
+
+ it "should return as map if control" do
+ @control.to_map.should == "c0"
+ end
+
+ it "should raise error if calling to_map on an audio bus" do
+ lambda { @audio.to_map }.should raise_error(SCError)
+ end
+
+ it "should print usefull information with to_s"
+
+ it "should be hardware out" do
+ @server.audio_buses[0].should be_audio_out
+ @audio.should_not be_audio_out
+ end
+
+ describe 'multichannel' do
+ before do
+ @server = Server.new
+ @audio = Bus.audio @server, 4
+ @control = Bus.control @server, 4
+ end
+
+ it "should allocate consecutive when passing more than one channel for audio" do
+ @audio.index.should == 16
+ buses = @server.audio_buses
+ buses[16..-1].should have(4).elements
+ Bus.audio(@server).index.should == 20
+ end
+
+ it "should allocate consecutive when passing more than one channel for control" do
+ @control.index.should == 0
+ @server.control_buses.should have(4).elements
+ Bus.control(@server).index.should == 4
+ end
+
+ it "should set the number of channels" do
+ @audio.channels.should == 4
+ @control.channels.should == 4
+ end
+
+ it "should depend on a main bus" do
+ @server.audio_buses[16].main_bus.should == @audio #main bus
+ @server.audio_buses[17].main_bus.should == @audio #main bus
+ @server.control_buses[0].main_bus.should == @control #main bus
+ @server.control_buses[1].main_bus.should == @control #main bus
+ end
+ end
+ end
+
+ describe "messaging" do
+ before :all do
+ @server = Server.new
+ @server.boot
+ @server.send "/dumpOSC", 3
+ @bus = Bus.control @server, 4
+ sleep 0.05
+ end
+
+ after :all do
+ @server.quit
+ end
+
+ before do
+ @server.flush
+ end
+
+ describe 'set' do
+ it "should send set message with one value" do
+ @bus.set 101
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_set", #{ @bus.index }, 101 \]}
+ end
+
+ it "should accept value list and send set with them" do
+ @bus.set 101, 202
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_set", #{ @bus.index }, 101, #{ @bus.index + 1}, 202 \]}
+ end
+
+ it "should accept an array and send set with them" do
+ @bus.set [101, 202]
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_set", #{ @bus.index }, 101, #{ @bus.index + 1}, 202 \]}
+ end
+
+ it "should warn but not set if trying to set more values than channels" do
+ @bus.should_receive(:warn).with("You tried to set 5 values for bus #{ @bus.index } that only has 4 channels, extra values are ignored.")
+ @bus.set 101, 202, 303, 404, 505
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_set", #{ @bus.index }, 101, #{ @bus.index + 1}, 202, #{ @bus.index + 2}, 303, #{ @bus.index + 3}, 404 \]}
+ end
+ end
+
+ describe 'set' do
+ it "should send fill just one channel" do
+ @bus.fill 101, 1
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_fill", #{ @bus.index }, 1, 101 \]}
+ end
+
+ it "should fill all channels" do
+ @bus.fill 101
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_fill", #{ @bus.index }, 4, 101 \]}
+ end
+
+ it "should raise error if trying to fill more than assigned channels" do
+ @bus.should_receive(:warn).with("You tried to set 5 values for bus #{ @bus.index } that only has 4 channels, extra values are ignored.")
+ @bus.fill 101, 5
+ sleep 0.01
+ @server.output.should =~ %r{\[ "/c_fill", #{ @bus.index }, 4, 101 \]}
+ end
+ end
+
+ describe 'get' do
+ it "should send get message with one value"
+ it "should send get message for various channels"
+ it "should accept an array and send set with them"
+ it "should raise error if trying to set more values than channels"
+ it "should actually get the response from the server"
+ end
+
+ end
+end
View
119 spec/core_ext/core_ext_spec.rb
@@ -0,0 +1,119 @@
+require File.expand_path(File.dirname(__FILE__)) + "/../helper"
+
+
+describe Numeric do
+ before :all do
+ @bin_op = mock 'binop'
+ ::BinaryOpUGen = mock 'BinaryOpUGen', :new => @bin_on
+ @ugen = mock 'ugen'
+ ::Ugen = mock 'Ugen', :new => @ugen
+ end
+
+ it "shoud have an scalar rate" do
+ 1.rate.should == :scalar
+ end
+
+ it "should have an scalar rate" do
+ 100.0.rate.should == :scalar
+ end
+
+ it "sum as usual" do
+ (100 + 100).should == 200
+ end
+
+ it "should #collect_constants" do
+ 1.send( :collect_constants ).should == 1
+ 1.5.send( :collect_constants ).should == 1.5
+ end
+
+ it "should spec #input_specs" do
+ synthdef = mock('synthdef', :constants => [200.0,1,3, 400.0] )
+ 200.0.send( :input_specs, synthdef ).should == [-1,0]
+ 3.send( :input_specs, synthdef ).should == [-1,2]
+ 400.0.send( :input_specs, synthdef ).should == [-1,3]
+ end
+
+ it "should spec encode"
+end
+
+
+describe Proc do
+ describe "#arguments" do
+
+ it do
+ Proc.new{}.should respond_to( :arguments )
+ end
+
+ it "should get empty array if proc has no args" do
+ Proc.new{}.arguments.should eql( [] )
+ end
+
+ it "should get one argument name" do
+ Proc.new{ |arg| }.arguments.should eql( [ :arg ] )
+ end
+
+ it "should get arg names with several args" do
+ Proc.new{ |arg, arg2, arg3| }.arguments.should eql( [ :arg, :arg2, :arg3 ] )
+ end
+ end
+end
+
+describe Array, "monkey patches" do
+ describe "#collect_with_index" do
+ it do
+ [].should respond_to( :collect_with_index )
+ end
+
+ it "should return an array the same size as the original" do
+ [1,2,3,4].collect_with_index{ nil }.should have( 4 ).items
+ end
+
+ it "should collect_with_index" do
+ array = %w(a, b, c, d)
+ array.collect_with_index{ |element, index| [index, element] }.should eql( [0,1,2,3].zip( array ) )
+ end
+
+ it "should wrap and zip" do
+ [:a,:b,:c].wrap_and_zip([1]).flatten.should == [:a,1,:b,1,:c,1]
+ [0.5, 0.5].wrap_and_zip([3],[5]).flatten.should == [0.5,3,5,0.5,3,5]
+ [0.01, 1.0].wrap_and_zip([-4.0],[5]).flatten.should == [0.01, -4.0, 5, 1.0, -4.0, 5]
+ end
+ end
+
+ describe "#wrap_to" do
+ it do
+ Array.new.should respond_to( :wrap_to )
+ end
+
+ it "should wrap_to!" do
+ [1,2].wrap_to!(4).should == [1,2,1,2]
+ end
+
+ it do
+ Array.new.should respond_to( :wrap_to )
+ end
+
+ it "should return self if the passed size is the same as self.size" do
+ a = [1,2,3,4]
+ a.wrap_to( 4 ).should == a
+ end
+ end
+
+ it "should sum with Ugen"
+ it "should collect constants"
+end
+
+describe String do
+ it "should encode" do
+ "SinOsc".encode.should == [6, 83, 105, 110, 79, 115, 99].pack('C*')
+ end
+
+ it "should encode large strings" do
+ 'set arguments cn.argNum << this is the size of controlNames when controlName was added'.encode.should ==
+ [86, 115, 101, 116, 32, 97, 114, 103, 117, 109, 101, 110, 116, 115, 32, 99, 110, 46, 97, 114, 103, 78, 117, 109, 32, 60, 60, 32, 116, 104, 105, 115, 32, 105, 115, 32, 116, 104, 101, 32, 115, 105, 122, 101, 32, 111, 102, 32, 99, 111, 110, 116, 114, 111, 108, 78, 97, 109, 101, 115, 32, 119, 104, 101, 110, 32, 99, 111, 110, 116, 114, 111, 108, 78, 97, 109, 101, 32, 119, 97, 115, 32, 97, 100, 100, 101, 100].pack('C*')
+ end
+
+end
+
+
+
View
140 spec/core_ext/delegator_array_spec.rb
@@ -0,0 +1,140 @@
+require File.expand_path(File.dirname(__FILE__)) + "/../helper"
+
+require "scruby/core_ext/array"
+require "scruby/core_ext/delegator_array"
+require "scruby/env"
+require "scruby/control_name"
+require "scruby/ugens/ugen"
+require "scruby/ugens/ugens"
+require "scruby/ugens/ugen_operations"
+require "scruby/ugens/operation_ugens"
+
+include Scruby
+include Ugens
+
+
+class SinOsc < Ugen
+ class << self
+ def ar freq = 440.0, phase = 0.0 #not interested in muladd by now
+ new :audio, freq, phase
+ end
+
+ def kr freq = 440.0, phase = 0.0
+ new :control, freq, phase
+ end
+ end
+end
+
+# Mocks
+class ControlName; end
+class Env; end
+
+describe DelegatorArray do
+
+ it "should have 'literal' notation" do
+ d(1,2).should == [1,2]
+ d(1,2).should be_instance_of(DelegatorArray)
+ d([1,2]).should == d(1,2)
+ end
+
+ it "should allow nil" do
+ d(nil)
+ end
+
+ it "should return DelegatorArray" do
+ sig = SinOsc.ar([100, [100, 100]])
+ sig.should be_a(DelegatorArray)
+ end
+
+ it "should convet to_da" do
+ [].to_da.should be_a(DelegatorArray)
+ end
+
+ it "should pass missing method" do
+ d(1,2).to_f.should == d(1.0,2.0)
+ end
+
+ it "should return a DelegatorArray for muladd" do
+ SinOsc.ar(100).muladd(1, 0.5).should be_a(BinaryOpUGen)
+ SinOsc.ar([100, [100, 100]]).muladd(0.5, 0.5).should be_a(DelegatorArray)
+ # SinOsc.ar([100, [100, 100]]).muladd(1, 0.5).should be_a(DelegatorArray)
+ end
+
+
+ it "should pass method missing" do
+ d(1,2,3).to_i.should == [1.0, 2.0, 3.0]
+ end
+
+ shared_examples_for 'aritmetic operand' do
+ before do
+ @numeric_op = eval %{ d(1,2) #{ @op } 3.0 }
+ @array_op = eval %{ d(1,2) #{ @op } d(1.0, 2.0) }
+ @asim_array_op1 = eval %{ d(1,2,3) #{ @op } d(1.0, 2.0) }
+ end
+
+ it "should do operation" do
+ @numeric_op.should == @numeric_op
+ @numeric_op.should be_a(DelegatorArray)
+ end
+
+ it "should do operation with array of the same size" do
+ @array_op.should == @array_result
+ @array_op.should be_a(DelegatorArray)
+ end
+
+ it "should do operation with array of diferent size (left bigger)" do
+ @asim_array_op1.should == @asim_result1
+ @asim_array_op1.should be_a(DelegatorArray)