ruck-ugen 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/README +31 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/ruck_ugen +30 -0
- data/examples/ex01.rb +24 -0
- data/examples/ex02.rb +2 -0
- data/examples/ex03.rb +8 -0
- data/examples/ex04.rb +14 -0
- data/examples/ex05.rb +9 -0
- data/examples/ex06.rb +10 -0
- data/examples/ex07.rb +35 -0
- data/examples/ex08.rb +28 -0
- data/examples/ex09.rb +26 -0
- data/examples/ex10.rb +10 -0
- data/examples/ex11.rb +9 -0
- data/lib/ruck/misc/linkage.rb +52 -0
- data/lib/ruck/misc/riff.rb +101 -0
- data/lib/ruck/ugen/basic.rb +235 -0
- data/lib/ruck/ugen/oscillators.rb +114 -0
- data/lib/ruck/ugen/time_helpers.rb +31 -0
- data/lib/ruck/ugen/ugen.rb +229 -0
- data/lib/ruck/ugen/wav.rb +187 -0
- data/lib/ruck/ugen.rb +7 -0
- data/ruck-ugen.gemspec +85 -0
- data/test/bench.rb +57 -0
- metadata +110 -0
data/.document
ADDED
data/.gitignore
ADDED
data/README
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
ruck-ugen's shreduler calculates an audio unit generator
|
2
|
+
graph's output in virtual time. The graph can be modified by
|
3
|
+
shreds all the while.
|
4
|
+
|
5
|
+
The graph is written in Ruby for flexibility, so it's
|
6
|
+
too slow (on my computer) for real time, so there is no
|
7
|
+
real-time playback. You can use WavOut, though.
|
8
|
+
(See examples/ex01.rb)
|
9
|
+
|
10
|
+
Check out the library of built-in unit generators in
|
11
|
+
lib/ruck/ugen/ugen/ and make your own.
|
12
|
+
|
13
|
+
Unit Generator Usage
|
14
|
+
====================
|
15
|
+
|
16
|
+
Play a sine wave (examples/ex02.rb):
|
17
|
+
|
18
|
+
s = SinOsc.new(:freq => 440)
|
19
|
+
w = WavOut.new(:filename => "ex02.wav")
|
20
|
+
s >> w >> blackhole
|
21
|
+
play 3.seconds
|
22
|
+
|
23
|
+
Attach lambdas to unit generator attributes
|
24
|
+
(examples/ex03.rb):
|
25
|
+
|
26
|
+
wav = WavOut.new(:filename => "ex03.wav")
|
27
|
+
sin2 = SinOsc.new(:freq => 3)
|
28
|
+
sin = SinOsc.new(:freq => L{ sin2.last * 220 + 660 },
|
29
|
+
:gain => L{ 0.5 + sin2.last * 0.5 })
|
30
|
+
[sin >> wav, sin2] >> blackhole
|
31
|
+
play 3.seconds
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "jeweler"
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ruck-ugen"
|
8
|
+
gem.email = "tom@alltom.com"
|
9
|
+
gem.homepage = "http://github.com/alltom/ruck-ugen"
|
10
|
+
gem.authors = ["Tom Lieber"]
|
11
|
+
gem.summary = %Q{ruck shreduler + mini audio unit generator framework inspired by ChucK}
|
12
|
+
gem.description = <<-EOF
|
13
|
+
A ruck shreduler and mini audio unit generator framework inspired by ChucK.
|
14
|
+
Includes library for reading and writing multi-channel WAV files, and some
|
15
|
+
basic audio filters.
|
16
|
+
EOF
|
17
|
+
gem.has_rdoc = false
|
18
|
+
gem.add_dependency "ruck", ">= 0"
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/bin/ruck_ugen
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
# for testing inside gem dir
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
|
8
|
+
require "ruck"
|
9
|
+
require "ruck/ugen"
|
10
|
+
|
11
|
+
SAMPLE_RATE = 22050
|
12
|
+
SHREDULER = Ruck::UGen::UGenShreduler.new
|
13
|
+
BLACKHOLE = Ruck::UGen::InChannel.new
|
14
|
+
|
15
|
+
filenames = ARGV
|
16
|
+
filenames.each do |filename|
|
17
|
+
unless File.readable?(filename)
|
18
|
+
LOG.fatal "Cannot read file #{filename}"
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
filenames.each do |filename|
|
24
|
+
SHREDULER.spork(filename) do
|
25
|
+
include Ruck::UGen::ShredLocal
|
26
|
+
include Ruck::UGen::Generators
|
27
|
+
require filename
|
28
|
+
end
|
29
|
+
end
|
30
|
+
SHREDULER.run
|
data/examples/ex01.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
def beep(wav, chan)
|
2
|
+
(s = SawOsc.new(:freq => 440, :gain => 0.25)) >> wav.in(chan)
|
3
|
+
10.times do
|
4
|
+
play 0.1.seconds
|
5
|
+
s.freq *= 1.2
|
6
|
+
end
|
7
|
+
s << wav
|
8
|
+
end
|
9
|
+
|
10
|
+
wav = WavOut.new(:filename => "ex01.wav", :num_channels => 2)
|
11
|
+
SinOsc.new(:freq => 440, :gain => 0.25) >> wav
|
12
|
+
SinOsc.new(:freq => 880, :gain => 0.25) >> wav
|
13
|
+
|
14
|
+
wav >> blackhole
|
15
|
+
|
16
|
+
chan = 0
|
17
|
+
|
18
|
+
10.times do
|
19
|
+
play 0.7.seconds
|
20
|
+
chan = (chan + 1) % 2
|
21
|
+
spork("beep") { beep(wav, chan) }
|
22
|
+
end
|
23
|
+
|
24
|
+
play 2.seconds
|
data/examples/ex02.rb
ADDED
data/examples/ex03.rb
ADDED
data/examples/ex04.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
(wav = WavOut.new(:filename => "ex04.wav")) >> blackhole
|
2
|
+
s = SawOsc.new(:freq => 440, :gain => 0.5)
|
3
|
+
adsr = ADSR.new(:attack_time => 50.ms,
|
4
|
+
:attack_gain => 1.0,
|
5
|
+
:decay_time => 50.ms,
|
6
|
+
:sustain_gain => 0.5,
|
7
|
+
:release_time => 1.second)
|
8
|
+
s >> adsr >> wav
|
9
|
+
|
10
|
+
play 1.second
|
11
|
+
adsr.on
|
12
|
+
play 2.seconds
|
13
|
+
adsr.off
|
14
|
+
play 2.seconds
|
data/examples/ex05.rb
ADDED
data/examples/ex06.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# run ex01.rb first
|
2
|
+
|
3
|
+
spoken = WavIn.new(:filename => "ex01.wav")
|
4
|
+
wav = WavOut.new(:filename => "ex06.wav")
|
5
|
+
spoken.out(0) >> wav >> blackhole
|
6
|
+
|
7
|
+
(sin = SinOsc.new(:freq => 3, :gain => 0.1)) >> blackhole
|
8
|
+
spoken.rate = L{ 1.0 + sin.last }
|
9
|
+
|
10
|
+
play spoken.duration
|
data/examples/ex07.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
@bpm = 130.0
|
2
|
+
@one_beat = (1.0 / @bpm).minutes
|
3
|
+
|
4
|
+
(@wav = WavOut.new(:filename => "ex07.wav")) >> blackhole
|
5
|
+
|
6
|
+
def smash(len = @one_beat)
|
7
|
+
(n = Noise.new(:gain => 0.4)) >> (a = ADSR.new) >> @wav
|
8
|
+
a.release_time = len
|
9
|
+
a.on; play @one_beat / 1.5
|
10
|
+
a.off; play len
|
11
|
+
a << @wav
|
12
|
+
end
|
13
|
+
|
14
|
+
def beat
|
15
|
+
(thump = SawOsc.new(:freq => 220, :gain => 0.7)) >> (a = ADSR.new) >> @wav
|
16
|
+
a.on; play @one_beat / 2.0
|
17
|
+
a.off; play a.release_time
|
18
|
+
a << @wav
|
19
|
+
end
|
20
|
+
|
21
|
+
4.times do
|
22
|
+
spork("beat 1") { beat }; spork("smash") { smash }
|
23
|
+
play @one_beat
|
24
|
+
|
25
|
+
spork("beat 2") { beat }
|
26
|
+
play @one_beat
|
27
|
+
|
28
|
+
spork("beat 3") { beat }
|
29
|
+
play @one_beat
|
30
|
+
|
31
|
+
spork("beat 4") { beat }
|
32
|
+
play @one_beat
|
33
|
+
end
|
34
|
+
|
35
|
+
smash(5.seconds)
|
data/examples/ex08.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# An experiment with formants
|
2
|
+
# http://en.wikipedia.org/wiki/Formant
|
3
|
+
|
4
|
+
wav = WavOut.new(:filename => "ex08.wav")
|
5
|
+
ramps = (1..4).map { Ramp.new(:duration => 50.ms) }
|
6
|
+
oscillators = (1..4).map { SinOsc.new }
|
7
|
+
[oscillators >> wav, ramps] >> blackhole
|
8
|
+
|
9
|
+
(0..3).each { |i| oscillators[i].freq = L{ ramps[i].last } }
|
10
|
+
|
11
|
+
#vowel_ah = [1000, 1400]
|
12
|
+
#vowel_eh = [500, 2300]
|
13
|
+
#vowel_oh = [500, 1000]
|
14
|
+
vowel_ee = [[320, 1.0], [2500, 1.0], [3200, 1.0], [4600, 0.6]]
|
15
|
+
vowel_oo = [[320, 1.0], [800, 0.3], [2500, 0.1], [3300, 0.1]]
|
16
|
+
|
17
|
+
5.times do
|
18
|
+
[vowel_ee, vowel_oo].each do |vowel|
|
19
|
+
puts "doing #{vowel.inspect}"
|
20
|
+
(0..3).each do |i|
|
21
|
+
ramps[i].reset
|
22
|
+
ramps[i].from = ramps[i].to
|
23
|
+
ramps[i].to = vowel[i].first
|
24
|
+
oscillators[i].gain = vowel[i].last / 4.0
|
25
|
+
end
|
26
|
+
play 1.second
|
27
|
+
end
|
28
|
+
end
|
data/examples/ex09.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# run ex01.rb first (or, preferably, use way better input than ex01.wav)
|
2
|
+
|
3
|
+
=begin
|
4
|
+
https://lists.cs.princeton.edu/pipermail/chuck-users/2008-May/002983.html
|
5
|
+
|
6
|
+
> One way of subjectively "widening" a stereo image is to do the following:
|
7
|
+
> feed the left channel back to the right with a short delay, inverted;
|
8
|
+
> feed the right channel back to the left with a short delay, inverted;
|
9
|
+
=end
|
10
|
+
|
11
|
+
mix = 0.5
|
12
|
+
|
13
|
+
wavin = WavIn.new :filename => "ex01.wav", :gain => (1.0 - mix)
|
14
|
+
wavout = WavOut.new :filename => "ex09.wav", :num_channels => 2
|
15
|
+
|
16
|
+
wavin.out(0) >> (delayed_left = Delay.new :time => 10.ms, :gain => mix)
|
17
|
+
wavin.out(1) >> (delayed_right = Delay.new :time => 10.ms, :gain => mix)
|
18
|
+
inverted_left = Step.new :value => L{ -delayed_left.next(now) }
|
19
|
+
inverted_right = Step.new :value => L{ -delayed_right.next(now) }
|
20
|
+
|
21
|
+
wavout >> blackhole
|
22
|
+
[wavin.out(0), inverted_right] >> wavout.in(0)
|
23
|
+
[wavin.out(1), inverted_left ] >> wavout.in(1)
|
24
|
+
|
25
|
+
play wavin.duration
|
26
|
+
puts "processed #{wavin.duration/SAMPLE_RATE} seconds"
|
data/examples/ex10.rb
ADDED
data/examples/ex11.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# adds 60 Hz hum to a stereo wav file
|
2
|
+
|
3
|
+
wavin = WavIn.new :filename => "ex10.wav", :gain => 0.5
|
4
|
+
wavout = WavOut.new :filename => "ex11.wav", :num_channels => 2
|
5
|
+
wavin >> wavout
|
6
|
+
SinOsc.new(:freq => 60, :gain => 0.5) >> wavout
|
7
|
+
wavout >> blackhole
|
8
|
+
|
9
|
+
play 3.seconds
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
# linkable_attr generates accessor methods for
|
3
|
+
# instance variables that behaves almost like
|
4
|
+
# attr_accessor.
|
5
|
+
|
6
|
+
# The difference is that if an object is assigned
|
7
|
+
# which responds to :call, accessing the method
|
8
|
+
# returns the result of invoking :call instead of
|
9
|
+
# the Proc/lambda/block itself.
|
10
|
+
|
11
|
+
# relies on _why's metaid, included below.
|
12
|
+
|
13
|
+
class Object
|
14
|
+
def linkable_attr(attr_sym)
|
15
|
+
attr_reader attr_sym
|
16
|
+
define_method("#{attr_sym}=") do |val|
|
17
|
+
instance_variable_set("@#{attr_sym}", val)
|
18
|
+
if val.respond_to? :call
|
19
|
+
meta_def attr_sym do
|
20
|
+
val.call
|
21
|
+
end
|
22
|
+
else
|
23
|
+
meta_def attr_sym do
|
24
|
+
val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def L(&block)
|
31
|
+
block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# stolen from metaid.rb:
|
36
|
+
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
37
|
+
# (that site does not exist, but a mirror can be found)
|
38
|
+
class Object
|
39
|
+
# The hidden singleton lurks behind everyone
|
40
|
+
def metaclass; class << self; self; end; end
|
41
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
42
|
+
|
43
|
+
# Adds methods to a metaclass
|
44
|
+
def meta_def name, &blk
|
45
|
+
meta_eval { define_method name, &blk }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Defines an instance method within a class
|
49
|
+
def class_def name, &blk
|
50
|
+
class_eval { define_method name, &blk }
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
|
2
|
+
# module for parsing RIFF files (the binary protocol
|
3
|
+
# used for packets in a WAV file, for example)
|
4
|
+
|
5
|
+
# get an array of all the RIFF chunks in a file:
|
6
|
+
# RiffReader.new("file.wav").chunks
|
7
|
+
|
8
|
+
# this library avoids loading the entire file into
|
9
|
+
# memory by LEAVING THE FILE OPEN FOR READING FOREVER.
|
10
|
+
|
11
|
+
module Riff
|
12
|
+
|
13
|
+
class RiffReaderChunk
|
14
|
+
attr_reader :data_start
|
15
|
+
attr_accessor :data_skip
|
16
|
+
|
17
|
+
def initialize(fn, start)
|
18
|
+
@fn, @start = fn, start
|
19
|
+
@size_start = @start + 4
|
20
|
+
@data_start = @start + 8
|
21
|
+
@data_skip = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
return @type if @type
|
26
|
+
return nil if @fn.closed?
|
27
|
+
|
28
|
+
@fn.seek @start
|
29
|
+
@type = @fn.read(4)
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
return @size - @data_skip if @size
|
34
|
+
return nil if @fn.closed?
|
35
|
+
|
36
|
+
@fn.seek @size_start
|
37
|
+
@size = @fn.read(4).unpack("L")[0]
|
38
|
+
@size - @data_skip
|
39
|
+
end
|
40
|
+
|
41
|
+
# pass a Range of bytes, or start and length
|
42
|
+
def [](*args)
|
43
|
+
return if @fn.closed?
|
44
|
+
|
45
|
+
first, last = case args.length
|
46
|
+
when 1; [args.first.begin, args.first.end]
|
47
|
+
when 2; [args[0], args[0] + args[1]]
|
48
|
+
end
|
49
|
+
@fn.seek @data_start + @data_skip + first
|
50
|
+
@fn.read(last - first + 1)
|
51
|
+
end
|
52
|
+
|
53
|
+
def chunks
|
54
|
+
return @chunks if @chunks
|
55
|
+
return [] if @fn.closed?
|
56
|
+
|
57
|
+
offset = @data_start + @data_skip
|
58
|
+
@chunks = []
|
59
|
+
while offset + @data_skip - @data_start < size
|
60
|
+
chunk = RiffReaderChunk.new(@fn, offset)
|
61
|
+
@chunks << chunk
|
62
|
+
offset += @data_start + chunk.size
|
63
|
+
end
|
64
|
+
|
65
|
+
@chunks
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"<RiffHeader type:#{type} size:#{size}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class RiffReader
|
75
|
+
def initialize(filename)
|
76
|
+
@fn = File.open(filename, "rb")
|
77
|
+
end
|
78
|
+
|
79
|
+
def chunks
|
80
|
+
return @chunks if @chunks
|
81
|
+
return [] if @fn.closed?
|
82
|
+
|
83
|
+
offset = 0
|
84
|
+
@chunks = []
|
85
|
+
until @fn.eof?
|
86
|
+
chunk = RiffReaderChunk.new(@fn, offset)
|
87
|
+
@chunks << chunk
|
88
|
+
offset += 8 + chunk.size
|
89
|
+
@fn.seek offset + 8
|
90
|
+
end
|
91
|
+
|
92
|
+
@chunks
|
93
|
+
end
|
94
|
+
|
95
|
+
def close
|
96
|
+
@fn.close unless @fn.closed?
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|