Permalink
Browse files

Live session is robust and can receive and eval varios kind of messages

  • Loading branch information...
1 parent 4c7ab8b commit 6001a255154d207b8780e15c24815269bf4fea82 Macario committed Nov 25, 2008
View
@@ -0,0 +1,26 @@
+
+
+
+== LICENSE:
+
+Copyright (c) 2008 Macario Ortega
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+== NOTES
+
+Ruby has operation precendence
+
+
+
View
@@ -0,0 +1,27 @@
+require 'rubygems'
+Gem::manage_gems
+require 'rake/gempackagetask'
+
+
+spec = Gem::Specification.new do |s|
+ s.name = "Scruby"
+ s.version = "0.0.7"
+ s.author = "Macario Ortega"
+ s.email = "macarui@gmail.com"
+ s.homepage = "http://github.org/maca/scruby"
+ s.platform = Gem::Platform::RUBY
+ s.summary = "Small client for doing sound synthesis from ruby using the SuperCollider synth, usage is quite similar from SuperCollider."
+ s.files = Dir["./*"] + Dir["*/**"] + Dir["*/**/**"] + Dir["*/**/**/**"]
+ s.require_path = "lib"
+ s.bindir = 'bin'
+ s.autorequire = "scruby"
+ s.test_files = Dir['spec/*'] + Dir['spec/**/*']
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README.rdoc"]
+ s.add_dependency "highline", ">= 1.4.0"
+ s.add_dependency "ruby2ruby", ">= 1.1.9"
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.need_tar = false
+end
View
@@ -0,0 +1,12 @@
+#!/usr/bin/env ruby
+live_session_dir = File.join( File.expand_path(File.dirname(__FILE__) ), '..', 'lib', 'live' )
+
+require 'tempfile'
+require "#{live_session_dir}/session"
+
+
+Live::Session.new
+
+
+
+
View
@@ -0,0 +1,144 @@
+require 'tempfile'
+require 'rubygems'
+require 'highline'
+require 'parse_tree'
+require 'ruby2ruby'
+
+module Kernel
+ alias :l :lambda
+
+ # Calls Kernel#eval with the given args but catches posible errors
+ def resilient_eval( *args )
+ begin
+ begin
+ eval( *args )
+ rescue SyntaxError => e
+ e
+ end
+ rescue => e
+ e
+ end
+ end
+
+ def p( obj ) #:nodoc:
+ puts obj.to_live_output
+ end
+
+end
+
+class Object
+
+ # Outputs an ANSI colored string with the object representation
+ def to_live_output
+ case self
+ when Exception
+ "\e[41m\e[33m#{self.inspect}\e[0m"
+ when Numeric, Symbol, TrueClass, FalseClass, NilClass
+ "\e[35m#{self.inspect}\e[0m"
+ when Notice
+ "\e[42m\e[30m#{self}\e[0m"
+ when Warning
+ "\e[43m\e[30m#{self}\e[0m"
+ when Special
+ "\e[44m\e[37m#{self}\e[0m"
+ when String
+ "\e[32m#{self.inspect}\e[0m"
+ when Array
+ "[#{ self.collect{ |i| i.to_live_output}.join(', ') }]"
+ when Hash
+ "{#{ self.collect{ |i| i.collect{|e| e.to_live_output}.join(' => ') } }}"
+ else
+ "\e[36m#{self}\e[0m"
+ end
+ end
+end
+
+class Notice < String; end
+class Warning < String; end
+class Special < String; end
+
+module Live
+ class Pipe < Tempfile
+ def make_tmpname( *args )
+ "ruby_live.pipe"
+ end
+ end
+
+ class Session
+ include HighLine::SystemExtensions
+
+ # Starts a live session using a named pipe to receive code from a remote source and evaluates it within a context, a bit like an IRB session but evaluates code sent from a text editor
+ def initialize
+ return p( Exception.new("Another session sems to be running") ) if File.exist?( "#{Dir.tmpdir}/ruby_live.pipe" )
+ p( Notice.new("Live Session") )
+ get_binding
+ init_pipe
+ expect_input
+ serve
+ end
+
+ def init_pipe
+ @pipe_path = Pipe.new('').path
+ `rm #{@pipe_path}; mkfifo #{@pipe_path}`
+ end
+
+ # Starts a loop that checks the named pipe and evaluate its contents, will be called on initialize
+ def serve
+ File.open( @pipe_path, File::RDONLY | File::NONBLOCK) do |f|
+ loop { p evaluate( f.gets.to_s.gsub("", "\n") ) }
+ end
+ end
+
+ # Starts a new Thread that will loop capturing keystrokes and evaluating the bound block within the @context Binding, will be called on initialize
+ def expect_input
+ Thread.new do
+ loop do
+ char = get_character
+ @bindings ||= []
+ bind = @bindings[ char ]
+ p evaluate( bind ) if bind
+ end
+ end
+ end
+
+ # Expects a one char Symbol or String which will bind to a passed block so it can be called later with a keystroke
+ def bind( key, &block )
+ @bindings = [] unless @bindings.instance_of?(Array)
+ block ||= Proc.new{}
+ @bindings[ key.to_s[0] ] = Ruby2Ruby.new.process( [:block, block.to_sexp.last] )
+ Notice.new( "Key '#{key}' is bound to an action")
+ end
+
+ # Evaluates a ruby expression within the @context Binding
+ def evaluate( string = nil )
+ return resilient_eval( string, @context ) if string
+ end
+
+ def clear
+ print "\e[2J\e[f"
+ end
+
+ def get_binding #:nodoc:
+ @context = binding
+ end
+
+ def run_updates( code )
+ source = ParseTree.new.parse_tree_for_string( code ).first
+ final = []
+ while iter = source.assoc(:iter)
+ source -= [iter]
+ final << [:block, iter.last] if iter[1].include?(:update)
+ end
+ evaluate( final.collect{ |exp| Ruby2Ruby.new.process(exp) }.join("\n") )
+ Notice.new('Update blocks evaluated')
+ end
+
+ def update # Allmost a stub
+ yield
+ end
+
+ alias :reaload! :get_binding
+ end
+end
+
+
View
@@ -20,29 +20,36 @@
require 'osc'
require 'yaml'
-LIB_DIR = File.join( File.expand_path(File.dirname(__FILE__) ), 'scruby' )
+SCRUBY_DIR = File.join( File.expand_path(File.dirname(__FILE__) ), 'scruby' )
-$:.unshift( File.dirname(__FILE__) ) unless
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+require "#{SCRUBY_DIR}/audio/ugens/ugen_operations"
+require "#{SCRUBY_DIR}/audio/ugens/ugen"
+require "#{SCRUBY_DIR}/audio/ugens/multi_out_ugens"
+require "#{SCRUBY_DIR}/audio/ugens/in_out"
-require "scruby/audio/ugens/ugen_operations"
-require "scruby/audio/ugens/ugen"
-require "scruby/audio/ugens/multi_out_ugens"
-require "scruby/audio/ugens/in_out"
+require "#{SCRUBY_DIR}/audio/ugens/operation_ugens"
+require "#{SCRUBY_DIR}/audio/ugens/ugen"
-require "scruby/audio/ugens/operation_ugens"
-require "scruby/audio/ugens/ugen"
+require "#{SCRUBY_DIR}/audio/ugens/ugens"
+require "#{SCRUBY_DIR}/audio/control_name"
+require "#{SCRUBY_DIR}/audio/synthdef"
+require "#{SCRUBY_DIR}/extensions"
-require "scruby/audio/ugens/ugens"
-require "scruby/audio/control_name"
-require "scruby/audio/synthdef"
-require "scruby/extensions"
+require "#{SCRUBY_DIR}/audio/server"
+
+require "#{SCRUBY_DIR}/audio/env"
+require "#{SCRUBY_DIR}/audio/ugens/env_gen"
-require "scruby/audio/server"
include Scruby
include Audio
include Ugens
include OperationUgens
+
+class Notice < String; end
+class Warning < String; end
+class Special < String; end
+
+
View
@@ -1,53 +1,80 @@
+require 'singleton'
+
module Scruby
module Audio
- class Server < OSC::UDPServer
- attr_reader :host, :port
- @@sc_path = '/Applications/SuperCollider/scsynth'
- def initialize( host = 'localhost', port = 57110)
- @host, @port = host, port
- end
+ class UDPServer < OSC::UDPServer
+ include Singleton
alias :udp_send :send
- def send( command, *args )
- udp_send( OSC::Message.new( command, type_tag(args), *args ), 0, @host, @port )
+ def send( command, host, port, *args )
+ udp_send( OSC::Message.new( command, type_tag(args), *args ), 0, host, port )
end
- def send_message( message )
- udp_send( message, 0, @host, @port )
+ def send_message( message, host, port )
+ udp_send( message, 0, host, port )
end
-
+
def type_tag(*args)
args = *args
args.collect{ |msg| OSC::Packet.tag( msg ) }.to_s
end
+ end
+
+ $UDP_SERVER = UDPServer.instance
+
+ class Server
+ attr_reader :host, :port
+ @@sc_path = '/Applications/SuperCollider/scsynth'
+
+ def initialize( host = 'localhost', port = 57110)
+ @host, @port = host, port
+ end
+
+ def boot
+ raise SCError.new('Scsynth not found in the given path') unless File.exists?( @@sc_path )
+ Thread.new do
+ path = @@sc_path.scan(/[^\/]+/)
+ @synth = IO.popen( "cd /#{ path[0..-2].join('/') }; ./#{ path.last } -u #{ @port }" ) do |f|
+ loop do
+ p Special.new( f.gets.chomp )
+ end
+ end
+ end
+ end
+
+ def stop
+ send('/quit')
+ @synth = nil
+ end
+
+ def send( command, *args )
+ return $UDP_SERVER.send( command, @host, @port, *args )
+ end
def send_synth_def( synth_def )
- *blob = OSC::Blob.new( synth_def.encode ), 0
- def_message = OSC::Message.new( '/d_recv', type_tag( blob ), *blob )
- self.send_message( OSC::Bundle.new( nil, def_message ) )
- end
-
- def stop
- end
-
- def boot
- raise SCError.new('Scsynth not found in the given path') unless File.exists?( @@sc_path )
- end
-
- class << self
- def sc_path=( path )
- @@sc_path = path
- end
-
- def sc_path
- @@sc_path
- end
- end
+ *blob = OSC::Blob.new( synth_def.encode ), 0
+ def_message = OSC::Message.new( '/d_recv', $UDP_SERVER.type_tag( blob ), *blob )
+ send_message( OSC::Bundle.new( nil, def_message ) )
+ return synth_def
+ end
+
+ def self.sc_path=( path )
+ @@sc_path = path
+ end
+
+ def self.sc_path
+ @@sc_path
+ end
- class SCError < StandardError
+ private
+ def send_message( message )
+ return $UDP_SERVER.send_message( message, @host, @port ) if @synth
end
end
-
+
+ class SCError < StandardError
+ end
+
end
end
@@ -2,7 +2,7 @@ module Scruby
module Audio
module Ugens
module UgenOperations
- operation_indices = YAML::load( File.open( "#{LIB_DIR}/audio/ugens/operation_indices.yaml" ) )
+ operation_indices = YAML::load( File.open( "#{SCRUBY_DIR}/audio/ugens/operation_indices.yaml" ) )
UNARY = operation_indices['unary']
BINARY = operation_indices['binary']
OP_SYMBOLS = { :+ => :plus, :- => :minus, :* => :mult, :/ => :div2, :<= => :less_than_or_eql, :>= => :more_than_or_eql }
Oops, something went wrong.

0 comments on commit 6001a25

Please sign in to comment.