alsa-rawmidi 0.2.14 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,84 +1,96 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  module AlsaRawMIDI
4
-
5
- #
2
+
6
3
  # Output device class
7
- #
8
4
  class Output
9
-
5
+
10
6
  include Device
11
-
12
- # close this output
7
+
8
+ # Close this output
9
+ # @return [Boolean]
13
10
  def close
14
- Map.snd_rawmidi_drain(@handle)
15
- Map.snd_rawmidi_close(@handle)
16
- @enabled = false
11
+ if @enabled
12
+ API::Device.close(@resource)
13
+ @enabled = false
14
+ true
15
+ else
16
+ false
17
+ end
17
18
  end
18
-
19
- # sends a MIDI message comprised of a String of hex digits
19
+
20
+ # Output a MIDI message in hex string format
21
+ # @param [String] data
22
+ # @return [Boolean]
20
23
  def puts_s(data)
21
24
  data = data.dup
22
- output = []
23
- until (str = data.slice!(0,2)).eql?("")
24
- output << str.hex
25
+ output = []
26
+ until (str = data.slice!(0,2)) == ""
27
+ output << str.hex
25
28
  end
26
29
  puts_bytes(*output)
30
+ true
27
31
  end
28
32
  alias_method :puts_bytestr, :puts_s
29
33
  alias_method :puts_hex, :puts_s
30
34
 
31
- # sends a MIDI messages comprised of Numeric bytes
35
+ # Output a MIDI message in numeric byte format
36
+ # @param [*Fixnum] data
37
+ # @return [Boolean]
32
38
  def puts_bytes(*data)
33
-
34
- format = "C" * data.size
35
- bytes = FFI::MemoryPointer.new(data.size).put_bytes(0, data.pack(format))
36
-
37
- Map.snd_rawmidi_write(@handle, bytes.to_i, data.size)
38
- Map.snd_rawmidi_drain(@handle)
39
-
39
+ API::Output.puts(@resource, data)
40
+ true
40
41
  end
41
-
42
- # send a MIDI message of an indeterminant type
43
- def puts(*a)
44
- case a.first
45
- when Array then puts_bytes(*a.first)
46
- when Numeric then puts_bytes(*a)
47
- when String then puts_s(*a)
42
+
43
+ # Output the given MIDI message
44
+ # @param [*Fixnum, *String] args
45
+ # @return [Boolean]
46
+ def puts(*args)
47
+ case args.first
48
+ when Array then args.each { |arg| puts(*arg) }
49
+ when Numeric then puts_bytes(*args)
50
+ when String then puts_bytestr(*args)
48
51
  end
49
52
  end
50
53
  alias_method :write, :puts
51
-
52
- # enable this device; also takes a block
54
+
55
+ # Enable this device; yields
56
+ # @param [Hash] options
57
+ # @param [Proc] block
58
+ # @return [Output]
53
59
  def enable(options = {}, &block)
54
- handle_ptr = FFI::MemoryPointer.new(FFI.type_size(:int))
55
- Map.snd_rawmidi_open(nil, handle_ptr, @system_id, 0)
56
- @handle = handle_ptr.read_int
57
- @enabled = true
58
- unless block.nil?
59
- begin
60
- yield(self)
61
- ensure
62
- close
63
- end
64
- else
65
- self
60
+ unless @enabled
61
+ @resource = API::Output.open(@system_id)
62
+ @enabled = true
63
+ end
64
+ if block_given?
65
+ begin
66
+ yield(self)
67
+ ensure
68
+ close
69
+ end
66
70
  end
71
+ self
67
72
  end
68
73
  alias_method :open, :enable
69
74
  alias_method :start, :enable
70
-
75
+
76
+ # The first available output
77
+ # @return [Output]
71
78
  def self.first
72
- Device.first(:output)
79
+ Device.first(:output)
73
80
  end
74
81
 
82
+ # The last available output
83
+ # @return [Output]
75
84
  def self.last
76
- Device.last(:output)
85
+ Device.last(:output)
77
86
  end
78
-
87
+
88
+ # All outputs
89
+ # @return [Array<Output>]
79
90
  def self.all
80
91
  Device.all_by_type[:output]
81
92
  end
93
+
82
94
  end
