rtmidi 0.2.2 → 0.3
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.
- 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
|