midi-communications-macos 0.5.0
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 +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
|