audio_stream 1.5.0 → 2.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 +4 -4
- data/examples/chorus.rb +1 -2
- data/examples/equalizer.ipynb +23 -23
- data/examples/example_options.rb +1 -1
- data/examples/lpf.rb +5 -5
- data/examples/tuner.rb +3 -3
- data/lib/audio_stream.rb +2 -1
- data/lib/audio_stream/audio_input_device.rb +1 -8
- data/lib/audio_stream/audio_input_file.rb +3 -4
- data/lib/audio_stream/audio_observable.rb +26 -0
- data/lib/audio_stream/audio_observable_fx.rb +1 -6
- data/lib/audio_stream/audio_observable_lambda.rb +20 -0
- data/lib/audio_stream/audio_observer_lambda.rb +19 -0
- data/lib/audio_stream/audio_output_device.rb +4 -7
- data/lib/audio_stream/audio_output_file.rb +1 -1
- data/lib/audio_stream/buffer.rb +191 -47
- data/lib/audio_stream/fx.rb +0 -2
- data/lib/audio_stream/fx/a_gain.rb +7 -13
- data/lib/audio_stream/fx/biquad_filter.rb +15 -25
- data/lib/audio_stream/fx/chorus.rb +25 -33
- data/lib/audio_stream/fx/compressor.rb +6 -22
- data/lib/audio_stream/fx/convolution_reverb.rb +30 -39
- data/lib/audio_stream/fx/delay.rb +25 -14
- data/lib/audio_stream/fx/distortion.rb +7 -24
- data/lib/audio_stream/fx/equalizer_2band.rb +5 -7
- data/lib/audio_stream/fx/equalizer_3band.rb +7 -9
- data/lib/audio_stream/fx/graphic_equalizer.rb +4 -6
- data/lib/audio_stream/fx/hanning_window.rb +8 -15
- data/lib/audio_stream/fx/high_pass_filter.rb +1 -3
- data/lib/audio_stream/fx/high_shelf_filter.rb +1 -3
- data/lib/audio_stream/fx/low_pass_filter.rb +1 -3
- data/lib/audio_stream/fx/low_shelf_filter.rb +1 -3
- data/lib/audio_stream/fx/noise_gate.rb +5 -23
- data/lib/audio_stream/fx/panning.rb +19 -13
- data/lib/audio_stream/fx/tremolo.rb +8 -16
- data/lib/audio_stream/fx/tuner.rb +12 -12
- data/lib/audio_stream/version.rb +1 -1
- metadata +4 -5
- data/lib/audio_stream/fx/mono_to_stereo.rb +0 -20
- data/lib/audio_stream/fx/stereo_to_mono.rb +0 -20
- data/lib/audio_stream/plot.rb +0 -53
data/lib/audio_stream/fx.rb
CHANGED
@@ -21,8 +21,6 @@ require 'audio_stream/fx/chorus'
|
|
21
21
|
require 'audio_stream/fx/convolution_reverb'
|
22
22
|
require 'audio_stream/fx/hanning_window'
|
23
23
|
|
24
|
-
require 'audio_stream/fx/stereo_to_mono'
|
25
|
-
require 'audio_stream/fx/mono_to_stereo'
|
26
24
|
require 'audio_stream/fx/tuner'
|
27
25
|
|
28
26
|
module AudioStream
|
@@ -1,25 +1,19 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class AGain
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
def initialize(level: 1.0)
|
7
5
|
@level = level
|
8
6
|
end
|
9
7
|
|
10
|
-
def process
|
11
|
-
return if @level==1.0
|
8
|
+
def process(input)
|
9
|
+
return input if @level==1.0
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
input[i] = f * @level
|
17
|
-
}
|
18
|
-
when 2
|
19
|
-
input.each_with_index {|fa, i|
|
20
|
-
input[i] = fa.map {|f| f * @level}
|
11
|
+
streams = input.streams.map {|stream|
|
12
|
+
stream.map {|f|
|
13
|
+
f * @level
|
21
14
|
}
|
22
|
-
|
15
|
+
}
|
16
|
+
Buffer.new(*streams)
|
23
17
|
end
|
24
18
|
end
|
25
19
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class BiquadFilter
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
FilterBuffer = Struct.new("FilterBuffer", :in1, :in2, :out1, :out2) do
|
7
5
|
def self.create
|
8
6
|
new(0.0, 0.0, 0.0, 0.0)
|
@@ -11,6 +9,8 @@ module AudioStream
|
|
11
9
|
|
12
10
|
FilterCoef = Struct.new("FilterCoef", :a0, :a1, :a2, :b0, :b1, :b2)
|
13
11
|
|
12
|
+
DEFAULT_Q = 1.0 / Math.sqrt(2.0)
|
13
|
+
|
14
14
|
def initialize(soundinfo)
|
15
15
|
@samplerate = soundinfo.samplerate.to_f
|
16
16
|
init_buffer
|
@@ -21,40 +21,30 @@ module AudioStream
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def update_coef(*args, **kwargs)
|
24
|
-
raise Error, "#{self.class.name}.
|
24
|
+
raise Error, "#{self.class.name}.update_coef is not implemented"
|
25
25
|
end
|
26
26
|
|
27
|
-
def process
|
28
|
-
window_size = input.
|
27
|
+
def process(input)
|
28
|
+
window_size = input.window_size
|
29
29
|
channels = input.channels
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
input[i] = process_one(input[i], b)
|
36
|
-
}
|
37
|
-
when 2
|
38
|
-
window_size.times {|i|
|
39
|
-
input_i = input[i]
|
40
|
-
input[i] = [
|
41
|
-
process_one(input_i[0], @filter_bufs[0]),
|
42
|
-
process_one(input_i[1], @filter_bufs[1]),
|
43
|
-
]
|
31
|
+
streams = input.streams.map.with_index {|stream, i|
|
32
|
+
b = @filter_bufs[i]
|
33
|
+
stream.map {|f|
|
34
|
+
process_one(f, b)
|
44
35
|
}
|
45
|
-
|
46
|
-
|
47
|
-
input
|
36
|
+
}
|
37
|
+
Buffer.new(*streams)
|
48
38
|
end
|
49
39
|
|
50
40
|
def process_mono(in0)
|
51
41
|
process_one(in0, @filter_bufs[0])
|
52
42
|
end
|
53
43
|
|
54
|
-
def process_stereo(
|
44
|
+
def process_stereo(in0, in1)
|
55
45
|
[
|
56
|
-
process_one(
|
57
|
-
process_one(
|
46
|
+
process_one(in0, @filter_bufs[0]),
|
47
|
+
process_one(in1, @filter_bufs[1])
|
58
48
|
]
|
59
49
|
end
|
60
50
|
|
@@ -70,7 +60,7 @@ module AudioStream
|
|
70
60
|
out0
|
71
61
|
end
|
72
62
|
|
73
|
-
def plot_data(width=
|
63
|
+
def plot_data(width=500)
|
74
64
|
c = @filter_coef
|
75
65
|
|
76
66
|
b0 = c.b0 / c.a0
|
@@ -1,54 +1,46 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class Chorus
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
def initialize(soundinfo, depth: 100, rate: 0.25)
|
7
5
|
@soundinfo = soundinfo
|
8
6
|
|
9
7
|
@depth = depth
|
10
8
|
@rate = rate
|
11
9
|
|
12
|
-
@
|
13
|
-
|
10
|
+
@delaybufs = [
|
11
|
+
RingBuffer.new(@depth * 3, 0.0),
|
12
|
+
RingBuffer.new(@depth * 3, 0.0)
|
13
|
+
]
|
14
14
|
|
15
15
|
@phase = 0
|
16
16
|
@speed = (2.0 * Math::PI * @rate) / @soundinfo.samplerate
|
17
17
|
end
|
18
18
|
|
19
|
-
def process
|
20
|
-
window_size = input.
|
19
|
+
def process(input)
|
20
|
+
window_size = input.window_size
|
21
21
|
channels = input.channels
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
wet = delta *
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
23
|
+
streams = channels.times.map {|ch|
|
24
|
+
delaybuf = @delaybufs[ch]
|
25
|
+
input.streams[ch].map.with_index {|f, i|
|
26
|
+
tau = @depth * (Math.sin(@speed * (@phase + i)) + 1)
|
27
|
+
t = i - tau
|
28
|
+
|
29
|
+
m = t.floor
|
30
|
+
delta = t - m
|
31
|
+
|
32
|
+
wet = delta * delaybuf[i-m+1] + (1.0 - delta) * delaybuf[i-m]
|
33
|
+
f = (f + wet) * 0.5
|
34
|
+
|
35
|
+
delaybuf.current = f
|
36
|
+
delaybuf.rotate
|
37
|
+
|
38
|
+
f
|
39
|
+
}
|
50
40
|
}
|
51
41
|
@phase = (@phase + window_size) % (window_size / @speed)
|
42
|
+
|
43
|
+
Buffer.new(*streams)
|
52
44
|
end
|
53
45
|
|
54
46
|
def lerp(start, stop, step)
|
@@ -1,40 +1,24 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class Compressor
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
def initialize(threshold: 0.5, ratio: 0.5)
|
7
5
|
@threshold = threshold
|
8
6
|
@ratio = ratio
|
9
7
|
@zoom = 1.0 / (@ratio * (1.0 - @threshold) + @threshold)
|
10
8
|
end
|
11
9
|
|
12
|
-
def process
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
case channels
|
17
|
-
when 1
|
18
|
-
input.each_with_index {|f, i|
|
10
|
+
def process(input)
|
11
|
+
streams = input.streams.map {|stream|
|
12
|
+
stream.map {|f|
|
19
13
|
sign = f.negative? ? -1 : 1
|
20
14
|
f = f.abs
|
21
15
|
if @threshold<f
|
22
16
|
f = (f - @threshold) * @ratio + @threshold
|
23
17
|
end
|
24
|
-
|
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
|
-
}
|
18
|
+
@zoom * f * sign
|
36
19
|
}
|
37
|
-
|
20
|
+
}
|
21
|
+
Buffer.new(*streams)
|
38
22
|
end
|
39
23
|
end
|
40
24
|
end
|
@@ -1,38 +1,22 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class ConvolutionReverb
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(impulse, dry: 0.5, wet: 0.5, window: nil)
|
4
|
+
def initialize(impulse, dry: 0.5, wet: 0.5)
|
7
5
|
impulse_bufs = impulse.to_a
|
8
6
|
@impulse_size = impulse_bufs.size
|
9
7
|
@channels = impulse_bufs[0].channels
|
10
|
-
@window_size = impulse_bufs[0].
|
8
|
+
@window_size = impulse_bufs[0].window_size
|
11
9
|
@dry_gain = dry
|
12
10
|
@wet_gain = wet
|
13
|
-
@window = window || HanningWindow.instance
|
14
|
-
|
15
|
-
zero_buf = Buffer.float(@window_size, @channels)
|
16
|
-
if @channels==1
|
17
|
-
zero_buf.size.times {|i| zero_buf[i] = 0}
|
18
|
-
else
|
19
|
-
zero_buf.size.times {|i| zero_buf[i] = Array.new(@channels, 0)}
|
20
|
-
end
|
21
11
|
|
12
|
+
zero_buf = Buffer.create(@window_size, @channels)
|
22
13
|
impulse_bufs = [zero_buf.clone] + impulse_bufs
|
23
14
|
|
24
15
|
@impulse_ffts = []
|
25
16
|
@impulse_size.times {|i|
|
26
17
|
na = NArray.float(@channels, @window_size*2)
|
27
|
-
|
28
|
-
|
29
|
-
buf1_flat = buf1.to_a.flatten
|
30
|
-
na[0...buf1_flat.size] = buf1_flat
|
31
|
-
|
32
|
-
buf2 = impulse_bufs[i+1]
|
33
|
-
buf2_flat = buf2.to_a.flatten
|
34
|
-
na[buf1.size...(buf1.size+buf2_flat.size)] = buf2_flat
|
35
|
-
|
18
|
+
impulse_bufs[i].to_float_na(na, 0)
|
19
|
+
impulse_bufs[i+1].to_float_na(na, @window_size)
|
36
20
|
@impulse_ffts << FFTW3.fft(na, FFTW3::FORWARD) / na.length
|
37
21
|
}
|
38
22
|
|
@@ -45,9 +29,9 @@ module AudioStream
|
|
45
29
|
@prev_input = zero_buf.clone
|
46
30
|
end
|
47
31
|
|
48
|
-
def process
|
49
|
-
if @window_size!=input.
|
50
|
-
raise "window size is not match: impulse.size=#{@window_size} input.size=#{input.
|
32
|
+
def process(input)
|
33
|
+
if @window_size!=input.window_size
|
34
|
+
raise "window size is not match: impulse.size=#{@window_size} input.size=#{input.window_size}"
|
51
35
|
end
|
52
36
|
if @channels!=input.channels
|
53
37
|
raise "channels is not match: impulse.channels=#{@channels} input.channels=#{input.channels}"
|
@@ -55,14 +39,9 @@ module AudioStream
|
|
55
39
|
|
56
40
|
# current dry to wet
|
57
41
|
na = NArray.float(@channels, @window_size*2)
|
42
|
+
@prev_input.to_float_na(na, 0)
|
43
|
+
input.to_float_na(na, @window_size)
|
58
44
|
|
59
|
-
prev_flat = @prev_input.to_a.flatten
|
60
|
-
na[0...prev_flat.size] = prev_flat
|
61
|
-
|
62
|
-
input_flat = input.to_a.flatten
|
63
|
-
na[@prev_input.size...(@prev_input.size+input_flat.size)] = input_flat
|
64
|
-
|
65
|
-
na = @window.process!(Buffer.from_na(na)).to_na
|
66
45
|
input_fft = FFTW3.fft(na, FFTW3::FORWARD) / na.length
|
67
46
|
|
68
47
|
@wet_ffts.current = @impulse_ffts.map {|impulse_fft|
|
@@ -80,27 +59,39 @@ module AudioStream
|
|
80
59
|
wet_na = FFTW3.fft(wet_fft, FFTW3::BACKWARD)[(@channels*@window_size)...(@channels*@window_size*2)] * (@wet_gain / @impulse_max_gain)
|
81
60
|
|
82
61
|
# current dry + wet matrix sum
|
62
|
+
src0 = input.streams[0]
|
63
|
+
src1 = input.streams[1]
|
64
|
+
|
83
65
|
case @channels
|
84
66
|
when 1
|
67
|
+
output = Buffer.create_mono(@window_size)
|
68
|
+
dst0 = output.streams[0]
|
69
|
+
|
85
70
|
@window_size.times {|i|
|
86
|
-
dry =
|
71
|
+
dry = src0[i] * @dry_gain
|
87
72
|
wet = wet_na[i].real
|
88
|
-
|
73
|
+
dst0[i] = dry + wet
|
89
74
|
}
|
90
75
|
when 2
|
76
|
+
output = Buffer.create_stereo(@window_size)
|
77
|
+
dst0 = output.streams[0]
|
78
|
+
dst1 = output.streams[1]
|
79
|
+
|
91
80
|
@window_size.times {|i|
|
92
81
|
# dry
|
93
|
-
|
94
|
-
dry1 =
|
95
|
-
dry2 = dry[1] * @dry_gain
|
82
|
+
dry0 = src0[i] * @dry_gain
|
83
|
+
dry1 = src1[i] * @dry_gain
|
96
84
|
|
97
85
|
# wet
|
98
|
-
|
99
|
-
|
86
|
+
wet0 = wet_na[i*2].real
|
87
|
+
wet1 = wet_na[(i*2)+1].real
|
100
88
|
|
101
|
-
|
89
|
+
dst0[i] = dry0 + wet0
|
90
|
+
dst1[i] = dry1 + wet1
|
102
91
|
}
|
103
92
|
end
|
93
|
+
|
94
|
+
output
|
104
95
|
end
|
105
96
|
end
|
106
97
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class Delay
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
def initialize(soundinfo, time:, level:, feedback:)
|
7
5
|
@time = time
|
8
6
|
@level = level
|
@@ -14,30 +12,43 @@ module AudioStream
|
|
14
12
|
@seek = 0
|
15
13
|
end
|
16
14
|
|
17
|
-
def process
|
18
|
-
window_size = input.
|
15
|
+
def process(input)
|
16
|
+
window_size = input.window_size
|
19
17
|
channels = input.channels
|
20
18
|
|
19
|
+
src0 = input.streams[0]
|
20
|
+
src1 = input.streams[1]
|
21
|
+
|
21
22
|
case channels
|
22
23
|
when 1
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
output = Buffer.create_mono(window_size)
|
25
|
+
dst0 = output.streams[0]
|
26
|
+
|
27
|
+
src0.each_with_index {|f, i|
|
28
|
+
tmp0 = f + @level * @delaybuf0[@seek]
|
29
|
+
@delaybuf0[@seek] = f + @feedback * @delaybuf0[@seek]
|
30
|
+
dst0[i] = tmp0
|
27
31
|
@seek = (@seek + 1) % @delaysample
|
28
32
|
}
|
33
|
+
output
|
29
34
|
when 2
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
output = Buffer.create_stereo(window_size)
|
36
|
+
dst0 = output.streams[0]
|
37
|
+
dst1 = output.streams[1]
|
38
|
+
|
39
|
+
window_size.times {|i|
|
40
|
+
tmp0 = src0[i] + @level * @delaybuf0[@seek]
|
41
|
+
tmp1 = src1[i] + @level * @delaybuf1[@seek]
|
33
42
|
|
34
|
-
@delaybuf0[@seek] =
|
35
|
-
@delaybuf1[@seek] =
|
43
|
+
@delaybuf0[@seek] = src0[i] + @feedback * @delaybuf0[@seek]
|
44
|
+
@delaybuf1[@seek] = src1[i] + @feedback * @delaybuf1[@seek]
|
36
45
|
|
37
|
-
|
46
|
+
dst0[i] = tmp0
|
47
|
+
dst1[i] = tmp1
|
38
48
|
|
39
49
|
@seek = (@seek + 1) % @delaysample
|
40
50
|
}
|
51
|
+
output
|
41
52
|
end
|
42
53
|
end
|
43
54
|
end
|
@@ -1,41 +1,24 @@
|
|
1
1
|
module AudioStream
|
2
2
|
module Fx
|
3
3
|
class Distortion
|
4
|
-
include BangProcess
|
5
|
-
|
6
4
|
def initialize(gain: 100, level: 0.1)
|
7
5
|
@gain = gain
|
8
6
|
@level = level
|
9
7
|
end
|
10
8
|
|
11
|
-
def process
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
case channels
|
16
|
-
when 1
|
17
|
-
input.each_with_index {|f, i|
|
18
|
-
val = input[i] * @gain
|
9
|
+
def process(input)
|
10
|
+
streams = input.streams.map {|stream|
|
11
|
+
stream.map {|f|
|
12
|
+
val = f * @gain
|
19
13
|
if 1.0 < val
|
20
14
|
val = 1.0
|
21
15
|
elsif val < -1.0
|
22
16
|
val = -1.0
|
23
17
|
end
|
24
|
-
|
25
|
-
}
|
26
|
-
when 2
|
27
|
-
input.each_with_index {|fa, i|
|
28
|
-
input[i] = fa.map {|f|
|
29
|
-
val = f * @gain
|
30
|
-
if 1.0 < val
|
31
|
-
val = 1.0
|
32
|
-
elsif val < -1.0
|
33
|
-
val = -1.0
|
34
|
-
end
|
35
|
-
val * @level
|
36
|
-
}
|
18
|
+
val * @level
|
37
19
|
}
|
38
|
-
|
20
|
+
}
|
21
|
+
Buffer.new(*streams)
|
39
22
|
end
|
40
23
|
end
|
41
24
|
end
|