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.
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: [:test]
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
@@ -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