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.
@@ -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