midi-communications 0.6.1 → 0.7.1

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.
@@ -1,14 +1,43 @@
1
1
  require 'midi-communications/input/stream_reader'
2
2
 
3
3
  module MIDICommunications
4
- # A MIDI input device
4
+ # A MIDI input device for receiving MIDI messages.
5
+ #
6
+ # Input devices receive MIDI data from external controllers, instruments,
7
+ # or other MIDI sources. Use the class methods to discover and select
8
+ # available input devices.
9
+ #
10
+ # @example List available inputs
11
+ # MIDICommunications::Input.list
12
+ #
13
+ # @example Open the first input and read messages
14
+ # input = MIDICommunications::Input.first
15
+ # messages = input.gets
16
+ # # => [{ data: [144, 60, 100], timestamp: 1024 }]
17
+ #
18
+ # @example Interactive selection
19
+ # input = MIDICommunications::Input.gets
20
+ #
21
+ # @example Find by name
22
+ # input = MIDICommunications::Input.find_by_name("USB MIDI Device")
23
+ # input.open
24
+ #
25
+ # @see Output For sending MIDI messages
26
+ # @see StreamReader For reading methods (gets, gets_s, etc.)
27
+ #
28
+ # @api public
5
29
  class Input
6
30
  extend Device::ClassMethods
7
31
  include Device::InstanceMethods
8
32
  include StreamReader
9
33
 
10
- # All MIDI input devices -- used to populate the class
11
- # @return [Array<Input>]
34
+ # Returns all available MIDI input devices.
35
+ #
36
+ # @return [Array<Input>] array of input devices
37
+ #
38
+ # @example
39
+ # inputs = MIDICommunications::Input.all
40
+ # inputs.each { |i| puts i.name }
12
41
  def self.all
13
42
  Loader.devices(direction: :input)
14
43
  end
@@ -1,28 +1,104 @@
1
1
  module MIDICommunications
2
-
3
- # Populate UniMIDI devices using the underlying device objects from the platform-specific gems
2
+ # Populates MIDI devices from the platform adapter.
3
+ #
4
+ # The device list is built once and kept, because enumerating is not free —
5
+ # on macOS it walks every device, entity and endpoint Core MIDI knows about —
6
+ # and most programs ask for it far more often than the machine's hardware
7
+ # changes. {refresh} is how a program that outlives a hardware change asks
8
+ # for the list again.
9
+ #
10
+ # ## What refreshing does not do
11
+ #
12
+ # A device that is still present comes back as the object it came back as
13
+ # before, rather than as a new wrapper around the same port. That matters
14
+ # because callers hold on to these: `Musa::Clock::InputMidiClock` keeps an
15
+ # {Input} for the length of a piece, `Musa::MIDIVoices` keeps an {Output}, and
16
+ # {Device::InstanceMethods#open} registers an `at_exit` on the instance.
17
+ # Replacing the list wholesale would leave those objects open, still due to be
18
+ # closed at exit, and no longer in {Input.all} — a device the program is
19
+ # actively using that the program can no longer find.
20
+ #
21
+ # "Still present" means the same id reporting the same name.
22
+ #
23
+ # @api private
4
24
  class Loader
5
25
  class << self
6
- # Use the given platform-specific adapter to load devices
7
- # @param [MIDICommunications::Adapter::Loader] loader
26
+ # Sets the platform-specific loader to use.
27
+ #
28
+ # Any devices already enumerated are discarded: they came from a
29
+ # different platform adapter and mean nothing to this one.
30
+ #
31
+ # @param loader [Module] a loader answering `inputs` and `outputs`, and
32
+ # optionally `refresh`; see {PhysicalLayer}
33
+ # @return [Module] the loader
8
34
  def use(loader)
9
35
  @loader = loader
