ruck-ugen 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|