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
data/examples/tuner.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'audio_stream/core_ext'
2
+
3
+ include AudioStream
4
+ include AudioStream::Fx
5
+
6
+
7
+ soundinfo = SoundInfo.new(
8
+ channels: 2,
9
+ samplerate: 44100,
10
+ window_size: 1024,
11
+ format: RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
12
+ )
13
+
14
+ # Track
15
+
16
+ #track1 = AudioInput.sin(454.0, 100, 2048, soundinfo: soundinfo)
17
+ track1 = AudioInput.device(1024*2*2*2)
18
+
19
+
20
+ # Audio FX
21
+
22
+ tuner = Tuner.new(soundinfo)
23
+
24
+
25
+ # Bus
26
+
27
+ stereo_out = AudioOutput.device(soundinfo: soundinfo)
28
+
29
+
30
+ # Mixer
31
+
32
+ track1
33
+ .stream
34
+ .fx(tuner)
35
+ .subscribe_on_next {|tone|
36
+ width = 30
37
+ if tone.diff
38
+ diff = (tone.diff * width / 100).round
39
+ bar = ""
40
+ if diff.negative?
41
+ diff = diff.abs
42
+ if width/2<diff
43
+ diff = width/2
44
+ end
45
+ bar += "_" * (width/2 - diff)
46
+ bar += "#" * diff
47
+ bar += "@"
48
+ bar += "_" * (width/2)
49
+ else
50
+ if width/2<diff
51
+ diff = width/2
52
+ end
53
+ bar += "_" * (width/2)
54
+ bar += "@"
55
+ bar += "#" * diff
56
+ bar += "_" * (width/2 - diff)
57
+ end
58
+ print "\r% 4.3fhz % 5s% 2d %s % 2.3f" % [tone.freq, tone.note, tone.octave, bar, tone.diff]
59
+
60
+ else
61
+ print "\r ---.---hz NOINPUT"
62
+ end
63
+ }
64
+
65
+ #track1
66
+ # .stream
67
+ # .send_to(stereo_out)
68
+
69
+
70
+ # start
71
+
72
+ conductor = Conductor.new(
73
+ input: track1,
74
+ output: nil
75
+ )
76
+ conductor.connect
77
+ conductor.join
@@ -0,0 +1,30 @@
1
+ require 'ruby-audio'
2
+ require 'coreaudio'
3
+ require 'numru/fftw3'
4
+ require 'rx'
5
+ require 'rbplotly'
6
+
7
+ require 'audio_stream/version'
8
+ require 'audio_stream/error'
9
+ require 'audio_stream/sound_info'
10
+ require 'audio_stream/buffer'
11
+ require 'audio_stream/ring_buffer'
12
+ require 'audio_stream/sync'
13
+ require 'audio_stream/conductor'
14
+ require 'audio_stream/audio_input'
15
+ require 'audio_stream/audio_input_file'
16
+ require 'audio_stream/audio_input_device'
17
+ require 'audio_stream/audio_input_buffer'
18
+ require 'audio_stream/audio_input_sin'
19
+ require 'audio_stream/audio_input_metronome'
20
+ require 'audio_stream/audio_bus'
21
+ require 'audio_stream/audio_output'
22
+ require 'audio_stream/audio_output_file'
23
+ require 'audio_stream/audio_output_device'
24
+ require 'audio_stream/fx'
25
+ require 'audio_stream/plot'
26
+ require 'audio_stream/utils'
27
+
28
+ module AudioStream
29
+ include NumRu
30
+ end
@@ -0,0 +1,28 @@
1
+ module AudioStream
2
+ class AudioBus < Rx::Subject
3
+ def initialize
4
+ super
5
+ @observables = []
6
+ @zip_observable = nil
7
+ @detach = nil
8
+ end
9
+
10
+ def add(observable, gain:, pan:)
11
+ if gain && gain!=1.0
12
+ observable = observable.map(&Fx::AGain.new(level: gain).method(:process))
13
+ end
14
+
15
+ if pan && pan!=0.0
16
+ observable = observable.map(&Fx::Panning.new(pan: pan).method(:process))
17
+ end
18
+
19
+ @observables << observable
20
+ if @detach
21
+ @detach.unsubscribe
22
+ end
23
+
24
+ @zip_observable = Rx::Observable.zip(*@observables).map{|a| a.inject(:+)}
25
+ @detach = @zip_observable.subscribe(self)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ module AudioStream
2
+
3
+ module AudioInput
4
+ include Enumerable
5
+
6
+ attr_reader :connection
7
+
8
+ def sync
9
+ @sync ||= Sync.new
10
+ end
11
+
12
+ def connect
13
+ @connection = Thread.start {
14
+ stream.connect
15
+ }
16
+ end
17
+
18
+ def stream
19
+ @stream ||= Rx::Observable.create do |observer|
20
+ each {|buf|
21
+ sync.resume_wait
22
+ observer.on_next(buf)
23
+ sync.yield
24
+ }
25
+ sync.resume_wait
26
+ sync.finish
27
+ observer.on_completed
28
+ end.publish
29
+ end
30
+
31
+
32
+ def self.file(fname, soundinfo:)
33
+ AudioInputFile.new(fname, soundinfo: soundinfo)
34
+ end
35
+
36
+ def self.buffer(buf)
37
+ AudioInputBuffer.new(buf)
38
+ end
39
+
40
+ def self.device(soundinfo:)
41
+ AudioInputDevice.default_device(soundinfo: soundinfo)
42
+ end
43
+
44
+ def self.sin(hz, repeat, soundinfo:)
45
+ AudioInputSin.new(hz, repeat, soundinfo: soundinfo)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ module AudioStream
2
+ class AudioInputBuffer
3
+ include AudioInput
4
+
5
+ def initialize(buffers)
6
+ @buffers = [buffers].flatten.compact
7
+ end
8
+
9
+ def name
10
+ "Buffer"
11
+ end
12
+
13
+ def each(&block)
14
+ Enumerator.new do |y|
15
+ @buffers.each {|buf|
16
+ y << buf
17
+ }
18
+ end.each(&block)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ module AudioStream
2
+ class AudioInputDevice
3
+ include AudioInput
4
+
5
+ attr_reader :dev
6
+
7
+ def initialize(dev, soundinfo:)
8
+ @dev = dev
9
+ @soundinfo = soundinfo
10
+ end
11
+
12
+ def name
13
+ @dev.name
14
+ end
15
+
16
+ def each(&block)
17
+ Enumerator.new do |y|
18
+ @inbuf = @dev.input_buffer(@soundinfo.window_size)
19
+ @inbuf.start
20
+
21
+ channels = @dev.input_stream.channels
22
+ buf = Buffer.float(@soundinfo.window_size, channels)
23
+
24
+ loop {
25
+ na = @inbuf.read(@soundinfo.window_size)
26
+ @soundinfo.window_size.times {|i|
27
+ buf[i] = na[(na.dim*i)...(na.dim*(i+1))].to_a.map{|s| s / 0x7FFF.to_f}
28
+ }
29
+
30
+ y << buf.clone
31
+ }
32
+ end.each(&block)
33
+ end
34
+
35
+ def self.default_device(soundinfo:)
36
+ dev = CoreAudio.default_input_device
37
+ new(dev, soundinfo: soundinfo)
38
+ end
39
+
40
+ def self.devices(soundinfo:)
41
+ CoreAudio.devices
42
+ .select{|dev|
43
+ 0<dev.input_stream.channels
44
+ }
45
+ .map {|dev|
46
+ new(dev, soundinfo: soundinfo)
47
+ }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ module AudioStream
2
+ class AudioInputFile
3
+ include AudioInput
4
+
5
+ def initialize(path, soundinfo:)
6
+ @path = path
7
+ @sound = RubyAudio::Sound.open(path)
8
+ @soundinfo = soundinfo
9
+ end
10
+
11
+ def name
12
+ @path
13
+ end
14
+
15
+ def seek(frames, whence=IO::SEEK_SET)
16
+ @sound.seek(frames, whence)
17
+ self
18
+ end
19
+
20
+ def each(&block)
21
+ Enumerator.new do |y|
22
+ buf = Buffer.float(@soundinfo.window_size, @sound.info.channels)
23
+ while @sound.read(buf)!=0
24
+ buf.real_size = buf.size
25
+ y << buf.clone
26
+ end
27
+ end.each(&block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ module AudioStream
2
+ class AudioInputMetronome
3
+ include AudioInput
4
+
5
+ def initialize(bpm, repeat=nil, soundinfo:)
6
+ @bpm = bpm
7
+ @repeat = repeat
8
+ @soundinfo = soundinfo
9
+ end
10
+
11
+ def name
12
+ "Metronome"
13
+ end
14
+
15
+ def each(&block)
16
+ Enumerator.new do |y|
17
+ period = @soundinfo.samplerate / @soundinfo.window_size * 60.0 / @bpm
18
+ count = 0
19
+
20
+ empty_buf = Buffer.float(@soundinfo.window_size, @soundinfo.channels)
21
+ phase = 440.0 / @soundinfo.samplerate * 2 * Math::PI
22
+ offset = 0
23
+
24
+ Range.new(0, @repeat).each {|_|
25
+ if count<1
26
+ buf = Buffer.float(@soundinfo.window_size, @soundinfo.channels)
27
+ case @soundinfo.channels
28
+ when 1
29
+ @soundinfo.window_size.times.each {|i|
30
+ buf[i] = Math.sin(phase * (i + offset))
31
+ }
32
+ when 2
33
+ @soundinfo.window_size.times.each {|i|
34
+ val = Math.sin(phase * (i + offset))
35
+ buf[i] = [val, val]
36
+ }
37
+ end
38
+ offset += @soundinfo.window_size
39
+
40
+ y << buf
41
+ else
42
+ y << empty_buf.clone
43
+ end
44
+ count = (count + 1) % period
45
+ }
46
+ end.each(&block)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module AudioStream
2
+ class AudioInputSin
3
+ include AudioInput
4
+
5
+ def initialize(hz, repeat=nil, soundinfo:)
6
+ @hz = hz
7
+ @repeat = repeat
8
+ @soundinfo = soundinfo
9
+ end
10
+
11
+ def name
12
+ "SinWave"
13
+ end
14
+
15
+ def each(&block)
16
+ Enumerator.new do |y|
17
+ buf = Buffer.float(@soundinfo.window_size, @soundinfo.channels)
18
+
19
+ phase = @hz.to_f / @soundinfo.samplerate * 2 * Math::PI
20
+ offset = 0
21
+
22
+ Range.new(0, @repeat).each {|_|
23
+ case @soundinfo.channels
24
+ when 1
25
+ @soundinfo.window_size.times.each {|i|
26
+ buf[i] = Math.sin(phase * (i + offset))
27
+ }
28
+ when 2
29
+ @soundinfo.window_size.times.each {|i|
30
+ val = Math.sin(phase * (i + offset))
31
+ buf[i] = [val, val]
32
+ }
33
+ end
34
+ offset += @soundinfo.window_size
35
+
36
+ y << buf.clone
37
+ }
38
+ end.each(&block)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module AudioStream
2
+ class AudioOutput < AudioBus
3
+
4
+ def initialize
5
+ super()
6
+ @sync = Sync.new
7
+ end
8
+
9
+ def self.file(fname, soundinfo:)
10
+ AudioOutputFile.new(fname, soundinfo: soundinfo)
11
+ end
12
+
13
+ def self.device(window_size=1024)
14
+ AudioOutputDevice.default_device(window_size)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+ module AudioStream
2
+ class AudioOutputDevice < AudioOutput
3
+ attr_reader :dev
4
+
5
+ def initialize(dev, soundinfo:)
6
+ super()
7
+ @dev = dev
8
+ @channels = dev.output_stream.channels
9
+ @buf = dev.output_buffer(soundinfo.window_size)
10
+ end
11
+
12
+ def connect
13
+ @buf.start
14
+ end
15
+
16
+ def disconnect
17
+ @buf.stop
18
+ end
19
+
20
+ def join
21
+ @sync.yield_wait
22
+ end
23
+
24
+ def on_next(input)
25
+ window_size = input.size
26
+ channels = input.channels
27
+
28
+ case @channels
29
+ when 1
30
+ input = Fx::StereoToMono.new.process(input)
31
+ when 2
32
+ input = Fx::MonoToStereo.new.process(input)
33
+ end
34
+
35
+ sint_a = input.to_a.flatten.map{|f| (f*0x7FFF).round}
36
+ na = NArray.sint(@channels, window_size)
37
+ na[0...sint_a.length] = sint_a
38
+ @buf << na
39
+ end
40
+
41
+ def on_error(error)
42
+ puts error
43
+ puts error.backtrace.join("\n")
44
+ end
45
+
46
+ def on_completed
47
+ @sync.finish
48
+ end
49
+
50
+ def self.default_device(soundinfo:)
51
+ dev = CoreAudio.default_output_device
52
+ new(dev, soundinfo: soundinfo)
53
+ end
54
+
55
+ def self.devices(soundinfo:)
56
+ CoreAudio.devices
57
+ .select{|dev|
58
+ 0<dev.output_stream.channels
59
+ }
60
+ .map {|dev|
61
+ new(dev, soundinfo: soundinfo)
62
+ }
63
+ end
64
+ end
65
+ end