36
+ @devices = nil
37
+
38
+ loader
39
+ end
40
+
41
+ # Returns all MIDI devices, optionally filtered by direction.
42
+ #
43
+ # Enumerates on the first call, and then answers from what it found.
44
+ #
45
+ # @param direction [Symbol, nil] `:input` or `:output`, or nil for both
46
+ # @param refresh [Boolean] ask the platform adapter again first
47
+ # @return [Array<Input>, Array<Output>] the devices
48
+ def devices(direction: nil, refresh: false)
49
+ populate if refresh || @devices.nil?
50
+
51
+ direction.nil? ? @devices.values.flatten : @devices[direction]
52
+ end
53
+
54
+ # Asks the platform adapter what devices exist now.
55
+ #
56
+ # Call this when devices may have been plugged in or unplugged since the
57
+ # program started. Devices that are still there keep their identity; see
58
+ # the note on the class.
59
+ #
60
+ # @return [Array<Input, Output>] every device, after re-enumerating
61
+ #
62
+ # @example
63
+ # MIDICommunications::Loader.refresh
64
+ # MIDICommunications::Output.all # now includes what was just plugged in
65
+ def refresh
66
+ devices(refresh: true)
67
+ end
68
+
69
+ private
70
+
71
+ # Asks the adapter for its devices and wraps them, keeping the wrapper of
72
+ # every device that was already there.
73
+ #
74
+ # The adapter is told to re-enumerate first if it can. Without that, an
75
+ # adapter with a cache of its own — the macOS one has — would hand back
76
+ # the same list it handed back before, and refreshing here would rebuild
77
+ # wrappers around a stale answer.
78
+ #
79
+ # Not on the first enumeration, though: there is nothing to invalidate
80
+ # yet, and asking a platform to walk its devices twice to answer the
81
+ # first question would make every program pay for a capability most of
82
+ # them never use.
83
+ #
84
+ # @return [void]
85
+ def populate
86
+ @loader.refresh if @devices && @loader.respond_to?(:refresh)
87
+
88
+ @devices = {
89
+ input: wrap(:input, @loader.inputs) { |device| Input.new(device) },
90
+ output: wrap(:output, @loader.outputs) { |device| Output.new(device) }
91
+ }
10
92
  end
11
93
 
12
- # Get all MIDI devices
13
- # @param [Hash] options
14
- # @option options [Symbol] :direction Return only a particular direction of device eg :input, :output
94
+ # @param direction [Symbol] `:input` or `:output`
95
+ # @param devices [Array] the adapter's device objects
96
+ # @yield [device] builds a wrapper for a device not seen before
15
97
  # @return [Array<Input>, Array<Output>]
16
- def devices(options = {})
17
- if @devices.nil?
18
- inputs = @loader.inputs.map { |device| ::MIDICommunications::Input.new(device) }
19
- outputs = @loader.outputs.map { |device| ::MIDICommunications::Output.new(device) }
20
- @devices = {
21
- input: inputs,
22
- output: outputs
23
- }
24
- end
25
- options[:direction].nil? ? @devices.values.flatten : @devices[options[:direction]]
98
+ def wrap(direction, devices)
99
+ known = (@devices && @devices[direction] || []).to_h { |device| [[device.id, device.name], device] }
100
+
101
+ devices.map { |device| known[[device.id, device.name]] || yield(device) }
26
102
  end
27
103
  end
28
104
  end
@@ -1,27 +1,69 @@
1
1
  module MIDICommunications
2
-
3
- # A MIDI output device
2
+ # A MIDI output device for sending MIDI messages.
3
+ #
4
+ # Output devices send MIDI data to external instruments, software synthesizers,
5
+ # or other MIDI destinations. Use the class methods to discover and select
6
+ # available output devices.
7
+ #
8
+ # @example List available outputs
9
+ # MIDICommunications::Output.list
10
+ #
11
+ # @example Send a note to the first output
12
+ # output = MIDICommunications::Output.first
13
+ # output.puts(0x90, 60, 100) # Note On, middle C, velocity 100
14
+ # sleep(0.5)
15
+ # output.puts(0x80, 60, 0) # Note Off
16
+ #
17
+ # @example Send messages as hex strings
18
+ # output = MIDICommunications::Output.first
19
+ # output.puts_s("904060") # Note On
20
+ # output.puts_s("804060") # Note Off
21
+ #
22
+ # @example Interactive selection
23
+ # output = MIDICommunications::Output.gets
24
+ #
25
+ # @example Find by name
26
+ # output = MIDICommunications::Output.find_by_name("IAC Driver Bus 1")
27
+ # output.open
28
+ #
29
+ # @see Input For receiving MIDI messages
30
+ #
31
+ # @api public
4
32
  class Output
5
33
  extend Device::ClassMethods
6
34
  include Device::InstanceMethods
7
35
 
