├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.rdoc ├── Rakefile ├── example ├── abstraction.rb ├── example.pd ├── example.rb └── save.rb ├── lib ├── puredata.rb └── puredata │ ├── canvas.rb │ ├── connection.rb │ ├── pd.rb │ ├── pdobject.rb │ ├── pdobject │ ├── dac.rb │ ├── osc.rb │ ├── pdobject.rb │ └── receive.rb │ └── version.rb └── puredata.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /vendor/bundle 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in puredata.gemspec 4 | gemspec 5 | 6 | "bundler" 7 | "rake" 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tomoyuki Chikanaga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Ruby/PureData 2 | Ruby library to manipulate PureData (Pd-extended) via socket. 3 | 4 | == Requirements 5 | Mac OS X 6 | Pd-extended 7 | 8 | == Synopsis 9 | 10 | require "puredata" 11 | 12 | Pd.start do |pd| 13 | canvas = pd.canvas("sample") 14 | 15 | osc = canvas.obj("osc~", 440) 16 | mul = canvas.obj("*~", 0.1) 17 | dac = canvas.obj("dac~") 18 | osc >> mul 19 | dac.left << mul 20 | dac.right << mul 21 | 22 | pd.dsp = true 23 | sleep 5 24 | pd.dsp = false 25 | end 26 | 27 | == Description 28 | 29 | == Installation 30 | 31 | == License 32 | 33 | This software is released under the MIT License, see LICENSE.txt. 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /example/abstraction.rb: -------------------------------------------------------------------------------- 1 | require "puredata" 2 | 3 | Pd.start do |pd| 4 | # create abstraction "osc440.pd" 5 | sample = pd.abstraction("osc440") do |abst| 6 | abst.add_outlet(true) 7 | osc = abst.obj("osc~", 440) 8 | mul = abst.obj("*~", 0.3) 9 | osc >> mul 10 | abst.outlet(0) << mul 11 | end 12 | 13 | canvas = pd.canvas("sample") 14 | osc440 = canvas.obj("osc440") 15 | dac = canvas.obj("dac~") 16 | osc440 >> dac.left 17 | osc440 >> dac.right 18 | canvas.save 19 | 20 | pd.dsp = true 21 | sleep 3 22 | pd.dsp = false 23 | end 24 | -------------------------------------------------------------------------------- /example/example.pd: -------------------------------------------------------------------------------- 1 | #N canvas 0 22 450 300 10; 2 | #X obj 40 40 netreceive 10002 0 old; 3 | -------------------------------------------------------------------------------- /example/example.rb: -------------------------------------------------------------------------------- 1 | require "puredata" 2 | 3 | PureData.start(:port => 10002) do |pd| 4 | # create canvas "sample.pd" 5 | canvas = pd.canvas("sample") 6 | 7 | # osc~ => *~ 0.1 => dac~ 8 | osc = canvas.obj("osc~", 440) 9 | mul = canvas.obj("*~", 0.3) 10 | dac = canvas.obj("dac~") 11 | canvas.connect(osc.outlet, mul.inlet(0)) 12 | canvas.connect(mul.outlet, dac.left) 13 | canvas.connect(mul.outlet, dac.right) 14 | 15 | pd.dsp = true 16 | sleep 3 17 | pd.dsp = false 18 | 19 | # add receive object for modify frequency of osc~ 20 | freq = canvas.obj("r", "freq") 21 | line = canvas.obj("line") 22 | canvas.connect(freq.outlet, line.inlet) 23 | canvas.connect(line.outlet, osc.freq) 24 | 25 | pd.dsp = true 26 | pd.msg(:freq, 500, 4000) 27 | puts "freq 500 4000" 28 | sleep 3 29 | pd.msg(:freq, 220, 5000) 30 | puts "freq 220 5000" 31 | sleep 5 32 | pd.msg(:freq, 1000, 1000) 33 | puts "freq 1000 1000" 34 | sleep 1 35 | pd.msg(:freq, 100, 50) 36 | puts "freq 100 50" 37 | sleep 1 38 | pd.dsp = false 39 | end 40 | -------------------------------------------------------------------------------- /example/save.rb: -------------------------------------------------------------------------------- 1 | require "puredata" 2 | 3 | Pd.start do |pd| 4 | # create canvas "sample.pd" 5 | canvas = pd.canvas("sample") 6 | 7 | # osc~ => *~ 0.1 => dac~ 8 | osc = canvas.obj("osc~", 440) 9 | mul = canvas.obj("*~", 0.3) 10 | dac = canvas.obj("dac~") 11 | canvas.connect(osc.outlet, mul.inlet(0)) 12 | canvas.connect(mul.outlet, dac.left) 13 | canvas.connect(mul.outlet, dac.right) 14 | 15 | # save canvas 16 | canvas.save 17 | 18 | # Canvas#save only messaging to Pd and there's no reply. 19 | # So we can't confirm Pd save patch to file. 20 | sleep 1 21 | end 22 | -------------------------------------------------------------------------------- /lib/puredata.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # Ruby/PureData early scrach version. 4 | 5 | require "puredata/pd" 6 | require "puredata/canvas" 7 | require "puredata/connection" 8 | require "puredata/pdobject" 9 | -------------------------------------------------------------------------------- /lib/puredata/canvas.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # Ruby/PureData Canvas, Abstraction class 4 | 5 | class PureData 6 | class Canvas 7 | def initialize(pd, name, opt={}) 8 | @pd = pd 9 | @name = name.dup 10 | unless /\.pd\Z/ =~ @name 11 | @name += ".pd" 12 | end 13 | @dir = File.expand_path(opt[:dir] || Dir.pwd) 14 | @pdobjid = 0 15 | pos = opt[:position] || [100, 100] 16 | size = opt[:size] || [300, 300] 17 | font = opt[:font] || 10 18 | pd.msg("pd", "filename", @name, @dir) 19 | pd.msg("#N", "canvas #{pos.join(" ")} #{size.join(" ")} #{font}") 20 | pd.msg("#X", "pop", "1") 21 | end 22 | 23 | def msg(*args) 24 | @pd.msg("pd-#{@name}", *args) 25 | end 26 | 27 | def obj(klass, *args) 28 | self.msg("obj", 10, 10, klass, *args) 29 | id = @pdobjid 30 | @pdobjid += 1 31 | cls = PureData.dispatch_object_class(klass, *args) 32 | cls.new(@pd, self, id, klass, *args) 33 | end 34 | 35 | def connect(outlet, inlet) 36 | obj1, outletid = outlet.outlet_tuple 37 | obj2, inletid = inlet.inlet_tuple 38 | oid1 = obj1.pdobjid 39 | oid2 = obj2.pdobjid 40 | self.msg("connect", oid1, outletid, oid2, inletid) 41 | end 42 | 43 | def save(path=nil) 44 | if path 45 | name = File.basename(path) 46 | unless /\.pd\Z/ =~ name 47 | name += ".pd" 48 | end 49 | dir = File.expand_path(File.dirname(path)) 50 | else 51 | name = @name 52 | dir = @dir 53 | end 54 | self.msg("savetofile", name, dir) 55 | @name = name 56 | @dir = dir 57 | nil 58 | end 59 | end 60 | 61 | class Abstraction < Canvas 62 | def initialize(pd, name, opt={}) 63 | super(pd, name, opt) 64 | @inlets = [] 65 | @outlets = [] 66 | end 67 | 68 | def inlet(idx=0) 69 | @inlets[idx].outlet(0) 70 | end 71 | 72 | def outlet(idx=0) 73 | @outlets[idx].inlet(0) 74 | end 75 | 76 | def add_inlet(audio=false) 77 | if audio 78 | @inlets << self.obj("inlet~") 79 | else 80 | @inlets << self.obj("inlet") 81 | end 82 | end 83 | 84 | def add_outlet(audio=false) 85 | if audio 86 | @outlets << self.obj("outlet~") 87 | else 88 | @outlets << self.obj("outlet") 89 | end 90 | end 91 | 92 | def self.create(pd, name, opt={}) 93 | abstract = self.new(pd, name, opt) 94 | yield(abstract) 95 | abstract.save 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/puredata/connection.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # PureData connection (inlet/outlet) control 4 | 5 | class PureData 6 | class IOlet 7 | def initialize(obj, idx=0) 8 | @obj = obj 9 | @idx = idx 10 | end 11 | 12 | attr_reader :idx 13 | 14 | def canvas 15 | @obj.canvas 16 | end 17 | 18 | def pdobjid 19 | @obj.pdobjid 20 | end 21 | 22 | def name 23 | @obj.name 24 | end 25 | 26 | def check_canvas(other) 27 | unless @obj.canvas == other.canvas 28 | raise "#{@obj.name} and #{other.name} must be in same canvas" 29 | end 30 | end 31 | 32 | def pair 33 | [@obj, @idx] 34 | end 35 | end 36 | 37 | class Inlet < IOlet 38 | def inlet_tuple 39 | [@obj, @idx] 40 | end 41 | 42 | def <<(other) 43 | if other.is_a?(PdObject) 44 | other = other.outlet 45 | end 46 | check_canvas(other) 47 | @obj.canvas.connect(other, self) 48 | other 49 | end 50 | end 51 | 52 | class Outlet < IOlet 53 | def outlet_tuple 54 | [@obj, @idx] 55 | end 56 | 57 | def >>(other) 58 | if other.is_a?(PdObject) 59 | other = other.inlet 60 | end 61 | check_canvas(other) 62 | @obj.canvas.connect(self, other) 63 | other 64 | end 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /lib/puredata/pd.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # Ruby/PureData early scrach version. 4 | 5 | require "socket" 6 | 7 | require "puredata/canvas" 8 | 9 | class PureData 10 | case RUBY_PLATFORM 11 | when /darwin/ 12 | @@pd_app_path = "/Applications/Pd-extended.app/Contents/Resources/bin/pd" 13 | else 14 | @@pd_app_path = nil 15 | end 16 | 17 | def self.start(opt={}, &blk) 18 | pd = self.new(opt) 19 | pd.fork_pd(opt) 20 | pd.start(opt, &blk) 21 | end 22 | 23 | def self.attach(opt={}, &blk) 24 | pd = self.new(opt) 25 | pd.start(opt, &blk) 26 | end 27 | 28 | def initialize(opt={}) 29 | @portno = (opt[:port] || 10002).to_i 30 | end 31 | 32 | def fork_pd(opt={}) 33 | path = opt[:pd_app_path] || @@pd_app_path 34 | pd_params = opt[:pd_params] || [] 35 | if path.nil? or not File.executable?(path) 36 | raise "option :pd_app_path (Pd-extended executable) must be specified." 37 | end 38 | cmd = [ 39 | path, 40 | pd_params, 41 | "-nogui", 42 | "-send", "pd filename ruby-pd.pd ./;", 43 | "-send", "#N canvas 10 10 200 200 10;", 44 | "-send", "#X pop 1;", 45 | "-send", "pd-ruby-pd.pd obj 50 50 netreceive #{@portno} 0 old;", 46 | ].flatten 47 | @pid = fork do 48 | Process.setsid 49 | exec(*cmd) 50 | end 51 | end 52 | 53 | def bind(opt={}) 54 | err = nil 55 | 200.times do 56 | sleep 0.1 57 | begin 58 | @sock = TCPSocket.new("localhost", @portno) 59 | break 60 | rescue 61 | err = $! 62 | end 63 | end 64 | unless @sock 65 | $stderr.puts("connect to localhost:#{@portno} failed") 66 | raise err 67 | end 68 | end 69 | 70 | def start(opt={}, &blk) 71 | begin 72 | bind(opt) 73 | if blk 74 | blk.call(self) 75 | end 76 | ensure 77 | if blk 78 | stop 79 | end 80 | end 81 | end 82 | 83 | def stop 84 | if @sock 85 | @sock.close unless @sock.closed? 86 | @sock = nil 87 | end 88 | 89 | return if @pid.nil? 90 | 91 | begin 92 | Process.kill(:TERM, -@pid) 93 | rescue Errno::ESRCH 94 | rescue Errno::EPERM 95 | raise "fail to kill process(#{@pid}): #{$!}" 96 | end 97 | Process.waitpid(@pid) 98 | ensure 99 | @sock = nil 100 | @pid = nil 101 | end 102 | 103 | def msg(*args) 104 | @sock.puts(args.map{|l| l.to_s}.join(" ") + ";") 105 | end 106 | 107 | def canvas(name, opt={}) 108 | Canvas.new(self, name, opt) 109 | end 110 | 111 | def abstraction(name, opt={}, &blk) 112 | if blk 113 | Abstraction.create(self, name, opt, &blk) 114 | else 115 | Abstraction.new(self, name, opt) 116 | end 117 | end 118 | 119 | def dsp=(flag) 120 | if flag 121 | self.msg("pd", "dsp", 1) 122 | else 123 | self.msg("pd", "dsp", 0) 124 | end 125 | end 126 | 127 | def quit 128 | self.msg("pd", "quit") 129 | end 130 | end 131 | 132 | Pd = PureData 133 | -------------------------------------------------------------------------------- /lib/puredata/pdobject.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # Ruby/PureData Pd object wrapper classes 4 | 5 | require "puredata/pdobject/pdobject" 6 | require "puredata/pdobject/osc" 7 | require "puredata/pdobject/dac" 8 | require "puredata/pdobject/receive" 9 | 10 | -------------------------------------------------------------------------------- /lib/puredata/pdobject/dac.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # dac~ object class 4 | 5 | class PureData 6 | class Dac < PdObject 7 | def left 8 | inlet(0) 9 | end 10 | def right 11 | inlet(1) 12 | end 13 | end 14 | end 15 | 16 | PureData.register_pdobject(PureData::Dac, "dac~") 17 | -------------------------------------------------------------------------------- /lib/puredata/pdobject/osc.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # osc~ object class 4 | 5 | class PureData 6 | class Osc < PdObject 7 | def freq 8 | inlet(0) 9 | end 10 | end 11 | end 12 | PureData.register_pdobject(PureData::Osc, "osc~") 13 | -------------------------------------------------------------------------------- /lib/puredata/pdobject/pdobject.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # Ruby/PureData ojbect wrapper base class 4 | 5 | class PureData 6 | 7 | @@pdclass = {} 8 | 9 | def self.register_pdobject(klass, *names) 10 | names.each do |n| 11 | @@pdclass[n] = klass 12 | end 13 | end 14 | 15 | def self.dispatch_object_class(klass, *args) 16 | cls = @@pdclass[klass.to_s] 17 | cls ||= PdObject 18 | cls 19 | end 20 | 21 | class PdObject 22 | def initialize(pd, canvas, pdobjid, name, *args) 23 | @pd = pd 24 | @canvas = canvas 25 | @pdobjid = pdobjid 26 | @name = name 27 | @args = args 28 | end 29 | attr_reader :canvas, :pdobjid, :name 30 | 31 | def inlet(idx=0) 32 | Inlet.new(self, idx) 33 | end 34 | 35 | def inlet_pair 36 | [self, 0] 37 | end 38 | 39 | def outlet(idx=0) 40 | Outlet.new(self, idx) 41 | end 42 | 43 | def outlet_pair 44 | [self, 0] 45 | end 46 | 47 | def <<(other) 48 | self.inlet << other 49 | end 50 | 51 | def >>(other) 52 | self.outlet >> other 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/puredata/pdobject/receive.rb: -------------------------------------------------------------------------------- 1 | # vim:encoding=utf-8 2 | # 3 | # receive object class 4 | 5 | class PureData 6 | class Receive < PdObject 7 | def msg(*args) 8 | @pd.msg(@args[0], *args) 9 | end 10 | end 11 | end 12 | 13 | PureData.register_pdobject(PureData::Receive, "receive", "r") 14 | -------------------------------------------------------------------------------- /lib/puredata/version.rb: -------------------------------------------------------------------------------- 1 | module Puredata 2 | VERSION = "0.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /puredata.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'puredata/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "puredata" 8 | spec.version = Puredata::VERSION 9 | spec.authors = ["CHIKANAGA Tomoyuki"] 10 | spec.email = ["nagachika00@gmail.com"] 11 | 12 | spec.summary = "Ruby library to manipulate PureData (Pd-extended) via socket." 13 | spec.description = "Ruby library to manipulate PureData (Pd-extended) via socket." 14 | spec.homepage = "https://github.com/nagachika/ruby-puredata" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.require_paths = ["lib"] 18 | end 19 | --------------------------------------------------------------------------------