ruby-alsa 0.0.3 → 0.0.4

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.
data/COPYRIGHT ADDED
@@ -0,0 +1,14 @@
1
+ Copyright © 2010 Alban Peignier, Florent Peyraud
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU General public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General public License for more details.
12
+
13
+ You should have received a copy of the GNU General public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Manifest.txt CHANGED
@@ -1,13 +1,37 @@
1
+ .autotest
2
+ COPYING
3
+ COPYRIGHT
1
4
  Gemfile
2
5
  History.txt
3
6
  Manifest.txt
4
7
  README.rdoc
5
8
  Rakefile
6
9
  lib/alsa.rb
10
+ lib/alsa/logger.rb
11
+ lib/alsa/native.rb
12
+ lib/alsa/pcm/capture.rb
13
+ lib/alsa/pcm/hw_parameters.rb
14
+ lib/alsa/pcm/native.rb
15
+ lib/alsa/pcm/playback.rb
16
+ lib/alsa/pcm/stream.rb
17
+ log/test.log
18
+ ruby-alsa.gemspec
7
19
  script/console
8
20
  script/destroy
9
21
  script/generate
22
+ script/play
23
+ script/record
24
+ spec.html
25
+ spec/alsa/logger_spec.rb
26
+ spec/alsa/native_spec.rb
27
+ spec/alsa/pcm/capture_spec.rb
28
+ spec/alsa/pcm/native_spec.rb
29
+ spec/alsa/pcm/playback_spec.rb
30
+ spec/alsa/pcm/stream_spec.rb
10
31
  spec/alsa/pcm_spec.rb
32
+ spec/alsa_spec.rb
11
33
  spec/spec.opts
12
34
  spec/spec_helper.rb
35
+ spec/support/logger.rb
36
+ tasks/buildbot.rake
13
37
  tasks/rspec.rake
data/Rakefile CHANGED
@@ -4,6 +4,12 @@ require 'hoe'
4
4
  require 'fileutils'
5
5
  require 'lib/alsa.rb'
6
6
 
7
+ rubyforge_user_config = File.expand_path("~/.rubyforge/user-config.yml")
8
+ unless File.exists?(rubyforge_user_config)
9
+ mkdir_p File.dirname(rubyforge_user_config)
10
+ touch rubyforge_user_config
11
+ end
12
+
7
13
  Hoe.plugin :newgem
8
14
  # Hoe.plugin :website
9
15
  # Hoe.plugin :cucumberfeatures
data/lib/alsa.rb CHANGED
@@ -2,11 +2,10 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  require 'ffi'
5
- require 'ffi_ext'
6
5
 
7
6
  module ALSA
8
7
 
9
- VERSION = "0.0.3"
8
+ VERSION = "0.0.4"
10
9
 
11
10
  end
12
11
 
@@ -15,7 +14,9 @@ require 'alsa/logger'
15
14
  require 'alsa/native'
16
15
  require 'alsa/pcm/native'
17
16
  require 'alsa/pcm/hw_parameters'
17
+ require 'alsa/pcm/stream'
18
18
  require 'alsa/pcm/capture'
19
+ require 'alsa/pcm/playback'
19
20
 
20
21
  module ALSA
21
22
 
