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 +13 -0
- data/README.rdoc +45 -0
- data/lib/alsa-rawmidi.rb +16 -0
- data/lib/alsa-rawmidi/device.rb +79 -0
- data/lib/alsa-rawmidi/input.rb +168 -0
- data/lib/alsa-rawmidi/map.rb +242 -0
- data/lib/alsa-rawmidi/output.rb +84 -0
- data/lib/alsa-rawmidi/soundcard.rb +78 -0
- metadata +74 -0
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
|
data/lib/alsa-rawmidi.rb
ADDED
@@ -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
|
+
|