alsa-rawmidi 0.2.14 → 0.3.1

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.
@@ -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