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
|