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.
- checksums.yaml +7 -0
- data/LICENSE +2 -2
- data/README.md +48 -0
- data/lib/alsa-rawmidi.rb +20 -12
- data/lib/alsa-rawmidi/api.rb +435 -0
- data/lib/alsa-rawmidi/device.rb +85 -56
- data/lib/alsa-rawmidi/input.rb +199 -168
- data/lib/alsa-rawmidi/output.rb +63 -51
- data/lib/alsa-rawmidi/soundcard.rb +45 -62
- data/test/helper.rb +51 -0
- data/test/input_buffer_test.rb +46 -0
- data/test/io_test.rb +80 -0
- metadata +42 -47
- data/README.rdoc +0 -45
- data/lib/alsa-rawmidi/map.rb +0 -242
data/lib/alsa-rawmidi/output.rb
CHANGED
@@ -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
|
-
#
|
7
|
+
|
8
|
+
# Close this output
|
9
|
+
# @return [Boolean]
|
13
10
|
def close
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
if @enabled
|
12
|
+
API::Device.close(@resource)
|
13
|
+
@enabled = false
|
14
|
+
true
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
17
18
|
end
|
18
|
-
|
19
|
-
#
|
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
|
-
|
23
|
-
until (str = data.slice!(0,2))
|
24
|
-
|
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
|
-
#
|
35
|
+
# Output a MIDI message in numeric byte format
|
36
|
+
# @param [*Fixnum] data
|
37
|
+
# @return [Boolean]
|
32
38
|
def puts_bytes(*data)
|
33
|
-
|
34
|
-
|
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
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
#
|
54
|
+
|
55
|
+
# Enable this device; yields
|
56
|
+
# @param [Hash] options
|
57
|
+
# @param [Proc] block
|
58
|
+
# @return [Output]
|
53
59
|
def enable(options = {}, &block)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
29
|
-
Soundcard.new(card_num) if Map.snd_card_load(card_num).eql?(1)
|
30
|
-
end
|
3
|
+
class Soundcard
|
31
4
|
|
32
|
-
|
5
|
+
attr_reader :id, :subdevices
|
33
6
|
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
data/test/helper.rb
ADDED
@@ -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
|
data/test/io_test.rb
ADDED
@@ -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
|