ruck 0.1.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/.gitignore +2 -0
- data/README +85 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/bin/ruck_glapp +77 -0
- data/bin/ruck_midi +143 -0
- data/bin/ruck_ugen +53 -0
- data/examples/glapp/ex01.rb +13 -0
- data/examples/midi/ex01.rb +24 -0
- data/examples/ugen/ex01.rb +24 -0
- data/examples/ugen/ex02.rb +2 -0
- data/examples/ugen/ex03.rb +8 -0
- data/examples/ugen/ex04.rb +14 -0
- data/examples/ugen/ex05.rb +9 -0
- data/examples/ugen/ex06.rb +10 -0
- data/examples/ugen/ex07.rb +35 -0
- data/examples/ugen/ex08.rb +28 -0
- data/examples/ugen/ex09.rb +26 -0
- data/examples/ugen/ex10.rb +15 -0
- data/examples/ugen/ex11.rb +10 -0
- data/examples/ugen/ex12.rb +9 -0
- data/lib/ruck.rb +13 -0
- data/lib/ruck/bench.rb +44 -0
- data/lib/ruck/misc/linkage.rb +22 -0
- data/lib/ruck/misc/metaid.rb +18 -0
- data/lib/ruck/misc/pcm_time_helpers.rb +29 -0
- data/lib/ruck/misc/riff.rb +71 -0
- data/lib/ruck/misc/wavparse.rb +35 -0
- data/lib/ruck/shreduling.rb +147 -0
- data/lib/ruck/ugen/general.rb +408 -0
- data/lib/ruck/ugen/oscillators.rb +106 -0
- data/lib/ruck/ugen/wav.rb +185 -0
- data/ruck.gemspec +94 -0
- metadata +102 -0
@@ -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
|
@@ -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
|
@@ -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)
|
@@ -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
|
@@ -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"
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# adds 60 Hz hum to a stereo wav file
|
2
|
+
|
3
|
+
wavin = WavIn.new :filename => "ex11.wav", :gain => 0.5
|
4
|
+
wavout = WavOut.new :filename => "ex12.wav", :num_channels => 2
|
5
|
+
wavin >> wavout
|
6
|
+
SinOsc.new(:freq => 60, :gain => 0.5) >> wavout
|
7
|
+
wavout >> blackhole
|
8
|
+
|
9
|
+
play 3.seconds
|
data/lib/ruck.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Ruck
|
3
|
+
require "logger"
|
4
|
+
LOG = Logger.new(STDOUT)
|
5
|
+
LOG.level = Logger::WARN
|
6
|
+
end
|
7
|
+
|
8
|
+
require File.join(File.dirname(__FILE__), "ruck", "shreduling")
|
9
|
+
require File.join(File.dirname(__FILE__), "ruck", "misc", "metaid")
|
10
|
+
require File.join(File.dirname(__FILE__), "ruck", "misc", "linkage")
|
11
|
+
require File.join(File.dirname(__FILE__), "ruck", "ugen", "general")
|
12
|
+
require File.join(File.dirname(__FILE__), "ruck", "ugen", "wav")
|
13
|
+
require File.join(File.dirname(__FILE__), "ruck", "ugen", "oscillators")
|
data/lib/ruck/bench.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
SAMPLE_RATE = 44100
|
3
|
+
|
4
|
+
if ARGV.include? "bench.rb"
|
5
|
+
# benchmark UGens with shreduling
|
6
|
+
|
7
|
+
TIME = 1.seconds
|
8
|
+
count = 0
|
9
|
+
puts "Simulating #{TIME / 1.second} seconds"
|
10
|
+
loop do
|
11
|
+
Step.new >> blackhole
|
12
|
+
count += 1
|
13
|
+
|
14
|
+
start = Time.now
|
15
|
+
play TIME
|
16
|
+
time = Time.now - start
|
17
|
+
puts "#{count}: #{time}"
|
18
|
+
|
19
|
+
break if time > (TIME / 1.second)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
# benchmark UGens without shreduling
|
23
|
+
|
24
|
+
require "ruck"
|
25
|
+
TIME = 1.seconds
|
26
|
+
dac = Ruck::InChannel.new
|
27
|
+
count = 0
|
28
|
+
@now = 0
|
29
|
+
puts "Simulating #{TIME / 1.second} seconds"
|
30
|
+
loop do
|
31
|
+
Ruck::Generators::Step.new >> dac
|
32
|
+
count += 1
|
33
|
+
|
34
|
+
start = Time.now
|
35
|
+
TIME.to_i.times do
|
36
|
+
dac.next(@now)
|
37
|
+
@now += 1
|
38
|
+
end
|
39
|
+
time = Time.now - start
|
40
|
+
puts "#{count}: #{time}"
|
41
|
+
|
42
|
+
break if time > (TIME / 1.second)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
class Object
|
3
|
+
def linkable_attr(attr_sym)
|
4
|
+
attr_reader attr_sym
|
5
|
+
define_method("#{attr_sym}=") do |val|
|
6
|
+
instance_variable_set("@#{attr_sym}", val)
|
7
|
+
if val.respond_to? :call
|
8
|
+
meta_def attr_sym do
|
9
|
+
val.call
|
10
|
+
end
|
11
|
+
else
|
12
|
+
meta_def attr_sym do
|
13
|
+
val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def L(&block)
|
20
|
+
block
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# stolen from:
|
2
|
+
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
3
|
+
|
4
|
+
class Object
|
5
|
+
# The hidden singleton lurks behind everyone
|
6
|
+
def metaclass; class << self; self; end; end
|
7
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
8
|
+
|
9
|
+
# Adds methods to a metaclass
|
10
|
+
def meta_def name, &blk
|
11
|
+
meta_eval { define_method name, &blk }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Defines an instance method within a class
|
15
|
+
def class_def name, &blk
|
16
|
+
class_eval { define_method name, &blk }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module RuckTime
|
3
|
+
def sample
|
4
|
+
self
|
5
|
+
end
|
6
|
+
alias_method :samples, :sample
|
7
|
+
|
8
|
+
def ms
|
9
|
+
self.to_f * SAMPLE_RATE / 1000.0
|
10
|
+
end
|
11
|
+
|
12
|
+
def second
|
13
|
+
self.to_f * SAMPLE_RATE
|
14
|
+
end
|
15
|
+
alias_method :seconds, :second
|
16
|
+
|
17
|
+
def minute
|
18
|
+
self.to_f * SAMPLE_RATE * 60.0
|
19
|
+
end
|
20
|
+
alias_method :minutes, :minute
|
21
|
+
end
|
22
|
+
|
23
|
+
class Fixnum
|
24
|
+
include RuckTime
|
25
|
+
end
|
26
|
+
|
27
|
+
class Float
|
28
|
+
include RuckTime
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module Riff
|
3
|
+
|
4
|
+
class RiffReaderChunk
|
5
|
+
attr_reader :data_start
|
6
|
+
attr_accessor :data_skip
|
7
|
+
|
8
|
+
def initialize(fn, start)
|
9
|
+
@fn, @start = fn, start
|
10
|
+
@size_start = @start + 4
|
11
|
+
@data_start = @start + 8
|
12
|
+
@data_skip = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
return @type if @type
|
17
|
+
@fn.seek @start
|
18
|
+
@type = @fn.read(4)
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
return @size - @data_skip if @size
|
23
|
+
@fn.seek @size_start
|
24
|
+
@size = @fn.read(4).unpack("L")[0]
|
25
|
+
@size - @data_skip
|
26
|
+
end
|
27
|
+
|
28
|
+
# pass a Range of bytes, or start and length
|
29
|
+
def [](*args)
|
30
|
+
first, last = case args.length
|
31
|
+
when 1; [args.first.begin, args.first.end]
|
32
|
+
when 2; [args[0], args[0] + args[1]]
|
33
|
+
end
|
34
|
+
@fn.seek @data_start + @data_skip + first
|
35
|
+
@fn.read(last - first + 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def chunks
|
39
|
+
offset = @data_start + @data_skip
|
40
|
+
chunks = []
|
41
|
+
while offset + @data_skip - @data_start < size
|
42
|
+
chunks << chunk = RiffReaderChunk.new(@fn, offset)
|
43
|
+
offset += @data_start + chunk.size
|
44
|
+
end
|
45
|
+
chunks
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"<RiffHeader type:#{type} size:#{size}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class RiffReader
|
55
|
+
def initialize(filename)
|
56
|
+
@fn = File.open(filename, "rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
def chunks
|
60
|
+
offset = 0
|
61
|
+
chunks = []
|
62
|
+
until @fn.eof?
|
63
|
+
chunks << chunk = RiffReaderChunk.new(@fn, offset)
|
64
|
+
offset += 8 + chunk.size
|
65
|
+
@fn.seek offset + 8
|
66
|
+
end
|
67
|
+
chunks
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# stolen from
|
2
|
+
# http://www.mokehehe.com/assari/index.php?WAV%A5%D5%A5%A1%A5%A4%A5%EB%C6%E2%A4%CE%A5%C1%A5%E3%A5%F3%A5%AF%A4%F2%CE%F3%B5%F3%A4%B9%A4%EB
|
3
|
+
|
4
|
+
if ARGV.length < 1
|
5
|
+
print "usage: [input wav filename]\n"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
inwavfn = ARGV[0]
|
10
|
+
|
11
|
+
File::open(inwavfn, "rb") {|f|
|
12
|
+
riff = f.read(4)
|
13
|
+
if riff != 'RIFF'
|
14
|
+
STDERR.print "not RIFF\n"
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
totalsizestr = f.read(4)
|
18
|
+
totalsize = totalsizestr.unpack("L")[0]
|
19
|
+
|
20
|
+
wave = f.read(4)
|
21
|
+
if wave != 'WAVE'
|
22
|
+
STDERR.print "not WAVE\n"
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
while !f.eof
|
27
|
+
chk = f.read(4)
|
28
|
+
chksizestr = f.read(4)
|
29
|
+
chksize = chksizestr.unpack("L")[0]
|
30
|
+
|
31
|
+
print chk, ",#{chksize}\n"
|
32
|
+
|
33
|
+
f.seek(chksize, IO::SEEK_CUR)
|
34
|
+
end
|
35
|
+
}
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
module Ruck
|
3
|
+
|
4
|
+
class Shred
|
5
|
+
attr_reader :now
|
6
|
+
attr_accessor :finished
|
7
|
+
|
8
|
+
def initialize(shreduler, now, name, &block)
|
9
|
+
@shreduler = shreduler
|
10
|
+
@now = now
|
11
|
+
@name = name
|
12
|
+
@block = block
|
13
|
+
@finished = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def go(resume)
|
17
|
+
@resume = resume
|
18
|
+
begin
|
19
|
+
@block.call self
|
20
|
+
rescue => e
|
21
|
+
LOG.error "#{self} exited uncleanly:\n#{e}\n#{e.backtrace}"
|
22
|
+
end
|
23
|
+
@finished = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def yield(samples)
|
27
|
+
LOG.debug "shred #{self} yielding #{samples}"
|
28
|
+
samples = samples
|
29
|
+
@now += samples
|
30
|
+
callcc do |cont|
|
31
|
+
@block = cont # save where we are
|
32
|
+
@resume.call # jump back to shreduler
|
33
|
+
end
|
34
|
+
samples
|
35
|
+
end
|
36
|
+
|
37
|
+
def finish
|
38
|
+
@resume.call # last save point
|
39
|
+
end
|
40
|
+
|
41
|
+
# shreds sort in order of position in time
|
42
|
+
def <=>(shred)
|
43
|
+
@now <=> shred.now
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
"<Shred: #{@name}>"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Shreduler
|
52
|
+
attr_reader :running
|
53
|
+
attr_reader :current_shred
|
54
|
+
attr_reader :shreds
|
55
|
+
attr_reader :now
|
56
|
+
|
57
|
+
def initialize
|
58
|
+
@shreds = []
|
59
|
+
@now = 0
|
60
|
+
@running = false
|
61
|
+
end
|
62
|
+
|
63
|
+
def spork(name = "", &shred)
|
64
|
+
LOG.debug "Adding shred \"#{name}\" at #{@now}"
|
65
|
+
@shreds << Shred.new(self, @now, name, &shred)
|
66
|
+
@shred
|
67
|
+
end
|
68
|
+
|
69
|
+
def remove_shred(shred)
|
70
|
+
LOG.debug "Removing shred \"#{name}\" at #{@now}"
|
71
|
+
@shreds.delete shred
|
72
|
+
end
|
73
|
+
|
74
|
+
def next_shred
|
75
|
+
@shreds.min # furthest behind (Shred#<=> uses Shred's current time)
|
76
|
+
end
|
77
|
+
|
78
|
+
# called when shreds allow time to pass
|
79
|
+
# a convnient method to override
|
80
|
+
def sim_to(new_now)
|
81
|
+
@now = new_now
|
82
|
+
end
|
83
|
+
|
84
|
+
# invokes the next shred, simulates to the new VM time, then returns
|
85
|
+
def run_one
|
86
|
+
@current_shred = next_shred
|
87
|
+
|
88
|
+
sim_to(@current_shred.now)
|
89
|
+
|
90
|
+
# execute shred, saving this as the resume point
|
91
|
+
LOG.debug "resuming shred #{@current_shred} at #{now}"
|
92
|
+
callcc { |cont| @current_shred.go(cont) }
|
93
|
+
LOG.debug "back to run loop"
|
94
|
+
|
95
|
+
if @current_shred.finished
|
96
|
+
LOG.debug "#{@current_shred} finished"
|
97
|
+
@shreds.delete(@current_shred)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# ruck main loop
|
102
|
+
# executes all shreds and synthesizes audio
|
103
|
+
# until all shreds exit
|
104
|
+
def run
|
105
|
+
LOG.debug "shreduler starting"
|
106
|
+
@running = true
|
107
|
+
|
108
|
+
while @shreds.length > 0
|
109
|
+
run_one
|
110
|
+
end
|
111
|
+
|
112
|
+
@running = false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class UGenShreduler < Shreduler
|
117
|
+
def run
|
118
|
+
require File.join(File.dirname(__FILE__), "misc", "pcm_time_helpers")
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
def sim_to(new_now)
|
123
|
+
while @now < new_now.to_i
|
124
|
+
BLACKHOLE.next @now
|
125
|
+
@now += 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class RealTimeShreduler < Shreduler
|
131
|
+
def run
|
132
|
+
@start_time = Time.now
|
133
|
+
super
|
134
|
+
end
|
135
|
+
|
136
|
+
def sim_to(new_now)
|
137
|
+
actual_now = Time.now
|
138
|
+
simulated_now = @start_time + (new_now.to_f / SAMPLE_RATE)
|
139
|
+
if simulated_now > actual_now
|
140
|
+
sleep(simulated_now - actual_now)
|
141
|
+
end
|
142
|
+
|
143
|
+
@now = new_now
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|