ffi-coremidi 0.2.2 → 0.2.3
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.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/lib/coremidi.rb +1 -1
- data/lib/coremidi/destination.rb +60 -31
- data/lib/coremidi/device.rb +36 -30
- data/lib/coremidi/endpoint.rb +22 -5
- data/lib/coremidi/entity.rb +72 -40
- data/lib/coremidi/map.rb +22 -0
- data/lib/coremidi/source.rb +22 -11
- data/test/input_buffer_test.rb +24 -25
- data/test/io_test.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c86ac17739ef22c4478c0b6074602c35e6797d7
|
4
|
+
data.tar.gz: 612c5638da885ab668b64952087d52d49112fbdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dad0821c9b3019da6993960a098d0275a79d009535e09a8e63509908542c3a29b888cfc739cf06b2933e775f8575150c0092d63af9400812c7c0c46dc7db07b0
|
7
|
+
data.tar.gz: 379c75695cea32e8f7ff3347bc6556eb1ead7301781d0ad845261ccf9630b00d39bf006dc5f4294a3193ebdb6a334cb5f0e27fe93c2636864a5a48d277250c9b
|
data/README.md
CHANGED
@@ -4,15 +4,15 @@
|
|
4
4
|
|
5
5
|
This is a Ruby implementation of the [Apple Core MIDI framework API](https://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CACoreMIDIRef/MIDIServices/).
|
6
6
|
|
7
|
-
Note that in the interest of allowing people on other platforms to utilize your code, please consider using [UniMIDI](http://github.com/arirusso/unimidi). UniMIDI is a platform independent wrapper which implements this library
|
7
|
+
Note that in the interest of allowing people on other platforms to utilize your code, please consider using [UniMIDI](http://github.com/arirusso/unimidi). UniMIDI is a platform independent wrapper which implements this library and has a similar API.
|
8
8
|
|
9
9
|
### Features
|
10
10
|
|
11
|
-
*
|
11
|
+
* Simplified API
|
12
12
|
* Input and output on multiple devices concurrently
|
13
|
-
*
|
13
|
+
* Generalized handling of different MIDI Message types (including SysEx)
|
14
14
|
* Timestamped input events
|
15
|
-
*
|
15
|
+
* Patch MIDI via software to other programs using IAC
|
16
16
|
|
17
17
|
### Requirements
|
18
18
|
|
data/lib/coremidi.rb
CHANGED
data/lib/coremidi/destination.rb
CHANGED
@@ -8,74 +8,85 @@ module CoreMIDI
|
|
8
8
|
attr_reader :entity
|
9
9
|
|
10
10
|
# Close this output
|
11
|
+
# @return [Boolean]
|
11
12
|
def close
|
12
|
-
@enabled
|
13
|
+
if @enabled
|
14
|
+
@enabled = false
|
15
|
+
true
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
13
19
|
end
|
14
20
|
|
15
21
|
# Send a MIDI message comprised of a String of hex digits
|
22
|
+
# @param [String] data A string of hex digits eg "904040"
|
23
|
+
# @return [Boolean]
|
16
24
|
def puts_s(data)
|
17
25
|
data = data.dup
|
18
|
-
|
26
|
+
bytes = []
|
19
27
|
until (str = data.slice!(0,2)).eql?("")
|
20
|
-
|
28
|
+
bytes << str.hex
|
21
29
|
end
|
22
|
-
|
23
|
-
|
30
|
+
puts_bytes(*bytes)
|
31
|
+
true
|
24
32
|
end
|
25
33
|
alias_method :puts_bytestr, :puts_s
|
26
34
|
alias_method :puts_hex, :puts_s
|
27
35
|
|
28
|
-
# Send a MIDI
|
36
|
+
# Send a MIDI message comprised of numeric bytes
|
37
|
+
# @param [*Fixnum] data Numeric bytes eg 0x90, 0x40, 0x40
|
38
|
+
# @return [Boolean]
|
29
39
|
def puts_bytes(*data)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
if data.first.eql?(0xF0) && data.last.eql?(0xF7)
|
36
|
-
puts_sysex(bytes, data.size)
|
37
|
-
else
|
38
|
-
puts_small(bytes, data.size)
|
39
|
-
end
|
40
|
+
type = sysex?(data) ? :sysex : :small
|
41
|
+
bytes = pack_data(data)
|
42
|
+
send("puts_#{type.to_s}", bytes, data.size)
|
43
|
+
true
|
40
44
|
end
|
41
45
|
|
42
|
-
# Send a MIDI message of
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
# Send a MIDI message of indeterminate type
|
47
|
+
# @param [*Array<Fixnum>, *Array<String>, *Fixnum, *String] args
|
48
|
+
# @return [Boolean]
|
49
|
+
def puts(*args)
|
50
|
+
case args.first
|
51
|
+
when Array then puts_bytes(*args.first)
|
52
|
+
when Fixnum then puts_bytes(*args)
|
53
|
+
when String then puts_bytestr(*args)
|
48
54
|
end
|
49
55
|
end
|
50
56
|
alias_method :write, :puts
|
51
57
|
|
52
58
|
# Enable this device
|
59
|
+
# @return [Destination]
|
53
60
|
def enable(options = {}, &block)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
if !@enabled
|
62
|
+
@enabled = true
|
63
|
+
if block_given?
|
64
|
+
begin
|
65
|
+
yield(self)
|
66
|
+
ensure
|
67
|
+
close
|
68
|
+
end
|
69
|
+
end
|
63
70
|
end
|
71
|
+
self
|
64
72
|
end
|
65
73
|
alias_method :open, :enable
|
66
74
|
alias_method :start, :enable
|
67
75
|
|
68
76
|
# Shortcut to the first output endpoint available
|
77
|
+
# @return [Destination]
|
69
78
|
def self.first
|
70
79
|
Endpoint.first(:destination)
|
71
80
|
end
|
72
81
|
|
73
82
|
# Shortcut to the last output endpoint available
|
83
|
+
# @return [Destination]
|
74
84
|
def self.last
|
75
85
|
Endpoint.last(:destination)
|
76
86
|
end
|
77
87
|
|
78
88
|
# All output endpoints
|
89
|
+
# @return [Array<Destination>]
|
79
90
|
def self.all
|
80
91
|
Endpoint.all_by_type[:destination]
|
81
92
|
end
|
@@ -84,6 +95,7 @@ module CoreMIDI
|
|
84
95
|
|
85
96
|
# Base initialization for this endpoint -- done whether or not the endpoint is enabled to
|
86
97
|
# check whether it is truly available for use
|
98
|
+
# @return [Boolean]
|
87
99
|
def connect
|
88
100
|
client_error = enable_client
|
89
101
|
port_error = initialize_port
|
@@ -105,6 +117,7 @@ module CoreMIDI
|
|
105
117
|
packet_ptr = Map.MIDIPacketListAdd(packet_list, 256, packet_ptr, 0, 0, size, bytes)
|
106
118
|
end
|
107
119
|
Map.MIDISend( @handle, @resource, packet_list )
|
120
|
+
true
|
108
121
|
end
|
109
122
|
|
110
123
|
# Output a System Exclusive MIDI message
|
@@ -117,6 +130,7 @@ module CoreMIDI
|
|
117
130
|
request[:completion_proc] = SysexCompletionCallback
|
118
131
|
request[:completion_ref_con] = request
|
119
132
|
Map.MIDISendSysex(request)
|
133
|
+
true
|
120
134
|
end
|
121
135
|
|
122
136
|
SysexCompletionCallback =
|
@@ -133,6 +147,21 @@ module CoreMIDI
|
|
133
147
|
error
|
134
148
|
end
|
135
149
|
|
150
|
+
# Pack the given data into a coremidi MIDI packet
|
151
|
+
def pack_data(data)
|
152
|
+
format = "C" * data.size
|
153
|
+
packed_data = data.pack(format)
|
154
|
+
char_size = FFI.type_size(:char) * data.size
|
155
|
+
bytes = FFI::MemoryPointer.new(char_size)
|
156
|
+
bytes.write_string(packed_data)
|
157
|
+
bytes
|
158
|
+
end
|
159
|
+
|
160
|
+
# Is the given data a MIDI sysex message?
|
161
|
+
def sysex?(data)
|
162
|
+
data.first.eql?(0xF0) && data.last.eql?(0xF7)
|
163
|
+
end
|
164
|
+
|
136
165
|
end
|
137
166
|
|
138
167
|
end
|
data/lib/coremidi/device.rb
CHANGED
@@ -6,21 +6,24 @@ module CoreMIDI
|
|
6
6
|
:id, # Unique Numeric id
|
7
7
|
:name # Device name from coremidi
|
8
8
|
|
9
|
+
# @param [Fixnum] id The ID for the device
|
10
|
+
# @param [Object] device_pointer The underlying device pointer
|
11
|
+
# @param [Hash] options
|
12
|
+
# @option options [Boolean] :include_offline Whether to include offline entities (default: false)
|
9
13
|
def initialize(id, device_pointer, options = {})
|
10
|
-
include_if_offline = options.fetch(:include_offline, false)
|
11
14
|
@id = id
|
12
15
|
@resource = device_pointer
|
13
16
|
@entities = []
|
14
|
-
|
15
|
-
populate_entities(:include_offline => include_if_offline)
|
17
|
+
populate(options)
|
16
18
|
end
|
17
19
|
|
18
20
|
# Endpoints for this device
|
19
21
|
# @return [Array<Endpoint>]
|
20
22
|
def endpoints
|
21
23
|
endpoints = { :source => [], :destination => [] }
|
22
|
-
endpoints.keys.each do |
|
23
|
-
|
24
|
+
endpoints.keys.each do |key|
|
25
|
+
endpoint_group = entities.map { |entity| entity.endpoints[key] }.flatten
|
26
|
+
endpoints[key] += endpoint_group
|
24
27
|
end
|
25
28
|
endpoints
|
26
29
|
end
|
@@ -29,9 +32,9 @@ module CoreMIDI
|
|
29
32
|
# @param [Integer] last_id The highest already used endpoint ID
|
30
33
|
# @return [Integer] The highest used endpoint ID after populating this device's endpoints
|
31
34
|
def populate_endpoint_ids(last_id)
|
32
|
-
|
33
|
-
entities.each { |entity|
|
34
|
-
|
35
|
+
id = 0
|
36
|
+
entities.each { |entity| id += entity.populate_endpoint_ids(id + last_id) }
|
37
|
+
id
|
35
38
|
end
|
36
39
|
|
37
40
|
# All cached devices
|
@@ -42,13 +45,13 @@ module CoreMIDI
|
|
42
45
|
def self.all(options = {})
|
43
46
|
use_cache = options[:cache] || true
|
44
47
|
include_offline = options[:include_offline] || false
|
45
|
-
if
|
48
|
+
if !populated? || !use_cache
|
46
49
|
@devices = []
|
47
|
-
|
48
|
-
while !(device_pointer = Map.MIDIGetDevice(
|
49
|
-
device = new(
|
50
|
+
counter = 0
|
51
|
+
while !(device_pointer = Map.MIDIGetDevice(counter)).null?
|
52
|
+
device = new(counter, device_pointer, :include_offline => include_offline)
|
50
53
|
@devices << device
|
51
|
-
|
54
|
+
counter += 1
|
52
55
|
end
|
53
56
|
populate_endpoint_ids
|
54
57
|
end
|
@@ -62,34 +65,30 @@ module CoreMIDI
|
|
62
65
|
@devices
|
63
66
|
end
|
64
67
|
|
68
|
+
# Has the device list been populated?
|
69
|
+
def self.populated?
|
70
|
+
!@devices.nil? && !@devices.empty?
|
71
|
+
end
|
72
|
+
|
65
73
|
private
|
66
74
|
|
67
75
|
# Populate the device name
|
68
76
|
def populate_name
|
69
|
-
|
70
|
-
|
71
|
-
begin
|
72
|
-
name_ptr = FFI::MemoryPointer.new(:pointer)
|
73
|
-
Map::MIDIObjectGetStringProperty(@resource, prop, name_ptr)
|
74
|
-
name = name_ptr.read_pointer
|
75
|
-
len = Map::CF.CFStringGetMaximumSizeForEncoding(Map::CF.CFStringGetLength(name), :kCFStringEncodingUTF8)
|
76
|
-
bytes = FFI::MemoryPointer.new(len + 1)
|
77
|
-
raise RuntimeError.new("CFStringGetCString") unless Map::CF.CFStringGetCString(name, bytes, len, :kCFStringEncodingUTF8)
|
78
|
-
@name = bytes.read_string
|
79
|
-
ensure
|
80
|
-
Map::CF.CFRelease(name) unless name.nil? || name.null?
|
81
|
-
Map::CF.CFRelease(prop) unless prop.null?
|
82
|
-
end
|
77
|
+
@name = Map.get_string(@resource, "name")
|
78
|
+
raise RuntimeError.new("Can't get device name") unless @name
|
83
79
|
end
|
84
80
|
|
85
81
|
# All of the endpoints for all devices a consecutive local id
|
86
82
|
def self.populate_endpoint_ids
|
87
|
-
|
88
|
-
all.each { |device|
|
83
|
+
counter = 0
|
84
|
+
all.each { |device| counter += device.populate_endpoint_ids(counter) }
|
85
|
+
counter
|
89
86
|
end
|
90
87
|
|
91
88
|
# Populates the entities for this device. These entities are in turn used to gather the endpoints.
|
92
|
-
#
|
89
|
+
# @param [Hash] options
|
90
|
+
# @option options [Boolean] :include_offline Whether to include offline entities (default: false)
|
91
|
+
# @return [Fixnum] The number of entities populated
|
93
92
|
def populate_entities(options = {})
|
94
93
|
include_if_offline = options[:include_offline] || false
|
95
94
|
i = 0
|
@@ -97,6 +96,13 @@ module CoreMIDI
|
|
97
96
|
@entities << Entity.new(entity_pointer, :include_offline => include_if_offline)
|
98
97
|
i += 1
|
99
98
|
end
|
99
|
+
i
|
100
|
+
end
|
101
|
+
|
102
|
+
# Populate the instance
|
103
|
+
def populate(options = {})
|
104
|
+
populate_name
|
105
|
+
populate_entities(:include_offline => options[:include_offline])
|
100
106
|
end
|
101
107
|
|
102
108
|
end
|
data/lib/coremidi/endpoint.rb
CHANGED
@@ -22,26 +22,32 @@ module CoreMIDI
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# Is this endpoint online?
|
25
|
+
# @return [Boolean]
|
25
26
|
def online?
|
26
27
|
@entity.online? && connect?
|
27
28
|
end
|
28
29
|
|
29
|
-
# Set the id for this endpoint (the id is immutable
|
30
|
-
|
31
|
-
|
30
|
+
# Set the id for this endpoint (the id is immutable)
|
31
|
+
# @param [Fixnum] val
|
32
|
+
# @return [Fixnum]
|
33
|
+
def id=(id)
|
34
|
+
@id ||= id
|
32
35
|
end
|
33
36
|
|
34
37
|
# Select the first endpoint of the specified type
|
38
|
+
# @return [Destination, Source]
|
35
39
|
def self.first(type)
|
36
40
|
all_by_type[type].first
|
37
41
|
end
|
38
42
|
|
39
43
|
# Select the last endpoint of the specified type
|
44
|
+
# @return [Destination, Source]
|
40
45
|
def self.last(type)
|
41
46
|
all_by_type[type].last
|
42
47
|
end
|
43
48
|
|
44
49
|
# A Hash of :source and :destination endpoints
|
50
|
+
# @return [Hash]
|
45
51
|
def self.all_by_type
|
46
52
|
{
|
47
53
|
:source => Device.all.map { |d| d.endpoints[:source] }.flatten,
|
@@ -50,15 +56,26 @@ module CoreMIDI
|
|
50
56
|
end
|
51
57
|
|
52
58
|
# All endpoints of both types
|
59
|
+
# @return [Array<Destination, Source>]
|
53
60
|
def self.all
|
54
|
-
Device.all.map
|
61
|
+
Device.all.map(&:endpoints).flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get the class for the given endpoint type name
|
65
|
+
# @param [Symbol] type The endpoint type eg :source, :destination
|
66
|
+
# @return [Class] eg Source, Destination
|
67
|
+
def self.get_class(type)
|
68
|
+
case type
|
69
|
+
when :source then Source
|
70
|
+
when :destination then Destination
|
71
|
+
end
|
55
72
|
end
|
56
73
|
|
57
74
|
protected
|
58
75
|
|
59
76
|
# Enables the coremidi MIDI client that will go with this endpoint
|
60
77
|
def enable_client
|
61
|
-
client_name = Map::CF.CFStringCreateWithCString(
|
78
|
+
client_name = Map::CF.CFStringCreateWithCString(nil, "Client #{@resource_id} #{name}", 0)
|
62
79
|
client_ptr = FFI::MemoryPointer.new(:pointer)
|
63
80
|
error = Map.MIDIClientCreate(client_name, nil, nil, client_ptr)
|
64
81
|
@client = client_ptr.read_pointer
|
data/lib/coremidi/entity.rb
CHANGED
@@ -9,46 +9,68 @@ module CoreMIDI
|
|
9
9
|
:name,
|
10
10
|
:resource
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# @param [FFI::Pointer] resource A pointer to the underlying entity
|
13
|
+
# @param [Hash] options
|
14
14
|
def initialize(resource, options = {}, &block)
|
15
|
-
@endpoints = {
|
15
|
+
@endpoints = {
|
16
|
+
:source => [],
|
17
|
+
:destination => []
|
18
|
+
}
|
16
19
|
@resource = resource
|
17
|
-
|
18
|
-
@model = get_property(:model)
|
19
|
-
@name = "#{@manufacturer} #{@model}"
|
20
|
-
@is_online = get_property(:offline, :type => :int) == 0
|
21
|
-
@endpoints.keys.each { |type| populate_endpoints(type) }
|
20
|
+
populate
|
22
21
|
end
|
23
22
|
|
24
23
|
# Assign all of this Entity's endpoints an consecutive local id
|
24
|
+
# @param [Fixnum] starting_id
|
25
|
+
# @return [Fixnum]
|
25
26
|
def populate_endpoint_ids(starting_id)
|
26
|
-
|
27
|
-
@endpoints.values.flatten.each do |
|
28
|
-
|
29
|
-
|
27
|
+
counter = 0
|
28
|
+
@endpoints.values.flatten.each do |endpoint|
|
29
|
+
endpoint.id = counter + starting_id
|
30
|
+
counter += 1
|
30
31
|
end
|
31
|
-
|
32
|
+
counter
|
32
33
|
end
|
33
34
|
|
35
|
+
# Is the entity online?
|
36
|
+
# @return [Boolean]
|
37
|
+
def online?
|
38
|
+
get_property(:offline, :type => :int) == 0
|
39
|
+
end
|
40
|
+
|
34
41
|
private
|
42
|
+
|
43
|
+
# Construct a display name for the entity
|
44
|
+
# @return [String]
|
45
|
+
def get_name
|
46
|
+
"#{@manufacturer} #{@model}"
|
47
|
+
end
|
35
48
|
|
36
49
|
# Populate endpoints of a specified type for this entity
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
50
|
+
# @param [Symbol] type The endpoint type eg :source, :destination
|
51
|
+
# @param [Hash] options
|
52
|
+
# @option options [Boolean] :include_offline Include offline endpoints in the list
|
53
|
+
# @return [Fixnum]
|
54
|
+
def populate_endpoints_by_type(type, options = {})
|
55
|
+
endpoint_class = Endpoint.get_class(type)
|
43
56
|
num_endpoints = number_of_endpoints(type)
|
44
57
|
(0..num_endpoints).each do |i|
|
45
|
-
|
46
|
-
|
58
|
+
endpoint = endpoint_class.new(i, self)
|
59
|
+
if endpoint.online? || options[:include_offline]
|
60
|
+
@endpoints[type] << endpoint
|
61
|
+
end
|
47
62
|
end
|
48
63
|
@endpoints[type].size
|
49
64
|
end
|
65
|
+
|
66
|
+
# Populate the endpoints for this entity
|
67
|
+
# @return [Fixnum]
|
68
|
+
def populate_endpoints
|
69
|
+
@endpoints.keys.map { |type| populate_endpoints_by_type(type) }.reduce(&:+)
|
70
|
+
end
|
50
71
|
|
51
72
|
# The number of endpoints for this entity
|
73
|
+
# @param [Symbol] type The endpoint type eg :source, :destination
|
52
74
|
def number_of_endpoints(type)
|
53
75
|
case type
|
54
76
|
when :source then Map.MIDIEntityGetNumberOfSources(@resource)
|
@@ -56,33 +78,43 @@ module CoreMIDI
|
|
56
78
|
end
|
57
79
|
end
|
58
80
|
|
59
|
-
# A CFString property
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
Map
|
64
|
-
Map::CF.CFStringGetCStringPtr(val.read_pointer, 0).read_string rescue nil
|
81
|
+
# A CFString property from the underlying entity
|
82
|
+
# @param [Symbol, String] name The property name
|
83
|
+
# @return [String, nil]
|
84
|
+
def get_string(name)
|
85
|
+
Map.get_string(@resource, name)
|
65
86
|
end
|
66
87
|
|
67
|
-
# An Integer property
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
Map::
|
72
|
-
|
88
|
+
# An Integer property from the underlying entity
|
89
|
+
# @param [Symbol, String] name The property name
|
90
|
+
# @return [Fixnum, nil]
|
91
|
+
def get_int(name)
|
92
|
+
property = Map::CF.CFStringCreateWithCString(nil, name.to_s, 0)
|
93
|
+
value = FFI::MemoryPointer.new(:pointer, 32)
|
94
|
+
Map::MIDIObjectGetIntegerProperty(@resource, property, value)
|
95
|
+
value.read_int
|
73
96
|
end
|
74
97
|
|
75
|
-
# A CString or Integer property from
|
98
|
+
# A CString or Integer property from the underlying entity
|
99
|
+
# @param [Symbol, String] name The property name
|
100
|
+
# @param [Hash] options
|
101
|
+
# @option options [Symbol] :type The property type eg :int, :string (default :string)
|
102
|
+
# @return [Fixnum, String, nil]
|
76
103
|
def get_property(name, options = {})
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
case type
|
81
|
-
when :string then get_string(name, from)
|
82
|
-
when :int then get_int(name, from)
|
104
|
+
case options[:type]
|
105
|
+
when :string, nil then get_string(name)
|
106
|
+
when :int then get_int(name)
|
83
107
|
end
|
84
108
|
end
|
85
109
|
|
110
|
+
# Populate the entity properties from the underlying resource
|
111
|
+
def populate
|
112
|
+
@manufacturer = get_property(:manufacturer)
|
113
|
+
@model = get_property(:model)
|
114
|
+
@name = get_name
|
115
|
+
populate_endpoints
|
116
|
+
end
|
117
|
+
|
86
118
|
end
|
87
119
|
|
88
120
|
end
|
data/lib/coremidi/map.rb
CHANGED
@@ -46,6 +46,28 @@ module CoreMIDI
|
|
46
46
|
|
47
47
|
end
|
48
48
|
|
49
|
+
# @param [FFI::Pointer] resource A pointer to an underlying struct
|
50
|
+
# @param [String, Symbol] name The property name to get
|
51
|
+
# @return [String]
|
52
|
+
def self.get_string(resource, name)
|
53
|
+
property = Map::CF.CFStringCreateWithCString(nil, name.to_s, 0)
|
54
|
+
begin
|
55
|
+
pointer = FFI::MemoryPointer.new(:pointer)
|
56
|
+
Map::MIDIObjectGetStringProperty(resource, property, pointer)
|
57
|
+
string = pointer.read_pointer
|
58
|
+
length = Map::CF.CFStringGetMaximumSizeForEncoding(Map::CF.CFStringGetLength(string), :kCFStringEncodingUTF8)
|
59
|
+
|
60
|
+
bytes = FFI::MemoryPointer.new(length + 1)
|
61
|
+
|
62
|
+
if Map::CF.CFStringGetCString(string, bytes, length + 1, :kCFStringEncodingUTF8)
|
63
|
+
bytes.read_string.force_encoding("utf-8")
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
Map::CF.CFRelease(string) unless string.nil? || string.null?
|
67
|
+
Map::CF.CFRelease(property) unless property.null?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
49
71
|
callback :MIDIReadProc, [MIDIPacketList.by_ref, :pointer, :pointer], :pointer
|
50
72
|
|
51
73
|
attach_function :MIDIClientCreate, [:pointer, :pointer, :pointer, :pointer], :int
|
data/lib/coremidi/source.rb
CHANGED
@@ -49,23 +49,25 @@ module CoreMIDI
|
|
49
49
|
alias_method :gets_bytestr, :gets_s
|
50
50
|
|
51
51
|
# Enable this the input for use; can be passed a block
|
52
|
+
# @return [Source]
|
52
53
|
def enable(options = {}, &block)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
if !@enabled
|
55
|
+
@enabled = true
|
56
|
+
if block_given?
|
57
|
+
begin
|
58
|
+
yield(self)
|
59
|
+
ensure
|
60
|
+
close
|
61
|
+
end
|
60
62
|
end
|
61
|
-
else
|
62
|
-
self
|
63
63
|
end
|
64
|
+
self
|
64
65
|
end
|
65
66
|
alias_method :open, :enable
|
66
67
|
alias_method :start, :enable
|
67
68
|
|
68
69
|
# Close this input
|
70
|
+
# @return [Boolean]
|
69
71
|
def close
|
70
72
|
#error = Map.MIDIPortDisconnectSource( @handle, @resource )
|
71
73
|
#raise "MIDIPortDisconnectSource returned error code #{error}" unless error.zero?
|
@@ -75,21 +77,28 @@ module CoreMIDI
|
|
75
77
|
#raise "MIDIPortDispose returned error code #{error}" unless error.zero?
|
76
78
|
#error = Map.MIDIEndpointDispose(@resource)
|
77
79
|
#raise "MIDIEndpointDispose returned error code #{error}" unless error.zero?
|
78
|
-
@enabled
|
79
|
-
|
80
|
+
if @enabled
|
81
|
+
@enabled = false
|
82
|
+
true
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
80
86
|
end
|
81
87
|
|
82
88
|
# Shortcut to the first available input endpoint
|
89
|
+
# @return [Source]
|
83
90
|
def self.first
|
84
91
|
Endpoint.first(:source)
|
85
92
|
end
|
86
93
|
|
87
94
|
# Shortcut to the last available input endpoint
|
95
|
+
# @return [Source]
|
88
96
|
def self.last
|
89
97
|
Endpoint.last(:source)
|
90
98
|
end
|
91
99
|
|
92
100
|
# All input endpoints
|
101
|
+
# @return [Array<Source>]
|
93
102
|
def self.all
|
94
103
|
Endpoint.all_by_type[:source]
|
95
104
|
end
|
@@ -181,6 +190,8 @@ module CoreMIDI
|
|
181
190
|
end
|
182
191
|
|
183
192
|
# Convert an array of numeric byes to a hex string (e.g. [0x90, 0x40, 0x40] becomes "904040")
|
193
|
+
# @param [Array<Fixnum>] bytes
|
194
|
+
# @return [String]
|
184
195
|
def numeric_bytes_to_hex_string(bytes)
|
185
196
|
string_bytes = bytes.map do |byte|
|
186
197
|
str = byte.to_s(16).upcase
|
data/test/input_buffer_test.rb
CHANGED
@@ -2,42 +2,41 @@ require "helper"
|
|
2
2
|
|
3
3
|
class InputBufferTest < Test::Unit::TestCase
|
4
4
|
|
5
|
-
|
6
|
-
include TestHelper
|
5
|
+
context "CoreMIDI" do
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
setup do
|
8
|
+
sleep(1)
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
bytes = []
|
11
|
+
context "Source#buffer" do
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
setup do
|
14
|
+
@messages = TestHelper::VariousMIDIMessages
|
15
|
+
@messages_arr = @messages.inject { |a,b| a+b }.flatten
|
16
|
+
@received_arr = []
|
17
|
+
@pointer = 0
|
18
18
|
|
19
|
-
|
19
|
+
@output = $test_device[:output].open
|
20
|
+
@input = $test_device[:input].open
|
21
|
+
@input.buffer.clear
|
22
|
+
end
|
20
23
|
|
21
|
-
|
24
|
+
should "have the correct messages in the buffer" do
|
25
|
+
bytes = []
|
26
|
+
@messages.each do |message|
|
27
|
+
puts "sending: #{message.inspect}"
|
28
|
+
@output.puts(message)
|
29
|
+
bytes += message
|
22
30
|
|
23
|
-
output.puts(msg)
|
24
|
-
|
25
|
-
bytes += msg
|
26
|
-
|
27
31
|
sleep(0.5)
|
28
|
-
|
29
|
-
buffer = input.buffer.map { |m| m[:data] }.flatten
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
buffer = @input.buffer.map { |m| m[:data] }.flatten
|
34
|
+
puts "received: #{buffer.to_s}"
|
33
35
|
assert_equal(bytes, buffer)
|
34
|
-
|
35
36
|
end
|
36
|
-
|
37
|
-
assert_equal(bytes.length, input.buffer.map { |m| m[:data] }.flatten.length)
|
38
|
-
|
37
|
+
assert_equal(bytes.length, @input.buffer.map { |m| m[:data] }.flatten.length)
|
39
38
|
end
|
40
39
|
end
|
41
|
-
end
|
42
40
|
|
41
|
+
end
|
43
42
|
end
|
data/test/io_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-coremidi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ari Russo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|