ffi-coremidi 0.1.7
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 +48 -0
- data/lib/coremidi.rb +24 -0
- data/lib/coremidi/destination.rb +154 -0
- data/lib/coremidi/device.rb +92 -0
- data/lib/coremidi/endpoint.rb +75 -0
- data/lib/coremidi/entity.rb +90 -0
- data/lib/coremidi/map.rb +141 -0
- data/lib/coremidi/source.rb +184 -0
- data/test/helper.rb +58 -0
- data/test/test_input_buffer.rb +45 -0
- data/test/test_io.rb +83 -0
- metadata +68 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 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,48 @@
|
|
1
|
+
= ffi-coremidi
|
2
|
+
|
3
|
+
== Summary
|
4
|
+
|
5
|
+
Realtime MIDI IO with Ruby for OSX
|
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 that implements this library and has a similar API.
|
8
|
+
|
9
|
+
== Features
|
10
|
+
|
11
|
+
* Simplified API
|
12
|
+
* Input and output on multiple devices concurrently
|
13
|
+
* Agnostically handle different MIDI Message types (including SysEx)
|
14
|
+
* Timestamped input events
|
15
|
+
* Internally patch MIDI to other programs using IAC (and {MIDI Patch Bay}[http://notahat.com/midi_patchbay] if you're using OSX Snow Leopard or earlier)
|
16
|
+
== Requirements
|
17
|
+
|
18
|
+
* {ffi}[http://github.com/ffi/ffi] (gem install ffi)
|
19
|
+
|
20
|
+
== Install
|
21
|
+
|
22
|
+
gem install ffi-coremidi
|
23
|
+
|
24
|
+
== Documentation
|
25
|
+
|
26
|
+
* {rdoc}[http://rubydoc.info/github/arirusso/ffi-coremidi]
|
27
|
+
|
28
|
+
== Author
|
29
|
+
|
30
|
+
* {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
|
31
|
+
|
32
|
+
== Credits
|
33
|
+
|
34
|
+
This library began with some coremidi/ffi binding code for MIDI output by
|
35
|
+
|
36
|
+
* Colin Harris -- http://github.com/aberant
|
37
|
+
|
38
|
+
contained in {his fork of MIDIator}[http://github.com/aberant/midiator] and a {blog post}[http://aberant.tumblr.com/post/694878119/sending-midi-sysex-with-core-midi-and-ruby-ffi]
|
39
|
+
|
40
|
+
{MIDIator}[http://github.com/bleything/midiator] is (c)2008 by Ben Bleything and Topher Cyll and released under the MIT license (see LICENSE.midiator and LICENSE.prp)
|
41
|
+
|
42
|
+
Also thank you to {Jeremy Voorhis}[http://github.com/jvoorhis] for some useful debugging
|
43
|
+
|
44
|
+
== License
|
45
|
+
|
46
|
+
Apache 2.0, See the file LICENSE
|
47
|
+
|
48
|
+
Copyright (c) 2011 Ari Russo
|
data/lib/coremidi.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# ffi-coremidi
|
4
|
+
# Realtime MIDI IO with Ruby for OSX
|
5
|
+
# (c)2011 Ari Russo
|
6
|
+
#
|
7
|
+
|
8
|
+
# libs
|
9
|
+
require 'ffi'
|
10
|
+
require 'forwardable'
|
11
|
+
|
12
|
+
# modules
|
13
|
+
require 'coremidi/endpoint'
|
14
|
+
require 'coremidi/map'
|
15
|
+
|
16
|
+
# classes
|
17
|
+
require 'coremidi/entity'
|
18
|
+
require 'coremidi/device'
|
19
|
+
require 'coremidi/source'
|
20
|
+
require 'coremidi/destination'
|
21
|
+
|
22
|
+
module CoreMIDI
|
23
|
+
VERSION = "0.1.7"
|
24
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
#
|
6
|
+
# Output/Destination endpoint class
|
7
|
+
#
|
8
|
+
class Destination
|
9
|
+
|
10
|
+
include Endpoint
|
11
|
+
|
12
|
+
# close this output
|
13
|
+
def close
|
14
|
+
#error = Map.MIDIClientDispose(@handle)
|
15
|
+
#raise "MIDIClientDispose returned error code #{error}" unless error.zero?
|
16
|
+
#error = Map.MIDIPortDispose(@handle)
|
17
|
+
#raise "MIDIPortDispose returned error code #{error}" unless error.zero?
|
18
|
+
#error = Map.MIDIEndpointDispose(@resource)
|
19
|
+
#raise "MIDIEndpointDispose returned error code #{error}" unless error.zero?
|
20
|
+
@enabled = false
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# sends a MIDI message comprised of a String of hex digits
|
25
|
+
def puts_s(data)
|
26
|
+
data = data.dup
|
27
|
+
output = []
|
28
|
+
until (str = data.slice!(0,2)).eql?("")
|
29
|
+
output << str.hex
|
30
|
+
end
|
31
|
+
puts_bytes(*output)
|
32
|
+
end
|
33
|
+
alias_method :puts_bytestr, :puts_s
|
34
|
+
alias_method :puts_hex, :puts_s
|
35
|
+
|
36
|
+
# sends a MIDI messages comprised of Numeric bytes
|
37
|
+
def puts_bytes(*data)
|
38
|
+
|
39
|
+
format = "C" * data.size
|
40
|
+
bytes = (FFI::MemoryPointer.new FFI.type_size(:char) * data.size)
|
41
|
+
bytes.write_string(data.pack(format))
|
42
|
+
|
43
|
+
if data.first.eql?(0xF0) && data.last.eql?(0xF7)
|
44
|
+
puts_sysex(bytes, data.size)
|
45
|
+
else
|
46
|
+
puts_small(bytes, data.size)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# send a MIDI message of an indeterminant type
|
51
|
+
def puts(*a)
|
52
|
+
case a.first
|
53
|
+
when Array then puts_bytes(*a.first)
|
54
|
+
when Numeric then puts_bytes(*a)
|
55
|
+
when String then puts_bytestr(*a)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
alias_method :write, :puts
|
59
|
+
|
60
|
+
# enable this device; also takes a block
|
61
|
+
def enable(options = {}, &block)
|
62
|
+
#connect
|
63
|
+
@enabled = true
|
64
|
+
unless block.nil?
|
65
|
+
begin
|
66
|
+
yield(self)
|
67
|
+
ensure
|
68
|
+
close
|
69
|
+
end
|
70
|
+
else
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
alias_method :open, :enable
|
75
|
+
alias_method :start, :enable
|
76
|
+
|
77
|
+
# shortcut to the first output endpoint available
|
78
|
+
def self.first
|
79
|
+
Endpoint.first(:destination)
|
80
|
+
end
|
81
|
+
|
82
|
+
# shortcut to the last output endpoint available
|
83
|
+
def self.last
|
84
|
+
Endpoint.last(:destination)
|
85
|
+
end
|
86
|
+
|
87
|
+
# all output endpoints
|
88
|
+
def self.all
|
89
|
+
Endpoint.all_by_type[:destination]
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
# base initialization for this endpoint -- done whether or not the endpoint is enabled to
|
95
|
+
# check whether it is truly available for use
|
96
|
+
def connect
|
97
|
+
client_error = enable_client
|
98
|
+
port_error = initialize_port
|
99
|
+
|
100
|
+
@resource = Map.MIDIEntityGetDestination( @entity.resource, @resource_id )
|
101
|
+
!@resource.address.zero? && client_error.zero? && port_error.zero?
|
102
|
+
end
|
103
|
+
alias_method :connect?, :connect
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# output a short MIDI message
|
108
|
+
def puts_small(bytes, size)
|
109
|
+
packet_list = FFI::MemoryPointer.new(256)
|
110
|
+
packet_ptr = Map.MIDIPacketListInit(packet_list)
|
111
|
+
|
112
|
+
if Map::SnowLeopard
|
113
|
+
packet_ptr = Map.MIDIPacketListAdd(packet_list, 256, packet_ptr, 0, size, bytes)
|
114
|
+
else
|
115
|
+
# Pass in two 32-bit 0s for the 64 bit time
|
116
|
+
packet_ptr = Map.MIDIPacketListAdd(packet_list, 256, packet_ptr, 0, 0, size, bytes)
|
117
|
+
end
|
118
|
+
|
119
|
+
Map.MIDISend( @handle, @resource, packet_list )
|
120
|
+
end
|
121
|
+
|
122
|
+
# output a System Exclusive MIDI message
|
123
|
+
def puts_sysex(bytes, size)
|
124
|
+
|
125
|
+
request = Map::MIDISysexSendRequest.new
|
126
|
+
request[:destination] = @resource
|
127
|
+
request[:data] = bytes
|
128
|
+
request[:bytes_to_send] = size
|
129
|
+
request[:complete] = 0
|
130
|
+
request[:completion_proc] = SysexCompletionCallback
|
131
|
+
request[:completion_ref_con] = request
|
132
|
+
|
133
|
+
Map.MIDISendSysex(request)
|
134
|
+
end
|
135
|
+
|
136
|
+
SysexCompletionCallback =
|
137
|
+
FFI::Function.new(:void, [:pointer]) do |sysex_request_ptr|
|
138
|
+
p 'hi'
|
139
|
+
# this isn't working for some reason
|
140
|
+
# as of now, we don't need it though
|
141
|
+
end
|
142
|
+
|
143
|
+
# initialize a coremidi port for this endpoint
|
144
|
+
def initialize_port
|
145
|
+
port_name = Map::CF.CFStringCreateWithCString(nil, "Port #{@resource_id}: #{name}", 0)
|
146
|
+
outport_ptr = FFI::MemoryPointer.new(:pointer)
|
147
|
+
error = Map.MIDIOutputPortCreate(@client, port_name, outport_ptr)
|
148
|
+
@handle = outport_ptr.read_pointer
|
149
|
+
error
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
class Device
|
6
|
+
|
7
|
+
attr_reader :entities,
|
8
|
+
# unique Numeric id
|
9
|
+
:id,
|
10
|
+
# device name from coremidi
|
11
|
+
:name
|
12
|
+
|
13
|
+
def initialize(id, device_pointer, options = {})
|
14
|
+
include_if_offline = options[:include_offline] || false
|
15
|
+
@id = id
|
16
|
+
@resource = device_pointer
|
17
|
+
@entities = []
|
18
|
+
|
19
|
+
prop = Map::CF.CFStringCreateWithCString( nil, "name", 0 )
|
20
|
+
name = Map::CF.CFStringCreateWithCString( nil, id.to_s, 0 )
|
21
|
+
Map::MIDIObjectGetStringProperty(@resource, prop, name)
|
22
|
+
|
23
|
+
@name = Map::CF.CFStringGetCStringPtr(name.read_pointer, 0).read_string
|
24
|
+
populate_entities(:include_offline => include_if_offline)
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns all devices which are cached in an instance variable @devices on the Device class
|
28
|
+
#
|
29
|
+
# options:
|
30
|
+
#
|
31
|
+
# * <b>cache</b>: if false, the device list will never be cached. this would be useful if one needs to alter the device list (e.g. plug in a USB MIDI interface) while their program is running.
|
32
|
+
# * <b>include_offline</b>: if true, devices marked offline by coremidi will be included in the list
|
33
|
+
#
|
34
|
+
def self.all(options = {})
|
35
|
+
use_cache = options[:cache] || true
|
36
|
+
include_offline = options[:include_offline] || false
|
37
|
+
if @devices.nil? || @devices.empty? || !use_cache
|
38
|
+
@devices = []
|
39
|
+
i = 0
|
40
|
+
while !(device_pointer = Map.MIDIGetDevice(i)).null?
|
41
|
+
device = new(i, device_pointer, :include_offline => include_offline)
|
42
|
+
@devices << device
|
43
|
+
i+=1
|
44
|
+
end
|
45
|
+
populate_endpoint_ids
|
46
|
+
end
|
47
|
+
@devices
|
48
|
+
end
|
49
|
+
|
50
|
+
# Refresh the Device cash. You'll need to do this if, for instance, you plug in
|
51
|
+
# a USB MIDI device while the program is running
|
52
|
+
def self.refresh
|
53
|
+
@devices.clear
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns all of the Endpoints for this device
|
57
|
+
def endpoints
|
58
|
+
endpoints = { :source => [], :destination => [] }
|
59
|
+
endpoints.keys.each do |k|
|
60
|
+
endpoints[k] += entities.map { |entity| entity.endpoints[k] }.flatten
|
61
|
+
end
|
62
|
+
endpoints
|
63
|
+
end
|
64
|
+
|
65
|
+
# assign all of this Device's endpoints an consecutive local id
|
66
|
+
def populate_endpoint_ids(starting_id)
|
67
|
+
i = 0
|
68
|
+
entities.each_with_index { |entity| i += entity.populate_endpoint_ids(i + starting_id) }
|
69
|
+
i
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# gives all of the endpoints for all devices a consecutive local id
|
75
|
+
def self.populate_endpoint_ids
|
76
|
+
i = 0
|
77
|
+
all.each { |device| i += device.populate_endpoint_ids(i) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# populates the entities for this device. these are in turn used to gather the endpoints
|
81
|
+
def populate_entities(options = {})
|
82
|
+
include_if_offline = options[:include_offline] || false
|
83
|
+
i = 0
|
84
|
+
while !(entity_pointer = Map.MIDIDeviceGetEntity(@resource, i)).null?
|
85
|
+
@entities << Entity.new(entity_pointer, :include_offline => include_if_offline)
|
86
|
+
i += 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
module Endpoint
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
# has the endpoint been initialized?
|
10
|
+
attr_reader :enabled,
|
11
|
+
:entity,
|
12
|
+
# unique local Numeric id of the endpoint
|
13
|
+
:id,
|
14
|
+
:resource_id,
|
15
|
+
# :input or :output
|
16
|
+
:type
|
17
|
+
|
18
|
+
def_delegators :entity, :manufacturer, :model, :name
|
19
|
+
|
20
|
+
alias_method :enabled?, :enabled
|
21
|
+
|
22
|
+
def initialize(resource_id, entity, options = {}, &block)
|
23
|
+
@entity = entity
|
24
|
+
@resource_id = resource_id
|
25
|
+
@type = self.class.name.split('::').last.downcase.to_sym
|
26
|
+
@enabled = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# is this endpoint online?
|
30
|
+
def online?
|
31
|
+
@entity.online? && connect?
|
32
|
+
end
|
33
|
+
|
34
|
+
# sets the id for this endpoint (the id is immutable once its set)
|
35
|
+
def id=(val)
|
36
|
+
@id ||= val
|
37
|
+
end
|
38
|
+
|
39
|
+
# select the first endpoint of type <em>type</em>
|
40
|
+
def self.first(type)
|
41
|
+
all_by_type[type].first
|
42
|
+
end
|
43
|
+
|
44
|
+
# select the last endpoint of type <em>type</em>
|
45
|
+
def self.last(type)
|
46
|
+
all_by_type[type].last
|
47
|
+
end
|
48
|
+
|
49
|
+
# a Hash of :source and :destination endpoints
|
50
|
+
def self.all_by_type
|
51
|
+
{
|
52
|
+
:source => Device.all.map { |d| d.endpoints[:source] }.flatten,
|
53
|
+
:destination => Device.all.map { |d| d.endpoints[:destination] }.flatten
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# all endpoints of both types
|
58
|
+
def self.all
|
59
|
+
Device.all.map { |d| d.endpoints }.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# enables the coremidi MIDI client that will go with this endpoint
|
65
|
+
def enable_client
|
66
|
+
client_name = Map::CF.CFStringCreateWithCString( nil, "Client #{@resource_id} #{name}", 0 )
|
67
|
+
client_ptr = FFI::MemoryPointer.new(:pointer)
|
68
|
+
error = Map.MIDIClientCreate(client_name, nil, nil, client_ptr)
|
69
|
+
@client = client_ptr.read_pointer
|
70
|
+
error
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
class Entity
|
6
|
+
|
7
|
+
attr_reader :endpoints,
|
8
|
+
:is_online,
|
9
|
+
:manufacturer,
|
10
|
+
:model,
|
11
|
+
:name,
|
12
|
+
:resource
|
13
|
+
|
14
|
+
alias_method :online?, :is_online
|
15
|
+
|
16
|
+
def initialize(resource, options = {}, &block)
|
17
|
+
@endpoints = { :source => [], :destination => [] }
|
18
|
+
@resource = resource
|
19
|
+
@manufacturer = get_property(:manufacturer)
|
20
|
+
@model = get_property(:model)
|
21
|
+
@name = "#{@manufacturer} #{@model}"
|
22
|
+
@is_online = get_property(:offline, :type => :int) == 0
|
23
|
+
@endpoints.keys.each { |type| populate_endpoints(type) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# assign all of this Entity's endpoints an consecutive local id
|
27
|
+
def populate_endpoint_ids(starting_id)
|
28
|
+
i = 0
|
29
|
+
@endpoints.values.flatten.each do |e|
|
30
|
+
e.id = (i + starting_id)
|
31
|
+
i += 1
|
32
|
+
end
|
33
|
+
i
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# populate endpoints of <em>type</em> for this entity
|
39
|
+
def populate_endpoints(type, options = {})
|
40
|
+
include_if_offline = options[:include_offline] || false
|
41
|
+
endpoint_class = case type
|
42
|
+
when :source then Source
|
43
|
+
when :destination then Destination
|
44
|
+
end
|
45
|
+
num_endpoints = number_of_endpoints(type)
|
46
|
+
(0..num_endpoints).each do |i|
|
47
|
+
ep = endpoint_class.new(i, self)
|
48
|
+
@endpoints[type] << ep if ep.online? || include_if_offline
|
49
|
+
end
|
50
|
+
@endpoints[type].size
|
51
|
+
end
|
52
|
+
|
53
|
+
# gets the number of endpoints for this entity
|
54
|
+
def number_of_endpoints(type)
|
55
|
+
case type
|
56
|
+
when :source then Map.MIDIEntityGetNumberOfSources(@resource)
|
57
|
+
when :destination then Map.MIDIEntityGetNumberOfDestinations(@resource)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# gets a CFString property
|
62
|
+
def get_string(name, pointer)
|
63
|
+
prop = Map::CF.CFStringCreateWithCString( nil, name.to_s, 0 )
|
64
|
+
val = Map::CF.CFStringCreateWithCString( nil, name.to_s, 0 ) # placeholder
|
65
|
+
Map::MIDIObjectGetStringProperty(pointer, prop, val)
|
66
|
+
Map::CF.CFStringGetCStringPtr(val.read_pointer, 0).read_string rescue nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# gets an Integer property
|
70
|
+
def get_int(name, pointer)
|
71
|
+
prop = Map::CF.CFStringCreateWithCString( nil, name.to_s, 0 )
|
72
|
+
val = FFI::MemoryPointer.new(:pointer, 32)
|
73
|
+
Map::MIDIObjectGetIntegerProperty(pointer, prop, val)
|
74
|
+
val.read_int
|
75
|
+
end
|
76
|
+
|
77
|
+
# gets a CString or Integer property from this Endpoint's entity
|
78
|
+
def get_property(name, options = {})
|
79
|
+
from = options[:from] || @resource
|
80
|
+
type = options[:type] || :string
|
81
|
+
|
82
|
+
case type
|
83
|
+
when :string then get_string(name, from)
|
84
|
+
when :int then get_int(name, from)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
data/lib/coremidi/map.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
#
|
6
|
+
# coremidi binding
|
7
|
+
#
|
8
|
+
#
|
9
|
+
module Map
|
10
|
+
|
11
|
+
extend FFI::Library
|
12
|
+
ffi_lib '/System/Library/Frameworks/CoreMIDI.framework/Versions/Current/CoreMIDI'
|
13
|
+
|
14
|
+
SnowLeopard = `uname -r`.scan(/\d*\.\d*/).first.to_f >= 10.6
|
15
|
+
|
16
|
+
typedef :pointer, :CFStringRef
|
17
|
+
typedef :int32, :ItemCount
|
18
|
+
typedef :pointer, :MIDIClientRef
|
19
|
+
typedef :pointer, :MIDIDeviceRef
|
20
|
+
typedef :pointer, :MIDIEndpointRef
|
21
|
+
typedef :pointer, :MIDIEntityRef
|
22
|
+
typedef :pointer, :MIDIObjectRef
|
23
|
+
typedef :pointer, :MIDIPortRef
|
24
|
+
#typedef :pointer, :MIDIReadProc
|
25
|
+
typedef :uint32, :MIDITimeStamp
|
26
|
+
typedef :int32, :OSStatus
|
27
|
+
|
28
|
+
class MIDISysexSendRequest < FFI::Struct
|
29
|
+
|
30
|
+
layout :destination, :MIDIEndpointRef,
|
31
|
+
:data, :pointer,
|
32
|
+
:bytes_to_send, :uint32,
|
33
|
+
:complete, :int,
|
34
|
+
:reserved, [:char, 3],
|
35
|
+
:completion_proc, :pointer,
|
36
|
+
:completion_ref_con, :pointer
|
37
|
+
end
|
38
|
+
|
39
|
+
class MIDIPacket < FFI::Struct
|
40
|
+
|
41
|
+
layout :timestamp, :MIDITimeStamp,
|
42
|
+
:nothing, :uint32, # no idea...
|
43
|
+
:length, :uint16,
|
44
|
+
:data, [:uint8, 256]
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class MIDIPacketList < FFI::Struct
|
49
|
+
layout :numPackets, :uint32,
|
50
|
+
:packet, [MIDIPacket.by_value, 1]
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
callback :MIDIReadProc, [MIDIPacketList.by_ref, :pointer, :pointer], :pointer
|
55
|
+
|
56
|
+
attach_function :MIDIClientCreate, [:pointer, :pointer, :pointer, :pointer], :int
|
57
|
+
|
58
|
+
attach_function :MIDIClientDispose, [:pointer], :int
|
59
|
+
|
60
|
+
# MIDIEntityRef MIDIDeviceGetEntity (MIDIDeviceRef device, ItemCount entityIndex0);
|
61
|
+
attach_function :MIDIDeviceGetEntity, [:MIDIDeviceRef, :ItemCount], :MIDIEntityRef
|
62
|
+
|
63
|
+
attach_function :MIDIGetNumberOfDestinations, [], :ItemCount
|
64
|
+
|
65
|
+
attach_function :MIDIGetNumberOfDevices, [], :ItemCount
|
66
|
+
|
67
|
+
attach_function :MIDIGetDestination, [:int], :pointer
|
68
|
+
|
69
|
+
#extern OSStatus MIDIEndpointDispose( MIDIEndpointRef endpt );
|
70
|
+
attach_function :MIDIEndpointDispose, [:MIDIEndpointRef], :OSStatus
|
71
|
+
|
72
|
+
# MIDIEndpointRef MIDIEntityGetDestination( MIDIEntityRef entity, ItemCount destIndex0 );
|
73
|
+
attach_function :MIDIEntityGetDestination, [:MIDIEntityRef, :int], :MIDIEndpointRef
|
74
|
+
|
75
|
+
# ItemCount MIDIEntityGetNumberOfDestinations (MIDIEntityRef entity);
|
76
|
+
attach_function :MIDIEntityGetNumberOfDestinations, [:MIDIEntityRef], :ItemCount
|
77
|
+
|
78
|
+
# ItemCount MIDIEntityGetNumberOfSources (MIDIEntityRef entity);
|
79
|
+
attach_function :MIDIEntityGetNumberOfSources, [:MIDIEntityRef], :ItemCount
|
80
|
+
|
81
|
+
# MIDIEndpointRef MIDIEntityGetSource (MIDIEntityRef entity, ItemCount sourceIndex0);
|
82
|
+
attach_function :MIDIEntityGetSource, [:MIDIEntityRef, :ItemCount], :MIDIEndpointRef
|
83
|
+
|
84
|
+
attach_function :MIDIGetDevice, [:ItemCount], :MIDIDeviceRef
|
85
|
+
|
86
|
+
# extern OSStatus MIDIInputPortCreate( MIDIClientRef client, CFStringRef portName, MIDIReadProc readProc, void * refCon, MIDIPortRef * outPort );
|
87
|
+
attach_function :MIDIInputPortCreate, [:MIDIClientRef, :CFStringRef, :MIDIReadProc, :pointer, :MIDIPortRef], :OSStatus
|
88
|
+
|
89
|
+
# extern OSStatus MIDIObjectGetIntegerProperty( MIDIObjectRef obj, CFStringRef propertyID, SInt32 * outValue );
|
90
|
+
attach_function :MIDIObjectGetIntegerProperty, [:MIDIObjectRef, :CFStringRef, :pointer], :OSStatus
|
91
|
+
# OSStatus MIDIObjectGetStringProperty (MIDIObjectRef obj, CFStringRef propertyID, CFStringRef *str);
|
92
|
+
attach_function :MIDIObjectGetStringProperty, [:MIDIObjectRef, :CFStringRef, :pointer], :OSStatus
|
93
|
+
\
|
94
|
+
# extern OSStatus MIDIOutputPortCreate( MIDIClientRef client, CFStringRef portName, MIDIPortRef * outPort );
|
95
|
+
attach_function :MIDIOutputPortCreate, [:MIDIClientRef, :CFStringRef, :pointer], :int
|
96
|
+
|
97
|
+
attach_function :MIDIPacketListInit, [:pointer], :pointer
|
98
|
+
|
99
|
+
#extern OSStatus MIDIPortConnectSource( MIDIPortRef port, MIDIEndpointRef source, void * connRefCon )
|
100
|
+
attach_function :MIDIPortConnectSource, [:MIDIPortRef, :MIDIEndpointRef, :pointer], :OSStatus
|
101
|
+
|
102
|
+
#extern OSStatus MIDIPortDisconnectSource( MIDIPortRef port, MIDIEndpointRef source );
|
103
|
+
attach_function :MIDIPortDisconnectSource, [:MIDIPortRef, :MIDIEndpointRef], :OSStatus
|
104
|
+
|
105
|
+
#extern OSStatus MIDIPortDispose(MIDIPortRef port );
|
106
|
+
attach_function :MIDIPortDispose, [:MIDIPortRef], :OSStatus
|
107
|
+
|
108
|
+
#extern OSStatus MIDISend(MIDIPortRef port,MIDIEndpointRef dest,const MIDIPacketList *pktlist);
|
109
|
+
attach_function :MIDISend, [:MIDIPortRef, :MIDIEndpointRef, :pointer], :int
|
110
|
+
|
111
|
+
attach_function :MIDISendSysex, [:pointer], :int
|
112
|
+
|
113
|
+
if SnowLeopard
|
114
|
+
attach_function :MIDIPacketListAdd, [:pointer, :int, :pointer, :int, :int, :pointer], :pointer
|
115
|
+
else
|
116
|
+
# extern MIDIPacket * MIDIPacketListAdd( MIDIPacketList * pktlist, ByteCount listSize, MIDIPacket * curPacket, MIDITimeStamp time, ByteCount nData, const Byte * data)
|
117
|
+
attach_function :MIDIPacketListAdd, [:pointer, :int, :pointer, :int, :int, :int, :pointer], :pointer
|
118
|
+
end
|
119
|
+
|
120
|
+
module CF
|
121
|
+
|
122
|
+
extend FFI::Library
|
123
|
+
ffi_lib '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation'
|
124
|
+
|
125
|
+
# CFString* CFStringCreateWithCString( ?, CString, encoding)
|
126
|
+
attach_function :CFStringCreateWithCString, [:pointer, :string, :int], :pointer
|
127
|
+
# CString* CFStringGetCStringPtr(CFString*, encoding)
|
128
|
+
attach_function :CFStringGetCStringPtr, [:pointer, :int], :pointer
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
module HostTime
|
133
|
+
extend FFI::Library
|
134
|
+
ffi_lib '/System/Library/Frameworks/CoreAudio.framework/Versions/Current/CoreAudio'
|
135
|
+
|
136
|
+
attach_function :AudioConvertHostTimeToNanos, [:uint64], :uint64
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module CoreMIDI
|
4
|
+
|
5
|
+
#
|
6
|
+
# Input/Source endpoint class
|
7
|
+
#
|
8
|
+
class Source
|
9
|
+
|
10
|
+
include Endpoint
|
11
|
+
|
12
|
+
attr_reader :buffer
|
13
|
+
|
14
|
+
#
|
15
|
+
# returns an array of MIDI event hashes as such:
|
16
|
+
# [
|
17
|
+
# { :data => [144, 60, 100], :timestamp => 1024 },
|
18
|
+
# { :data => [128, 60, 100], :timestamp => 1100 },
|
19
|
+
# { :data => [144, 40, 120], :timestamp => 1200 }
|
20
|
+
# ]
|
21
|
+
#
|
22
|
+
# the data is an array of Numeric bytes
|
23
|
+
# the timestamp is the number of millis since this input was enabled
|
24
|
+
#
|
25
|
+
def gets
|
26
|
+
until queued_messages?
|
27
|
+
end
|
28
|
+
msgs = queued_messages
|
29
|
+
@pointer = @buffer.length
|
30
|
+
msgs
|
31
|
+
end
|
32
|
+
alias_method :read, :gets
|
33
|
+
|
34
|
+
# same as gets but returns message data as string of hex digits as such:
|
35
|
+
# [
|
36
|
+
# { :data => "904060", :timestamp => 904 },
|
37
|
+
# { :data => "804060", :timestamp => 1150 },
|
38
|
+
# { :data => "90447F", :timestamp => 1300 }
|
39
|
+
# ]
|
40
|
+
#
|
41
|
+
#
|
42
|
+
def gets_s
|
43
|
+
msgs = gets
|
44
|
+
msgs.each { |msg| msg[:data] = numeric_bytes_to_hex_string(msg[:data]) }
|
45
|
+
msgs
|
46
|
+
end
|
47
|
+
alias_method :gets_bytestr, :gets_s
|
48
|
+
|
49
|
+
# enable this the input for use; can be passed a block
|
50
|
+
def enable(options = {}, &block)
|
51
|
+
@enabled = true
|
52
|
+
|
53
|
+
unless block.nil?
|
54
|
+
begin
|
55
|
+
yield(self)
|
56
|
+
ensure
|
57
|
+
close
|
58
|
+
end
|
59
|
+
else
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias_method :open, :enable
|
64
|
+
alias_method :start, :enable
|
65
|
+
|
66
|
+
# close this input
|
67
|
+
def close
|
68
|
+
#error = Map.MIDIPortDisconnectSource( @handle, @resource )
|
69
|
+
#raise "MIDIPortDisconnectSource returned error code #{error}" unless error.zero?
|
70
|
+
#error = Map.MIDIClientDispose(@handle)
|
71
|
+
#raise "MIDIClientDispose returned error code #{error}" unless error.zero?
|
72
|
+
#error = Map.MIDIPortDispose(@handle)
|
73
|
+
#raise "MIDIPortDispose returned error code #{error}" unless error.zero?
|
74
|
+
#error = Map.MIDIEndpointDispose(@resource)
|
75
|
+
#raise "MIDIEndpointDispose returned error code #{error}" unless error.zero?
|
76
|
+
@enabled = false
|
77
|
+
end
|
78
|
+
|
79
|
+
# shortcut to the first available input endpoint
|
80
|
+
def self.first
|
81
|
+
Endpoint.first(:source)
|
82
|
+
end
|
83
|
+
|
84
|
+
# shortcut to the last available input endpoint
|
85
|
+
def self.last
|
86
|
+
Endpoint.last(:source)
|
87
|
+
end
|
88
|
+
|
89
|
+
# all input endpoints
|
90
|
+
def self.all
|
91
|
+
Endpoint.all_by_type[:source]
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
# base initialization for this endpoint -- done whether or not the endpoint is enabled to
|
97
|
+
# check whether it is truly available for use
|
98
|
+
def connect
|
99
|
+
enable_client
|
100
|
+
initialize_port
|
101
|
+
@resource = Map.MIDIEntityGetSource(@entity.resource, @resource_id)
|
102
|
+
error = Map.MIDIPortConnectSource(@handle, @resource, nil )
|
103
|
+
initialize_buffer
|
104
|
+
@sysex_buffer = []
|
105
|
+
@start_time = Time.now.to_f
|
106
|
+
|
107
|
+
error.zero?
|
108
|
+
end
|
109
|
+
alias_method :connect?, :connect
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# returns new MIDI messages from the queue
|
114
|
+
def queued_messages
|
115
|
+
@buffer.slice(@pointer, @buffer.length - @pointer)
|
116
|
+
end
|
117
|
+
|
118
|
+
# are there new MIDI messages in the queue?
|
119
|
+
def queued_messages?
|
120
|
+
@pointer < @buffer.length
|
121
|
+
end
|
122
|
+
|
123
|
+
# the callback which is called by coremidi when new MIDI messages are in the buffer
|
124
|
+
def get_event_callback
|
125
|
+
Proc.new do | new_packets, refCon_ptr, connRefCon_ptr |
|
126
|
+
time = Time.now.to_f
|
127
|
+
packet = new_packets[:packet][0]
|
128
|
+
len = packet[:length]
|
129
|
+
#p "packets received: #{new_packets[:numPackets]}"
|
130
|
+
#p "first packet length: #{len} data: #{packet[:data].to_a.to_s}"
|
131
|
+
if len > 0
|
132
|
+
bytes = packet[:data].to_a[0, len]
|
133
|
+
if bytes.first.eql?(0xF0) || !@sysex_buffer.empty?
|
134
|
+
@sysex_buffer += bytes
|
135
|
+
if bytes.last.eql?(0xF7)
|
136
|
+
bytes = @sysex_buffer.dup
|
137
|
+
@sysex_buffer.clear
|
138
|
+
end
|
139
|
+
end
|
140
|
+
@buffer << get_message_formatted(bytes, time) if @sysex_buffer.empty?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# timestamp
|
146
|
+
def timestamp(now)
|
147
|
+
((now - @start_time) * 1000)
|
148
|
+
end
|
149
|
+
|
150
|
+
# give a message its timestamp and package it in a Hash
|
151
|
+
def get_message_formatted(raw, time)
|
152
|
+
{ :data => raw, :timestamp => timestamp(time) }
|
153
|
+
end
|
154
|
+
|
155
|
+
# initialize a coremidi port for this endpoint
|
156
|
+
def initialize_port
|
157
|
+
port_name = Map::CF.CFStringCreateWithCString(nil, "Port #{@resource_id}: #{name}", 0)
|
158
|
+
handle_ptr = FFI::MemoryPointer.new(:pointer)
|
159
|
+
@callback = get_event_callback
|
160
|
+
error = Map.MIDIInputPortCreate(@client, port_name, @callback, nil, handle_ptr)
|
161
|
+
@handle = handle_ptr.read_pointer
|
162
|
+
raise "MIDIInputPortCreate returned error code #{error}" unless error.zero?
|
163
|
+
end
|
164
|
+
|
165
|
+
# initialize the MIDI message buffer
|
166
|
+
def initialize_buffer
|
167
|
+
@pointer = 0
|
168
|
+
@buffer = []
|
169
|
+
def @buffer.clear
|
170
|
+
super
|
171
|
+
@pointer = 0
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# convert an array of numeric byes to a hex string
|
176
|
+
# e.g.
|
177
|
+
# [0x90, 0x40, 0x40] -> "904040"
|
178
|
+
def numeric_bytes_to_hex_string(bytes)
|
179
|
+
bytes.map { |b| s = b.to_s(16).upcase; b < 16 ? s = "0" + s : s; s }.join
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
4
|
+
$LOAD_PATH.unshift dir + '/../lib'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'coremidi'
|
8
|
+
|
9
|
+
module TestHelper
|
10
|
+
|
11
|
+
def self.select_devices
|
12
|
+
$test_device ||= {}
|
13
|
+
{ :input => CoreMIDI::Source.all, :output => CoreMIDI::Destination.all }.each do |type, devs|
|
14
|
+
puts ""
|
15
|
+
puts "select an #{type.to_s}..."
|
16
|
+
while $test_device[type].nil?
|
17
|
+
devs.each do |device|
|
18
|
+
puts "#{device.id}: #{device.name}"
|
19
|
+
end
|
20
|
+
selection = $stdin.gets.chomp
|
21
|
+
if selection != ""
|
22
|
+
selection = selection.to_i
|
23
|
+
$test_device[type] = devs.find { |d| d.id == selection }
|
24
|
+
puts "selected #{selection} for #{type.to_s}" unless $test_device[type]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def bytestrs_to_ints(arr)
|
31
|
+
data = arr.map { |m| m[:data] }.join
|
32
|
+
output = []
|
33
|
+
until (bytestr = data.slice!(0,2)).eql?("")
|
34
|
+
output << bytestr.hex
|
35
|
+
end
|
36
|
+
output
|
37
|
+
end
|
38
|
+
|
39
|
+
# some MIDI messages
|
40
|
+
VariousMIDIMessages = [
|
41
|
+
[0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], # SysEx
|
42
|
+
[0x90, 100, 100], # note on
|
43
|
+
[0x90, 43, 100], # note on
|
44
|
+
[0x90, 76, 100], # note on
|
45
|
+
[0x90, 60, 100], # note on
|
46
|
+
[0x80, 100, 100] # note off
|
47
|
+
]
|
48
|
+
|
49
|
+
# some MIDI messages
|
50
|
+
VariousMIDIByteStrMessages = [
|
51
|
+
"F04110421240007F0041F7", # SysEx
|
52
|
+
"906440", # note on
|
53
|
+
"804340" # note off
|
54
|
+
]
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
TestHelper.select_devices
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
|
5
|
+
class InputBufferTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include CoreMIDI
|
8
|
+
include TestHelper
|
9
|
+
|
10
|
+
def test_input_buffer
|
11
|
+
sleep(1)
|
12
|
+
|
13
|
+
messages = VariousMIDIMessages
|
14
|
+
bytes = []
|
15
|
+
|
16
|
+
$test_device[:output].open do |output|
|
17
|
+
$test_device[:input].open do |input|
|
18
|
+
|
19
|
+
input.buffer.clear
|
20
|
+
|
21
|
+
messages.each do |msg|
|
22
|
+
|
23
|
+
$>.puts "sending: " + msg.inspect
|
24
|
+
|
25
|
+
output.puts(msg)
|
26
|
+
|
27
|
+
bytes += msg
|
28
|
+
|
29
|
+
sleep(0.5)
|
30
|
+
|
31
|
+
buffer = input.buffer.map { |m| m[:data] }.flatten
|
32
|
+
|
33
|
+
$>.puts "received: " + buffer.to_s
|
34
|
+
|
35
|
+
assert_equal(bytes, buffer)
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal(bytes.length, input.buffer.map { |m| m[:data] }.flatten.length)
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/test/test_io.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
|
5
|
+
class IoTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include CoreMIDI
|
8
|
+
include TestHelper
|
9
|
+
|
10
|
+
def test_full_io
|
11
|
+
sleep(2)
|
12
|
+
messages = VariousMIDIMessages
|
13
|
+
messages_arr = messages.inject { |a,b| a+b }.flatten
|
14
|
+
received_arr = []
|
15
|
+
pointer = 0
|
16
|
+
$test_device[:output].open do |output|
|
17
|
+
$test_device[:input].open do |input|
|
18
|
+
|
19
|
+
input.buffer.clear
|
20
|
+
|
21
|
+
messages.each do |msg|
|
22
|
+
|
23
|
+
$>.puts "sending: " + msg.inspect
|
24
|
+
|
25
|
+
output.puts(msg)
|
26
|
+
sleep(1)
|
27
|
+
received = input.gets.map { |m| m[:data] }.flatten
|
28
|
+
|
29
|
+
$>.puts "received: " + received.inspect
|
30
|
+
|
31
|
+
assert_equal(messages_arr.slice(pointer, received.length), received)
|
32
|
+
|
33
|
+
pointer += received.length
|
34
|
+
|
35
|
+
received_arr += received
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal(messages_arr.length, received_arr.length)
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# ** this test assumes that TestOutput is connected to TestInput
|
46
|
+
def test_full_io_bytestr
|
47
|
+
sleep(2) # pause between tests
|
48
|
+
|
49
|
+
messages = VariousMIDIByteStrMessages
|
50
|
+
messages_str = messages.join
|
51
|
+
received_str = ""
|
52
|
+
pointer = 0
|
53
|
+
|
54
|
+
$test_device[:output].open do |output|
|
55
|
+
$test_device[:input].open do |input|
|
56
|
+
|
57
|
+
input.buffer.clear
|
58
|
+
|
59
|
+
messages.each do |msg|
|
60
|
+
|
61
|
+
$>.puts "sending: " + msg.inspect
|
62
|
+
|
63
|
+
output.puts_s(msg)
|
64
|
+
sleep(1)
|
65
|
+
received = input.gets_bytestr.map { |m| m[:data] }.flatten.join
|
66
|
+
$>.puts "received: " + received.inspect
|
67
|
+
|
68
|
+
assert_equal(messages_str.slice(pointer, received.length), received)
|
69
|
+
|
70
|
+
pointer += received.length
|
71
|
+
|
72
|
+
received_str += received
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
assert_equal(messages_str, received_str)
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-coremidi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.7
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ari Russo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi
|
16
|
+
requirement: &70309759821860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70309759821860
|
25
|
+
description: Realtime MIDI IO with Ruby for OSX
|
26
|
+
email:
|
27
|
+
- ari.russo@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- lib/coremidi/destination.rb
|
33
|
+
- lib/coremidi/device.rb
|
34
|
+
- lib/coremidi/endpoint.rb
|
35
|
+
- lib/coremidi/entity.rb
|
36
|
+
- lib/coremidi/map.rb
|
37
|
+
- lib/coremidi/source.rb
|
38
|
+
- lib/coremidi.rb
|
39
|
+
- test/helper.rb
|
40
|
+
- test/test_input_buffer.rb
|
41
|
+
- test/test_io.rb
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
homepage: http://github.com/arirusso/ffi-coremidi
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.6
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project: ffi-coremidi
|
64
|
+
rubygems_version: 1.8.6
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Realtime MIDI IO with Ruby for OSX
|
68
|
+
test_files: []
|