83
-
84
- end
95
+
96
+ end
@@ -1,78 +1,61 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  module AlsaRawMIDI
4
-
5
- class Soundcard
6
-
7
- attr_reader :subdevices
8
-
9
- def initialize(card_num)
10
- @subdevices = { :input => [], :output => [] }
11
- name = "hw:#{card_num}"
12
- handle_ptr = FFI::MemoryPointer.new(FFI.type_size(:int))
13
- #snd_ctl = Map::SndCtl.new
14
- #snd_ctl_pointer = FFI::MemoryPointer.new(:pointer).write_pointer(snd_ctl.pointer)
15
- Map.snd_ctl_open(handle_ptr, name, 0)
16
- handle = handle_ptr.read_int
17
- 32.times do |i|
18
- devnum = FFI::MemoryPointer.new(:int).write_int(i)
19
- if (err = Map.snd_ctl_rawmidi_next_device(handle, devnum)) < 0
20
- break # fix this
21
- end
22
- @subdevices.keys.each do |type|
23
- populate_subdevices(type, handle, card_num, i)
24
- end
25
- end
26
- end
27
2
 
28
- def self.find(card_num)
29
- Soundcard.new(card_num) if Map.snd_card_load(card_num).eql?(1)
30
- end
3
+ class Soundcard
31
4
 
32
- private
5
+ attr_reader :id, :subdevices
33
6
 
34
- def unpack(string)
35
- arr = string.delete_if { |n| n.zero? }
36
- arr.pack("C#{arr.length}")
7
+ # @param [Fixnum] id
8
+ def initialize(id)
9
+ @subdevices = {
10
+ :input => [],
11
+ :output => []
12
+ }
13
+ @id = id
14
+ populate_subdevices
37
15
  end
38
16
 
39
- def get_alsa_subdev_id(card_num, device_num, subdev_count, i)
40
- ext = (subdev_count > 1) ? ",#{i}" : ''
41
- "hw:#{card_num},#{device_num}#{ext}"
17
+ # Find a soundcard by its ID
18
+ # @param [Fixnum] id
19
+ # @return [Soundcard]
20
+ def self.find(id)
21
+ @soundcards ||= {}
22
+ if API::Soundcard.exists?(id)
23
+ @soundcards[id] ||= Soundcard.new(id)
24
+ end
42
25
  end
43
26
 
44
- def populate_subdevices(type, ctl_ptr, card_num, device_num)
45
- ctype, dtype = *case type
46
- when :input then [Map::Constants[:SND_RAWMIDI_STREAM_INPUT], Input]
47
- when :output then [Map::Constants[:SND_RAWMIDI_STREAM_OUTPUT], Output]
48
- end
49
- info = Map::SndRawMIDIInfo.new
50
- Map.snd_rawmidi_info_set_device(info.pointer, device_num)
51
- Map.snd_rawmidi_info_set_stream(info.pointer, ctype)
52
- i = 0
53
- subdev_count = 1
54
- available = []
55
- while (i <= subdev_count)
56
- Map.snd_rawmidi_info_set_subdevice(info.pointer, i)
57
- # fix this
58
- if (err = Map.snd_ctl_rawmidi_info(ctl_ptr, info.pointer)) < 0
59
- break
60
- end
27
+ private
61
28
 
62
- if (i < 1)
63
- subdev_count = Map.snd_rawmidi_info_get_subdevices_count(info.pointer)
64
- subdev_count = (subdev_count > 32) ? 0 : subdev_count
29
+ # @return [Hash]
30
+ def populate_subdevices
31
+ device_ids = API::Soundcard.get_device_ids(@id)
32
+ device_ids.each do |device_id|
33
+ @subdevices.keys.each do |direction|
34
+ devices = API::Soundcard.get_subdevices(direction, @id, device_id) do |device_hash|
35
+ new_device(direction, device_hash)
36
+ end
37
+ @subdevices[direction] += devices
65
38
  end
39
+ end
40
+ @subdevices
41
+ end
66
42
 
67
- name = info[:name].to_s
68
- system_id = get_alsa_subdev_id(card_num, device_num, subdev_count, i)
69
- dev = dtype.new(:system_id => system_id, :name => info[:name].to_s, :subname => info[:subname].to_s)
70
- available << dev
71
- i += 1
43
+ # Instantiate a new device object
44
+ # @param [Hash]
45
+ # @return [Input, Output]
46
+ def new_device(direction, device_hash)
47
+ device_class = case direction
48
+ when :input then Input
49
+ when :output then Output
72
50
  end
73
- @subdevices[type] += available
51
+ device_properties = {
52
+ :system_id => device_hash[:id],
53
+ :name => device_hash[:name],
54
+ :subname => device_hash[:subname]
55
+ }
56
+ device_class.new(device_properties)
74
57
  end
75
58
 
76
59
  end
77
60
 