@@ -0,0 +1,12 @@
1
+ module ALSA
2
+ def self.logger
3
+ unless @logger
4
+ @logger = Logger.new(STDERR)
5
+ @logger.level = Logger::WARN
6
+ end
7
+
8
+ @logger
9
+ end
10
+
11
+ def self.logger=(logger); @logger = logger; end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ALSA
2
+ module Native
3
+ extend FFI::Library
4
+ ffi_lib "libasound.so.2"
5
+
6
+ attach_function :strerror, :snd_strerror, [:int], :string
7
+
8
+ def self.error_code?(response)
9
+ response and response < 0
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ module ALSA::PCM
2
+ class Capture < Stream
3
+
4
+ def native_constant
5
+ ALSA::PCM::Native::STREAM_CAPTURE
6
+ end
7
+
8
+ def read
9
+ check_handle!
10
+
11
+ ALSA.logger.debug { "start read with #{hw_params.sample_rate}, #{hw_params.channels} channels"}
12
+
13
+ # use an 500ms buffer
14
+ frame_count = hw_params.sample_rate / 2
15
+ ALSA.logger.debug { "allocate #{hw_params.buffer_size_for(frame_count)} bytes for #{frame_count} frames" }
16
+ FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
17
+ begin
18
+ read_buffer buffer, frame_count
19
+ end while yield buffer, frame_count
20
+ end
21
+ end
22
+
23
+ def read_buffer(buffer, frame_count)
24
+ check_handle!
25
+
26
+ read_count = ALSA::try_to "read from audio interface" do
27
+ response = ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
28
+ if ALSA::Native::error_code?(response)
29
+ ALSA.logger.warn { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
30
+ ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
31
+ else
32
+ response
33
+ end
34
+ end
35
+
36
+ missing_frame_count = frame_count - read_count
37
+ if missing_frame_count > 0
38
+ ALSA.logger.debug { "re-read missing frame count: #{missing_frame_count}"}
39
+ read_buffer_size = hw_params.buffer_size_for(read_count)
40
+ # buffer[read_buffer_size] doesn't return a MemoryPointer
41
+ read_buffer(buffer + read_buffer_size, missing_frame_count)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,124 @@
1
+ module ALSA::PCM
2
+ class HwParameters
3
+
4
+ attr_accessor :handle, :device
5
+
6
+ def initialize(device = nil)
7
+ hw_params_pointer = FFI::MemoryPointer.new :pointer
8
+
9
+ ALSA::PCM::Native::hw_params_malloc hw_params_pointer
10
+ self.handle = hw_params_pointer.read_pointer
11
+
12
+ self.device = device if device
13
+ end
14
+
15
+ def update_attributes(attributes)
16
+ attributes.each_pair { |name, value| send("#{name}=", value) }
17
+ end
18
+
19
+ def default_for_device
20
+ ALSA::try_to "initialize hardware parameter structure" do
21
+ ALSA::PCM::Native::hw_params_any device.handle, self.handle
22
+ end
23
+ self
24
+ end
25
+
26
+ def current_for_device
27
+ ALSA::try_to "retrieve current hardware parameters" do
28
+ ALSA::PCM::Native::hw_params_current device.handle, self.handle
29
+ end
30
+ self
31
+ end
32
+
33
+ def access=(access)
34
+ ALSA::try_to "set access type" do
35
+ ALSA::PCM::Native::hw_params_set_access self.device.handle, self.handle, ALSA::PCM::Native::Access.const_get(access.to_s.upcase)
36
+ end
37
+ end
38
+
39
+ def channels=(channels)
40
+ ALSA::try_to "set channel count : #{channels}" do
41
+ ALSA::PCM::Native::hw_params_set_channels self.device.handle, self.handle, channels
42
+ end
43
+ end
44
+
45
+ def sample_rate=(sample_rate)
46
+ ALSA::try_to "set sample rate" do
47
+ rate = FFI::MemoryPointer.new(:int)
48
+ rate.write_int(sample_rate)
49
+
50
+ dir = FFI::MemoryPointer.new(:int)
51
+ dir.write_int(0)
52
+
53
+ error_code = ALSA::PCM::Native::hw_params_set_rate_near self.device.handle, self.handle, rate, dir
54
+
55
+ rate.free
56
+ dir.free
57
+
58
+ error_code
59
+ end
60
+ end
61
+
62
+ def sample_rate
63
+ rate = nil
64
+ ALSA::try_to "get sample rate" do
65
+ rate_pointer = FFI::MemoryPointer.new(:int)
66
+ dir_pointer = FFI::MemoryPointer.new(:int)
67
+ dir_pointer.write_int(0)
68
+
69
+ error_code = ALSA::PCM::Native::hw_params_get_rate self.handle, rate_pointer, dir_pointer
70
+
71
+ rate = rate_pointer.read_int
72
+
73
+ rate_pointer.free
74
+ dir_pointer.free
75
+
76
+ error_code
77
+ end
78
+ rate
79
+ end
80
+
81
+ def sample_format=(sample_format)
82
+ ALSA::try_to "set sample format" do
83
+ ALSA::PCM::Native::hw_params_set_format self.device.handle, self.handle, ALSA::PCM::Native::Format.const_get(sample_format.to_s.upcase)
84
+ end
85
+ end
86
+
87
+ def sample_format
88
+ format = nil
89
+ FFI::MemoryPointer.new(:int) do |format_pointer|
90
+ ALSA::try_to "get sample format" do
91
+ ALSA::PCM::Native::hw_params_get_format self.handle, format_pointer
92
+ end
93
+ format = format_pointer.read_int
94
+ end
95
+ format
96
+ end
97
+
98
+ def channels
99
+ channels = nil
100
+ FFI::MemoryPointer.new(:int) do |channels_pointer|
101
+ ALSA::try_to "get channels" do
102
+ ALSA::PCM::Native::hw_params_get_channels self.handle, channels_pointer
103
+ end
104
+ channels = channels_pointer.read_int
105
+ end
106
+ channels
107
+ end
108
+
109
+ def buffer_size_for(frame_count)
110
+ ALSA::PCM::Native::format_size(self.sample_format, frame_count) * self.channels
111
+ end
112
+
113
+ def frame_count_for(byte_count)
114
+ ALSA::PCM::Native::bytes_to_frames(self.device.handle, byte_count)
115
+ end
116
+
117
+ def free
118
+ ALSA::try_to "unallocate hw_params" do
119
+ ALSA::PCM::Native::hw_params_free self.handle
120
+ end
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,52 @@
1
+ module ALSA::PCM
2
+
3
+ module Native
4
+ extend FFI::Library
5
+ ffi_lib "libasound.so.2"
6
+
7
+ STREAM_PLAYBACK = 0
8
+ STREAM_CAPTURE = 1
9
+
10
+ BLOCK = 0
11
+ attach_function :open, :snd_pcm_open, [:pointer, :string, :int, :int], :int
12
+ attach_function :prepare, :snd_pcm_prepare, [ :pointer ], :int
13
+ attach_function :close, :snd_pcm_close, [:pointer], :int
14
+
15
+ attach_function :readi, :snd_pcm_readi, [ :pointer, :pointer, :ulong ], :long
16
+ attach_function :writei, :snd_pcm_writei, [ :pointer, :pointer, :ulong ], :long
17
+
18
+ attach_function :pcm_recover, :snd_pcm_recover, [ :pointer, :int, :int ], :int
19
+
20
+ attach_function :hw_params_malloc, :snd_pcm_hw_params_malloc, [:pointer], :int
21
+ attach_function :hw_params_free, :snd_pcm_hw_params_free, [:pointer], :int
22
+
23
+ attach_function :hw_params, :snd_pcm_hw_params, [ :pointer, :pointer ], :int
24
+ attach_function :hw_params_any, :snd_pcm_hw_params_any, [:pointer, :pointer], :int
25
+ attach_function :hw_params_current, :snd_pcm_hw_params_current, [ :pointer, :pointer ], :int
26
+
27
+ module Access
28
+ MMAP_INTERLEAVED = 0
29
+ MMAP_NONINTERLEAVED = 1
30
+ MMAP_COMPLEX = 2
31
+ RW_INTERLEAVED = 3
32
+ RW_NONINTERLEAVED = 4
33
+ end
34
+
35
+ attach_function :hw_params_set_access, :snd_pcm_hw_params_set_access, [ :pointer, :pointer, :int ], :int
36
+
37
+ module Format
38
+ S16_LE = 2
39
+ end
40
+
41
+ attach_function :hw_params_set_format, :snd_pcm_hw_params_set_format, [ :pointer, :pointer, :int ], :int
42
+ attach_function :hw_params_get_format, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
43
+ attach_function :hw_params_get_rate, :snd_pcm_hw_params_get_rate, [ :pointer, :pointer, :pointer ], :int
44
+ attach_function :hw_params_set_rate_near, :snd_pcm_hw_params_set_rate_near, [ :pointer, :pointer, :pointer, :pointer ], :int
45
+ attach_function :hw_params_set_channels, :snd_pcm_hw_params_set_channels, [ :pointer, :pointer, :uint ], :int
46
+ attach_function :hw_params_get_channels, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
47
+ attach_function :hw_params_set_periods, :snd_pcm_hw_params_set_periods, [ :pointer, :pointer, :uint, :int ], :int
48
+
49
+ attach_function :format_size, :snd_pcm_format_size, [ :int, :uint ], :int
50
+ attach_function :bytes_to_frames, :snd_pcm_bytes_to_frames, [ :pointer, :int ], :int
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ module ALSA::PCM
2
+ class Playback < Stream
3
+
4
+ def native_constant
5
+ ALSA::PCM::Native::STREAM_PLAYBACK
6
+ end
7
+
8
+ def write_buffer(buffer, frame_count)
9
+ check_handle!
10
+
11
+ write_count = ALSA::try_to "write in audio interface" do
12
+ response = ALSA::PCM::Native::writei(self.handle, buffer, frame_count)
13
+ if ALSA::Native::error_code?(response)
14
+ ALSA.logger.warn { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
15
+ ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
16
+ else
17
+ response
18
+ end
19
+ end
20
+
21
+ missing_frame_count = frame_count - write_count
22
+ if missing_frame_count > 0
23
+ ALSA.logger.debug { "missing wroted frame count: #{missing_frame_count}"}
24
+ end
25
+ end
26
+
27
+ def write
28
+ check_handle!
29
+
30
+ frame_count = hw_params.sample_rate / 2
31
+ FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
32
+ while audio_content = yield(buffer.size)
33
+ buffer.write_string audio_content
34
+
35
+ read_frame_count =
36
+ if audio_content.size == buffer.size
37
+ frame_count
38
+ else
39
+ hw_params.frame_count_for(audio_content.size)
40
+ end
41
+
42
+ write_buffer buffer, read_frame_count
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,76 @@
1
+ module ALSA::PCM
2
+ class Stream
3
+
4
+ attr_accessor :handle
5
+
6
+ def self.open(device = "default", hardware_attributes = {}, &block)
7
+ new.open(device, hardware_attributes, &block)
8
+ end
9
+
10
+ def open(device = "default", hardware_attributes = {}, &block)
11
+ handle_pointer = FFI::MemoryPointer.new :pointer
12
+ ALSA::try_to "open audio device #{device}" do
13
+ ALSA::PCM::Native::open handle_pointer, device, native_constant, ALSA::PCM::Native::BLOCK
14
+ end
15
+ self.handle = handle_pointer.read_pointer
16
+
17
+ self.hardware_parameters = hardware_attributes
18
+
19
+ if block_given?
20
+ begin
21
+ yield self
22
+ ensure
23
+ self.close
24
+ end
25
+ end
26
+ end
27
+
28
+ def change_hardware_parameters
29
+ hw_params = ALSA::PCM::HwParameters.new(self).default_for_device
30
+
31
+ begin
32
+ yield hw_params
33
+
34
+ ALSA::try_to "set hw parameters" do
35
+ ALSA::PCM::Native::hw_params self.handle, hw_params.handle
36
+ end
37
+ ensure
38
+ hw_params.free
39
+ end
40
+ end
41
+
42
+ def hardware_parameters
43
+ ALSA::PCM::HwParameters.new(self).current_for_device
44
+ end
45
+ alias_method :hw_params, :hardware_parameters
46
+
47
+ def hardware_parameters=(attributes= {})
48
+ attributes = {
49
+ :access => :rw_interleaved,
50
+ :channels => 2,
51
+ :sample_format => :s16_le,
52
+ :sample_rate => 44100
53
+ }.update(attributes)
54
+
55
+ change_hardware_parameters do |hw_params|
56
+ hw_params.update_attributes(attributes)
57
+ end
58
+ end
59
+
60
+ def opened?
61
+ not self.handle.nil?
62
+ end
63
+
64
+ def check_handle!
65
+ raise "Stream isn't opened" unless opened?
66
+ end
67
+
68
+ def close
69
+ ALSA::try_to "close audio device" do
70
+ ALSA::PCM::Native::close self.handle
71
+ self.handle = nil
72
+ end
73
+ end
74
+
75
+ end
76
+ end