audio_stream 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/audio_stream.gemspec +38 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/adsr.ipynb +88 -0
- data/examples/buffer.ipynb +160 -0
- data/examples/chorus.rb +41 -0
- data/examples/curves.ipynb +105 -0
- data/examples/distortion.rb +43 -0
- data/examples/example_options.rb +56 -0
- data/examples/rec.rb +53 -0
- data/examples/shapes.ipynb +116 -0
- data/examples/sin.rb +62 -0
- data/examples/tremolo.rb +38 -0
- data/examples/tuner.rb +77 -0
- data/lib/audio_stream.rb +30 -0
- data/lib/audio_stream/audio_bus.rb +28 -0
- data/lib/audio_stream/audio_input.rb +48 -0
- data/lib/audio_stream/audio_input_buffer.rb +21 -0
- data/lib/audio_stream/audio_input_device.rb +50 -0
- data/lib/audio_stream/audio_input_file.rb +30 -0
- data/lib/audio_stream/audio_input_metronome.rb +49 -0
- data/lib/audio_stream/audio_input_sin.rb +41 -0
- data/lib/audio_stream/audio_output.rb +17 -0
- data/lib/audio_stream/audio_output_device.rb +65 -0
- data/lib/audio_stream/audio_output_file.rb +35 -0
- data/lib/audio_stream/buffer.rb +84 -0
- data/lib/audio_stream/conductor.rb +38 -0
- data/lib/audio_stream/core_ext.rb +13 -0
- data/lib/audio_stream/error.rb +4 -0
- data/lib/audio_stream/fx.rb +30 -0
- data/lib/audio_stream/fx/a_gain.rb +26 -0
- data/lib/audio_stream/fx/band_pass_filter.rb +29 -0
- data/lib/audio_stream/fx/bang_process.rb +11 -0
- data/lib/audio_stream/fx/biquad_filter.rb +58 -0
- data/lib/audio_stream/fx/chorus.rb +59 -0
- data/lib/audio_stream/fx/compressor.rb +41 -0
- data/lib/audio_stream/fx/convolution_reverb.rb +107 -0
- data/lib/audio_stream/fx/delay.rb +45 -0
- data/lib/audio_stream/fx/distortion.rb +42 -0
- data/lib/audio_stream/fx/equalizer_2band.rb +23 -0
- data/lib/audio_stream/fx/equalizer_3band.rb +28 -0
- data/lib/audio_stream/fx/hanning_window.rb +29 -0
- data/lib/audio_stream/fx/high_pass_filter.rb +29 -0
- data/lib/audio_stream/fx/high_shelf_filter.rb +32 -0
- data/lib/audio_stream/fx/low_pass_filter.rb +29 -0
- data/lib/audio_stream/fx/low_shelf_filter.rb +32 -0
- data/lib/audio_stream/fx/mono_to_stereo.rb +18 -0
- data/lib/audio_stream/fx/noise_gate.rb +47 -0
- data/lib/audio_stream/fx/panning.rb +41 -0
- data/lib/audio_stream/fx/peaking_filter.rb +31 -0
- data/lib/audio_stream/fx/stereo_to_mono.rb +18 -0
- data/lib/audio_stream/fx/tremolo.rb +34 -0
- data/lib/audio_stream/fx/tuner.rb +98 -0
- data/lib/audio_stream/plot.rb +53 -0
- data/lib/audio_stream/ring_buffer.rb +39 -0
- data/lib/audio_stream/sound_info.rb +9 -0
- data/lib/audio_stream/sync.rb +32 -0
- data/lib/audio_stream/utils.rb +45 -0
- data/lib/audio_stream/version.rb +3 -0
- metadata +223 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module AudioStream
|
2
|
+
class AudioOutputFile < AudioOutput
|
3
|
+
def initialize(fname, soundinfo:)
|
4
|
+
super()
|
5
|
+
@fname = fname
|
6
|
+
@soundinfo = soundinfo
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect
|
10
|
+
@sound = RubyAudio::Sound.open(@fname, "w", @soundinfo)
|
11
|
+
end
|
12
|
+
|
13
|
+
def disconnect
|
14
|
+
end
|
15
|
+
|
16
|
+
def join
|
17
|
+
@sync.yield_wait
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_next(input)
|
21
|
+
@sound.write(input)
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_error(error)
|
25
|
+
puts error
|
26
|
+
puts error.backtrace.join("\n")
|
27
|
+
@sound.close
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_completed
|
31
|
+
@sound.close
|
32
|
+
@sync.finish
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module AudioStream
|
2
|
+
class Buffer < RubyAudio::Buffer
|
3
|
+
def plot(soundinfo=nil)
|
4
|
+
Plot.new(self, soundinfo)
|
5
|
+
end
|
6
|
+
|
7
|
+
def +(other)
|
8
|
+
unless RubyAudio::Buffer===other
|
9
|
+
raise Error, "right operand is not Buffer: #{other}"
|
10
|
+
end
|
11
|
+
if self.size!=other.size
|
12
|
+
raise Error, "Buffer.size is not match: self.size=#{self.size} other.size=#{other.size}"
|
13
|
+
end
|
14
|
+
|
15
|
+
channels = [self.channels, other.channels].max
|
16
|
+
window_size = self.size
|
17
|
+
|
18
|
+
buf = Buffer.float(window_size, channels)
|
19
|
+
|
20
|
+
case channels
|
21
|
+
when 1
|
22
|
+
[self, other].each {|x|
|
23
|
+
x.size.times.each {|i|
|
24
|
+
buf[i] += x[i]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
when 2
|
28
|
+
m2s = Fx::MonoToStereo.new
|
29
|
+
a = [
|
30
|
+
m2s.process(self),
|
31
|
+
m2s.process(other),
|
32
|
+
]
|
33
|
+
a.each {|x|
|
34
|
+
x.size.times.each {|i|
|
35
|
+
buf[i] = buf[i].zip(x[i]).map {|a| a[0] + a[1]}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
buf
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_na
|
44
|
+
window_size = self.size
|
45
|
+
channels = self.channels
|
46
|
+
|
47
|
+
na = NArray.float(channels, window_size)
|
48
|
+
na[0...na.size] = self.to_a.flatten
|
49
|
+
|
50
|
+
na
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.from_na(na)
|
54
|
+
channels = na.shape[0]
|
55
|
+
window_size = na.size / channels
|
56
|
+
|
57
|
+
buf = self.float(window_size, channels)
|
58
|
+
|
59
|
+
case channels
|
60
|
+
when 1
|
61
|
+
window_size.times {|i|
|
62
|
+
buf[i] = na[i].real
|
63
|
+
}
|
64
|
+
when 2
|
65
|
+
window_size.times {|i|
|
66
|
+
ch1 = na[i*2].real
|
67
|
+
ch2 = na[(i*2)+1].real
|
68
|
+
|
69
|
+
buf[i] = [ch1, ch2]
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
buf
|
74
|
+
end
|
75
|
+
|
76
|
+
[:short, :int, :float, :double].each do |type|
|
77
|
+
eval "def self.#{type}(frames, channels=1)
|
78
|
+
buf = self.new(:#{type}, frames, channels)
|
79
|
+
buf.real_size = buf.size
|
80
|
+
buf
|
81
|
+
end"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AudioStream
|
2
|
+
class Conductor
|
3
|
+
def initialize(input:, output:)
|
4
|
+
@inputs = Set[*[input].flatten.compact]
|
5
|
+
@outputs = Set[*[output].flatten.compact]
|
6
|
+
end
|
7
|
+
|
8
|
+
def connect
|
9
|
+
@outputs.map(&:connect)
|
10
|
+
@input_connections = @inputs.map(&:connect)
|
11
|
+
|
12
|
+
@sync_thread = Thread.start {
|
13
|
+
loop {
|
14
|
+
@inputs.each {|t|
|
15
|
+
t.sync.resume
|
16
|
+
}
|
17
|
+
|
18
|
+
@inputs.each {|t|
|
19
|
+
stat = t.sync.yield_wait
|
20
|
+
if stat==Sync::COMPLETED
|
21
|
+
@inputs.delete(t)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
if @inputs.length==0
|
26
|
+
break
|
27
|
+
end
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def join
|
33
|
+
@outputs.map(&:join)
|
34
|
+
@input_connections.map(&:join)
|
35
|
+
@sync_thread.join
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'audio_stream/fx/bang_process'
|
2
|
+
|
3
|
+
require 'audio_stream/fx/a_gain'
|
4
|
+
require 'audio_stream/fx/panning'
|
5
|
+
require 'audio_stream/fx/distortion'
|
6
|
+
require 'audio_stream/fx/noise_gate'
|
7
|
+
require 'audio_stream/fx/compressor'
|
8
|
+
require 'audio_stream/fx/biquad_filter'
|
9
|
+
require 'audio_stream/fx/low_pass_filter'
|
10
|
+
require 'audio_stream/fx/high_pass_filter'
|
11
|
+
require 'audio_stream/fx/band_pass_filter'
|
12
|
+
require 'audio_stream/fx/low_shelf_filter'
|
13
|
+
require 'audio_stream/fx/high_shelf_filter'
|
14
|
+
require 'audio_stream/fx/peaking_filter'
|
15
|
+
require 'audio_stream/fx/equalizer_2band'
|
16
|
+
require 'audio_stream/fx/equalizer_3band'
|
17
|
+
require 'audio_stream/fx/tremolo'
|
18
|
+
require 'audio_stream/fx/delay'
|
19
|
+
require 'audio_stream/fx/chorus'
|
20
|
+
require 'audio_stream/fx/convolution_reverb'
|
21
|
+
require 'audio_stream/fx/hanning_window'
|
22
|
+
|
23
|
+
require 'audio_stream/fx/stereo_to_mono'
|
24
|
+
require 'audio_stream/fx/mono_to_stereo'
|
25
|
+
require 'audio_stream/fx/tuner'
|
26
|
+
|
27
|
+
module AudioStream
|
28
|
+
module Fx
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AudioStream
|
2
|
+
module Fx
|
3
|
+
class AGain
|
4
|
+
include BangProcess
|
5
|
+
|
6
|
+
def initialize(level: 1.0)
|
7
|
+
@level = level
|
8
|
+
end
|
9
|
+
|
10
|
+
def process!(input)
|
11
|
+
return if @level==1.0
|
12
|
+
|
13
|
+
case input.channels
|
14
|
+
when 1
|
15
|
+
input.each_with_index {|f, i|
|
16
|
+
input[i] = f * @level
|
17
|
+
}
|
18
|
+
when 2
|
19
|
+
input.each_with_index {|fa, i|
|
20
|
+
input[i] = fa.map {|f| f * @level}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module AudioStream
|
2
|
+
module Fx
|
3
|
+
class BandPassFilter < BiquadFilter
|
4
|
+
|
5
|
+
def initialize(soundinfo, freq:, bandwidth: 1.0)
|
6
|
+
super()
|
7
|
+
@samplerate = soundinfo.samplerate.to_f
|
8
|
+
@freq = freq
|
9
|
+
@bandwidth = bandwidth
|
10
|
+
|
11
|
+
filter_coef
|
12
|
+
end
|
13
|
+
|
14
|
+
def filter_coef
|
15
|
+
omega = 2.0 * Math::PI * @freq / @samplerate
|
16
|
+
alpha = Math.sin(omega) * Math.sinh(Math.log(2.0) / 2.0 * @bandwidth * omega / Math.sin(omega))
|
17
|
+
|
18
|
+
a0 = 1.0 + alpha
|
19
|
+
a1 = -2.0 * Math.cos(omega)
|
20
|
+
a2 = 1.0 - alpha
|
21
|
+
b0 = alpha
|
22
|
+
b1 = 0.0
|
23
|
+
b2 = -alpha
|
24
|
+
|
25
|
+
@filter_coef = FilterCoef.new(a0, a1, a2, b0, b1, b2)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module AudioStream
|
2
|
+
module Fx
|
3
|
+
class BiquadFilter
|
4
|
+
include BangProcess
|
5
|
+
|
6
|
+
FilterBuffer = Struct.new("FilterBuffer", :in1, :in2, :out1, :out2) do
|
7
|
+
def self.create
|
8
|
+
new(0.0, 0.0, 0.0, 0.0)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
FilterCoef = Struct.new("FilterCoef", :a0, :a1, :a2, :b0, :b1, :b2)
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@filter_bufs = [FilterBuffer.create, FilterBuffer.create]
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter_coef
|
19
|
+
raise Error, "#{self.class.name}.filter_coef is not implemented"
|
20
|
+
end
|
21
|
+
|
22
|
+
def process!(input)
|
23
|
+
window_size = input.size
|
24
|
+
channels = input.channels
|
25
|
+
|
26
|
+
case channels
|
27
|
+
when 1
|
28
|
+
b = @filter_bufs[0]
|
29
|
+
window_size.times {|i|
|
30
|
+
input[i] = process_one(input[i], b)
|
31
|
+
}
|
32
|
+
when 2
|
33
|
+
window_size.times {|i|
|
34
|
+
input[i] = channels.times.map {|j|
|
35
|
+
b = @filter_bufs[j]
|
36
|
+
in0 = input[i][j]
|
37
|
+
process_one(in0, b)
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
input
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_one(in0, b)
|
46
|
+
c = @filter_coef
|
47
|
+
out0 = c.b0/c.a0 * in0 + c.b1/c.a0 * b.in1 + c.b2/c.a0 * b.in2 - c.a1/c.a0 * b.out1 - c.a2/c.a0 * b.out2
|
48
|
+
|
49
|
+
b.in2 = b.in1
|
50
|
+
b.in1 = in0
|
51
|
+
b.out2 = b.out1
|
52
|
+
b.out1 = out0
|
53
|
+
|
54
|
+
out0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module AudioStream
|
2
|
+
module Fx
|
3
|
+
class Chorus
|
4
|
+
include BangProcess
|
5
|
+
|
6
|
+
def initialize(soundinfo, depth: 100, rate: 0.25)
|
7
|
+
@soundinfo = soundinfo
|
8
|
+
|
9
|
+
@depth = depth
|
10
|
+
@rate = rate
|
11
|
+
|
12
|
+
@delaybuf0 = RingBuffer.new(@depth * 3, 0.0)
|
13
|
+
@delaybuf1 = RingBuffer.new(@depth * 3, 0.0)
|
14
|
+
|
15
|
+
@phase = 0
|
16
|
+
@speed = (2.0 * Math::PI * @rate) / @soundinfo.samplerate
|
17
|
+
end
|
18
|
+
|
19
|
+
def process!(input)
|
20
|
+
window_size = input.size
|
21
|
+
channels = input.channels
|
22
|
+
|
23
|
+
window_size.times {|i|
|
24
|
+
tau = @depth * (Math.sin(@speed * (@phase + i)) + 1)
|
25
|
+
t = i - tau
|
26
|
+
|
27
|
+
m = t.floor
|
28
|
+
delta = t - m
|
29
|
+
|
30
|
+
case channels
|
31
|
+
when 1
|
32
|
+
wet = delta * @delaybuf0[i-m+1] + (1.0 - delta) * @delaybuf0[i-m]
|
33
|
+
input[i] = (input[i] + wet) * 0.5
|
34
|
+
when 2
|
35
|
+
wet0 = delta * @delaybuf0[i-m+1] + (1.0 - delta) * @delaybuf0[i-m]
|
36
|
+
wet1 = delta * @delaybuf1[i-m+1] + (1.0 - delta) * @delaybuf1[i-m]
|
37
|
+
input[i] = [(input[i][0] + wet0) * 0.5, (input[i][1] + wet1) * 0.5]
|
38
|
+
end
|
39
|
+
|
40
|
+
case channels
|
41
|
+
when 1
|
42
|
+
@delaybuf0.current = input[i]
|
43
|
+
@delaybuf0.rotate
|
44
|
+
when 2
|
45
|
+
@delaybuf0.current = input[i][0]
|
46
|
+
@delaybuf1.current = input[i][1]
|
47
|
+
@delaybuf0.rotate
|
48
|
+
@delaybuf1.rotate
|
49
|
+
end
|
50
|
+
}
|
51
|
+
@phase = (@phase + window_size) % (window_size / @speed)
|
52
|
+
end
|
53
|
+
|
54
|
+
def lerp(start, stop, step)
|
55
|
+
(stop * step) + (start * (1.0 - step))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AudioStream
|
2
|
+
module Fx
|
3
|
+
class Compressor
|
4
|
+
include BangProcess
|
5
|
+
|
6
|
+
def initialize(threshold: 0.5, ratio: 0.5)
|
7
|
+
@threshold = threshold
|
8
|
+
@ratio = ratio
|
9
|
+
@zoom = 1.0 / (@ratio * (1.0 - @threshold) + @threshold)
|
10
|
+
end
|
11
|
+
|
12
|
+
def process!(input)
|
13
|
+
window_size = input.size
|
14
|
+
channels = input.channels
|
15
|
+
|
16
|
+
case channels
|
17
|
+
when 1
|
18
|
+
input.each_with_index {|f, i|
|
19
|
+
sign = f.negative? ? -1 : 1
|
20
|
+
f = f.abs
|
21
|
+
if @threshold<f
|
22
|
+
f = (f - @threshold) * @ratio + @threshold
|
23
|
+
end
|
24
|
+
input[i] = @zoom * f * sign
|
25
|
+
}
|
26
|
+
when 2
|
27
|
+
input.each_with_index {|fa, i|
|
28
|
+
input[i] = fa.map {|f|
|
29
|
+
sign = f.negative? ? -1 : 1
|
30
|
+
f = f.abs
|
31
|
+
if @threshold<f
|
32
|
+
f = (f - @threshold) * @ratio + @threshold
|
33
|
+
end
|
34
|
+
@zoom * f * sign
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|