rtmidi 0.2.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -11
- data/Rakefile +24 -7
- data/examples/list_ports.rb +2 -2
- data/examples/receive_any_message.rb +35 -0
- data/examples/receive_channel_message.rb +35 -0
- data/examples/send_any_message.rb +31 -0
- data/examples/send_channel_message.rb +34 -0
- data/ext/Rakefile +7 -93
- data/ext/ruby-rtmidi.cpp +24 -0
- data/ext/ruby-rtmidi.h +5 -0
- data/ext/ruby-rtmidi.o +0 -0
- data/ext/ruby-rtmidi.so +0 -0
- data/lib/rtmidi.rb +5 -0
- data/lib/rtmidi/build/compiler.rb +132 -0
- data/lib/rtmidi/build/system.rb +58 -0
- data/lib/rtmidi/in.rb +70 -19
- data/lib/rtmidi/interface.rb +11 -6
- data/lib/rtmidi/out.rb +35 -11
- data/spec/rtmidi/build/compiler_spec.rb +438 -0
- data/spec/rtmidi/build/system_spec.rb +130 -0
- data/spec/rtmidi/in_spec.rb +131 -0
- data/spec/rtmidi/out_spec.rb +87 -0
- data/spec/spec_helper.rb +12 -0
- metadata +27 -4
- data/examples/monitor_input.rb +0 -31
- data/examples/play_notes.rb +0 -28
data/ext/ruby-rtmidi.o
CHANGED
Binary file
|
data/ext/ruby-rtmidi.so
CHANGED
Binary file
|
data/lib/rtmidi.rb
CHANGED
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative 'system'
|
2
|
+
|
3
|
+
module RtMidi
|
4
|
+
|
5
|
+
# @private
|
6
|
+
module Build
|
7
|
+
|
8
|
+
# @private
|
9
|
+
class Compiler
|
10
|
+
include RtMidi::Build::System
|
11
|
+
|
12
|
+
def initialize(ext_dir, rtdmidi_dir, options={})
|
13
|
+
@ext_dir = ext_dir
|
14
|
+
@rtmidi_dir = rtdmidi_dir
|
15
|
+
@options = options
|
16
|
+
configure # also validates the configuration and gives appropriate errors before we start compiling
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile
|
20
|
+
compile_rtmidi
|
21
|
+
compile_ruby_rtmidi_wrapper
|
22
|
+
create_shared_library
|
23
|
+
puts "\nCompilation complete" if verbose?
|
24
|
+
end
|
25
|
+
|
26
|
+
def compile_rtmidi
|
27
|
+
puts "\nCompiling RtMidi C++ library" if verbose?
|
28
|
+
cd @rtmidi_dir
|
29
|
+
if gcc?
|
30
|
+
run "g++ -O3 -Wall -Iinclude -fPIC #{@predefines} -o RtMidi.o -c RtMidi.cpp"
|
31
|
+
else
|
32
|
+
run "cl /O2 /Iinclude #{@predefines} /EHsc /FoRtMidi.obj /c RtMidi.cpp"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def compile_ruby_rtmidi_wrapper
|
37
|
+
puts "\nCompiling Ruby RtMidi wrapper" if verbose?
|
38
|
+
cd @ext_dir
|
39
|
+
if gcc?
|
40
|
+
run "g++ -g -Wall -I#{@rtmidi_dir} -fPIC -o ruby-rtmidi.o -c ruby-rtmidi.cpp"
|
41
|
+
else
|
42
|
+
run "cl /I#{@rtmidi_dir} /D__RUBY_RTMIDI_DLL__ /EHsc /Foruby-rtmidi.obj /c ruby-rtmidi.cpp"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_shared_library
|
47
|
+
puts "\nCreating the RtMidi + wrapper shared library" if verbose?
|
48
|
+
cd @ext_dir
|
49
|
+
if gcc?
|
50
|
+
run "g++ -g -Wall -I#{@rtmidi_dir} -I#{@rtmidi_dir}/include #{@predefines} -fPIC -shared -o ruby-rtmidi.so " +
|
51
|
+
"ruby-rtmidi.o #{@rtmidi_dir}/RtMidi.o #{@system_libs}"
|
52
|
+
else
|
53
|
+
run "cl /I#{@rtmidi_dir} /I#{@rtmidi_dir}/include #{@predefines} /LD ruby-rtmidi.obj #{@rtmidi_dir}/RtMidi.obj #{@system_libs}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
############################
|
59
|
+
private
|
60
|
+
|
61
|
+
def verbose?
|
62
|
+
@verbose ||= @options.fetch(:verbose, false)
|
63
|
+
end
|
64
|
+
|
65
|
+
def gcc?
|
66
|
+
@compiler == :gcc
|
67
|
+
end
|
68
|
+
|
69
|
+
def cl?
|
70
|
+
@compiler == :cl
|
71
|
+
end
|
72
|
+
|
73
|
+
def jack?
|
74
|
+
@api == :jack
|
75
|
+
end
|
76
|
+
|
77
|
+
def alsa?
|
78
|
+
@api == :alsa
|
79
|
+
end
|
80
|
+
|
81
|
+
def configure
|
82
|
+
configure_compiler
|
83
|
+
configure_midi_api
|
84
|
+
configure_predefines
|
85
|
+
configure_system_libs
|
86
|
+
end
|
87
|
+
|
88
|
+
def configure_compiler
|
89
|
+
@compiler = case
|
90
|
+
when windows? && can_run('cl.exe') then :cl
|
91
|
+
when can_run('gcc') && can_run('g++') then :gcc
|
92
|
+
else raise "Cannot find gcc/g++#{' or cl.exe' if windows?} compiler"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def configure_midi_api
|
97
|
+
@api = case
|
98
|
+
when osx? then :coremidi
|
99
|
+
when windows? then :winmm
|
100
|
+
when linux?
|
101
|
+
case
|
102
|
+
when linux_package_exists(:jack) then :jack
|
103
|
+
when linux_package_exists(:alsa) then :alsa
|
104
|
+
else raise 'Neither JACK or ALSA detected using pkg-config. Please install one of them first.'
|
105
|
+
end
|
106
|
+
else raise "Unsupported platform #{platform}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_predefines
|
111
|
+
@predefines = case
|
112
|
+
when osx? then '-D__MACOSX_CORE__'
|
113
|
+
when windows? && gcc? then '-D__WINDOWS_MM__'
|
114
|
+
when windows? && cl? then '/D__WINDOWS_MM__'
|
115
|
+
when linux? && jack? then '-D__UNIX_JACK__'
|
116
|
+
when linux? && alsa? then '-D__LINUX_ALSA__'
|
117
|
+
else raise "Could not set predefines"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def configure_system_libs
|
122
|
+
@system_libs = case
|
123
|
+
when osx? then '-framework CoreMIDI -framework CoreAudio -framework CoreFoundation'
|
124
|
+
when windows? && gcc? then '-lwinmm'
|
125
|
+
when windows? && cl? then 'winmm.lib'
|
126
|
+
when linux? then linux_library(@api)
|
127
|
+
else raise "Could not set system_libs"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
module RtMidi
|
4
|
+
|
5
|
+
# @private
|
6
|
+
module Build
|
7
|
+
|
8
|
+
# @private
|
9
|
+
module System
|
10
|
+
|
11
|
+
def platform
|
12
|
+
case RbConfig::CONFIG['host_os'].downcase
|
13
|
+
when /darwin/ then :osx # check this first since the next case would match darwin for :windows
|
14
|
+
when /(win)|(mingw)/ then :windows
|
15
|
+
when /linux/ then :linux
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def osx?
|
20
|
+
platform == :osx
|
21
|
+
end
|
22
|
+
|
23
|
+
def windows?
|
24
|
+
platform == :windows
|
25
|
+
end
|
26
|
+
|
27
|
+
def linux?
|
28
|
+
platform == :linux
|
29
|
+
end
|
30
|
+
|
31
|
+
def cd(dir)
|
32
|
+
puts "cd #{dir}"
|
33
|
+
Dir.chdir(dir)
|
34
|
+
end
|
35
|
+
|
36
|
+
def can_run(cmd)
|
37
|
+
find_executable(cmd)
|
38
|
+
end
|
39
|
+
|
40
|
+
def run(cmd)
|
41
|
+
puts cmd
|
42
|
+
unless system(cmd)
|
43
|
+
puts "Error: command exited with return value #{$?.exitstatus}"
|
44
|
+
exit $?.exitstatus
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def linux_package_exists(pkg)
|
49
|
+
system "pkg-config --exists #{pkg}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def linux_library(pkg)
|
53
|
+
`pkg-config --libs #{pkg}`.chomp
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/rtmidi/in.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module RtMidi
|
2
2
|
|
3
|
-
#
|
3
|
+
# Object for handling MIDI input.
|
4
|
+
# The Ruby representation of a {http://www.music.mcgill.ca/~gary/rtmidi/classRtMidiIn.html RtMidiIn C++ object}..
|
4
5
|
# @see Out
|
5
6
|
class In
|
6
7
|
|
7
|
-
# Create a new
|
8
|
+
# Create a new instance.
|
8
9
|
def initialize
|
9
10
|
@midiin = Interface::midiin_new()
|
10
11
|
at_exit{ Interface::midiin_delete @midiin }
|
@@ -28,49 +29,99 @@ module RtMidi
|
|
28
29
|
# @see #port_name
|
29
30
|
# @see #open_port
|
30
31
|
def port_names
|
31
|
-
@port_names ||= (
|
32
|
-
names = []
|
33
|
-
port_count.times{|i| names << Interface::midiin_port_name(@midiin, i) }
|
34
|
-
names
|
35
|
-
)
|
32
|
+
@port_names ||= (0...port_count).map{|port_index| Interface::midiin_port_name(@midiin, port_index) }
|
36
33
|
end
|
37
34
|
|
38
35
|
# Open the MIDI input port at the given index.
|
36
|
+
# @note only one port may be open per RtMidi::In instance
|
39
37
|
# @see #port_names
|
40
38
|
def open_port(index)
|
41
39
|
Interface::midiin_open_port(@midiin, index)
|
42
40
|
end
|
43
41
|
|
44
|
-
# Close
|
45
|
-
|
42
|
+
# Close the port, if opened.
|
43
|
+
# @see #open_port
|
44
|
+
def close_port()
|
46
45
|
Interface::midiin_close_port(@midiin)
|
47
46
|
end
|
48
47
|
|
49
|
-
#
|
50
|
-
|
48
|
+
# @deprecated use {#close_port}
|
49
|
+
def close_ports
|
50
|
+
puts "Deprecated, use #close_port instead"
|
51
|
+
close_port
|
52
|
+
end
|
51
53
|
|
52
54
|
# Setup a callback block to handle incoming MIDI channel messages from opened ports.
|
55
|
+
# @note Only one receive callback may be active per RtMidi::In instance.
|
56
|
+
# Calling this method or {#receive_message} multiple times will replace the previous receive callback.
|
53
57
|
#
|
54
|
-
# The block should receive 3 bytes (Ruby integers)
|
58
|
+
# The block should receive 3 bytes (Ruby Fixnum integers)
|
55
59
|
#
|
56
|
-
# All messages are assumed to have 3 bytes.
|
60
|
+
# All messages are assumed to have 3 bytes. SysEx messages will be ignored.
|
57
61
|
# Some channel messages only have 2 bytes in which case the 3rd byte is 0.
|
62
|
+
#
|
63
|
+
# This is more efficient than {#receive_message} and should be used when handling only MIDI channel messages.
|
64
|
+
#
|
65
|
+
# @yield [Fixnum, Fixnum, Fixnum] MIDI channel message as individual bytes
|
66
|
+
#
|
58
67
|
# @example
|
59
|
-
# midiin.
|
68
|
+
# midiin.receive_channel_message do |byte1, byte2, byte3|
|
60
69
|
# puts "#{byte1} #{byte2} #{byte3}"
|
61
70
|
# end
|
71
|
+
#
|
62
72
|
# @see #open_port
|
73
|
+
# @see #receive_message
|
63
74
|
# @see #cancel_callback
|
64
|
-
def
|
75
|
+
def receive_channel_message &callback
|
76
|
+
cancel_callback # otherwise first callback wins. I think last wins is more intuitive
|
77
|
+
Interface::midiin_ignore_types(@midiin, true, true, true) # Ignore sysex, timing, or active sensing messages.
|
65
78
|
Interface::midiin_set_callback(@midiin, callback)
|
79
|
+
@callback_set = true
|
66
80
|
end
|
67
81
|
|
68
|
-
|
69
|
-
#
|
70
|
-
|
71
|
-
|
82
|
+
|
83
|
+
# Setup a callback block to handle all incoming MIDI messages from opened ports.
|
84
|
+
# @note Only one receive callback may be active per RtMidi::In instance.
|
85
|
+
# Calling this method or {#receive_message} multiple times will replace the previous receive callback.
|
86
|
+
#
|
87
|
+
# The block should receive a variable number of Ruby Fixnum integers.
|
88
|
+
# SysEx messages will be passed to the callback.
|
89
|
+
#
|
90
|
+
# This is less efficient than {#receive_channel_message} and should only be used when handling
|
91
|
+
# SysEx, timing, or active sensing messages.
|
92
|
+
#
|
93
|
+
# @yield [*Fixnum] MIDI channel message as individual bytes
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# midiin.receive_message do |*bytes|
|
97
|
+
# puts bytes.inspect
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# @see #open_port
|
101
|
+
# @see #receive_channel_message
|
102
|
+
# @see #cancel_callback
|
103
|
+
def receive_message &callback
|
104
|
+
cancel_callback # otherwise first callback wins. I think last wins is more intuitive
|
105
|
+
Interface::midiin_ignore_types(@midiin, false, false, false) # Don't ignore sysex, timing, or active sensing messages.
|
106
|
+
Interface::midiin_set_varargs_callback(@midiin, ->(bytes,size){ yield *bytes.read_array_of_uchar(size) })
|
107
|
+
@callback_set = true
|
72
108
|
end
|
73
109
|
|
110
|
+
alias set_callback receive_message
|
111
|
+
|
112
|
+
|
113
|
+
# Cancel the current receive callback, if any.
|
114
|
+
# @see #receive_message
|
115
|
+
# @see #receive_channel_message
|
116
|
+
def stop_receiving
|
117
|
+
if @callback_set
|
118
|
+
Interface::midiin_cancel_callback(@midiin)
|
119
|
+
@callback_set = false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
alias cancel_callback stop_receiving
|
124
|
+
|
74
125
|
end
|
75
126
|
|
76
127
|
end
|
data/lib/rtmidi/interface.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
module RtMidi
|
2
2
|
|
3
|
-
#
|
4
|
-
# @see RtMidi::In
|
5
|
-
# @see RtMidi::Out
|
3
|
+
# @private
|
6
4
|
module Interface
|
7
5
|
extend FFI::Library
|
8
6
|
ffi_lib [
|
@@ -37,7 +35,11 @@ module RtMidi
|
|
37
35
|
# void midiin_set_callback(rtmidi_ptr p, rtmidi_callback callback);
|
38
36
|
callback :rtmidi_callback, [:int, :int, :int], :void
|
39
37
|
attach_function :midiin_set_callback, [:pointer, :rtmidi_callback], :void
|
40
|
-
|
38
|
+
|
39
|
+
# void midiin_set_varargs_callback(rtmidi_ptr p, rtmidi_varargs_callback callback);
|
40
|
+
callback :rtmidi_varargs_callback, [:pointer, :int], :void
|
41
|
+
attach_function :midiin_set_varargs_callback, [:pointer, :rtmidi_varargs_callback], :void
|
42
|
+
|
41
43
|
# void midiin_cancel_callback(rtmidi_ptr p);
|
42
44
|
attach_function :midiin_cancel_callback, [:pointer], :void
|
43
45
|
|
@@ -63,8 +65,11 @@ module RtMidi
|
|
63
65
|
# void midiout_close_port(rtmidi_ptr p);
|
64
66
|
attach_function :midiout_close_port, [:pointer], :void
|
65
67
|
|
66
|
-
# void
|
67
|
-
attach_function :midiout_send_message, [:pointer, :int, :int, :int], :void
|
68
|
+
# void midiout_send_message(rtmidi_ptr p, int byte1, int byte2, int byte3);
|
69
|
+
attach_function :midiout_send_message, [:pointer, :int, :int, :int], :void
|
70
|
+
|
71
|
+
# void midiout_send_bytes(rtmidi_ptr p, int* bytes);
|
72
|
+
attach_function :midiout_send_bytes, [:pointer, :pointer, :int], :void
|
68
73
|
end
|
69
74
|
|
70
75
|
end
|
data/lib/rtmidi/out.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module RtMidi
|
2
2
|
|
3
|
-
#
|
3
|
+
# Object for handling MIDI output.
|
4
|
+
# The Ruby representation of a {http://www.music.mcgill.ca/~gary/rtmidi/classRtMidiOut.html RtMidiOut C++ object}.
|
4
5
|
# @see In
|
5
6
|
class Out
|
6
7
|
|
7
|
-
# Create a new
|
8
|
+
# Create a new instance.
|
8
9
|
def initialize
|
9
10
|
@midiout = Interface::midiout_new()
|
10
11
|
at_exit{ Interface::midiout_delete @midiout }
|
@@ -28,11 +29,7 @@ module RtMidi
|
|
28
29
|
# @see #port_name
|
29
30
|
# @see #open_port
|
30
31
|
def port_names
|
31
|
-
@port_names ||= (
|
32
|
-
names = []
|
33
|
-
port_count.times{|i| names << Interface::midiout_port_name(@midiout, i) }
|
34
|
-
names
|
35
|
-
)
|
32
|
+
@port_names ||= (0...port_count).map{|port_index| Interface::midiout_port_name(@midiout, port_index) }
|
36
33
|
end
|
37
34
|
|
38
35
|
# Open the MIDI output port at the given index.
|
@@ -41,19 +38,46 @@ module RtMidi
|
|
41
38
|
Interface::midiout_open_port(@midiout, index)
|
42
39
|
end
|
43
40
|
|
44
|
-
# Close
|
45
|
-
def
|
41
|
+
# Close the port, if opened.
|
42
|
+
def close_port()
|
46
43
|
Interface::midiout_close_port(@midiout)
|
47
44
|
end
|
48
45
|
|
49
|
-
#
|
46
|
+
# @deprecated use {#close_port}
|
47
|
+
def close_ports
|
48
|
+
puts "Deprecated, use #close_port instead"
|
49
|
+
close_port
|
50
|
+
end
|
51
|
+
|
52
|
+
# Send a 2 or 3 byte MIDI channel message to the opened port.
|
50
53
|
#
|
51
54
|
# Some channel messages only have 2 bytes in which case the 3rd byte is ignored.
|
55
|
+
#
|
56
|
+
# This is more efficient than {#send_message} and should be used when handling only MIDI channel messages.
|
57
|
+
#
|
52
58
|
# @see #open_port
|
53
|
-
|
59
|
+
# @see #send_message
|
60
|
+
def send_channel_message(byte1, byte2, byte3=0)
|
54
61
|
Interface::midiout_send_message(@midiout, byte1, byte2, byte3)
|
55
62
|
end
|
56
63
|
|
64
|
+
# Send an arbitrary multi-byte MIDI message to the opened port.
|
65
|
+
#
|
66
|
+
# This supports SysEx messages.
|
67
|
+
#
|
68
|
+
# This is less efficient than {#send_channel_message} and should only be used when handling
|
69
|
+
# SysEx, timing, or active sensing messages.
|
70
|
+
#
|
71
|
+
# @see #open_port
|
72
|
+
# @see #send_channel_message
|
73
|
+
def send_message(*bytes)
|
74
|
+
bytes.flatten!
|
75
|
+
FFI::MemoryPointer.new(:int, bytes.length) do |p|
|
76
|
+
p.write_array_of_int(bytes)
|
77
|
+
Interface::midiout_send_bytes(@midiout, p, bytes.length)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
57
81
|
end
|
58
82
|
|
59
83
|
end
|