8
- # All MIDI output devices -- used to populate the class
9
- # @return [Array<Output>]
36
+ # Returns all available MIDI output devices.
37
+ #
38
+ # @return [Array<Output>] array of output devices
39
+ #
40
+ # @example
41
+ # outputs = MIDICommunications::Output.all
42
+ # outputs.each { |o| puts o.name }
10
43
  def self.all
11
44
  Loader.devices(direction: :output)
12
45
  end
13
46
 
14
- # Sends a message to the output.
47
+ # Sends a MIDI message to the output.
15
48
  #
16
- # The message format can be:
49
+ # Accepts multiple message formats for flexibility:
50
+ # - Numeric bytes: `output.puts(0x90, 0x40, 0x40)`
51
+ # - Array of numeric bytes: `output.puts([0x90, 0x40, 0x40])`
52
+ # - Hex string: `output.puts("904040")`
53
+ # - Array of strings: `output.puts(["904040", "804040"])`
54
+ # - Objects with `to_bytes` method: `output.puts(midi_event)`
17
55
  #
18
- # 1. Numeric bytes eg output.puts(0x90, 0x40, 0x40)
19
- # 2. An array of numeric bytes [0x90, 0x40, 0x40]
20
- # 3. A string of bytes eg "904040"
21
- # 4. An array of strings ["904040", "804040"]
56
+ # @param messages [Array<Integer>, Array<String>, Integer, String] MIDI messages in any supported format
57
+ # @return [Array<Integer>, Array<String>] the messages sent
22
58
  #
23
- # @param [*Array<Integer>, *Array<String>, *Integer, *String] messages
24
- # @return [Array<Integer>, Array<String>]
59
+ # @example Send Note On as bytes
60
+ # output.puts(0x90, 60, 100)
61
+ #
62
+ # @example Send Note On as array
63
+ # output.puts([0x90, 60, 100])
64
+ #
65
+ # @example Send Note On as hex string
66
+ # output.puts("903C64")
25
67
  def puts(*messages)
26
68
  message = messages.first
27
69
  case message
@@ -37,10 +79,17 @@ module MIDICommunications
37
79
  end
38
80
  end
39
81
 
40
- # Sends a message to the output in a form of a string eg "904040". This method does not do
41
- # type checking
42
- # @param [*String] messages
43
- # @return [Array<String>, Array<Array<String>>]
82
+ # Sends a MIDI message as a hex string.
83
+ #
84
+ # This is a lower-level method that does not perform type checking.
85
+ # Use {#puts} for automatic format detection.
86
+ #
87
+ # @param messages [String] one or more hex strings (e.g., "904040")
88
+ # @return [String, Array<String>] the message(s) sent
89
+ #
90
+ # @example
91
+ # output.puts_s("904060") # Note On
92
+ # output.puts_s("804060") # Note Off
44
93
  def puts_s(*messages)
45
94
  @device.puts_s(*messages)
46
95
  messages.count < 2 ? messages[0] : messages
@@ -48,10 +97,16 @@ module MIDICommunications
48
97
  alias puts_bytestr puts_s
49
98
  alias puts_hex puts_s
50
99
 
51
- # Sends a message to the output in a form of bytes eg output.puts_bytes(0x90, 0x40, 0x40).
52
- # This method does not do type checking.
53
- # @param [*Array<Integer>] messages
54
- # @return [Array<Integer>, Array<Array<Integer>>]
100
+ # Sends a MIDI message as numeric bytes.
101
+ #
102
+ # This is a lower-level method that does not perform type checking.
103
+ # Use {#puts} for automatic format detection.
104
+ #
105
+ # @param messages [Integer] numeric byte values (e.g., 0x90, 0x40, 0x40)
106
+ # @return [Integer, Array<Integer>] the message bytes sent
107
+ #
108
+ # @example
109
+ # output.puts_bytes(0x90, 0x40, 0x40) # Note On, note 64, velocity 64
55
110
  def puts_bytes(*messages)
56
111
  @device.puts_bytes(*messages)
57
112
  messages.count < 2 ? messages[0] : messages
