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,79 +1,108 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  module AlsaRawMIDI
4
2
 
5
- #
6
- # Module containing methods used by both input and output devices when using the
7
- # ALSA driver interface
8
- #
3
+ # Functionality common to both inputs and outputs
9
4
  module Device
10
5
 
11
- # has the device been initialized?
12
- attr_reader :enabled,
13
- # the alsa id of the device
14
- :system_id,
15
- # a unique numerical id for the device
16
- :id,
17
- :name,
18
- :subname,
19
- # :input or :output
20
- :type
6
+ module ClassMethods
21
7
 
22
- alias_method :enabled?, :enabled
23
-
24
- def initialize(options = {}, &block)
25
- @id = options[:id]
26
- @name = options[:name]
27
- @subname = options[:subname]
28
- @system_id = options[:system_id]
29
-
30
- # cache the type name so that inspecting the class isn't necessary each time
31
- @type = self.class.name.split('::').last.downcase.to_sym
8
+ # Select the first device of the given direction
9
+ # @param [Symbol] direction
10
+ # @return [Input, Output]
11
+ def first(direction)
12
+ all_by_type[direction].first
13
+ end
32
14
 
33
- @enabled = false
34
- end
15
+ # Select the last device of the given direction
16
+ # @param [Symbol] direction
17
+ # @return [Input, Output]
18
+ def last(direction)
19
+ all_by_type[direction].last
20
+ end
35
21
 
36
- # select the first device of type <em>type</em>
37
- def self.first(type)
38
- all_by_type[type].first
39
- end
22
+ # A hash of devices, partitioned by direction
23
+ # @return [Hash]
24
+ def all_by_type
25
+ @devices ||= get_devices
26
+ end
40
27
 
41
- # select the last device of type <em>type</em>
42
- def self.last(type)
43
- all_by_type[type].last
44
- end
28
+ # All devices
29
+ # @return [Array<Input, Output>]
30
+ def all
31
+ all_by_type.values.flatten
32
+ end
45
33
 
46
- # a Hash of :input and :output devices
47
- def self.all_by_type
48
- available_devices = { :input => [], :output => [] }
49
- device_count = 0
50
- 32.times do |i|
51
- card = Soundcard.find(i)
52
- unless card.nil?
53
- available_devices.keys.each do |type|
54
- devices = card.subdevices[type]
55
- devices.each do |dev|
56
- dev.send(:id=, device_count)
57
- device_count += 1
34
+ private
35
+
36
+ # Get all available devices from the system
37
+ # @return [Hash]
38
+ def get_devices
39
+ available_devices = {
40
+ :input => [],
41
+ :output => []
42
+ }
43
+ device_count = 0
44
+ 32.times do |i|
45
+ card = Soundcard.find(i)
46
+ unless card.nil?
47
+ available_devices.keys.each do |direction|
48
+ devices = card.subdevices[direction]
49
+ devices.each do |dev|
50
+ dev.send(:id=, device_count)
51
+ device_count += 1
52
+ end
53
+ available_devices[direction] += devices
58
54
  end
59
- available_devices[type] += devices
60
55
  end
61
56
  end
57
+ available_devices
62
58
  end
63
- available_devices
59
+
64
60
  end
65
61
 
66
- # all devices of both types
67
- def self.all
68
- all_by_type.values.flatten
62
+ extend ClassMethods
63
+
64
+ attr_reader :enabled, # has the device been initialized?
65
+ :system_id, # the alsa id of the device
66
+ :id, # a local uuid for the device
67
+ :name,
68
+ :subname,
69
+ :type # :input or :output
70
+
71
+ alias_method :enabled?, :enabled
72
+
73
+ def self.included(base)
74
+ base.send(:extend, ClassMethods)
69
75
  end
70
-
76
+
77
+ # @param [Hash] options
78
+ # @option options [Fixnum] :id
79
+ # @option options [String] :name
80
+ # @option options [String] :subname
81
+ # @option options [String] :system_id
82
+ def initialize(options = {})
83
+ @id = options[:id]
84
+ @name = options[:name]
85
+ @subname = options[:subname]
86
+ @system_id = options[:system_id]
87
+ @type = get_type
88
+ @enabled = false
89
+ end
90
+
71
91
  private
