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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +39 -0
  7. data/Rakefile +10 -0
  8. data/audio_stream.gemspec +38 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/examples/adsr.ipynb +88 -0
  12. data/examples/buffer.ipynb +160 -0
  13. data/examples/chorus.rb +41 -0
  14. data/examples/curves.ipynb +105 -0
  15. data/examples/distortion.rb +43 -0
  16. data/examples/example_options.rb +56 -0
  17. data/examples/rec.rb +53 -0
  18. data/examples/shapes.ipynb +116 -0
  19. data/examples/sin.rb +62 -0
  20. data/examples/tremolo.rb +38 -0
  21. data/examples/tuner.rb +77 -0
  22. data/lib/audio_stream.rb +30 -0
  23. data/lib/audio_stream/audio_bus.rb +28 -0
  24. data/lib/audio_stream/audio_input.rb +48 -0
  25. data/lib/audio_stream/audio_input_buffer.rb +21 -0
  26. data/lib/audio_stream/audio_input_device.rb +50 -0
  27. data/lib/audio_stream/audio_input_file.rb +30 -0
  28. data/lib/audio_stream/audio_input_metronome.rb +49 -0
  29. data/lib/audio_stream/audio_input_sin.rb +41 -0
  30. data/lib/audio_stream/audio_output.rb +17 -0
  31. data/lib/audio_stream/audio_output_device.rb +65 -0
  32. data/lib/audio_stream/audio_output_file.rb +35 -0
  33. data/lib/audio_stream/buffer.rb +84 -0
  34. data/lib/audio_stream/conductor.rb +38 -0
  35. data/lib/audio_stream/core_ext.rb +13 -0
  36. data/lib/audio_stream/error.rb +4 -0
  37. data/lib/audio_stream/fx.rb +30 -0
  38. data/lib/audio_stream/fx/a_gain.rb +26 -0
  39. data/lib/audio_stream/fx/band_pass_filter.rb +29 -0
  40. data/lib/audio_stream/fx/bang_process.rb +11 -0
  41. data/lib/audio_stream/fx/biquad_filter.rb +58 -0
  42. data/lib/audio_stream/fx/chorus.rb +59 -0
  43. data/lib/audio_stream/fx/compressor.rb +41 -0
  44. data/lib/audio_stream/fx/convolution_reverb.rb +107 -0
  45. data/lib/audio_stream/fx/delay.rb +45 -0
  46. data/lib/audio_stream/fx/distortion.rb +42 -0
  47. data/lib/audio_stream/fx/equalizer_2band.rb +23 -0
  48. data/lib/audio_stream/fx/equalizer_3band.rb +28 -0
  49. data/lib/audio_stream/fx/hanning_window.rb +29 -0
  50. data/lib/audio_stream/fx/high_pass_filter.rb +29 -0
  51. data/lib/audio_stream/fx/high_shelf_filter.rb +32 -0
  52. data/lib/audio_stream/fx/low_pass_filter.rb +29 -0
  53. data/lib/audio_stream/fx/low_shelf_filter.rb +32 -0
  54. data/lib/audio_stream/fx/mono_to_stereo.rb +18 -0
  55. data/lib/audio_stream/fx/noise_gate.rb +47 -0
  56. data/lib/audio_stream/fx/panning.rb +41 -0
  57. data/lib/audio_stream/fx/peaking_filter.rb +31 -0
  58. data/lib/audio_stream/fx/stereo_to_mono.rb +18 -0
  59. data/lib/audio_stream/fx/tremolo.rb +34 -0
  60. data/lib/audio_stream/fx/tuner.rb +98 -0
  61. data/lib/audio_stream/plot.rb +53 -0
  62. data/lib/audio_stream/ring_buffer.rb +39 -0
  63. data/lib/audio_stream/sound_info.rb +9 -0
  64. data/lib/audio_stream/sync.rb +32 -0
  65. data/lib/audio_stream/utils.rb +45 -0
  66. data/lib/audio_stream/version.rb +3 -0
  67. metadata +223 -0
