midi-communications-macos 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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