@@ -0,0 +1,105 @@
1
+ module MIDICommunications
2
+ # The contract a platform adapter's device objects must satisfy.
3
+ #
4
+ # This module defines no behaviour. It exists because the contract between
5
+ # `midi-communications` and the low-level gem underneath it was, until now,
6
+ # implicit: spread across {Loader}, {Device}, {Input::StreamReader} and
7
+ # {Output}, and discoverable only by reading all four and inferring what they
8
+ # assume. That is workable while one adapter exists. It stops being workable
9
+ # the moment a second one is written, because the second author reads the
10
+ # documentation rather than the macOS gem's source, and the documentation did
11
+ # not say any of this.
12
+ #
13
+ # An adapter supplies a loader module answering `inputs` and `outputs` with
14
+ # arrays of device objects, and optionally `refresh` (see {Loader.refresh}).
15
+ # Each device object must behave as described below. {Input} and {Output}
16
+ # wrap them; nothing else in this library touches them.
17
+ #
18
+ # ## Attributes, readable before the device is opened
19
+ #
20
+ # {Input} and {Output} read these in their constructor, which runs while the
21
+ # device list is being built and long before anyone opens anything. An
22
+ # adapter that only knows a device's name once it is open does not satisfy
23
+ # this contract.
24
+ #
25
+ # | Method | Type | Meaning |
26
+ # | --- | --- | --- |
27
+ # | `id` | Integer | identifies the device; see the note on uniqueness below |
28
+ # | `name` | String | the device's name |
29
+ # | `display_name` | String | the name to show a person choosing a device |
30
+ # | `manufacturer` | String, nil | **nil when the platform does not report it** |
31
+ # | `model` | String, nil | **nil when the platform does not report it** |
32
+ # | `type` | Symbol | `:input`/`:source`, or `:output`/`:destination` |
33
+ #
34
+ # `manufacturer` and `model` are nullable on purpose. Core MIDI reports both
35
+ # as strings; the Windows Multimedia API reports numeric codes from a
36
+ # registry that stopped being maintained in the 1990s, from which no honest
37
+ # string can be derived. An adapter in that position returns nil rather than
38
+ # an empty string or an invention, so that a consumer filtering on either can
39
+ # tell the difference between "does not match" and "not known here".
40
+ #
41
+ # ### On the uniqueness of `name`
42
+ #
43
+ # `name` is a label, not an identifier. Two devices may report the same one,
44
+ # and a consumer matching on it may therefore be matching the wrong device.
45
+ #
46
+ # This is measured, not defensive. The Windows Multimedia API stores 31
47
+ # characters of a name and drops the rest without saying so, and two endpoints
48
+ # whose names differed only past that point came back identical in every field
49
+ # it reports — same name, same manufacturer code, same product code, same
50
+ # driver version — distinguishable only by their index. Two ports of one
51
+ # interface whose long names differ at the end collapse the same way.
52
+ #
53
+ # {Device::ClassMethods#find_by_name} returns the first match, which is all it
54
+ # can do.
55
+ #
56
+ # ### On the uniqueness of `id`
57
+ #
58
+ # `id` is unique **within a direction**. It is not necessarily unique across
59
+ # both: on Windows a device is identified by its index among inputs or among
60
+ # outputs, so input 0 and output 0 are different devices and both are valid.
61
+ # Core MIDI happens to number endpoints of both directions from a single
62
+ # counter, but that is a property of Core MIDI and not something a consumer
63
+ # may rely on. Nothing in this library compares an id across directions —
64
+ # `Input.all` and `Output.all` each search their own list.
65
+ #
66
+ # ## Lifecycle
67
+ #
68
+ # - `open(*args)` — makes the device usable. Opening an already-open device
69
+ # must succeed and do nothing, because {Device::InstanceMethods#open} may be
70
+ # called on a device a caller already holds open.
71
+ # - `close(*args)` — releases it. Closing an already-closed device must
72
+ # succeed.
73
+ #
74
+ # ## Input
75
+ #
76
+ # - `gets` — **blocks** until at least one message has arrived, then returns
77
+ # every message accumulated, as an Array of Hashes with:
78
+ # - `:data`, an Array of Integer bytes making up **one complete message**,
79
+ # System Exclusive included, already split per message;
80
+ # - `:timestamp`, a Float of seconds — when the library received the
81
+ # message, not a stamp applied by the driver. Both existing adapters take
82
+ # it with `Time.now.to_f` at the moment the message reaches Ruby.
83
+ # - `gets_s` — the same, with `:data` as a hex String.
84
+ #
85
+ # The blocking is the part most easily got wrong, and it is load-bearing.
86
+ # `Musa::Clock::InputMidiClock` reads MIDI Clock in a loop with no delay of
87
+ # its own, relying on `gets` to be where the thread waits. An adapter whose
88
+ # `gets` returned an empty array immediately would turn that loop into a spin
89
+ # on a full core — and would do it silently, because the music would still
90
+ # play.
91
+ #
92
+ # ## Output
93
+ #
94
+ # - `puts_bytes(*bytes)` — sends one message given as Integer bytes.
95
+ # - `puts_s(hex_string)` — sends one message given as hex.
96
+ #
97
+ # Both must accept System Exclusive.
98
+ #
99
+ # @see Loader how adapters are registered and their devices enumerated
100
+ # @see Input::StreamReader the reading methods built on `gets`
101
+ #
102
+ # @api public
103
+ module PhysicalLayer
104
+ end
105
+ end
@@ -1,10 +1,19 @@
1
1
  module MIDICommunications
