midi-communications-macos 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +10 -0
- data/LICENSE +674 -0
- data/LICENSE.ffi-coremidi +13 -0
- data/LICENSE.midiator +22 -0
- data/LICENSE.prp +22 -0
- data/README.md +118 -0
- data/Rakefile +10 -0
- data/examples/input.rb +25 -0
- data/examples/list_endpoints.rb +9 -0
- data/examples/output.rb +23 -0
- data/examples/sysex_output.rb +12 -0
- data/lib/midi-communications-macos/api.rb +259 -0
- data/lib/midi-communications-macos/destination.rb +142 -0
- data/lib/midi-communications-macos/device.rb +110 -0
- data/lib/midi-communications-macos/endpoint.rb +109 -0
- data/lib/midi-communications-macos/entity.rb +106 -0
- data/lib/midi-communications-macos/source.rb +234 -0
- data/lib/midi-communications-macos/type_conversion.rb +18 -0
- data/lib/midi-communications-macos.rb +26 -0
- data/midi-communications-macos.gemspec +24 -0
- metadata +84 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011-2017 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/LICENSE.midiator
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2008, Ben Bleything <ben@bleything.net>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included
|
14
|
+
in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/LICENSE.prp
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2006, 2007 Topher Cyll
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included
|
14
|
+
in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# MIDI Communications MacOS Layer
|
2
|
+
|
3
|
+
**Realtime MIDI IO with Ruby for OSX.**
|
4
|
+
|
5
|
+
Access the [Apple Core MIDI framework API](https://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CACoreMIDIRef/MIDIServices/) with Ruby.
|
6
|
+
|
7
|
+
This library is part of a suite of Ruby libraries for MIDI:
|
8
|
+
|
9
|
+
| Function | Library |
|
10
|
+
| --- | --- |
|
11
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) |
|
12
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) |
|
13
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) |
|
14
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) |
|
15
|
+
| Low level MIDI interface to Linux | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [alsa-rawmidi](http://github.com/arirusso/alsa-rawmidi)) |
|
16
|
+
| Low level MIDI interface to JRuby | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-jruby](http://github.com/arirusso/midi-jruby))|
|
17
|
+
| Low level MIDI interface to Windows | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-winm](http://github.com/arirusso/midi-winmm)) |
|
18
|
+
|
19
|
+
This library is based on [Ari Russo's](http://github.com/arirusso) library [ffi-coremidi](https://github.com/arirusso/ffi-coremidi).
|
20
|
+
|
21
|
+
## Features
|
22
|
+
|
23
|
+
* Simplified API
|
24
|
+
* Input and output on multiple devices concurrently
|
25
|
+
* Generalized handling of different MIDI Message types (including SysEx)
|
26
|
+
* Timestamped input events
|
27
|
+
* Patch MIDI via software to other programs using IAC
|
28
|
+
|
29
|
+
## Requirements
|
30
|
+
|
31
|
+
* [ffi](http://github.com/ffi/ffi)
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
|
35
|
+
If you're using Bundler, add this line to your application's Gemfile:
|
36
|
+
|
37
|
+
`gem "midi-communications-macos"`
|
38
|
+
|
39
|
+
Otherwise
|
40
|
+
|
41
|
+
`gem install midi-communications-macos`
|
42
|
+
|
43
|
+
## Documentation
|
44
|
+
|
45
|
+
(**TO DO**) [rdoc](http://rubydoc.info/github/javier-sy/midi-communications-macos)
|
46
|
+
|
47
|
+
## Differences between [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) library and [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) library
|
48
|
+
|
49
|
+
[MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) is mostly a clone of [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) with some modifications:
|
50
|
+
* Added locking behaviour when waiting midi events
|
51
|
+
* Removed process history information logging (to reduce CPU usage in some scenarios)
|
52
|
+
* Improved MIDI devices name detection
|
53
|
+
* Source updated to Ruby 2.7 code conventions (method keyword parameters instead of options = {}, hash keys as 'key:' instead of ':key =>', etc.)
|
54
|
+
* Updated dependencies versions
|
55
|
+
* Renamed module to MIDICommunicationsMacOS instead of CoreMIDI
|
56
|
+
* Renamed gem to midi-communications-macos instead of ffi-coremidi
|
57
|
+
* TODO: update tests to use rspec instead of rake
|
58
|
+
* TODO: migrate to (or confirm it's working ok on) Ruby 3.0 and Ruby 3.1
|
59
|
+
|
60
|
+
## Then, why does exist this library if it is mostly a clone of another library?
|
61
|
+
|
62
|
+
The author has been developing since 2016 a Ruby project called
|
63
|
+
[Musa DSL](https://github.com/javier-sy/musa-dsl) that needs a way
|
64
|
+
of representing MIDI Events and a way of communicating with
|
65
|
+
MIDI Instruments and MIDI Control Surfaces.
|
66
|
+
|
67
|
+
[Ari Russo](https://github.com/arirusso) has done a great job creating
|
68
|
+
several interdependent Ruby libraries that allow
|
69
|
+
MIDI Events representation ([MIDI Message](https://github.com/arirusso/midi-message)
|
70
|
+
and [Nibbler](https://github.com/arirusso/nibbler))
|
71
|
+
and communication with MIDI Instruments and MIDI Control Surfaces
|
72
|
+
([unimidi](https://github.com/arirusso/unimidi),
|
73
|
+
[ffi-coremidi](https://github.com/arirusso/ffi-coremidi) and others)
|
74
|
+
that, **with some modifications**, I've been using in MusaDSL.
|
75
|
+
|
76
|
+
After thinking about the best approach to publish MusaDSL
|
77
|
+
I've decided to publish my own renamed version of the modified dependencies because:
|
78
|
+
|
79
|
+
* Some differences on the approach of the modifications vs the original library doesn't allow to merge the modifications on the original libraries.
|
80
|
+
* Then the renaming of the libraries is needed to avoid confusing existent users of the original libraries.
|
81
|
+
* Due to some of the interdependencies of Ari Russo libraries,
|
82
|
+
the modification and renaming on some of the low level libraries (ffi-coremidi, etc.)
|
83
|
+
forces to modify and rename unimidi library.
|
84
|
+
* The original libraries have features
|
85
|
+
(very detailed logging and processing history information, not locking behaviour when waiting input midi messages)
|
86
|
+
that are not needed in MusaDSL and, in fact,
|
87
|
+
can degrade the performance on some use case scenarios in MusaDSL.
|
88
|
+
|
89
|
+
All in all I have decided to publish a suite of libraries optimized for MusaDSL use case that also can be used by other people in their projects.
|
90
|
+
|
91
|
+
| Function | Library | Based on Ari Russo's| Difference |
|
92
|
+
| --- | --- | --- | --- |
|
93
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) | [MIDI Message](https://github.com/arirusso/midi-message) | removed parsing, small improvements |
|
94
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) | [Nibbler](https://github.com/arirusso/nibbler) | removed process history information, minor optimizations |
|
95
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) | [unimidi](https://github.com/arirusso/unimidi) | use of [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos)
|
96
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) | [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) | removed process history information, locking behaviour when waiting midi events, improved midi devices name detection, minor optimizations |
|
97
|
+
| Low level MIDI interface to Linux | **TO DO** | | |
|
98
|
+
| Low level MIDI interface to JRuby | **TO DO** | | |
|
99
|
+
| Low level MIDI interface to Windows | **TO DO** | | |
|
100
|
+
|
101
|
+
## Author
|
102
|
+
|
103
|
+
* [Javier Sánchez Yeste](https://github.com/javier-sy)
|
104
|
+
|
105
|
+
## Acknowledgements
|
106
|
+
|
107
|
+
Thanks to [Ari Russo](http://github.com/arirusso) for his ruby library [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) licensed as Apache License 2.0.
|
108
|
+
|
109
|
+
As explained by **Ari Russo** regarding his library **ffi-coremidi**:
|
110
|
+
* **ffi-coremidi** began with some coremidi/ffi binding code for MIDI output by [Colin Harris](http://github.com/aberant) 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).
|
111
|
+
* [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)
|
112
|
+
* Also thank you to [Jeremy Voorhis](http://github.com/jvoorhis) for some useful debugging.
|
113
|
+
|
114
|
+
### License
|
115
|
+
|
116
|
+
[MIDI Events](https://github.com/javier-sy/midi-events) Copyright (c) 2021 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
|
117
|
+
|
118
|
+
[ffi-coremidi](https://github.com/arirusso/ffi-coremidi) Copyright (c) 2011-2017 [Ari Russo](http://arirusso.com), licensed under Apache License 2.0 (see the file LICENSE.ffi-coremidi)
|
data/Rakefile
ADDED
data/examples/input.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join('..', 'lib'))
|
3
|
+
|
4
|
+
require 'midi-communications-macos'
|
5
|
+
|
6
|
+
# This program selects the first midi input and sends an inspection of the first 10 messages
|
7
|
+
# messages it receives to standard out
|
8
|
+
|
9
|
+
num_messages = 10
|
10
|
+
|
11
|
+
# CoreMIDI::Device.all.to_s will list your midi devices
|
12
|
+
# or amidi -l from the command line
|
13
|
+
|
14
|
+
MIDICommunicationsMacOS::Source.all[0].open do |input|
|
15
|
+
puts "Using input: #{input.id}, #{input.name}"
|
16
|
+
|
17
|
+
puts 'send some MIDI to your input now...'
|
18
|
+
|
19
|
+
num_messages.times do
|
20
|
+
m = input.gets
|
21
|
+
puts(m)
|
22
|
+
end
|
23
|
+
|
24
|
+
puts 'finished'
|
25
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join('..', 'lib'))
|
3
|
+
|
4
|
+
require 'midi-communications-macos'
|
5
|
+
|
6
|
+
# This will output a big list of Endpoint objects. Endpoint objects are what's used to input
|
7
|
+
# and output MIDI messages
|
8
|
+
|
9
|
+
pp MIDICommunicationsMacOS::Device.all.map { |device| device.endpoints.values }.flatten
|
data/examples/output.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join('..', 'lib'))
|
3
|
+
|
4
|
+
require 'midi-communications-macos'
|
5
|
+
|
6
|
+
# This program selects the first midi output and sends some arpeggiated chords to it
|
7
|
+
|
8
|
+
notes = [36, 40, 43] # C E G
|
9
|
+
octaves = 5
|
10
|
+
duration = 0.1
|
11
|
+
|
12
|
+
# CoreMIDI::Device.all.to_s will list your midi devices
|
13
|
+
# or amidi -l from the command line
|
14
|
+
|
15
|
+
MIDICommunicationsMacOS::Destination.first.open do |output|
|
16
|
+
(0..((octaves-1)*12)).step(12) do |oct|
|
17
|
+
notes.each do |note|
|
18
|
+
output.puts(0x90, note + oct, 100) # note on
|
19
|
+
sleep(duration) # wait
|
20
|
+
output.puts(0x80, note + oct, 100) # note off
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join('..', 'lib'))
|
3
|
+
|
4
|
+
require 'midi-communications-macos'
|
5
|
+
|
6
|
+
# This example outputs a raw sysex message to the first Output endpoint
|
7
|
+
# there will not be any output to the console
|
8
|
+
|
9
|
+
output = MIDICommunicationsMacOS::Destination.first
|
10
|
+
sysex_msg = [0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7]
|
11
|
+
|
12
|
+
output.open { |output| output.puts(sysex_msg) }
|
@@ -0,0 +1,259 @@
|
|
1
|
+
module MIDICommunicationsMacOS
|
2
|
+
# Coremidi C binding
|
3
|
+
module API
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib '/System/Library/Frameworks/CoreMIDI.framework/Versions/Current/CoreMIDI'
|
6
|
+
|
7
|
+
# if osx is 10.6 or higher, there are some differences with 32 vs 64 bit handling
|
8
|
+
X86_64 = `uname -r`.scan(/\d*\.\d*/).first.to_f >= 10.6
|
9
|
+
|
10
|
+
typedef :pointer, :CFStringRef
|
11
|
+
typedef :int32, :ItemCount
|
12
|
+
typedef :pointer, :MIDIClientRef
|
13
|
+
typedef :pointer, :MIDIDeviceRef
|
14
|
+
typedef :pointer, :MIDIEndpointRef
|
15
|
+
typedef :pointer, :MIDIEntityRef
|
16
|
+
typedef :pointer, :MIDIObjectRef
|
17
|
+
typedef :pointer, :MIDIPortRef
|
18
|
+
#typedef :pointer, :MIDIReadProc
|
19
|
+
typedef :uint32, :MIDITimeStamp
|
20
|
+
typedef :int32, :OSStatus
|
21
|
+
|
22
|
+
class MIDISysexSendRequest < FFI::Struct
|
23
|
+
|
24
|
+
layout :destination, :MIDIEndpointRef,
|
25
|
+
:data, :pointer,
|
26
|
+
:bytes_to_send, :uint32,
|
27
|
+
:complete, :int,
|
28
|
+
:reserved, [:char, 3],
|
29
|
+
:completion_proc, :pointer,
|
30
|
+
:completion_ref_con, :pointer
|
31
|
+
end
|
32
|
+
|
33
|
+
class MIDIPacket < FFI::Struct
|
34
|
+
|
35
|
+
layout :timestamp, :MIDITimeStamp,
|
36
|
+
:nothing, :uint32, # no idea...
|
37
|
+
:length, :uint16,
|
38
|
+
:data, [:uint8, 256]
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class MIDIPacketList < FFI::Struct
|
43
|
+
layout :numPackets, :uint32,
|
44
|
+
:packet, [MIDIPacket.by_value, 1]
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.get_callback(*args, &block)
|
49
|
+
FFI::Function.new(:void, *args, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Pack the given data into a midi-communications-macos MIDI packet (used by Destination)
|
53
|
+
def self.get_midi_packet(data)
|
54
|
+
format = "C" * data.size
|
55
|
+
packed_data = data.pack(format)
|
56
|
+
char_size = FFI.type_size(:char) * data.size
|
57
|
+
bytes = FFI::MemoryPointer.new(char_size)
|
58
|
+
bytes.write_string(packed_data)
|
59
|
+
bytes
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.create_midi_client(resource_id, name)
|
63
|
+
client_name = API::CF.CFStringCreateWithCString(nil, "Client #{resource_id} #{name}", 0)
|
64
|
+
client_pointer = FFI::MemoryPointer.new(:pointer)
|
65
|
+
error = API.MIDIClientCreate(client_name, nil, nil, client_pointer)
|
66
|
+
client = client_pointer.read_pointer
|
67
|
+
{
|
68
|
+
error: error,
|
69
|
+
resource: client
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.create_midi_input_port(client, resource_id, name, callback)
|
74
|
+
port_name = API::CF.CFStringCreateWithCString(nil, "Port #{resource_id}: #{name}", 0)
|
75
|
+
handle_ptr = FFI::MemoryPointer.new(:pointer)
|
76
|
+
error = API.MIDIInputPortCreate(client, port_name, callback, nil, handle_ptr)
|
77
|
+
handle = handle_ptr.read_pointer
|
78
|
+
{
|
79
|
+
error: error,
|
80
|
+
handle: handle
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.create_midi_output_port(client, resource_id, name)
|
85
|
+
port_name = CF.CFStringCreateWithCString(nil, "Port #{resource_id}: #{name}", 0)
|
86
|
+
port_pointer = FFI::MemoryPointer.new(:pointer)
|
87
|
+
error = API.MIDIOutputPortCreate(client, port_name, port_pointer)
|
88
|
+
handle = port_pointer.read_pointer
|
89
|
+
{
|
90
|
+
error: error,
|
91
|
+
handle: handle
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
# (used by Destination)
|
96
|
+
def self.get_midi_packet_list(bytes, size)
|
97
|
+
packet_list = FFI::MemoryPointer.new(256)
|
98
|
+
packet_ptr = API.MIDIPacketListInit(packet_list)
|
99
|
+
time = HostTime.AudioGetCurrentHostTime
|
100
|
+
packet_ptr = if X86_64
|
101
|
+
API.MIDIPacketListAdd(packet_list, 256, packet_ptr, time, size, bytes)
|
102
|
+
else
|
103
|
+
# Pass in two 32-bit 0s for the 64 bit time
|
104
|
+
time1 = API.MIDIPacketListAdd(packet_list, 256, packet_ptr, time >> 32, time & 0xFFFFFFFF, size, bytes)
|
105
|
+
end
|
106
|
+
packet_list
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param [FFI::Pointer] resource A pointer to an underlying struct
|
110
|
+
# @param [String, Symbol] name The property name to get
|
111
|
+
# @return [Integer]
|
112
|
+
def self.get_int(resource, name)
|
113
|
+
property = API::CF.CFStringCreateWithCString(nil, name.to_s, 0)
|
114
|
+
value = FFI::MemoryPointer.new(:pointer, 32)
|
115
|
+
API::MIDIObjectGetIntegerProperty(resource, property, value)
|
116
|
+
value.read_int
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [FFI::Pointer] resource A pointer to an underlying struct
|
120
|
+
# @param [String, Symbol] name The property name to get
|
121
|
+
# @return [String]
|
122
|
+
def self.get_string(resource, name)
|
123
|
+
property = CF.CFStringCreateWithCString(nil, name.to_s, 0)
|
124
|
+
begin
|
125
|
+
pointer = FFI::MemoryPointer.new(:pointer)
|
126
|
+
MIDIObjectGetStringProperty(resource, property, pointer)
|
127
|
+
string = pointer.read_pointer
|
128
|
+
length = CF.CFStringGetMaximumSizeForEncoding(CF.CFStringGetLength(string), :kCFStringEncodingUTF8)
|
129
|
+
|
130
|
+
bytes = FFI::MemoryPointer.new(length + 1)
|
131
|
+
|
132
|
+
if CF.CFStringGetCString(string, bytes, length + 1, :kCFStringEncodingUTF8)
|
133
|
+
bytes.read_string.force_encoding("utf-8")
|
134
|
+
end
|
135
|
+
ensure
|
136
|
+
CF.CFRelease(string) unless string.nil? || string.null?
|
137
|
+
CF.CFRelease(property) unless property.null?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Called when the system has one or more incoming MIDI messages to deliver to your app.
|
142
|
+
# typedef void (*MIDIReadProc) (const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon);
|
143
|
+
callback :MIDIReadProc, [MIDIPacketList.by_ref, :pointer, :pointer], :pointer
|
144
|
+
|
145
|
+
# OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notifyProc, void *notifyRefCon, MIDIClientRef *outClient);
|
146
|
+
attach_function :MIDIClientCreate, [:pointer, :pointer, :pointer, :pointer], :int
|
147
|
+
|
148
|
+
# OSStatus MIDIClientDispose(MIDIClientRef client);
|
149
|
+
attach_function :MIDIClientDispose, [:pointer], :int
|
150
|
+
|
151
|
+
# MIDIEntityRef MIDIDeviceGetEntity(MIDIDeviceRef device, ItemCount entityIndex0);
|
152
|
+
attach_function :MIDIDeviceGetEntity, [:MIDIDeviceRef, :ItemCount], :MIDIEntityRef
|
153
|
+
|
154
|
+
# MIDIEndpointRef MIDIGetDestination(ItemCount destIndex0);
|
155
|
+
attach_function :MIDIGetNumberOfDestinations, [], :ItemCount
|
156
|
+
|
157
|
+
# ItemCount MIDIGetNumberOfDevices();
|
158
|
+
attach_function :MIDIGetNumberOfDevices, [], :ItemCount
|
159
|
+
|
160
|
+
# MIDIEndpointRef MIDIEntityGetDestination(MIDIEntityRef entity, ItemCount destIndex0);
|
161
|
+
attach_function :MIDIGetDestination, [:int], :pointer
|
162
|
+
|
163
|
+
#extern OSStatus MIDIEndpointDispose( MIDIEndpointRef endpt );
|
164
|
+
attach_function :MIDIEndpointDispose, [:MIDIEndpointRef], :OSStatus
|
165
|
+
|
166
|
+
# MIDIEndpointRef MIDIEntityGetDestination( MIDIEntityRef entity, ItemCount destIndex0 );
|
167
|
+
attach_function :MIDIEntityGetDestination, [:MIDIEntityRef, :int], :MIDIEndpointRef
|
168
|
+
|
169
|
+
# ItemCount MIDIEntityGetNumberOfDestinations (MIDIEntityRef entity);
|
170
|
+
attach_function :MIDIEntityGetNumberOfDestinations, [:MIDIEntityRef], :ItemCount
|
171
|
+
|
172
|
+
# ItemCount MIDIEntityGetNumberOfSources (MIDIEntityRef entity);
|
173
|
+
attach_function :MIDIEntityGetNumberOfSources, [:MIDIEntityRef], :ItemCount
|
174
|
+
|
175
|
+
# MIDIEndpointRef MIDIEntityGetSource (MIDIEntityRef entity, ItemCount sourceIndex0);
|
176
|
+
attach_function :MIDIEntityGetSource, [:MIDIEntityRef, :ItemCount], :MIDIEndpointRef
|
177
|
+
|
178
|
+
# MIDIDeviceRef MIDIGetDevice(ItemCount deviceIndex0);
|
179
|
+
attach_function :MIDIGetDevice, [:ItemCount], :MIDIDeviceRef
|
180
|
+
|
181
|
+
# extern OSStatus MIDIInputPortCreate( MIDIClientRef client, CFStringRef portName,
|
182
|
+
# MIDIReadProc readProc, void * refCon, MIDIPortRef * outPort );
|
183
|
+
attach_function :MIDIInputPortCreate, [:MIDIClientRef, :CFStringRef, :MIDIReadProc, :pointer, :MIDIPortRef], :OSStatus
|
184
|
+
|
185
|
+
# extern OSStatus MIDIObjectGetIntegerProperty( MIDIObjectRef obj, CFStringRef propertyID, SInt32 * outValue );
|
186
|
+
attach_function :MIDIObjectGetIntegerProperty, [:MIDIObjectRef, :CFStringRef, :pointer], :OSStatus
|
187
|
+
|
188
|
+
# OSStatus MIDIObjectGetStringProperty (MIDIObjectRef obj, CFStringRef propertyID, CFStringRef *str);
|
189
|
+
attach_function :MIDIObjectGetStringProperty, [:MIDIObjectRef, :CFStringRef, :pointer], :OSStatus
|
190
|
+
|
191
|
+
# extern OSStatus MIDIOutputPortCreate( MIDIClientRef client, CFStringRef portName, MIDIPortRef * outPort );
|
192
|
+
attach_function :MIDIOutputPortCreate, [:MIDIClientRef, :CFStringRef, :pointer], :int
|
193
|
+
|
194
|
+
# (MIDIPacket*) MIDIPacketListInit(MIDIPacketList *pktlist);
|
195
|
+
attach_function :MIDIPacketListInit, [:pointer], :pointer
|
196
|
+
|
197
|
+
#extern OSStatus MIDIPortConnectSource( MIDIPortRef port, MIDIEndpointRef source, void * connRefCon )
|
198
|
+
attach_function :MIDIPortConnectSource, [:MIDIPortRef, :MIDIEndpointRef, :pointer], :OSStatus
|
199
|
+
|
200
|
+
#extern OSStatus MIDIPortDisconnectSource( MIDIPortRef port, MIDIEndpointRef source );
|
201
|
+
attach_function :MIDIPortDisconnectSource, [:MIDIPortRef, :MIDIEndpointRef], :OSStatus
|
202
|
+
|
203
|
+
#extern OSStatus MIDIPortDispose(MIDIPortRef port );
|
204
|
+
attach_function :MIDIPortDispose, [:MIDIPortRef], :OSStatus
|
205
|
+
|
206
|
+
#extern OSStatus MIDISend(MIDIPortRef port,MIDIEndpointRef dest,const MIDIPacketList *pktlist);
|
207
|
+
attach_function :MIDISend, [:MIDIPortRef, :MIDIEndpointRef, :pointer], :int
|
208
|
+
|
209
|
+
#OSStatus MIDISendSysex(MIDISysexSendRequest *request);
|
210
|
+
attach_function :MIDISendSysex, [:pointer], :int
|
211
|
+
|
212
|
+
# extern MIDIPacket * MIDIPacketListAdd( MIDIPacketList * pktlist, ByteCount listSize,
|
213
|
+
# MIDIPacket * curPacket, MIDITimeStamp time,
|
214
|
+
# ByteCount nData, const Byte * data)
|
215
|
+
if X86_64
|
216
|
+
attach_function :MIDIPacketListAdd, [:pointer, :int, :pointer, :uint64, :int, :pointer], :pointer
|
217
|
+
else
|
218
|
+
attach_function :MIDIPacketListAdd, [:pointer, :int, :pointer, :int, :int, :int, :pointer], :pointer
|
219
|
+
end
|
220
|
+
|
221
|
+
module CF
|
222
|
+
|
223
|
+
extend FFI::Library
|
224
|
+
ffi_lib '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation'
|
225
|
+
|
226
|
+
typedef :pointer, :CFStringRef
|
227
|
+
typedef :long, :CFIndex
|
228
|
+
enum :CFStringEncoding, [ :kCFStringEncodingUTF8, 0x08000100 ]
|
229
|
+
|
230
|
+
# CFString* CFStringCreateWithCString( ?, CString, encoding)
|
231
|
+
attach_function :CFStringCreateWithCString, [:pointer, :string, :int], :pointer
|
232
|
+
# CString* CFStringGetCStringPtr(CFString*, encoding)
|
233
|
+
attach_function :CFStringGetCStringPtr, [:pointer, :int], :pointer
|
234
|
+
|
235
|
+
# CFIndex CFStringGetLength(CFStringRef theString);
|
236
|
+
attach_function :CFStringGetLength, [ :CFStringRef ], :CFIndex
|
237
|
+
|
238
|
+
# CFIndex CFStringGetMaximumSizeForEncoding(CFIndex length, CFStringEncoding encoding);
|
239
|
+
attach_function :CFStringGetMaximumSizeForEncoding, [ :CFIndex, :CFStringEncoding ], :long
|
240
|
+
|
241
|
+
# Boolean CFStringGetCString(CFStringRef theString, char *buffer, CFIndex bufferSize, CFStringEncoding encoding);
|
242
|
+
attach_function :CFStringGetCString, [ :CFStringRef, :pointer, :CFIndex, :CFStringEncoding ], :bool
|
243
|
+
|
244
|
+
# void CFRelease (CFTypeRef cf);
|
245
|
+
attach_function :CFRelease, [ :pointer ], :void
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
module HostTime
|
250
|
+
extend FFI::Library
|
251
|
+
ffi_lib '/System/Library/Frameworks/CoreAudio.framework/Versions/Current/CoreAudio'
|
252
|
+
|
253
|
+
# UInt64 AudioConvertHostTimeToNanos(UInt64 IO)
|
254
|
+
attach_function :AudioConvertHostTimeToNanos, [:uint64], :uint64
|
255
|
+
# UInt64 AudioGetCurrentHostTime()
|
256
|
+
attach_function :AudioGetCurrentHostTime, [], :uint64
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module MIDICommunicationsMacOS
|
2
|
+
# Type of endpoint used for output
|
3
|
+
class Destination
|
4
|
+
include Endpoint
|
5
|
+
|
6
|
+
attr_reader :entity
|
7
|
+
|
8
|
+
# Close this output
|
9
|
+
# @return [Boolean]
|
10
|
+
def close
|
11
|
+
if @enabled
|
12
|
+
@enabled = false
|
13
|
+
true
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Send a MIDI message comprised of a String of hex digits
|
20
|
+
# @param [String] data A string of hex digits eg "904040"
|
21
|
+
# @return [Boolean]
|
22
|
+
def puts_s(data)
|
23
|
+
data = data.dup
|
24
|
+
bytes = []
|
25
|
+
until (str = data.slice!(0, 2)).eql?('')
|
26
|
+
bytes << str.hex
|
27
|
+
end
|
28
|
+
puts_bytes(*bytes)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
alias puts_bytestr puts_s
|
32
|
+
alias puts_hex puts_s
|
33
|
+
|
34
|
+
# Send a MIDI message comprised of numeric bytes
|
35
|
+
# @param [*Integer] data Numeric bytes eg 0x90, 0x40, 0x40
|
36
|
+
# @return [Boolean]
|
37
|
+
def puts_bytes(*data)
|
38
|
+
type = sysex?(data) ? :sysex : :small
|
39
|
+
bytes = API.get_midi_packet(data)
|
40
|
+
send("puts_#{type.to_s}", bytes, data.size)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# Send a MIDI message of indeterminate type
|
45
|
+
# @param [*Array<Integer>, *Array<String>, *Integer, *String] args
|
46
|
+
# @return [Boolean]
|
47
|
+
def puts(*args)
|
48
|
+
case args.first
|
49
|
+
when Array then args.each { |arg| puts(*arg) }
|
50
|
+
when Integer then puts_bytes(*args)
|
51
|
+
when String then puts_bytestr(*args)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
alias write puts
|
55
|
+
|
56
|
+
# Enable this device
|
57
|
+
# @return [Destination]
|
58
|
+
def enable
|
59
|
+
@enabled ||= true
|
60
|
+
if block_given?
|
61
|
+
begin
|
62
|
+
yield(self)
|
63
|
+
ensure
|
64
|
+
close
|
65
|
+
end
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
alias open enable
|
70
|
+
alias start enable
|
71
|
+
|
72
|
+
# Shortcut to the first output endpoint available
|
73
|
+
# @return [Destination]
|
74
|
+
def self.first
|
75
|
+
Endpoint.first(:destination)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Shortcut to the last output endpoint available
|
79
|
+
# @return [Destination]
|
80
|
+
def self.last
|
81
|
+
Endpoint.last(:destination)
|
82
|
+
end
|
83
|
+
|
84
|
+
# All output endpoints
|
85
|
+
# @return [Array<Destination>]
|
86
|
+
def self.all
|
87
|
+
Endpoint.all_by_type[:destination]
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Base initialization for this endpoint -- done whether or not the endpoint is enabled to
|
93
|
+
# check whether it is truly available for use
|
94
|
+
# @return [Boolean]
|
95
|
+
def connect
|
96
|
+
client_error = enable_client
|
97
|
+
port_error = initialize_port
|
98
|
+
@resource = API.MIDIEntityGetDestination(@entity.resource, @resource_id)
|
99
|
+
!@resource.address.zero? && client_error.zero? && port_error.zero?
|
100
|
+
end
|
101
|
+
alias connect? connect
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Output a short MIDI message
|
106
|
+
def puts_small(bytes, size)
|
107
|
+
packet_list = API.get_midi_packet_list(bytes, size)
|
108
|
+
API.MIDISend(@handle, @resource, packet_list)
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Output a System Exclusive MIDI message
|
113
|
+
def puts_sysex(bytes, size)
|
114
|
+
request = API::MIDISysexSendRequest.new
|
115
|
+
request[:destination] = @resource
|
116
|
+
request[:data] = bytes
|
117
|
+
request[:bytes_to_send] = size
|
118
|
+
request[:complete] = 0
|
119
|
+
request[:completion_proc] = SysexCompletionCallback
|
120
|
+
request[:completion_ref_con] = request
|
121
|
+
API.MIDISendSysex(request)
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
SysexCompletionCallback =
|
126
|
+
API.get_callback([:pointer]) do |sysex_request_ptr|
|
127
|
+
# this isn't working for some reason. as of now, it's not needed though
|
128
|
+
end
|
129
|
+
|
130
|
+
# Initialize a midi-communications-macos port for this endpoint
|
131
|
+
def initialize_port
|
132
|
+
port = API.create_midi_output_port(@client, @resource_id, @name)
|
133
|
+
@handle = port[:handle]
|
134
|
+
port[:error]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Is the given data a MIDI sysex message?
|
138
|
+
def sysex?(data)
|
139
|
+
data.first.eql?(0xF0) && data.last.eql?(0xF7)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|