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