2
-
3
- # Deal with different dependencies between different user environments
2
+ # Handles platform detection and adapter loading.
3
+ #
4
+ # Automatically detects the current platform (macOS, Linux, Windows, JRuby)
5
+ # and loads the appropriate low-level MIDI adapter.
6
+ #
7
+ # @api private
4
8
  module Platform
5
9
  extend self
6
10
 
7
- # Loads the proper MIDI library and adapter for the user's environment
11
+ # Loads the correct MIDI adapter for the current platform.
12
+ #
13
+ # Called automatically when the library is required. Detects the
14
+ # platform and loads the corresponding adapter gem.
15
+ #
16
+ # @return [void]
8
17
  def bootstrap
9
18
  require("midi-communications/adapter/#{platform_lib}")
10
19
  Loader.use(platform_module::Loader)
@@ -1,14 +1,29 @@
1
1
  module MIDICommunications
2
-
3
- # Utility for converting between different data formats
2
+ # Utility methods for converting between MIDI data formats.
3
+ #
4
+ # @api public
4
5
  module TypeConversion
5
6
  extend self
6
7
 
7
- # Convert an array of numeric bytes to string of hex bytes
8
- # @param [Array<Integer>] bytes An array of numeric bytes eg [0x90, 0x40, 0x40]
9
- # @return [String] A string of hex bytes eg "904040"
8
+ # Converts an array of numeric bytes to a hex string.
9
+ #
10
+ # Every byte becomes exactly two characters. Without the padding a byte
11
+ # below 0x10 produces one, and the string can no longer be read back as
12
+ # bytes: [0x90, 0x0A, 0x64] came out as "90a64", which is five characters
13
+ # and describes no MIDI message at all.
14
+ #
15
+ # @param bytes [Array<Integer>] array of numeric bytes (e.g., [0x90, 0x40, 0x40])
16
+ # @return [String] hex string representation (e.g., "904040")
17
+ #
18
+ # @example
19
+ # TypeConversion.numeric_byte_array_to_hex_string([0x90, 0x40, 0x40])
20
+ # # => "904040"
21
+ #
22
+ # @example A byte below 0x10 still takes two characters
23
+ # TypeConversion.numeric_byte_array_to_hex_string([0x90, 0x0A, 0x64])
24
+ # # => "900A64"
10
25
  def numeric_byte_array_to_hex_string(bytes)
11
- bytes.map { |b| b.to_s(16) }.join
26
+ bytes.map { |byte| format('%02X', byte) }.join
12
27
  end
13
28
  end
14
29
  end
@@ -1,3 +1,4 @@
1
1
  module MIDICommunications
2
- VERSION = '0.6.1'.freeze
2
+ # Current version of the midi-communications gem.
3
+ VERSION = '0.7.1'.freeze
3
4
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  # modules
10
10
  require 'midi-communications/device'
11
+ require 'midi-communications/physical_layer'
11
12
  require 'midi-communications/platform'
12
13
  require 'midi-communications/type_conversion'
13
14
 
@@ -18,6 +19,44 @@ require 'midi-communications/output'
18
19
 
19
20
  require_relative 'midi-communications/version'
20
21
 