72
-
92
+
93
+ # Assign an id
94
+ # @param [Fixnum] id
95
+ # @return [Fixnum]
73
96
  def id=(id)
74
97
  @id = id
75
98
  end
76
99
 
100
+ # Get the device type
101
+ # @return [Symbol]
102
+ def get_type
103
+ self.class.name.split('::').last.downcase.to_sym
104
+ end
105
+
77
106
  end
78
107
 
79
- end
108
+ end
@@ -1,168 +1,199 @@
1
- #!/usr/bin/env ruby
2
-
3
- module AlsaRawMIDI
4
-
5
- #
6
- # Input device class
7
- #
8
- class Input
9
-
10
- include Device
11
-
12
- BufferSize = 256
13
-
14
- attr_reader :buffer
15
-
16
- #
17
- # returns an array of MIDI event hashes as such:
18
- # [
19
- # { :data => [144, 60, 100], :timestamp => 1024 },
20
- # { :data => [128, 60, 100], :timestamp => 1100 },
21
- # { :data => [144, 40, 120], :timestamp => 1200 }
22
- # ]
23
- #
24
- # the data is an array of Numeric bytes
25
- # the timestamp is the number of millis since this input was enabled
26
- #
27
- def gets
28
- until queued_messages?
29
- end
30
- msgs = queued_messages
31
- @pointer = @buffer.length
32
- msgs
33
- end
34
- alias_method :read, :gets
35
-
36
- # same as gets but returns message data as string of hex digits as such:
37
- # [
38
- # { :data => "904060", :timestamp => 904 },
39
- # { :data => "804060", :timestamp => 1150 },
40
- # { :data => "90447F", :timestamp => 1300 }
41
- # ]
42
- #
43
- def gets_s
44
- msgs = gets
45
- msgs.each { |m| m[:data] = numeric_bytes_to_hex_string(m[:data]) }
46
- msgs
47
- end
48
- alias_method :gets_bytestr, :gets_s
49
- alias_method :gets_hex, :gets_s
50
-
51
- # enable this the input for use; can be passed a block
52
- def enable(options = {}, &block)
53
- handle_ptr = FFI::MemoryPointer.new(FFI.type_size(:int))
54
- Map.snd_rawmidi_open(handle_ptr, nil, @system_id, Map::Constants[:SND_RAWMIDI_NONBLOCK])
55
- @handle = handle_ptr.read_int
56
- @enabled = true
57
- @start_time = Time.now.to_f
58
- initialize_buffer
59
- spawn_listener!
60
- unless block.nil?
61
- begin
62
- yield(self)
63
- ensure
64
- close
65
- end
66
- else
67
- self
68
- end
69
- end
70
- alias_method :open, :enable
71
- alias_method :start, :enable
72
-
73
- # close this input
74
- def close
75
- Thread.kill(@listener)
76
- Map.snd_rawmidi_drain(@handle)
77
- Map.snd_rawmidi_close(@handle)
78
- @enabled = false
79
- end
80
-
81
- def self.first
82
- Device.first(:input)
83
- end
84
-
85
- def self.last
86
- Device.last(:input)
87
- end
88
-
89
- def self.all
90
- Device.all_by_type[:input]
91
- end
92
-
93
- private
94
-
95
- def initialize_buffer
96
- @pointer = 0
97
- @buffer = []
98
- def @buffer.clear
99
- super
100
- @pointer = 0
101
- end
102
- end
103
-
104
- def now
105
- ((Time.now.to_f - @start_time) * 1000)
106
- end
107
-
108
- # give a message its timestamp and package it in a Hash
109
- def get_message_formatted(raw, time)
110
- { :data => hex_string_to_numeric_bytes(raw), :timestamp => time }
111
- end
112
-
113
- def queued_messages
114
- @buffer.slice(@pointer, @buffer.length - @pointer)
115
- end
116
-
117
- def queued_messages?
118
- @pointer < @buffer.length
119
- end
120
-
121
- # launch a background thread that collects messages
122
- # and holds them for the next call to gets*
123
- def spawn_listener!
124
- t = 1.0/1000
125
- @listener = Thread.fork do
126
- loop do
127
- while (raw = poll_system_buffer!).nil?
128
- sleep(t)
129
- end
130
- populate_local_buffer(raw) unless raw.nil?
131
- end
132
- end
133
- end
134
-
135
- # collect messages from the system buffer
136
- def populate_local_buffer(msgs)
137
- @buffer << get_message_formatted(msgs, now) unless msgs.nil?
138
- end
139
-
140
- # Get the next bytes from the buffer
141
- def poll_system_buffer!
142
- b = FFI::MemoryPointer.new(:uint8, Input::BufferSize)
143
- if (err = Map.snd_rawmidi_read(@handle, b, Input::BufferSize)) < 0
144
- raise "Can't read MIDI input: #{Map.snd_strerror(err)}" unless err.eql?(-11)
145
- end
146
- #Map.snd_rawmidi_drain(@handle)
147
- rawstr = b.get_bytes(0,Input::BufferSize)
148
- str = rawstr.unpack("A*").first.unpack("H*").first.upcase
149
- str.nil? || str.eql?("") ? nil : str
150
- end
151
-
152
- # convert byte str to byte array
153
- def hex_string_to_numeric_bytes(str)
154
- str = str.dup
155
- bytes = []
156
- until str.eql?("")
157
- bytes << str.slice!(0, 2).hex
158
- end
159
- bytes
160
- end
161
-
162
- def numeric_bytes_to_hex_string(bytes)
163
- bytes.map { |b| s = b.to_s(16).upcase; b < 16 ? s = "0" + s : s; s }.join
164
- end
165
-
166
- end
167
-
168
- end
1
+ module AlsaRawMIDI
2
+
3
+ # Input device class
4
+ class Input
5
+
6
+ include Device
7
+
8
+ attr_reader :buffer
9
+
10
+ #
11
+ # An array of MIDI event hashes as such:
12
+ # [
13
+ # { :data => [144, 60, 100], :timestamp => 1024 },
14
+ # { :data => [128, 60, 100], :timestamp => 1100 },
15
+ # { :data => [144, 40, 120], :timestamp => 1200 }
16
+ # ]
17
+ #
18
+ # The data is an array of numeric bytes
19
+ # The timestamp is the number of millis since this input was enabled
20
+ #
21
+ # @return [Array<Hash>]
22
+ def gets
23
+ loop until enqueued_messages?
24
+ msgs = enqueued_messages
25
+ @pointer = @buffer.length
26
+ msgs
27
+ end
28
+ alias_method :read, :gets
29
+
30
+ # Like Input#gets but returns message data as string of hex digits as such:
31
+ # [
32
+ # { :data => "904060", :timestamp => 904 },
33
+ # { :data => "804060", :timestamp => 1150 },
34
+ # { :data => "90447F", :timestamp => 1300 }
35
+ # ]
36
+ #
37
+ # @return [Array<Hash>]
38
+ def gets_s
39
+ msgs = gets
40
+ msgs.each { |m| m[:data] = numeric_bytes_to_hex_string(m[:data]) }
41
+ msgs
42
+ end
43
+ alias_method :gets_bytestr, :gets_s
44
+ alias_method :gets_hex, :gets_s
45
+
46
+ # Enable this the input for use; yields
47
+ # @param [Hash] options
48
+ # @param [Proc] block
49
+ # @return [Input] self
50
+ def enable(options = {}, &block)
51
+ unless @enabled
52
+ @start_time = Time.now.to_f
53
+ @resource = API::Input.open(@system_id)
54
+ @enabled = true
55
+ initialize_buffer
56
+ spawn_listener
57
+ end
58
+ if block_given?
59
+ begin
60
+ yield(self)
61
+ ensure
62
+ close
63
+ end
64
+ end
65
+ self
66
+ end
67
+ alias_method :open, :enable
68
+ alias_method :start, :enable
69
+
70
+ # Close this input
71
+ # @return [Boolean]
72
+ def close
73
+ if @enabled
74
+ Thread.kill(@listener)
75
+ API::Device.close(@resource)
76
+ @enabled = false
77
+ true
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ # The first input available
84
+ # @return [Input]
85
+ def self.first
86
+ Device.first(:input)
87
+ end
88
+
89
+ # The last input available
90
+ # @return [Input]
91
+ def self.last
92
+ Device.last(:input)
93
+ end
94
+
95
+ # All available inputs
96
+ # @return [Array<Input>]
97
+ def self.all
98
+ Device.all_by_type[:input]
99
+ end
100
+
101
+ private
102
+
103
+ # Initialize the input buffer
104
+ # @return [Array]
105
+ def initialize_buffer
106
+ @pointer = 0
107
+ @buffer = []
108
+ def @buffer.clear
109
+ super
110
+ @pointer = 0
111
+ end
112
+ @buffer
113
+ end
114
+
115
+ # A timestamp for the current time
116
+ # @return [Float]
117
+ def now
118
+ time = Time.now.to_f - @start_time
119
+ time * 1000
120
+ end
121
+
122
+ # A message paired with timestamp
123
+ # @param [String] hexstring
124
+ # @param [Float] timestamp
125
+ # @return [Hash]
126
+ def get_message_formatted(hexstring, timestamp)
127
+ {
128
+ :data => hex_string_to_numeric_bytes(hexstring),
129
+ :timestamp => timestamp
130
+ }
131
+ end
132
+
133
+ # The messages enqueued in the buffer
134
+ # @return [Array<Hash>]
135
+ def enqueued_messages
136
+ @buffer.slice(@pointer, @buffer.length - @pointer)
137
+ end
138
+
139
+ # Are there messages enqueued?
140
+ # @return [Boolean]
141
+ def enqueued_messages?
142
+ @pointer < @buffer.length
143
+ end
144
+
145
+ # Launch a background thread that collects messages
146
+ # and holds them for the next call to gets*
147
+ # @return [Thread]
148
+ def spawn_listener
149
+ interval = 1.0/1000
150
+ @listener = Thread.new do
151
+ begin
152
+ loop do
153
+ while (messages = API::Input.poll(@resource)).nil?
154
+ sleep(interval)
155
+ end
156
+ populate_buffer(messages) unless messages.nil?
157
+ end
158
+ rescue Exception => exception
159
+ Thread.main.raise(exception)
160
+ end
161
+ end
162
+ @listener.abort_on_exception = true
163
+ @listener
164
+ end
165
+
166
+ # Collect messages from the system buffer
167
+ # @return [Array<String>, nil]
168
+ def populate_buffer(messages)
169
+ @buffer << get_message_formatted(messages, now) unless messages.nil?
170
+ end
171
+
172
+ # Convert a hex string to an array of numeric bytes eg "904040" -> [0x90, 0x40, 0x40]
173
+ # @param [String] string
174
+ # @return [Array<Fixnum>]
175
+ def hex_string_to_numeric_bytes(string)
176
+ string = string.dup
177
+ bytes = []
178
+ until string.length.zero?
179
+ string_byte = string.slice!(0, 2)
180
+ bytes << string_byte.hex
181
+ end
182
+ bytes
183
+ end
184
+
185
+ # Convert an array of numeric bytes to a hex string eg [0x90, 0x40, 0x40] -> "904040"
186
+ # @param [Array<Fixnum>] bytes
187
+ # @return [String]
188
+ def numeric_bytes_to_hex_string(bytes)
189
+ string_bytes = bytes.map do |byte|
190
+ string_byte = byte.to_s(16).upcase
191
+ string_byte = "0#{string_byte}" if byte < 16
192
+ string_byte
193
+ end
194
+ string_bytes.join
195
+ end
196
+
197
+ end
198
+
199
+ end