audio_stream 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/examples/chorus.rb +1 -2
  3. data/examples/equalizer.ipynb +23 -23
  4. data/examples/example_options.rb +1 -1
  5. data/examples/lpf.rb +5 -5
  6. data/examples/tuner.rb +3 -3
  7. data/lib/audio_stream.rb +2 -1
  8. data/lib/audio_stream/audio_input_device.rb +1 -8
  9. data/lib/audio_stream/audio_input_file.rb +3 -4
  10. data/lib/audio_stream/audio_observable.rb +26 -0
  11. data/lib/audio_stream/audio_observable_fx.rb +1 -6
  12. data/lib/audio_stream/audio_observable_lambda.rb +20 -0
  13. data/lib/audio_stream/audio_observer_lambda.rb +19 -0
  14. data/lib/audio_stream/audio_output_device.rb +4 -7
  15. data/lib/audio_stream/audio_output_file.rb +1 -1
  16. data/lib/audio_stream/buffer.rb +191 -47
  17. data/lib/audio_stream/fx.rb +0 -2
  18. data/lib/audio_stream/fx/a_gain.rb +7 -13
  19. data/lib/audio_stream/fx/biquad_filter.rb +15 -25
  20. data/lib/audio_stream/fx/chorus.rb +25 -33
  21. data/lib/audio_stream/fx/compressor.rb +6 -22
  22. data/lib/audio_stream/fx/convolution_reverb.rb +30 -39
  23. data/lib/audio_stream/fx/delay.rb +25 -14
  24. data/lib/audio_stream/fx/distortion.rb +7 -24
  25. data/lib/audio_stream/fx/equalizer_2band.rb +5 -7
  26. data/lib/audio_stream/fx/equalizer_3band.rb +7 -9
  27. data/lib/audio_stream/fx/graphic_equalizer.rb +4 -6
  28. data/lib/audio_stream/fx/hanning_window.rb +8 -15
  29. data/lib/audio_stream/fx/high_pass_filter.rb +1 -3
  30. data/lib/audio_stream/fx/high_shelf_filter.rb +1 -3
  31. data/lib/audio_stream/fx/low_pass_filter.rb +1 -3
  32. data/lib/audio_stream/fx/low_shelf_filter.rb +1 -3
  33. data/lib/audio_stream/fx/noise_gate.rb +5 -23
  34. data/lib/audio_stream/fx/panning.rb +19 -13
  35. data/lib/audio_stream/fx/tremolo.rb +8 -16
  36. data/lib/audio_stream/fx/tuner.rb +12 -12
  37. data/lib/audio_stream/version.rb +1 -1
  38. metadata +4 -5
  39. data/lib/audio_stream/fx/mono_to_stereo.rb +0 -20
  40. data/lib/audio_stream/fx/stereo_to_mono.rb +0 -20
  41. data/lib/audio_stream/plot.rb +0 -53
@@ -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!(input)
11
- return if @level==1.0
8
+ def process(input)
9
+ return input if @level==1.0
12
10
 
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}
11
+ streams = input.streams.map {|stream|
12
+ stream.map {|f|
13
+ f * @level
21
14
  }
22
- end
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}.filter_coef is not implemented"
24
+ raise Error, "#{self.class.name}.update_coef is not implemented"
25
25
  end
26
26
 
27
- def process!(input)
28
- window_size = input.size
27
+ def process(input)
28
+ window_size = input.window_size
29
29
  channels = input.channels
30
30
 
31
- case channels
32
- when 1
33
- b = @filter_bufs[0]
34
- window_size.times {|i|
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
- end
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(inp)
44
+ def process_stereo(in0, in1)
55
45
  [
56
- process_one(inp[0], @filter_bufs[0]),
57
- process_one(inp[1], @filter_bufs[1])
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=1000)
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
- @delaybuf0 = RingBuffer.new(@depth * 3, 0.0)
13
- @delaybuf1 = RingBuffer.new(@depth * 3, 0.0)
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!(input)
20
- window_size = input.size
19
+ def process(input)
20
+ window_size = input.window_size
21
21
  channels = input.channels
22
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
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!(input)
13
- window_size = input.size
14
- channels = input.channels
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
- 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
- }
18
+ @zoom * f * sign
36
19
  }
37
- end
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
- include BangProcess
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].size
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
- buf1 = impulse_bufs[i]
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!(input)
49
- if @window_size!=input.size
50
- raise "window size is not match: impulse.size=#{@window_size} input.size=#{input.size}"
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 = input[i] * @dry_gain
71
+ dry = src0[i] * @dry_gain
87
72
  wet = wet_na[i].real
88
- input[i] = dry + wet
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
- dry = input[i]
94
- dry1 = dry[0] * @dry_gain
95
- dry2 = dry[1] * @dry_gain
82
+ dry0 = src0[i] * @dry_gain
83
+ dry1 = src1[i] * @dry_gain
96
84
 
97
85
  # wet
98
- wet1 = wet_na[i*2].real
99
- wet2 = wet_na[(i*2)+1].real
86
+ wet0 = wet_na[i*2].real
87
+ wet1 = wet_na[(i*2)+1].real
100
88
 
101
- input[i] = [dry1 + wet1, dry2 + wet2]
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!(input)
18
- window_size = input.size
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
- input.each_with_index {|f, i|
24
- tmp0 = input[i] + @level * @delaybuf0[@seek]
25
- @delaybuf0[@seek] = input[i] + @feedback * @delaybuf0[@seek]
26
- input[i] = tmp0
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
- input.each_with_index {|fa, i|
31
- tmp0 = input[i][0] + @level * @delaybuf0[@seek]
32
- tmp1 = input[i][1] + @level * @delaybuf1[@seek]
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] = input[i][0] + @feedback * @delaybuf0[@seek]
35
- @delaybuf1[@seek] = input[i][1] + @feedback * @delaybuf1[@seek]
43
+ @delaybuf0[@seek] = src0[i] + @feedback * @delaybuf0[@seek]
44
+ @delaybuf1[@seek] = src1[i] + @feedback * @delaybuf1[@seek]
36
45
 
37
- input[i] = [tmp0, tmp1]
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!(input)
12
- window_size = input.size
13
- channels = input.channels
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
- input[i] = val * @level
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
- end
20
+ }
21
+ Buffer.new(*streams)
39
22
  end
40
23
  end
41
24
  end