alsa-rawmidi 0.2.14

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2010-2011 Ari Russo
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = alsa-rawmidi
2
+
3
+ == Summary
4
+
5
+ Perform low level realtime MIDI input and output in Ruby for Linux. Uses the ALSA RawMIDI driver interface API.
6
+
7
+ Note that in the interest of allowing people on other platforms to utilize your code, you should consider using {unimidi}[http://github.com/arirusso/unimidi]. Unimidi is a platform independent wrapper which implements alsa-rawmidi.
8
+
9
+ == Features
10
+
11
+ * Input and output on multiple devices concurrently
12
+ * Agnostically handle different MIDI Message types (including SysEx)
13
+ * Timestamped input events
14
+
15
+ == Requirements
16
+
17
+ * {ffi}[http://github.com/ffi/ffi] (gem install ffi)
18
+ * libasound, libasound-dev packages
19
+
20
+ == Install
21
+
22
+ * gem install alsa-rawmidi
23
+
24
+ == Examples
25
+
26
+ * {input}[http://github.com/arirusso/alsa-rawmidi/blob/master/examples/input.rb]
27
+ * {output}[http://github.com/arirusso/alsa-rawmidi/blob/master/examples/output.rb]
28
+
29
+ == Tests
30
+
31
+ * please see {test/config.rb}[http://github.com/arirusso/alsa-rawmidi/blob/master/test/config.rb] before running tests
32
+
33
+ == Documentation
34
+
35
+ * {rdoc}[http://rdoc.info/gems/alsa-rawmidi]
36
+
37
+ == Author
38
+
39
+ {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
40
+
41
+ == License
42
+
43
+ Apache 2.0, See the file LICENSE
44
+
45
+ Copyright (c) 2010-2011 Ari Russo
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # Set of modules and classes for interacting with the ALSA Driver Interface
5
+ #
6
+ module AlsaRawMIDI
7
+ VERSION = "0.2.14"
8
+ end
9
+
10
+ require 'ffi'
11
+
12
+ require 'alsa-rawmidi/device'
13
+ require 'alsa-rawmidi/input'
14
+ require 'alsa-rawmidi/map'
15
+ require 'alsa-rawmidi/output'
16
+ require 'alsa-rawmidi/soundcard'
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module AlsaRawMIDI
4
+
5
+ #
6
+ # Module containing methods used by both input and output devices when using the
7
+ # ALSA driver interface
8
+ #
9
+ module Device
10
+
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
21
+
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
32
+
33
+ @enabled = false
34
+ end
35
+
36
+ # select the first device of type <em>type</em>
37
+ def self.first(type)
38
+ all_by_type[type].first
39
+ end
40
+
41
+ # select the last device of type <em>type</em>
42
+ def self.last(type)
43
+ all_by_type[type].last
44
+ end
45
+
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
58
+ end
59
+ available_devices[type] += devices
60
+ end
61
+ end
62
+ end
63
+ available_devices
64
+ end
65
+
66
+ # all devices of both types
67
+ def self.all
68
+ all_by_type.values.flatten
69
+ end
70
+
71
+ private
72
+
73
+ def id=(id)
74
+ @id = id
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,168 @@
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
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module AlsaRawMIDI
4
+
5
+ #
6
+ # libasound RawMIDI struct, enum and function bindings
7
+ #
8
+ module Map
9
+
10
+ extend FFI::Library
11
+ ffi_lib 'libasound'
12
+
13
+ Constants = {
14
+ :SND_RAWMIDI_STREAM_OUTPUT => 0,
15
+ :SND_RAWMIDI_STREAM_INPUT => 1,
16
+ :SND_RAWMIDI_APPEND => 0x0001,
17
+ :SND_RAWMIDI_NONBLOCK => 0x0002,
18
+ :SND_RAWMIDI_SYNC => 0x0004
19
+ }
20
+
21
+ typedef :ulong, :SndCtlType
22
+ typedef :ulong, :SndCtl
23
+ typedef :ulong, :SndRawMIDI
24
+
25
+ # snd_ctl
26
+ class SndCtl < FFI::Struct
27
+
28
+ layout :dl_handle, :pointer, # void*
29
+ :name, :pointer, # char*
30
+ :type, :SndCtlType,
31
+ :ops, :pointer, # const snd_ctl_ops_t*
32
+ :private_data, :pointer, # void*
33
+ :nonblock, :ulong,
34
+ :poll_fd, :ulong,
35
+ :async_handlers, :ulong
36
+ end
37
+
38
+ # snd_ctl_card_info
39
+ class SndCtlCardInfo < FFI::Struct
40
+ layout :card, :int, # card number
41
+ :pad, :int, # reserved for future (was type)
42
+ :id, [:uchar, 16], # ID of card (user selectable)
43
+ :driver, [:uchar, 16], # Driver name
44
+ :name, [:uchar, 32], # Short name of soundcard
45
+ :longname, [:uchar, 80], # name + info text about soundcard
46
+ :reserved_, [:uchar, 16], # reserved for future (was ID of mixer)
47
+ :mixername, [:uchar, 80], # visual mixer identification
48
+ :components, [:uchar, 128] # card components / fine identification, delimited with one space (AC97 etc..)
49
+ end
50
+
51
+ # snd_rawmidi_info
52
+ class SndRawMIDIInfo < FFI::Struct
53
+ layout :device, :uint, # RO/WR (control): device number
54
+ :subdevice, :uint, # RO/WR (control): subdevice number
55
+ :stream, :int, # WR: stream
56
+ :card, :int, # R: card number
57
+ :flags, :uint, # SNDRV_RAWMIDI_INFO_XXXX
58
+ :id, [:uchar, 64], # ID (user selectable)
59
+ :name, [:uchar, 80], # name of device
60
+ :subname, [:uchar, 32], # name of active or selected subdevice
61
+ :subdevices_count, :uint,
62
+ :subdevices_avail, :uint,
63
+ :reserved, [:uchar, 64] # reserved for future use
64
+ end
65
+
66
+ # timespec
67
+ class Timespec < FFI::Struct
68
+ layout :tv_sec, :time_t, # Seconds since 00:00:00 GMT
69
+ :tv_nsec, :long # Additional nanoseconds since
70
+ end
71
+
72
+ # snd_rawmidi_status
73
+ class SndRawMIDIStatus < FFI::Struct
74
+ layout :stream, :int,
75
+ :timestamp, Timespec.by_value, # Timestamp
76
+ :avail, :size_t, # available bytes
77
+ :xruns, :size_t, # count of overruns since last status (in bytes)
78
+ :reserved, [:uchar, 64] # reserved for future use
79
+ end
80
+
81
+ # Simple doubly linked list implementation
82
+ class LinkedList < FFI::Struct
83
+ layout :next, :pointer, # *LinkedList
84
+ :prev, :pointer # *LinkedList
85
+ end
86
+
87
+ # snd_rawmidi
88
+ class SndRawMIDI < FFI::Struct
89
+ layout :card, :pointer, # *snd_card
90
+ :list, LinkedList.by_value,
91
+ :device, :uint, # device number
92
+ :info_flags, :uint, # SNDRV_RAWMIDI_INFO_XXXX
93
+ :id, [:char, 64],
94
+ :name, [:char, 80]
95
+ end
96
+
97
+ # spinlock_t
98
+ class Spinlock < FFI::Struct
99
+ layout :lock, :uint
100
+ end
101
+
102
+ # wait_queue_head_t
103
+ class WaitQueueHead < FFI::Struct
104
+ layout :lock, Spinlock.by_value,
105
+ :task_list, LinkedList.by_value
106
+ end
107
+
108
+ class AtomicT < FFI::Struct
109
+ layout :counter, :int # volatile int counter
110
+ end
111
+
112
+ class Tasklet < FFI::Struct
113
+ layout :next, :pointer, # pointer to the next tasklet in the list / void (*func) (unsigned long)
114
+ :state, :ulong, # state of the tasklet
115
+ :count, AtomicT.by_value, # reference counter
116
+ :func, :pointer, # tasklet handler function / void (*func) (unsigned long)
117
+ :data, :ulong # argument to the tasklet function
118
+ end
119
+
120
+ # snd_rawmidi_runtime
121
+ class SndRawMIDIRuntime < FFI::Struct
122
+ layout :drain, :uint, 1, # drain stage
123
+ :oss, :uint, 1, # OSS compatible mode
124
+ # midi stream buffer
125
+ :buffer, :pointer, # uchar* / buffer for MIDI data
126
+ :buffer_size, :size_t, # size of buffer
127
+ :appl_ptr, :size_t, # application pointer
128
+ :hw_ptr, :size_t, # hardware pointer
129
+ :avail_min, :size_t, # min avail for wakeup
130
+ :avail, :size_t, # max used buffer for wakeup
131
+ :xruns, :size_t, # over/underruns counter
132
+ # misc
133
+ :lock, Spinlock.by_value,
134
+ :sleep, WaitQueueHead.by_value,
135
+ # event handler (new bytes, input only)
136
+ :substream, :pointer, # void (*event)(struct snd_rawmidi_substream *substream);
137
+ # defers calls to event [input] or ops->trigger [output]
138
+ :tasklet, Tasklet.by_value,
139
+ :private_data, :pointer, # void*
140
+ :private_free, :pointer # void (*private_free)(struct snd_rawmidi_substream *substream)
141
+ end
142
+
143
+ # snd_rawmidi_params
144
+ class SndRawMIDIParams < FFI::Struct
145
+ layout :stream, :int,
146
+ :buffer_size, :size_t, # queue size in bytes
147
+ :avail_min, :size_t, # minimum avail bytes for wakeup
148
+ :no_active_sensing, :uint, 1, # do not send active sensing byte in close()
149
+ :reserved, [:uchar, 16] # reserved for future use
150
+ end
151
+
152
+ #
153
+ # snd_card
154
+ #
155
+
156
+ # Try to load the driver for a card.
157
+ attach_function :snd_card_load, [:int], :int # (int card)
158
+ # Try to determine the next card.
159
+ attach_function :snd_card_next, [:pointer], :int # (int* card)
160
+ # Convert card string to an integer value.
161
+ attach_function :snd_card_get_index, [:pointer], :int # (const char* name)
162
+ # Obtain the card name.
163
+ attach_function :snd_card_get_name, [:int, :pointer], :int # (int card, char **name)
164
+ # Obtain the card long name.
165
+ attach_function :snd_card_get_longname, [:int, :pointer], :int # (int card, char **name)
166
+
167
+ #
168
+ # snd_ctl
169
+ #
170
+
171
+ # Opens a CTL.
172
+ attach_function :snd_ctl_open, [:pointer, :pointer, :int], :int # (snd_ctl_t **ctl, const char *name, int mode)
173
+ # Opens a CTL using local configuration.
174
+ attach_function :snd_ctl_open_lconf, [:pointer, :pointer, :int, :pointer], :int # (snd_ctl_t **ctl, const char *name, int mode, snd_config_t *lconf)
175
+ # close CTL handle
176
+ attach_function :snd_ctl_close, [:pointer], :int #(snd_ctl_t *ctl)
177
+ # set nonblock mode
178
+ attach_function :snd_ctl_nonblock, [:pointer, :int], :int # (snd_ctl_t *ctl, int nonblock)
179
+ # Get card related information.
180
+ attach_function :snd_ctl_card_info, [:pointer, :pointer], :int # (snd_ctl_t *ctl, snd_ctl_card_info_t *info)
181
+ # Get card name from a CTL card info.
182
+ attach_function :snd_ctl_card_info_get_name, [:pointer], :string # (const snd_ctl_card_info_t *obj) / const char*
183
+ # Get info about a RawMidi device.
184
+ attach_function :snd_ctl_rawmidi_info, [:SndCtl, :pointer], :int # (snd_ctl_t *ctl, snd_rawmidi_info_t *info)
185
+ # Get next RawMidi device number.
186
+ attach_function :snd_ctl_rawmidi_next_device, [:SndCtl, :pointer], :int # (snd_ctl_t *ctl, int *device)
187
+
188
+ #
189
+ # snd_rawmidi
190
+ #
191
+
192
+ # close RawMidi handle
193
+ attach_function :snd_rawmidi_close, [:SndRawMIDI], :int # (snd_rawmidi_t *rmidi)
194
+ # drain all bytes in the rawmidi I/O ring buffer
195
+ attach_function :snd_rawmidi_drain, [:SndRawMIDI], :int # (snd_rawmidi_t *rmidi)
196
+ # drop all bytes in the rawmidi I/O ring buffer immediately
197
+ attach_function :snd_rawmidi_drop, [:SndRawMIDI], :int # int ( snd_rawmidi_t * rawmidi)
198
+ # set nonblock mode
199
+ attach_function :snd_rawmidi_nonblock, [:SndRawMIDI, :int], :int # (snd_rawmidi_t *rmidi, int nonblock)
200
+ # Opens a new connection to the RawMidi interface.
201
+ attach_function :snd_rawmidi_open, [:pointer, :pointer, :string, :int], :int # (snd_rawmidi_t **in_rmidi, snd_rawmidi_t **out_rmidi, const char *name, int mode)
202
+ # Opens a new connection to the RawMidi interface using local configuration.
203
+ attach_function :snd_rawmidi_open_lconf, [:pointer, :pointer, :string, :int, :pointer], :int #(snd_rawmidi_t **in_rmidi, snd_rawmidi_t **out_rmidi, const char *name, int mode, snd_config_t *lconf)
204
+ # read MIDI bytes from MIDI stream
205
+ attach_function :snd_rawmidi_read, [:SndRawMIDI, :pointer, :size_t], :ssize_t # (snd_rawmidi_t *rmidi, void *buffer, size_t size)
206
+ # write MIDI bytes to MIDI stream
207
+ attach_function :snd_rawmidi_write, [:SndRawMIDI, :ulong, :size_t], :ssize_t # (snd_rawmidi_t *rmidi, const void *buffer, size_t size)
208
+
209
+ #
210
+ # snd_rawmidi_info
211
+ #
212
+
213
+ enum :snd_rawmidi_stream, [
214
+ 'SND_RAWMIDI_STREAM_OUTPUT', 0,
215
+ 'SND_RAWMIDI_STREAM_INPUT', 1,
216
+ 'SND_RAWMIDI_STREAM_LAST', 1
217
+ ]
218
+
219
+ # get information about RawMidi handle
220
+ attach_function :snd_rawmidi_info, [:pointer, :pointer], :int # (snd_rawmidi_t *rmidi, snd_rawmidi_info_t *info)
221
+ # get rawmidi count of subdevices
222
+ attach_function :snd_rawmidi_info_get_subdevices_count, [:pointer], :uint # (const snd_rawmidi_info_t *obj)
223
+ # set rawmidi device number
224
+ attach_function :snd_rawmidi_info_set_device, [:pointer, :uint], :void # (snd_rawmidi_info_t *obj, unsigned int val)
225
+ # set rawmidi subdevice number
226
+ attach_function :snd_rawmidi_info_set_subdevice, [:pointer, :uint], :void # (snd_rawmidi_info_t *obj, unsigned int val)
227
+ # set rawmidi stream identifier
228
+ attach_function :snd_rawmidi_info_set_stream, [:pointer, :snd_rawmidi_stream], :void # (snd_rawmidi_info_t *obj, snd_rawmidi_stream_t val)
229
+ # get size of the snd_rawmidi_info_t structure in bytes
230
+ attach_function :snd_rawmidi_info_sizeof, [], :size_t # (void)
231
+
232
+ #
233
+ # misc
234
+ #
235
+
236
+ # Convert an error code to a string
237
+ attach_function :snd_strerror, [:int], :string # (int errnum) / const char*
238
+ # Frees the global configuration tree in snd_config.
239
+ attach_function :snd_config_update_free_global, [], :int # (void)
240
+
241
+ end
242
+ end
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module AlsaRawMIDI
4
+
5
+ #
6
+ # Output device class
7
+ #
8
+ class Output
9
+
10
+ include Device
11
+
12
+ # close this output
13
+ def close
14
+ Map.snd_rawmidi_drain(@handle)
15
+ Map.snd_rawmidi_close(@handle)
16
+ @enabled = false
17
+ end
18
+
19
+ # sends a MIDI message comprised of a String of hex digits
20
+ def puts_s(data)
21
+ data = data.dup
22
+ output = []
23
+ until (str = data.slice!(0,2)).eql?("")
24
+ output << str.hex
25
+ end
26
+ puts_bytes(*output)
27
+ end
28
+ alias_method :puts_bytestr, :puts_s
29
+ alias_method :puts_hex, :puts_s
30
+
31
+ # sends a MIDI messages comprised of Numeric bytes
32
+ 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
+
40
+ 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)
48
+ end
49
+ end
50
+ alias_method :write, :puts
51
+
52
+ # enable this device; also takes a block
53
+ 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
66
+ end
67
+ end
68
+ alias_method :open, :enable
69
+ alias_method :start, :enable
70
+
71
+ def self.first
72
+ Device.first(:output)
73
+ end
74
+
75
+ def self.last
76
+ Device.last(:output)
77
+ end
78
+
79
+ def self.all
80
+ Device.all_by_type[:output]
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 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
+
28
+ def self.find(card_num)
29
+ Soundcard.new(card_num) if Map.snd_card_load(card_num).eql?(1)
30
+ end
31
+
32
+ private
33
+
34
+ def unpack(string)
35
+ arr = string.delete_if { |n| n.zero? }
36
+ arr.pack("C#{arr.length}")
37
+ end
38
+
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}"
42
+ end
43
+
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
61
+
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
65
+ end
66
+
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
72
+ end
73
+ @subdevices[type] += available
74
+ end
75
+
76
+ end
77
+
78
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alsa-rawmidi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.14
6
+ platform: ruby
7
+ authors:
8
+ - Ari Russo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-16 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: ffi
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "1.0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ description: Realtime MIDI input and output with Ruby for Linux.
28
+ email:
29
+ - ari.russo@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - lib/alsa-rawmidi.rb
38
+ - lib/alsa-rawmidi/map.rb
39
+ - lib/alsa-rawmidi/device.rb
40
+ - lib/alsa-rawmidi/output.rb
41
+ - lib/alsa-rawmidi/input.rb
42
+ - lib/alsa-rawmidi/soundcard.rb
43
+ - LICENSE
44
+ - README.rdoc
45
+ has_rdoc: true
46
+ homepage: http://github.com/arirusso/alsa-rawmidi
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.6
66
+ requirements: []
67
+
68
+ rubyforge_project: alsa-rawmidi
69
+ rubygems_version: 1.6.2
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Realtime MIDI input and output with Ruby for Linux.
73
+ test_files: []
74
+