midi-winmm 0.1.10

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.
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: []