midi-winmm 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2010-2011 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.
@@ -0,0 +1,46 @@
1
+ = midi-winmm
2
+
3
+ == Summary
4
+
5
+ Realtime MIDI input and output with Ruby for Windows/Cygwin. Uses the WinMM system API
6
+
7
+ Note that in the interest of allowing people on other platforms to utilize your code, you should consider using {unimidi}[http://github.com/arirusso/unimidi]. Unimidi is a platform independent wrapper which implements midi-winmm.
8
+
9
+ == Features
10
+
11
+ * Input and output on multiple devices concurrently
12
+ * Agnostically handle different MIDI Message types (including SysEx)
13
+ * Timestamped input events
14
+
15
+ == Requirements
16
+
17
+ * {ffi}[http://github.com/ffi/ffi] (gem install ffi)
18
+
19
+ == Install
20
+
21
+ gem install midi-winmm
22
+
23
+ == Examples
24
+
25
+ * {input}[http://github.com/arirusso/midi-winmm/blob/master/examples/input.rb]
26
+ * {output}[http://github.com/arirusso/midi-winmm/blob/master/examples/output.rb]
27
+
28
+ ({more}[http://github.com/arirusso/midi-winmm/blob/master/examples])
29
+
30
+ == Tests
31
+
32
+ please see {test/config.rb}[http://github.com/arirusso/midi-winmm/blob/master/test/config.rb] before running tests
33
+
34
+ == Documentation
35
+
36
+ {rdoc}[http://rdoc.info/gems/midi-winmm]
37
+
38
+ == Author
39
+
40
+ {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
41
+
42
+ == License
43
+
44
+ Apache 2.0, See the file LICENSE
45
+
46
+ Copyright (c) 2010-2011 Ari Russo
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Set of modules and classes for interacting with MIDI functions
4
+ # of the WinMM System API
5
+ #
6
+ module MIDIWinMM
7
+ VERSION = "0.1.10"
8
+ end
9
+
10
+ require 'ffi'
11
+
12
+ require 'midi-winmm/device'
13
+ require 'midi-winmm/input'
14
+ require 'midi-winmm/map'
15
+ require 'midi-winmm/output'
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ module MIDIWinMM
3
+
4
+ #
5
+ # Module containing methods used by both input and output devices when using the
6
+ # WinMM driver interface
7
+ #
8
+ module Device
9
+
10
+ attr_reader :enabled, :id, :info, :name, :type
11
+
12
+ alias_method :enabled?, :enabled
13
+
14
+ def initialize(id, name, options = {}, &block)
15
+ @id = id
16
+ @info = options[:info]
17
+ @type = self.class.name.split('::').last.downcase.to_sym
18
+ @name = name
19
+ end
20
+
21
+ def self.all
22
+ all_by_type.values.flatten
23
+ end
24
+
25
+ def self.all_by_type
26
+ types = { :input => Input,
27
+ :output => Output }
28
+ available_devices = { :input => [], :output => [] }
29
+ count = 0
30
+ types.each do |type, klass|
31
+ (0..(Map::cfunc(type, :getNumDevs)-1)).each do |i|
32
+ data = Map::DeviceInfo[type].new
33
+ Map::cfunc(type, :getDevCapsA, i, data.to_ptr, Map::DeviceInfo[type].size)
34
+ name = data[:szPname].to_s
35
+ dev = klass.new(i, name, :info => data)
36
+ available_devices[type] << dev
37
+ end
38
+ end
39
+ available_devices
40
+ end
41
+
42
+ # select the first device of type <em>type</em>
43
+ def self.first(type)
44
+ all_by_type[type].first
45
+ end
46
+
47
+ # select the last device of type <em>type</em>
48
+ def self.last(type)
49
+ all_by_type[type].last
50
+ end
51
+
52
+ WinmmCallbackFlag = 0x30000 # we plan to use a callback to collect events
53
+
54
+ private
55
+
56
+ # High word
57
+ # High-order byte: Not used.
58
+ # Low-order byte: Contains a second byte of MIDI data (when needed).
59
+ # Low word
60
+ # High-order byte: Contains the first byte of MIDI data (when needed).
61
+ # Low-order byte: Contains the MIDI status.
62
+ #
63
+ def dwmsg_to_array_of_bytes(m)
64
+ # there has to be a better way to do this
65
+ s = []
66
+ m = m.to_s(16)
67
+ (1..m.length).step(2) { |i| s << m[(m.length-i)-1, 2].hex }
68
+ s
69
+ end
70
+
71
+ def error?(num)
72
+ Map::error?(num)
73
+ end
74
+
75
+ def error(num)
76
+ Map::error(num)
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env ruby
2
+ module MIDIWinMM
3
+
4
+ #
5
+ # Input device class for the WinMM driver interface
6
+ #
7
+ class Input
8
+
9
+ include Device
10
+
11
+ BufferSize = 256
12
+
13
+ attr_reader :buffer
14
+
15
+ # initializes this device
16
+ def enable(options = {}, &block)
17
+ init_input_buffer
18
+ handle_ptr = FFI::MemoryPointer.new(FFI.type_size(:int))
19
+ initialize_local_buffer
20
+ @event_callback = get_event_callback
21
+
22
+ Map.winmm_func(:midiInOpen, handle_ptr, @id, @event_callback, 0, Device::WinmmCallbackFlag)
23
+
24
+ @handle = handle_ptr.read_int
25
+
26
+ Map.winmm_func(:midiInPrepareHeader, @handle, @header.pointer, @header.size)
27
+ Map.winmm_func(:midiInAddBuffer, @handle, @header.pointer, @header.size)
28
+ Map.winmm_func(:midiInStart, @handle)
29
+
30
+ @enabled = true
31
+
32
+ unless block.nil?
33
+ begin
34
+ yield(self)
35
+ ensure
36
+ close
37
+ end
38
+ else
39
+ self
40
+ end
41
+
42
+ end
43
+ alias_method :start, :enable
44
+ alias_method :open, :enable
45
+
46
+ #
47
+ # returns an array of MIDI event hashes as such:
48
+ # [
49
+ # { :data => [144, 90, 100], :timestamp => 1024 },
50
+ # { :data => [128, 90, 100], :timestamp => 1100 },
51
+ # { :data => [146, 60, 120], :timestamp => 1200 }
52
+ # ]
53
+ #
54
+ # message data is an array of Numeric bytes
55
+ #
56
+ def gets
57
+ until queued_messages?
58
+ end
59
+ msgs = queued_messages
60
+ @pointer = @buffer.length
61
+ msgs
62
+ end
63
+
64
+ # same as gets but returns message data as string of hex digits as such:
65
+ # [
66
+ # { :data => "904060", :timestamp => 904 },
67
+ # { :data => "804060", :timestamp => 1150 },
68
+ # { :data => "90447F", :timestamp => 1300 }
69
+ # ]
70
+ #
71
+ #
72
+ def gets_s
73
+ msgs = gets
74
+ msgs.each { |msg| msg[:data] = numeric_bytes_to_hex_string(msg[:data]) }
75
+ msgs
76
+ end
77
+ alias_method :gets_bytestr, :gets_s
78
+ alias_method :gets_hex, :gets_s
79
+
80
+ # close the device
81
+ def close
82
+ Map.winmm_func(:midiInUnprepareHeader, @handle, @header.pointer, @header.size)
83
+ Map.winmm_func(:midiInStop, @handle)
84
+ Map.winmm_func(:midiInClose, @handle)
85
+ @enabled = false
86
+ end
87
+
88
+ def self.first
89
+ Device.first(:input)
90
+ end
91
+
92
+ def self.last
93
+ Device.last(:input)
94
+ end
95
+
96
+ def self.all
97
+ Device.all_by_type[:input]
98
+ end
99
+
100
+ private
101
+
102
+ def queued_messages
103
+ @buffer.slice(@pointer, @buffer.length - @pointer)
104
+ end
105
+
106
+ def queued_messages?
107
+ @pointer < @buffer.length
108
+ end
109
+
110
+ # prepare the header struct where input event information is held
111
+ def init_input_buffer
112
+ @header = Map::MIDIHdr.new
113
+ @header.write_data(Input::BufferSize)
114
+ @header[:dwBytesRecorded] = 0
115
+ @header[:dwFlags] = 0
116
+ @header[:dwUser] = 0
117
+ @header[:dwBufferLength] = Input::BufferSize
118
+ end
119
+
120
+ # returns a Proc that is called when the device receives a message
121
+ def get_event_callback
122
+ Proc.new do |hMidiIn,wMsg,dwInstance,dwParam1,dwParam2|
123
+ msg_type = Map::CallbackMessageTypes[wMsg] || ''
124
+ case msg_type
125
+ when :input_data then
126
+ msg = { :data => dwmsg_to_array_of_bytes(dwParam1), :timestamp => dwParam2 }
127
+ @buffer << msg
128
+ when :input_long_data then
129
+ @receiving_sysex = true
130
+ data = @header[:lpData].read_string(Input::BufferSize).gsub(/ /, '')
131
+ unless data.eql?("")
132
+ str = data.unpack(("C" * (data.length-1)))
133
+ msg = { :data => str, :timestamp => dwParam2 }
134
+ @buffer << msg
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def initialize_local_buffer
141
+ @pointer = 0
142
+ @buffer = []
143
+ def @buffer.clear
144
+ super
145
+ @pointer = 0
146
+ end
147
+ end
148
+
149
+ def numeric_bytes_to_hex_string(bytes)
150
+ bytes.map { |b| s = b.to_s(16).upcase; b < 16 ? s = "0" + s : s; s }.join
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env ruby
2
+ module MIDIWinMM
3
+
4
+ #
5
+ # Module containing C function and struct binding for the WinMM
6
+ # driver interface library
7
+ #
8
+ module Map
9
+
10
+ extend FFI::Library
11
+ ffi_lib 'Winmm'
12
+ ffi_convention :stdcall
13
+
14
+ HeaderFlags = {
15
+ 0x00000001 => :done,
16
+ 0x00000002 => :prepared,
17
+ 0x00000004 => :inqueue,
18
+ 0x00000008 => :isstream
19
+ }
20
+
21
+ CallbackMessageTypes = {
22
+ 0x3C1 => :input_open,
23
+ 0x3C2 => :input_close,
24
+ 0x3C3 => :input_data,
25
+ 0x3C4 => :input_long_data,
26
+ 0x3C5 => :input_error,
27
+ 0x3C6 => :input_long_error,
28
+ 0x3C7 => :output_open,
29
+ 0x3C8 => :output_close,
30
+ 0x3C9 => :output_data,
31
+ 0x3CC => :output_more_data
32
+ }
33
+
34
+ Errors = {
35
+ 1 => "Unspecified",
36
+ 2 => "Bad Device ID",
37
+ 3 => "Not Enabled",
38
+ 4 => "Allocation",
39
+ 5 => "Invalid Handle",
40
+ 6 => "No Driver",
41
+ 7 => "No Memory",
42
+ 8 => "Not Supported",
43
+ 9 => "Bad Error Number",
44
+ 10 => "Invalid Flag",
45
+ 11 => "Invalid Parameter",
46
+ 12 => "Handle Busy",
47
+ 13 => "Invalid Alias"
48
+ }
49
+
50
+ # Byte (8 bits). Declared as unsigned char.
51
+ typedef :uchar, :BYTE
52
+ # 32-bit unsigned integer. The range is 0 through 4,294,967,295 decimal.
53
+ typedef :uint32, :DWORD
54
+ # Unsigned long type for pointer precision. Use when casting a pointer to a long type.
55
+ typedef :ulong, :DWORD_PTR
56
+ # (L) Handle to an object. WinNT.h: #typedef PVOID HANDLE;
57
+ typedef :ulong, :HANDLE
58
+ # Handle for a MIDI input device.
59
+ typedef :HANDLE, :HMIDIIN
60
+ # Handle for a MIDI output device.
61
+ typedef :HANDLE, :HMIDIOUT
62
+ # system function result codes
63
+ typedef :uint, :MMRESULT
64
+ # Unsigned INT_PTR.
65
+ typedef :uint, :UINT_PTR
66
+ # 16-bit unsigned integer. The range is 0 through 65535 decimal.
67
+ typedef :ushort, :WORD
68
+
69
+ class MIDIEvent < FFI::Struct
70
+ layout :dwDeltaTime, :ulong,
71
+ :dwStreamID, :ulong,
72
+ :dwEvent, :ulong,
73
+ :dwParms, [:ulong, 8]
74
+ end
75
+
76
+ class MIDIHdr < FFI::Struct
77
+ layout :lpData, :pointer,
78
+ :dwBufferLength, :DWORD,
79
+ :dwBytesRecorded, :DWORD,
80
+ :dwUser, :DWORD_PTR,
81
+ :dwFlags, :DWORD,
82
+ :lpNext, :pointer,
83
+ :reserved, :DWORD_PTR,
84
+ :dwOffset, :DWORD,
85
+ :dwReserved, :DWORD_PTR
86
+
87
+ def write_data(size, string = '')
88
+ ptr = FFI::MemoryPointer.new(:char, size)
89
+ blank = " " * (size-string.length-1)
90
+ ptr.put_string(0, string + blank)
91
+ self[:lpData] = ptr
92
+ self[:dwBufferLength] = string.length
93
+ end
94
+ end
95
+
96
+ class MIDIInputInfo < FFI::Struct
97
+ layout :wMid, :WORD,
98
+ :wPid, :WORD,
99
+ :vDriverVersion, :ulong,
100
+ :szPname, [:char, 32],
101
+ :dwSupport, :DWORD
102
+ end
103
+
104
+ class MIDIOutputInfo < FFI::Struct
105
+ layout :wMid, :WORD,
106
+ :wPid, :WORD,
107
+ :vDriverVersion, :ulong,
108
+ :szPname, [:char, 32],
109
+ :wTechnology, :WORD,
110
+ :wVoices, :WORD,
111
+ :wNotes, :WORD,
112
+ :wChannelMask, :WORD,
113
+ :dwSupport, :DWORD
114
+ end
115
+
116
+ DeviceInfo = {
117
+
118
+ :input => MIDIInputInfo,
119
+ :output => MIDIOutputInfo
120
+
121
+ }
122
+
123
+ # void CALLBACK MidiInProc(HMIDIIN hMidiIn,UINT wMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
124
+ callback :input_callback, [:pointer, :uint, :DWORD_PTR, :DWORD_PTR, :DWORD_PTR], :void
125
+
126
+ # void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
127
+ callback :output_callback, [:pointer, :uint, :DWORD_PTR, :DWORD_PTR, :DWORD_PTR], :void
128
+
129
+ #
130
+ # initialize/close devices
131
+ #
132
+
133
+ # MMRESULT midiInOpen(LPHMIDIIN lphMidiIn, UINT_PTR uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwCallbackInstance, DWORD dwFlags)
134
+ # LPHMIDIIN = *HMIDIIN
135
+ attach_function :midiInOpen, [:pointer, :UINT_PTR, :input_callback, :DWORD_PTR, :DWORD], :MMRESULT
136
+
137
+ # MMRESULT midiOutOpen(LPHMIDIOUT lphmo, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwCallbackInstance, DWORD dwFlags)
138
+ # LPHMIDIOUT = *HMIDIOUT
139
+ # :output_callback
140
+ attach_function :midiOutOpen, [:pointer, :uint, :output_callback, :DWORD_PTR, :DWORD], :MMRESULT
141
+
142
+ attach_function :midiInClose, [:ulong], :ulong
143
+ attach_function :midiOutClose, [:ulong], :ulong
144
+ attach_function :midiInReset, [:pointer], :ulong
145
+
146
+ # MMRESULT midiOutReset(HMIDIOUT hmo)
147
+ attach_function :midiOutReset, [:HMIDIOUT], :MMRESULT
148
+
149
+ #
150
+ # for message output
151
+ #
152
+
153
+ # MMRESULT midiOutShortMsg(HMIDIOUT hmo, DWORD dwMsg)
154
+ attach_function :midiOutShortMsg, [:HMIDIOUT, :DWORD], :MMRESULT
155
+
156
+ # MMRESULT midiOutLongMsg(HMIDIOUT hmo, LPMIDIHDR lpMidiOutHdr,UINT cbMidiOutHdr)
157
+ # LPMIDIHDR = *MIDIHDR
158
+ attach_function :midiOutLongMsg, [:HMIDIOUT, :pointer, :uint], :MMRESULT
159
+
160
+ # MMRESULT midiStreamOpen(LPHMIDISTRM lphStream,LPUINT puDeviceID, DWORD cMidi, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen)
161
+ attach_function :midiStreamOpen, [:pointer, :pointer, :DWORD, :DWORD_PTR, :DWORD_PTR, :DWORD], :MMRESULT
162
+
163
+ # MMRESULT midiOutPrepareHeader(HMIDIOUT hmo, LPMIDIHDR lpMidiOutHdr, UINT cbMidiOutHdr)
164
+ attach_function :midiOutPrepareHeader, [:HMIDIOUT, :pointer, :uint], :MMRESULT
165
+
166
+ #MMRESULT midiOutUnprepareHeader(HMIDIOUT hmo, LPMIDIHDR lpMidiOutHdr, UINT cbMidiOutHdr)
167
+ attach_function :midiOutUnprepareHeader, [:HMIDIOUT, :ulong, :uint], :MMRESULT
168
+
169
+ #MMRESULT midiOutGetVolume(HMIDIOUT hmo, LPDWORD lpdwVolume)
170
+ attach_function :midiOutGetVolume, [:HMIDIOUT, :pointer], :MMRESULT
171
+
172
+ # MMRESULT midiOutSetVolume(HMIDIOUT hmo, DWORD dwVolume)
173
+ attach_function :midiOutSetVolume, [:HMIDIOUT, :DWORD], :MMRESULT
174
+
175
+ #
176
+ # input
177
+ #
178
+
179
+ # MMRESULT midiInPrepareHeader(HMIDIIN hMidiIn, LPMIDIHDR lpMidiInHdr, UINT cbMidiInHdr)
180
+ attach_function :midiInPrepareHeader, [:HMIDIIN, :pointer, :uint], :MMRESULT
181
+
182
+ attach_function :midiInUnprepareHeader, [:HMIDIIN, :pointer, :uint], :MMRESULT
183
+ attach_function :midiInAddBuffer, [:HMIDIIN, :pointer, :uint], :MMRESULT
184
+
185
+ # MMRESULT midiInStart(HMIDIIN hMidiIn)
186
+ attach_function :midiInStart, [:HMIDIIN], :MMRESULT
187
+
188
+ # MMRESULT midiInStop(HMIDIIN hMidiIn)
189
+ attach_function :midiInStop, [:HMIDIIN], :MMRESULT
190
+
191
+ #
192
+ # enumerate devices
193
+ #
194
+
195
+ # UINT midiInGetNumDevs(void)
196
+ attach_function :midiInGetNumDevs, [], :uint
197
+
198
+ # UINT midiOutGetNumDevs(void)
199
+ attach_function :midiOutGetNumDevs, [], :uint
200
+
201
+ # MMRESULT midiInGetDevCaps(UINT_PTR uDeviceID, LPMIDIINCAPS lpMidiInCaps, UINT cbMidiInCaps);
202
+ attach_function :midiInGetDevCapsA, [:UINT_PTR, :pointer, :uint], :MMRESULT
203
+
204
+ attach_function :midiOutGetDevCapsA, [:UINT_PTR, :pointer, :uint], :MMRESULT
205
+
206
+ # shortcut for calling winmm midi functions
207
+ def self.cfunc(type, funcname, *args)
208
+ t = type.to_s.gsub(/put/,'') # convert output to out, input to in
209
+ name = funcname.to_s
210
+ name[0] = name[0,1].upcase # capitalize
211
+ t[0] = t[0,1].upcase # capitalize
212
+ self.send("midi#{t}#{name}", *args)
213
+ end
214
+
215
+ def self.winmm_func(name, *args)
216
+ status = self.send(name, *args)
217
+ raise "#{name.to_s}: #{error(status)}" if error?(status)
218
+ end
219
+
220
+ def self.error?(num)
221
+ !Map::Errors[num].nil?
222
+ end
223
+
224
+ def self.error(num)
225
+ Map::Errors[num]
226
+ end
227
+
228
+ end
229
+ end
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+ module MIDIWinMM
3
+
4
+ #
5
+ # Output device class for the WinMM driver interface
6
+ #
7
+ class Output
8
+
9
+ include Device
10
+
11
+ BufferSize = 2048
12
+
13
+ attr_reader :buffer
14
+
15
+ # initialize this device
16
+ def enable(options = {}, &block)
17
+ init_output_buffer
18
+ Map.winmm_func(:midiOutOpen, Output::HandlePointer, @id, Output::EventCallback, 0, Device::WinmmCallbackFlag)
19
+ @handle = HandlePointer.read_int
20
+ @enabled = true
21
+ unless block.nil?
22
+ begin
23
+ yield(self)
24
+ ensure
25
+ close
26
+ end
27
+ else
28
+ self
29
+ end
30
+ end
31
+ alias_method :start, :enable
32
+ alias_method :open, :enable
33
+
34
+ # close this device
35
+ def close
36
+ Map.winmm_func(:midiOutClose, @handle)
37
+ @enabled = false
38
+ end
39
+
40
+ # returns a hash of fixnum values { :left => n, :right => n2 }
41
+ def volume
42
+ volume = FFI::MemoryPointer.new(FFI.type_size(:ulong))
43
+
44
+ Map.winmm_func(:midiOutGetVolume, @handle, volume)
45
+
46
+ str = dwmsg_to_array_of_bytes(volume.read_ulong)
47
+ left = str.slice!(0,4)
48
+
49
+ { :left => left.hex, :right => str.hex }
50
+ end
51
+
52
+ # accepts either a hash of fixnums { :left => n, :right => n2 } or
53
+ # a single fixnum that will be applied to both channels
54
+ def volume=(val)
55
+ vol = val.kind_of?(Hash) ? (val[:left] + (val[:right] << 16)) : (val + (val << 16))
56
+ Map.winmm_func(:midiOutSetVolume, @handle, vol)
57
+ end
58
+
59
+ def reset
60
+ Map.winmm_func(:midiOutReset, @handle)
61
+ end
62
+
63
+ # send a message of an indeterminate type
64
+ def puts(*a)
65
+ case a.first
66
+ when Array then puts_bytes(*a.first)
67
+ when Numeric then puts_bytes(*a)
68
+ when String then puts_s(*a)
69
+ end
70
+ end
71
+
72
+ # send a message consisting of Numeric bytes
73
+ def puts_bytes(*message_bytes)
74
+ format = "C" * message_bytes.size
75
+
76
+ packed = message_bytes.pack(format)
77
+ data_pointer = FFI::MemoryPointer.new(message_bytes.size).put_bytes(0, packed)
78
+
79
+ @header[:dwBufferLength] = message_bytes.size
80
+ @header[:dwBytesRecorded] = message_bytes.size
81
+ @header[:lpData] = data_pointer
82
+
83
+ Map.winmm_func(:midiOutPrepareHeader, @handle, @header.pointer, @header.size)
84
+
85
+ Map.winmm_func(:midiOutLongMsg, @handle, @header.pointer, @header.size)
86
+
87
+ end
88
+
89
+ # send a message consisisting of a String of hex digits
90
+ def puts_s(data)
91
+ data = data.dup
92
+ output = []
93
+ until (str = data.slice!(0,2)).eql?("")
94
+ output << str.hex
95
+ end
96
+ puts_bytes(*output)
97
+ end
98
+ alias_method :puts_bytestr, :puts_s
99
+ alias_method :puts_hex, :puts_s
100
+
101
+ def self.first
102
+ Device::first(:output)
103
+ end
104
+
105
+ def self.last
106
+ Device::last(:output)
107
+ end
108
+
109
+ def self.all
110
+ Device.all_by_type[:output]
111
+ end
112
+
113
+ private
114
+
115
+ EventCallback = Proc.new do |hmo, wMsg, dwInstance, dwParam1, dwParam2|
116
+ msg_type = Map::CallbackMessageTypes[wMsg] || ''
117
+ if msg_type.eql?(:output_data)
118
+ header = dwParam1
119
+ handle = HandlePointer.read_int
120
+ Map.winmm_func(:midiOutUnprepareHeader, handle, header, Map::MIDIHdr.size)
121
+ end
122
+ end
123
+
124
+ HandlePointer = FFI::MemoryPointer.new(FFI.type_size(:int))
125
+
126
+ def init_output_buffer
127
+ @header = Map::MIDIHdr.new
128
+ d = FFI::MemoryPointer.new(:char, Output::BufferSize)
129
+ d.put_string(0, " " * (Output::BufferSize - 1)) # initialize with a string of spaces
130
+ @header[:lpData] = d.address
131
+ @header[:dwBufferLength] = Output::BufferSize
132
+ @header[:dwBytesRecorded] = 0
133
+ @header[:dwFlags] = 0
134
+ @header[:dwUser] = 0
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,12 @@
1
+ module TestHelper::Config
2
+
3
+ include MIDIWinMM
4
+
5
+ # adjust these constants to suit your hardware configuration
6
+ # before running tests
7
+
8
+ NumDevices = 4 # this is the total number of MIDI devices that are connected to your system
9
+ TestInput = Input.first # this is the device you wish to use to test input
10
+ TestOutput = Output.all[1] # likewise for output
11
+
12
+ end
@@ -0,0 +1,37 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + '/../lib'
3
+
4
+ require 'test/unit'
5
+ require 'midi-winmm'
6
+
7
+ module TestHelper
8
+
9
+ def bytestrs_to_ints(arr)
10
+ data = arr.map { |m| m[:data] }.join
11
+ output = []
12
+ until (bytestr = data.slice!(0,2)).eql?("")
13
+ output << bytestr.hex
14
+ end
15
+ output
16
+ end
17
+
18
+ # some MIDI messages
19
+ VariousMIDIMessages = [
20
+ [0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], # SysEx
21
+ [0x90, 100, 100], # note on
22
+ [0x90, 43, 100], # note on
23
+ [0x90, 76, 100], # note on
24
+ [0x90, 60, 100], # note on
25
+ [0x80, 100, 100] # note off
26
+ ]
27
+
28
+ # some MIDI messages
29
+ VariousMIDIByteStrMessages = [
30
+ "F04110421240007F0041F7", # SysEx
31
+ "906440", # note on
32
+ "804340" # note off
33
+ ]
34
+
35
+ end
36
+
37
+ require File.dirname(__FILE__) + '/config'
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'helper'
4
+
5
+ class InputBufferTest < Test::Unit::TestCase
6
+
7
+ include MIDIWinMM
8
+ include TestHelper
9
+ include TestHelper::Config # before running these tests, adjust the constants in config.rb to suit your hardware setup
10
+ # ** this test assumes that TestOutput is connected to TestInput
11
+
12
+ def test_input_buffer
13
+ sleep(1)
14
+
15
+ messages = VariousMIDIMessages
16
+ bytes = []
17
+
18
+ TestOutput.open do |output|
19
+ TestInput.open do |input|
20
+
21
+ messages.each do |msg|
22
+
23
+ $>.puts "sending: " + msg.inspect
24
+
25
+ output.puts(msg)
26
+
27
+ bytes += msg
28
+
29
+ sleep(0.5)
30
+
31
+ buffer = input.buffer.map { |m| m[:data] }.flatten
32
+
33
+ $>.puts "received: " + buffer.to_s
34
+
35
+ assert_equal(bytes, buffer)
36
+
37
+ end
38
+
39
+ assert_equal(bytes.length, input.buffer.map { |m| m[:data] }.flatten.length)
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'helper'
4
+
5
+ class IoTest < Test::Unit::TestCase
6
+
7
+ include MIDIWinMM
8
+ include TestHelper
9
+ include TestHelper::Config # before running these tests, adjust the constants in config.rb to suit your hardware setup
10
+ # ** this test assumes that TestOutput is connected to TestInput
11
+
12
+ def test_full_io
13
+ sleep(1)
14
+ messages = VariousMIDIMessages
15
+ messages_arr = messages.inject { |a,b| a+b }.flatten
16
+ received_arr = []
17
+ pointer = 0
18
+ TestOutput.open do |output|
19
+ TestInput.open do |input|
20
+
21
+ messages.each do |msg|
22
+
23
+ $>.puts "sending: " + msg.inspect
24
+
25
+ output.puts(msg)
26
+ sleep(1)
27
+ received = input.gets.map { |m| m[:data] }.flatten
28
+
29
+
30
+ $>.puts "received: " + received.inspect
31
+
32
+ assert_equal(messages_arr.slice(pointer, received.length), received)
33
+
34
+ pointer += received.length
35
+
36
+ received_arr += received
37
+
38
+ end
39
+
40
+ assert_equal(messages_arr.length, received_arr.length)
41
+
42
+ end
43
+ end
44
+ end
45
+
46
+ # ** this test assumes that TestOutput is connected to TestInput
47
+ def test_full_io_bytestr
48
+ sleep(1) # pause between tests
49
+
50
+ messages = VariousMIDIByteStrMessages
51
+ messages_str = messages.join
52
+ received_str = ""
53
+ pointer = 0
54
+
55
+ TestOutput.open do |output|
56
+ TestInput.open do |input|
57
+
58
+ messages.each do |msg|
59
+
60
+ $>.puts "sending: " + msg.inspect
61
+
62
+ output.puts(msg)
63
+ sleep(1)
64
+ received = input.gets_bytestr.map { |m| m[:data] }.flatten.join
65
+ $>.puts "received: " + received.inspect
66
+
67
+ assert_equal(messages_str.slice(pointer, received.length), received)
68
+
69
+ pointer += received.length
70
+
71
+ received_str += received
72
+
73
+ end
74
+
75
+ assert_equal(messages_str, received_str)
76
+
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: midi-winmm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ari Russo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-11 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: &70311577275740 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70311577275740
25
+ description: Realtime MIDI IO with Ruby in Windows/Cygwin using the WinMM system API
26
+ email:
27
+ - ari.russo@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/midi-winmm/device.rb
33
+ - lib/midi-winmm/input.rb
34
+ - lib/midi-winmm/map.rb
35
+ - lib/midi-winmm/output.rb
36
+ - lib/midi-winmm.rb
37
+ - test/config.rb
38
+ - test/helper.rb
39
+ - test/test_input_buffer.rb
40
+ - test/test_io.rb
41
+ - LICENSE
42
+ - README.rdoc
43
+ homepage: http://github.com/arirusso/midi-winmm
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.6
61
+ requirements: []
62
+ rubyforge_project: midi-winmm
63
+ rubygems_version: 1.8.6
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Realtime MIDI IO with Ruby in Windows/Cygwin
67
+ test_files: []