ruck 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ SinOsc.new(:freq => 440) >> WavOut.new(:filename => "ex02.wav") >> blackhole
2
+ play 3.seconds
@@ -0,0 +1,8 @@
1
+ wav = WavOut.new(:filename => "ex03.wav")
2
+ sin2 = SinOsc.new(:freq => 3)
3
+ sin = SinOsc.new(:freq => L{ sin2.last * 220 + 660 },
4
+ :gain => L{ 0.5 + sin2.last * 0.5 })
5
+
6
+ [sin >> wav, sin2] >> blackhole
7
+
8
+ play 3.seconds
@@ -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,9 @@
1
+ wav = WavIn.new(:filename => "ex01.wav")
2
+ wav >> WavOut.new(:filename => "ex05.wav") >> blackhole
3
+
4
+ wav.play
5
+ play 1.second
6
+
7
+ (r = Ramp.new(:from => 1.0, :to => 2.0, :duration => 3.seconds)) >> blackhole
8
+ wav.rate = L{ r.last }
9
+ play 3.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,15 @@
1
+ # when run with RealTimeShreduler, illustrates the passage of time
2
+
3
+ spork("a") do
4
+ loop do
5
+ play 1.second
6
+ puts "second"
7
+ end
8
+ end
9
+
10
+ spork("b") do
11
+ loop do
12
+ play 0.5.second
13
+ puts " half-second"
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # multi-channel WavOut
2
+
3
+ s1 = SinOsc.new :freq => 440
4
+ s2 = SinOsc.new :freq => 440 * 2
5
+ wav = WavOut.new :filename => "ex11.wav", :num_channels => 2
6
+ s1 >> wav.in(0)
7
+ s2 >> wav.in(1)
8
+ wav >> blackhole
9
+
10
+ play 3.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