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.
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