78
- end
61
+ end
@@ -0,0 +1,51 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + "/../lib"
3
+
4
+ require "test/unit"
5
+ require "mocha/test_unit"
6
+ require "shoulda-context"
7
+ require "alsa-rawmidi"
8
+
9
+ module TestHelper
10
+
11
+ extend self
12
+
13
+ def bytestrs_to_ints(arr)
14
+ data = arr.map { |m| m[:data] }.join
15
+ output = []
16
+ until (bytestr = data.slice!(0,2)).eql?("")
17
+ output << bytestr.hex
18
+ end
19
+ output
20
+ end
21
+
22
+ # some MIDI messages
23
+ def numeric_messages
24
+ [
25
+ [0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], # SysEx
26
+ [0x90, 100, 100], # note on
27
+ [0x90, 43, 100], # note on
28
+ [0x90, 76, 100], # note on
29
+ [0x90, 60, 100], # note on
30
+ [0x80, 100, 100] # note off
31
+ ]
32
+ end
33
+
34
+ # some MIDI messages
35
+ def string_messages
36
+ [
37
+ "F04110421240007F0041F7", # SysEx
38
+ "906440", # note on
39
+ "804340" # note off
40
+ ]
41
+ end
42
+
43
+ def input
44
+ AlsaRawMIDI::Input.first
45
+ end
46
+
47
+ def output
48
+ AlsaRawMIDI::Output.first
49
+ end
50
+
51
+ end
@@ -0,0 +1,46 @@
1
+ require "helper"
2
+
3
+ class AlsaRawMIDI::InputBufferTest < Test::Unit::TestCase
4
+
5
+ context "AlsaRawMIDI" do
6
+
7
+ setup do
8
+ sleep(1)
9
+ @input = TestHelper.input.open
10
+ @output = TestHelper.output.open
11
+ @input.buffer.clear
12
+ @pointer = 0
13
+ end
14
+
15
+ context "Source#buffer" do
16
+
17
+ setup do
18
+ @messages = TestHelper.numeric_messages
19
+ @messages_arr = @messages.inject(&:+).flatten
20
+ @received_arr = []
21
+ end
22
+
23
+ teardown do
24
+ @input.close
25
+ @output.close
26
+ end
27
+
28
+ should "have the correct messages in the buffer" do
29
+ bytes = []
30
+ @messages.each do |message|
31
+ p "sending: #{message}"
32
+ @output.puts(message)
33
+ bytes += message
34
+
35
+ sleep(1)
36
+
37
+ buffer = @input.buffer.map { |m| m[:data] }.flatten
38
+ p "received: #{buffer.to_s}"
39
+ assert_equal(bytes, buffer)
40
+ end
41
+ assert_equal(bytes.length, @input.buffer.map { |m| m[:data] }.flatten.length)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,80 @@
1
+ require "helper"
2
+
3
+ class AlsaRawMIDI::IoTest < Test::Unit::TestCase
4
+
5
+ # ** this test assumes that TestOutput is connected to TestInput
6
+ context "AlsaRawMIDI" do
7
+
8
+ setup do
9
+ sleep(1)
10
+ @input = TestHelper.input.open
11
+ @output = TestHelper.output.open
12
+ @input.buffer.clear
13
+ @pointer = 0
14
+ end
15
+
16
+ context "full IO" do
17
+
18
+ context "using Arrays" do
19
+
20
+ setup do
21
+ @messages = TestHelper.numeric_messages
22
+ @messages_arr = @messages.inject(&:+).flatten
23
+ @received_arr = []
24
+ end
25
+
26
+ teardown do
27
+ @input.close
28
+ @output.close
29
+ end
30
+
31
+ should "do IO" do
32
+ @messages.each do |message|
33
+
34
+ p "sending: #{message}"
35
+
36
+ @output.puts(message)
37
+ sleep(1)
38
+ received = @input.gets.map { |m| m[:data] }.flatten
39
+
40
+ p "received: #{received}"
41
+
42
+ assert_equal(@messages_arr.slice(@pointer, received.length), received)
43
+ @pointer += received.length
44
+ @received_arr += received
45
+ end
46
+ assert_equal(@messages_arr.length, @received_arr.length)
47
+ end
48
+ end
49
+
50
+ context "using byte Strings" do
51
+
52
+ setup do
53
+ @messages = TestHelper.string_messages
54
+ @messages_str = @messages.join
55
+ @received_str = ""
56
+ end
57
+
58
+ should "do IO" do
59
+ @messages.each do |message|
60
+
61
+ p "sending: #{message}"
62
+
63
+ @output.puts(message)
64
+ sleep(1)
65
+ received = @input.gets_bytestr.map { |m| m[:data] }.flatten.join
66
+ p "received: #{received}"
67
+
68
+ assert_equal(@messages_str.slice(@pointer, received.length), received)
69
+ @pointer += received.length
70
+ @received_str += received
71
+ end
72
+ assert_equal(@messages_str, @received_str)
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ end