rtmidi 0.2.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
@@ -1,4 +1,9 @@
1
1
  require 'ffi'
2
+
3
+ # The RtMidi namespace. The interface for this library consists of the {In} and {Out} classes for MIDI input and output.
4
+ module RtMidi
5
+ end
6
+
2
7
  require 'rtmidi/interface.rb'
3
8
  require 'rtmidi/in.rb'
4
9
  require 'rtmidi/out.rb'
@@ -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
@@ -1,10 +1,11 @@
1
1
  module RtMidi
2
2
 
3
- # Ruby representation of a RtMidiIn C++ object
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 RtMidiIn wrapper object.
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 all opened ports.
45
- def close_ports()
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
- # TODO: enable sysex listening by hooking up to midiin_ignore_types
50
- # but that will require a more flexible callback interface.
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.set_callback do |byte1, byte2, byte3|
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 set_callback &callback
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
- # Cancel previously registered callbacks.
69
- # @see #set_callback
70
- def cancel_callback
71
- Interface::midiin_cancel_callback(@midiin)
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
@@ -1,8 +1,6 @@
1
1
  module RtMidi
2
2
 
3
- # The interface to RtMidi C++ code
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 midiout_message(rtmidi_ptr p, int byte1, int byte2, int byte3);
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
@@ -1,10 +1,11 @@
1
1
  module RtMidi
2
2
 
3
- # Ruby representation of a RtMidiOut C++ object
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 RtMidiOut wrapper object.
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 all opened ports.
45
- def close_ports()
41
+ # Close the port, if opened.
42
+ def close_port()
46
43
  Interface::midiout_close_port(@midiout)
47
44
  end
48
45
 
49
- # Send a 3-byte MIDI channel message to the opened port.
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
- def send_message(byte1, byte2, byte3=0)
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