@@ -0,0 +1,107 @@
1
+ module AudioStream
2
+ module Fx
3
+ class ConvolutionReverb
4
+ include BangProcess
5
+
6
+ def initialize(impulse, dry: 0.5, wet: 0.5, window: nil)
7
+ impulse_bufs = impulse.to_a
8
+ @impulse_size = impulse_bufs.size
9
+ @channels = impulse_bufs[0].channels
10
+ @window_size = impulse_bufs[0].size
11
+ @dry_gain = dry
12
+ @wet_gain = wet
13
+ @window = window || HanningWindow.new
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
+
22
+ impulse_bufs = [zero_buf.clone] + impulse_bufs
23
+
24
+ @impulse_ffts = []
25
+ @impulse_size.times {|i|
26
+ 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
+
36
+ @impulse_ffts << FFTW3.fft(na, FFTW3::FORWARD) / na.length
37
+ }
38
+
39
+ @impulse_max_gain = @impulse_ffts.map{|c| c.real**2 + c.imag**2}.map(&:sum).max / @channels
40
+
41
+ @wet_ffts = RingBuffer.new(@impulse_size) {
42
+ Array.new(@impulse_size, NArray.float(@channels, @window_size*2))
43
+ }
44
+
45
+ @prev_input = zero_buf.clone
46
+ end
47
+
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}"
51
+ end
52
+ if @channels!=input.channels
53
+ raise "channels is not match: impulse.channels=#{@channels} input.channels=#{input.channels}"
54
+ end
55
+
56
+ # current dry to wet
57
+ na = NArray.float(@channels, @window_size*2)
58
+
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
+ input_fft = FFTW3.fft(na, FFTW3::FORWARD) / na.length
67
+
68
+ @wet_ffts.current = @impulse_ffts.map {|impulse_fft|
69
+ input_fft * impulse_fft
70
+ }
71
+ @wet_ffts.rotate
72
+ @prev_input = input.clone
73
+
74
+ # calc wet matrix sum
75
+ wet_fft = NArray.complex(@channels, @window_size*2)
76
+ @wet_ffts.each_with_index {|wet, i|
77
+ wet_fft += wet[@impulse_size-i-1]
78
+ }
79
+
80
+ wet_na = FFTW3.fft(wet_fft, FFTW3::BACKWARD)[(@channels*@window_size)...(@channels*@window_size*2)] * (@wet_gain / @impulse_max_gain)
81
+
82
+ # current dry + wet matrix sum
83
+ case @channels
84
+ when 1
85
+ @window_size.times {|i|
86
+ dry = input[i] * @dry_gain
87
+ wet = wet_na[i].real
88
+ input[i] = dry + wet
89
+ }
90
+ when 2
91
+ @window_size.times {|i|
92
+ # dry
93
+ dry = input[i]
94
+ dry1 = dry[0] * @dry_gain
95
+ dry2 = dry[1] * @dry_gain
96
+
97
+ # wet
98
+ wet1 = wet_na[i*2].real
99
+ wet2 = wet_na[(i*2)+1].real
100
+
101
+ input[i] = [dry1 + wet1, dry2 + wet2]
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,45 @@
1
+ module AudioStream
2
+ module Fx
3
+ class Delay
4
+ include BangProcess
5
+
6
+ def initialize(soundinfo, time:, level:, feedback:)
7
+ @time = time
8
+ @level = level
9
+ @feedback = feedback
10
+
11
+ @delaysample = soundinfo.samplerate * time / 1000.0
12
+ @delaybuf0 = Array.new(@delaysample, 0.0)
13
+ @delaybuf1 = Array.new(@delaysample, 0.0)
14
+ @seek = 0
15
+ end
16
+
17
+ def process!(input)
18
+ window_size = input.size
19
+ channels = input.channels
20
+
21
+ case channels
22
+ 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
27
+ @seek = (@seek + 1) % @delaysample
28
+ }
29
+ 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]
33
+
34
+ @delaybuf0[@seek] = input[i][0] + @feedback * @delaybuf0[@seek]
35
+ @delaybuf1[@seek] = input[i][1] + @feedback * @delaybuf1[@seek]
36
+
37
+ input[i] = [tmp0, tmp1]
38
+
39
+ @seek = (@seek + 1) % @delaysample
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ module AudioStream
2
+ module Fx
3
+ class Distortion
4
+ include BangProcess
5
+
6
+ def initialize(gain: 100, level: 0.1)
7
+ @gain = gain
8
+ @level = level
9
+ end
10
+
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
19
+ if 1.0 < val
20
+ val = 1.0
21
+ elsif val < -1.0
22
+ val = -1.0
23
+ 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
+ }
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module AudioStream
2
+ module Fx
3
+ class Equalizer2band
4
+ include BangProcess
5
+
6
+ def initialize(soundinfo, lowgain:, highgain:)
7
+ @lowfreq = 400.0
8
+ @lowgain = lowgain
9
+
10
+ @highfreq = 4000.0
11
+ @highgain = highgain
12
+
13
+ @low_filter = LowShelfFilter.new(soundinfo, freq: @lowfreq, q: 1.0/Math.sqrt(2.0), gain: @lowgain)
14
+ @high_filter = HighShelfFilter.new(soundinfo, freq: @highfreq, q: 1.0/Math.sqrt(2.0), gain: @highgain)
15
+ end
16
+
17
+ def process!(input)
18
+ @low_filter.process!(input)
19
+ @high_filter.process!(input)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module AudioStream
2
+ module Fx
3
+ class Equalizer3band
4
+ include BangProcess
5
+
6
+ def initialize(soundinfo, lowgain:, midgain:, highgain:)
7
+ @lowfreq = 400.0
8
+ @lowgain = lowgain
9
+
10
+ @midfreq = 1000.0
11
+ @midgain = midgain
12
+
13
+ @highfreq = 4000.0
14
+ @highgain = highgain
15
+
16
+ @low_filter = LowShelfFilter.new(soundinfo, freq: @lowfreq, q: 1.0/Math.sqrt(2.0), gain: @lowgain)
17
+ @mid_filter = PeakingFilter.new(soundinfo, freq: @midfreq, bandwidth: 1.0/Math.sqrt(2.0), gain: @midgain)
18
+ @high_filter = HighShelfFilter.new(soundinfo, freq: @highfreq, q: 1.0/Math.sqrt(2.0), gain: @highgain)
19
+ end
20
+
21
+ def process!(input)
22
+ @low_filter.process!(input)
23
+ @mid_filter.process!(input)
24
+ @high_filter.process!(input)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module AudioStream
2
+ module Fx
3
+ class HanningWindow
4
+ include BangProcess
5
+
6
+ def process!(input)
7
+ window_size = input.size
8
+ window_max = input.size - 1
9
+ channels = input.channels
10
+
11
+ period = 2 * Math::PI / window_max
12
+
13
+ case channels
14
+ when 1
15
+ input.each_with_index {|f, i|
16
+ input[i] *= 0.5 - 0.5 * Math.cos(i * period)
17
+ }
18
+ when 2
19
+ input.each_with_index {|fa, i|
20
+ gain = 0.5 - 0.5 * Math.cos(i * period)
21
+ input[i] = fa.map {|f| f * gain}
22
+ }
23
+ end
24
+
25
+ input
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module AudioStream
2
+ module Fx
3
+ class HighPassFilter < BiquadFilter
4
+
5
+ def initialize(soundinfo, freq:, q: nil)
6
+ super()
7
+ @samplerate = soundinfo.samplerate.to_f
8
+ @freq = freq
9
+ @q = q || 1.0 / Math.sqrt(2)
10
+
11
+ filter_coef
12
+ end
13
+
14
+ def filter_coef
15
+ omega = 2.0 * Math::PI * @freq / @samplerate
16
+ alpha = Math.sin(omega) / (2.0 * @q)
17
+
18
+ a0 = 1.0 + alpha
19
+ a1 = -2.0 * Math.cos(omega)
20
+ a2 = 1.0 - alpha
21
+ b0 = (1.0 + Math.cos(omega)) / 2.0
22
+ b1 = -(1.0 + Math.cos(omega))
23
+ b2 = (1.0 + Math.cos(omega)) / 2.0
24
+
25
+ @filter_coef = FilterCoef.new(a0, a1, a2, b0, b1, b2)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module AudioStream
2
+ module Fx
3
+ class HighShelfFilter < BiquadFilter
4
+
5
+ def initialize(soundinfo, freq:, q: nil, gain: 1.0)
6
+ super()
7
+ @samplerate = soundinfo.samplerate.to_f
8
+ @freq = freq
9
+ @q = q || 1.0 / Math.sqrt(2)
10
+ @gain = gain
11
+
12
+ filter_coef
13
+ end
14
+
15
+ def filter_coef
16
+ omega = 2.0 * Math::PI * @freq / @samplerate
17
+ alpha = Math.sin(omega) / (2.0 * @q)
18
+ a = 10.0 ** (@gain / 40.0)
19
+ beta = Math.sqrt(a) / @q
20
+
21
+ a0 = (a+1) - (a-1) * Math.cos(omega) + beta * Math.sin(omega)
22
+ a1 = 2.0 * ((a-1) - (a+1) * Math.cos(omega))
23
+ a2 = (a+1) - (a-1) * Math.cos(omega) - beta * Math.sin(omega)
24
+ b0 = a * ((a+1) + (a-1) * Math.cos(omega) + beta * Math.sin(omega))
25
+ b1 = -2.0 * a * ((a-1) + (a+1) * Math.cos(omega))
26
+ b2 = a * ((a+1) + (a-1) * Math.cos(omega) - beta * Math.sin(omega))
27
+
28
+ @filter_coef = FilterCoef.new(a0, a1, a2, b0, b1, b2)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ module AudioStream
2
+ module Fx
3
+ class LowPassFilter < BiquadFilter
4
+
5
+ def initialize(soundinfo, freq:, q: nil)
6
+ super()
7
+ @samplerate = soundinfo.samplerate.to_f
8
+ @freq = freq
9
+ @q = q || 1.0 / Math.sqrt(2)
10
+
11
+ filter_coef
12
+ end
13
+
14
+ def filter_coef
15
+ omega = 2.0 * Math::PI * @freq / @samplerate
16
+ alpha = Math.sin(omega) / (2.0 * @q)
17
+
18
+ a0 = 1.0 + alpha
19
+ a1 = -2.0 * Math.cos(omega)
20
+ a2 = 1.0 - alpha
21
+ b0 = (1.0 - Math.cos(omega)) / 2.0
22
+ b1 = 1.0 - Math.cos(omega)
23
+ b2 = (1.0 - Math.cos(omega)) / 2.0
24
+
25
+ @filter_coef = FilterCoef.new(a0, a1, a2, b0, b1, b2)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module AudioStream
2
+ module Fx
3
+ class LowShelfFilter < BiquadFilter
4
+
5
+ def initialize(soundinfo, freq:, q: nil, gain: 1.0)
6
+ super()
7
+ @samplerate = soundinfo.samplerate.to_f
8
+ @freq = freq
9
+ @q = q || 1.0 / Math.sqrt(2)
10
+ @gain = gain
11
+
12
+ filter_coef
13
+ end
14
+
15
+ def filter_coef
16
+ omega = 2.0 * Math::PI * @freq / @samplerate
17
+ alpha = Math.sin(omega) / (2.0 * @q)
18
+ a = 10.0 ** (@gain / 40.0)
19
+ beta = Math.sqrt(a) / @q
20
+
21
+ a0 = (a+1) + (a-1) * Math.cos(omega) + beta * Math.sin(omega)
22
+ a1 = -2.0 * ((a-1) + (a+1) * Math.cos(omega))
23
+ a2 = (a+1) + (a-1) * Math.cos(omega) - beta * Math.sin(omega)
24
+ b0 = a * ((a+1) - (a-1) * Math.cos(omega) + beta * Math.sin(omega))
25
+ b1 = 2.0 * a * ((a-1) - (a+1) * Math.cos(omega))
26
+ b2 = a * ((a+1) - (a-1) * Math.cos(omega) - beta * Math.sin(omega))
27
+
28
+ @filter_coef = FilterCoef.new(a0, a1, a2, b0, b1, b2)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module AudioStream
2
+ module Fx
3
+ class MonoToStereo
4
+ def process(input)
5
+ case input.channels
6
+ when 1
7
+ output = Buffer.float(input.size, 2)
8
+ input.each_with_index {|f, i|
9
+ output[i] = [f, f]
10
+ }
11
+ output
12
+ when 2
13
+ input.clone
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ module AudioStream
2
+ module Fx
3
+ class NoiseGate
4
+ include BangProcess
5
+
6
+ def initialize(threshold: 0.01)
7
+ @threshold = threshold
8
+ @window = HanningWindow.new
9
+ end
10
+
11
+ def process!(input)
12
+ window_size = input.size
13
+ channels = input.channels
14
+
15
+ # fft
16
+ @window.process!(input)
17
+ na = NArray.float(channels, window_size)
18
+ na[0...na.size] = input.to_a.flatten
19
+ fft = FFTW3.fft(na, FFTW3::FORWARD) / na.length
20
+
21
+ fft.size.times {|i|
22
+ if fft[i].abs < @threshold
23
+ fft[i] = 0i
24
+ end
25
+ }
26
+
27
+ wet_na = FFTW3.fft(fft, FFTW3::BACKWARD)
28
+
29
+ case channels
30
+ when 1
31
+ window_size.times {|i|
32
+ input[i] = wet_na[i].real
33
+ }
34
+ when 2
35
+ window_size.times {|i|
36
+ wet1 = wet_na[i*2].real
37
+ wet2 = wet_na[(i*2)+1].real
38
+
39
+ input[i] = [wet1, wet2]
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ NArray.include Enumerable
47
+ end