alsa-rawmidi 0.2.14

Sign up to get free protection for your applications and to get access to all the features.
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
+