22
+ # Platform-independent realtime MIDI input and output for Ruby.
23
+ #
24
+ # MIDICommunications provides a unified API for MIDI communication across
25
+ # different platforms (macOS, Linux, Windows, JRuby). It automatically
26
+ # detects the current platform and loads the appropriate low-level adapter.
27
+ #
28
+ # This library is part of the MusaDSL MIDI suite:
29
+ # - {https://github.com/javier-sy/midi-events MIDI Events} - MIDI message representation
30
+ # - {https://github.com/javier-sy/midi-parser MIDI Parser} - MIDI data parsing
31
+ # - {https://github.com/javier-sy/midi-communications MIDI Communications} - MIDI I/O (this library)
32
+ # - {https://github.com/javier-sy/midi-communications-macos MIDI Communications MacOS} - macOS adapter
33
+ #
34
+ # @example List all MIDI outputs
35
+ # MIDICommunications::Output.list
36
+ # # 0) IAC Driver Bus 1
37
+ # # 1) USB MIDI Device
38
+ #
39
+ # @example Send a note to the first output
40
+ # output = MIDICommunications::Output.first
41
+ # output.puts(0x90, 60, 100) # Note On, middle C, velocity 100
42
+ # sleep(0.5)
43
+ # output.puts(0x80, 60, 0) # Note Off
44
+ #
45
+ # @example Receive MIDI from an input
46
+ # input = MIDICommunications::Input.first
47
+ # loop do
48
+ # messages = input.gets
49
+ # messages.each { |m| puts m.inspect }
50
+ # end
51
+ #
52
+ # @example Interactive device selection
53
+ # output = MIDICommunications::Output.gets # Prompts user to select
54
+ # input = MIDICommunications::Input.gets
55
+ #
56
+ # @see Input MIDI input device class
57
+ # @see Output MIDI output device class
58
+ #
59
+ # @api public
21
60
  module MIDICommunications
22
61
  Platform.bootstrap
23
62
  end
@@ -3,31 +3,39 @@ require_relative 'lib/midi-communications/version'
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'midi-communications'
5
5
  s.version = MIDICommunications::VERSION
6
- s.date = '2025-08-23'
6
+ s.date = '2026-09-06'
7
7
  s.summary = 'Platform independent realtime MIDI input and output for Ruby'
8
8
  s.description = 'Access MIDI devices for MacOS, Linux (wip), Windows (wip) and JRuby (wip).'
9
9
  s.authors = ['Javier Sánchez Yeste']
10
10
  s.email = ['javier.sy@gmail.com']
11
11
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
12
- s.homepage = 'https://rubygems.org/gems/midi-communications'
12
+ s.homepage = 'https://github.com/javier-sy/midi-communications'
13
13
  s.license = 'LGPL-3.0-or-later'
14
14
 
15
15
  s.required_ruby_version = '>= 2.7'
16
16
 
17
- # TODO
18
- #s.metadata = {
19
- # "source_code_uri" => "https://",
20
- # "homepage_uri" => "",
21
- # "documentation_uri" => "",
22
- # "changelog_uri" => ""
23
- #}
17
+ s.metadata = {
18
+ 'homepage_uri' => s.homepage,
19
+ 'source_code_uri' => s.homepage,
20
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/midi-communications'
21
+ }
24
22
 
25
- s.add_runtime_dependency 'midi-communications-macos', '~> 0.6'
23
+ # RubyGems has no platform-conditional dependencies, so both platform layers
24
+ # are declared unconditionally. That is harmless only because both are pure
25
+ # Ruby over a library the operating system already provides -- CoreMIDI and
26
+ # winmm.dll -- so each installs anywhere and is simply not required where it
27
+ # does not apply. A layer that shipped compiled binaries per platform could
28
+ # not be declared this way.
29
+ s.add_runtime_dependency 'midi-communications-macos', '~> 0.7'
30
+ s.add_runtime_dependency 'midi-communications-windows', '~> 0.0.1'
26
31
  # s.add_runtime_dependency 'alsa-rawmidi', '~> 0.3', '>= 0.3.1'
27
32
  # s.add_runtime_dependency 'midi-jruby', '~> 0.1', '>= 0.1.4'
28
- # s.add_runtime_dependency 'midi-winmm', '~> 0.1', '>= 0.1.10'
29
33
 
30
34
  s.add_development_dependency 'minitest', '~>5', '>= 5.14.4'
31
35
  s.add_development_dependency 'rake', '~>13', '>= 13.0.6'
32
36
  s.add_development_dependency 'shoulda-context', '~>2', '>= 2.0.0'
37
+
38
+ s.add_development_dependency 'yard', '~> 0.9'
39
+ s.add_development_dependency 'redcarpet', '~> 3.6'
40
+ s.add_development_dependency 'webrick', '~> 1.8'
33
41
  end