midi-instrument 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +23 -0
- data/lib/midi-instrument.rb +32 -0
- data/lib/midi-instrument/api.rb +14 -0
- data/lib/midi-instrument/device.rb +38 -0
- data/lib/midi-instrument/input.rb +151 -0
- data/lib/midi-instrument/listener.rb +61 -0
- data/lib/midi-instrument/message.rb +87 -0
- data/lib/midi-instrument/node.rb +48 -0
- data/lib/midi-instrument/note_event.rb +29 -0
- data/lib/midi-instrument/output.rb +103 -0
- data/test/device_test.rb +26 -0
- data/test/helper.rb +18 -0
- data/test/input_test.rb +92 -0
- data/test/listener_test.rb +31 -0
- data/test/message_test.rb +106 -0
- data/test/note_event_test.rb +33 -0
- data/test/output_test.rb +100 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4dc9ef33817d2595aa97bec2239f28f1b1779bb4
|
4
|
+
data.tar.gz: 05d6227e8b18d1a7a36b439661ea0d7204ec2770
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b377e4696a2ab9f80d0c76a10287891883a1ab04162d78b0b0d5c88ba6981f384577ee0b82a1a8fab8da1c8780797699b9cb3f3e3896eb57369354da1cade4cf
|
7
|
+
data.tar.gz: c3c7bf02d6ab64895a10b0319bd7ecf387d40885b0c0c184fd79922149eda6edc776d26715f573d83afa6006448b87d86ec7e24453715d44155d1c5eea6c38be
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011-2014 Ari Russo
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#MIDI Instrument
|
2
|
+
|
3
|
+
Core MIDI instrument functionality
|
4
|
+
|
5
|
+
Used by [Diamond](http://github.com/arirusso/diamond)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
`gem install midi-instrument`
|
10
|
+
|
11
|
+
or with Bundler, add this to your Gemfile
|
12
|
+
|
13
|
+
`gem "midi-instrument"`
|
14
|
+
|
15
|
+
## Documentation
|
16
|
+
|
17
|
+
* [rdoc](http://rubydoc.info/github/arirusso/midi-instrument)
|
18
|
+
|
19
|
+
## License
|
20
|
+
|
21
|
+
Licensed under Apache 2.0, See the file LICENSE
|
22
|
+
|
23
|
+
Copyright © 2011-2014 Ari Russo
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# MIDI Instrument
|
3
|
+
# Core MIDI Instrument Functionality
|
4
|
+
#
|
5
|
+
# (c)2011-2014 Ari Russo
|
6
|
+
# Licensed under Apache 2.0
|
7
|
+
#
|
8
|
+
|
9
|
+
# libs
|
10
|
+
require "forwardable"
|
11
|
+
require "midi-eye"
|
12
|
+
require "midi-fx"
|
13
|
+
require "midi-message"
|
14
|
+
require "unimidi"
|
15
|
+
|
16
|
+
# modules
|
17
|
+
require "midi-instrument/api"
|
18
|
+
require "midi-instrument/device"
|
19
|
+
require "midi-instrument/message"
|
20
|
+
|
21
|
+
# classes
|
22
|
+
require "midi-instrument/input"
|
23
|
+
require "midi-instrument/listener"
|
24
|
+
require "midi-instrument/node"
|
25
|
+
require "midi-instrument/note_event"
|
26
|
+
require "midi-instrument/output"
|
27
|
+
|
28
|
+
module MIDIInstrument
|
29
|
+
|
30
|
+
VERSION = "0.4.1"
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
module API
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:extend, Forwardable)
|
7
|
+
base.send(:def_delegators, :@input, :omni_on)
|
8
|
+
base.send(:def_delegators, :@output, :mute, :toggle_mute, :mute=, :muted?, :mute?)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# Manage MIDI Devices
|
4
|
+
module Device
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Partition UniMIDI devices into a hash of inputs and outputs
|
9
|
+
# @param [Array<UniMIDI::Input, UniMIDI::Output>, UniMIDI::Input, UniMIDI::Output] devices Input or output device(s).
|
10
|
+
# @return [Hash] Partitioned arrays of inputs and outputs.
|
11
|
+
def partition(devices)
|
12
|
+
devices = [(devices || [])].flatten
|
13
|
+
outputs = devices.select { |device| output?(device) }
|
14
|
+
inputs = devices.select { |device| input?(device) }
|
15
|
+
{
|
16
|
+
:input => inputs,
|
17
|
+
:output => outputs
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Is this device an output?
|
24
|
+
# @param [UniMIDI::Output, Object] device
|
25
|
+
# @return [Boolean]
|
26
|
+
def output?(device)
|
27
|
+
device.respond_to?(:puts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is this device an input?
|
31
|
+
# @param [UniMIDI::Input, Object] device
|
32
|
+
# @return [Boolean]
|
33
|
+
def input?(device)
|
34
|
+
device.respond_to?(:type) && device.type == :input && device.respond_to?(:gets)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# Enable a node to listen for MIDI messages on a MIDI input
|
4
|
+
class Input
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :devices, :channel
|
9
|
+
def_delegators :@listener, :join
|
10
|
+
|
11
|
+
# @param [Hash] options
|
12
|
+
# @option options [Array<UniMIDI::Input>, UniMIDI::Input] :sources
|
13
|
+
# @option options [Hash] :input_map
|
14
|
+
def initialize(options = {})
|
15
|
+
@listener = Listener.new(options[:sources])
|
16
|
+
@devices = InputContainer.new(@listener)
|
17
|
+
@channel = nil
|
18
|
+
@channel_filter = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add a MIDI input callback
|
22
|
+
# @param [Hash] match Matching spec
|
23
|
+
# @param [Proc] callback
|
24
|
+
# @return [Listen]
|
25
|
+
def receive(match = {}, &block)
|
26
|
+
if block_given?
|
27
|
+
@listener.receive(match) do |event|
|
28
|
+
event = filter_event(event)
|
29
|
+
yield(event)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(*messages)
|
36
|
+
messages = Message.to_messages([messages].flatten)
|
37
|
+
messages = messages.map { |message| filter_message(message) }.compact
|
38
|
+
@listener.add(*messages) unless messages.empty?
|
39
|
+
messages
|
40
|
+
end
|
41
|
+
alias_method :<<, :add
|
42
|
+
|
43
|
+
# Set the listener to acknowledge notes on all channels
|
44
|
+
# @return [Boolean]
|
45
|
+
def omni
|
46
|
+
@channel_filter = nil
|
47
|
+
@channel = nil
|
48
|
+
true
|
49
|
+
end
|
50
|
+
alias_method :omni_on, :omni
|
51
|
+
|
52
|
+
# Set the listener to only acknowledge notes from a specific channel
|
53
|
+
# @return [Boolean]
|
54
|
+
def channel=(channel)
|
55
|
+
@channel_filter = channel.nil? ? nil : MIDIFX::Filter.new(:channel, channel, :name => :input_channel)
|
56
|
+
@channel = channel
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Replace the devices with the given devices
|
61
|
+
# @param [Array<UniMIDI::Inputs>] devices
|
62
|
+
# @return [InputContainer]
|
63
|
+
def devices=(devices)
|
64
|
+
@devices.clear
|
65
|
+
@devices += devices
|
66
|
+
@devices
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def filter_event(event)
|
72
|
+
if !@channel_filter.nil?
|
73
|
+
if !(message = filter_message(event[:message])).nil?
|
74
|
+
event[:message] = message
|
75
|
+
event
|
76
|
+
end
|
77
|
+
else
|
78
|
+
event
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def filter_message(message)
|
83
|
+
message = @channel_filter.process(message) unless @channel_filter.nil?
|
84
|
+
message
|
85
|
+
end
|
86
|
+
|
87
|
+
# Container class that handles updating the listener when changes are made
|
88
|
+
class InputContainer < Array
|
89
|
+
|
90
|
+
# @param [Listener] listener
|
91
|
+
def initialize(listener)
|
92
|
+
@listener = listener
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add an input
|
96
|
+
# @param [UniMIDI::Input] input
|
97
|
+
# @return [InputContainer]
|
98
|
+
def <<(input)
|
99
|
+
result = super
|
100
|
+
@listener.add_input(input)
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
# Add multiple devices
|
105
|
+
# @param [Array<UniMIDI::Input>] devices
|
106
|
+
# @return [InputContainer]
|
107
|
+
def +(devices)
|
108
|
+
result = super
|
109
|
+
@listener.add_input(devices)
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add multiple devices
|
114
|
+
# @param [Array<UniMIDI::Input>] devices
|
115
|
+
# @return [InputContainer]
|
116
|
+
def concat(devices)
|
117
|
+
result = super
|
118
|
+
@listener.add_input(devices)
|
119
|
+
result
|
120
|
+
end
|
121
|
+
|
122
|
+
# Delete an input
|
123
|
+
# @param [UniMIDI::Input]
|
124
|
+
# @return [UniMIDI::Input]
|
125
|
+
def delete(input)
|
126
|
+
result = super
|
127
|
+
@listener.remove_input(input)
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
# Clear all devices
|
132
|
+
# @return [InputContainer]
|
133
|
+
def clear
|
134
|
+
@listener.inputs.each { |input| delete(input) }
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
# Delete multiple devices
|
139
|
+
# @param [Proc] block
|
140
|
+
# @return [InputContainer]
|
141
|
+
def delete_if(&block)
|
142
|
+
devices = super
|
143
|
+
@listener.remove_input(devices)
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# A light wrapper for MIDIEye::Listener
|
4
|
+
class Listener
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@listener, :add_input, :remove_input, :stop
|
9
|
+
def_delegator :@listener, :remove_listener, :delete_event
|
10
|
+
|
11
|
+
# @param [Array<UniMIDI::Input>, UniMIDI::Input] sources
|
12
|
+
def initialize(sources)
|
13
|
+
@listener = MIDIEye::Listener.new([sources].flatten)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Manually add messages to the MIDI input buffer
|
17
|
+
# @param [Array<MIDIMessage>, MIDIMessage, *MIDIMessage] args
|
18
|
+
# @return [Array<MIDIMessage>]
|
19
|
+
def add(*messages)
|
20
|
+
[messages].flatten.map do |message|
|
21
|
+
report = {
|
22
|
+
:message => message,
|
23
|
+
:timestamp => Time.now.to_f
|
24
|
+
}
|
25
|
+
@listener.event.enqueue_all(report)
|
26
|
+
report
|
27
|
+
end
|
28
|
+
end
|
29
|
+
alias_method :<<, :add
|
30
|
+
|
31
|
+
# Join the listener thread
|
32
|
+
def join
|
33
|
+
start if !@listener.running?
|
34
|
+
@listener.join
|
35
|
+
end
|
36
|
+
|
37
|
+
# The active inputs
|
38
|
+
# @return [Array<UniMIDI::Input>]
|
39
|
+
def inputs
|
40
|
+
@listener.sources
|
41
|
+
end
|
42
|
+
|
43
|
+
# Start the listener
|
44
|
+
def start
|
45
|
+
@listener.start(:background => true)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Bind a message callback
|
49
|
+
# @param [Hash] match
|
50
|
+
# @param [Proc] callback
|
51
|
+
# @return [Boolean]
|
52
|
+
def receive(match = {}, &callback)
|
53
|
+
@listener.listen_for(match, &callback)
|
54
|
+
start if !@listener.running?
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# Helper for converting MIDI messages
|
4
|
+
module Message
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Convert the input to MIDI messages
|
9
|
+
# @param [Array<MIDIMessage>, Array<String>, MIDIMessage, *MIDIMessage, String] args
|
10
|
+
# @return [Array<MIDIMessage>]
|
11
|
+
def to_messages(*args)
|
12
|
+
data = [args.dup].flatten
|
13
|
+
if data.all? { |item| note?(item) }
|
14
|
+
Message.to_note_ons(*data) # string note names
|
15
|
+
elsif data.all? { |item| message?(item) }
|
16
|
+
data # messages
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Convert the input to bytes
|
21
|
+
# @param [Array<Fixnum>, Array<MIDIMessage>, Array<String>, MIDIMessage, *MIDIMessage, String] args
|
22
|
+
# @return [Array<Fixnum>]
|
23
|
+
def to_bytes(*args)
|
24
|
+
messages = to_messages(*args)
|
25
|
+
if !messages.nil?
|
26
|
+
messages.map(&:to_bytes).flatten
|
27
|
+
elsif args.all? { |arg| bytes?(arg)}
|
28
|
+
args
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Convert the input to MIDI note on messages
|
33
|
+
# @param [*MIDIMessage::NoteOn, *String] args
|
34
|
+
# @param [Hash] options
|
35
|
+
# @option options [Fixnum] :default_channel
|
36
|
+
# @option options [Fixnum] :default_velocity
|
37
|
+
# @return [Array<MIDIMessage::NoteOn, nil>]
|
38
|
+
def to_note_ons(*args)
|
39
|
+
notes = [args.dup].flatten
|
40
|
+
options = notes.last.kind_of?(Hash) ? notes.pop : {}
|
41
|
+
notes.map do |note|
|
42
|
+
case note
|
43
|
+
when String then string_to_note_on(note, options) if note?(note)
|
44
|
+
when MIDIMessage::NoteOn then note
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Does this object look like a MIDI byte?
|
50
|
+
# @param [Object] object
|
51
|
+
# @return [Boolean]
|
52
|
+
def bytes?(object)
|
53
|
+
object.kind_of?(Fixnum) && object >= 0x00 && object <= 0xFF
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Is this object a MIDI message?
|
59
|
+
# @param [Object] object
|
60
|
+
# @return [Boolean]
|
61
|
+
def message?(object)
|
62
|
+
object.class.name.match(/\AMIDIMessage::[a-zA-Z]+\z/)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Is this object a string note name? eg "A4"
|
66
|
+
# @param [Object] object
|
67
|
+
# @return [Boolean]
|
68
|
+
def note?(object)
|
69
|
+
object.kind_of?(String) && object.match(/\A[a-zA-Z]{1}(\#|b)?\-?\d{1}\z/)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert the given string (eg "A4") to a note on message object
|
73
|
+
# @param [String] string
|
74
|
+
# @param [Hash] options
|
75
|
+
# @option options [Fixnum] :default_channel
|
76
|
+
# @option options [Fixnum] :default_velocity
|
77
|
+
# @return [MIDIMessage::NoteOn]
|
78
|
+
def string_to_note_on(string, options = {})
|
79
|
+
channel = options.fetch(:default_channel, 0)
|
80
|
+
velocity = options.fetch(:default_velocity, 100)
|
81
|
+
MIDIMessage::NoteOn[string].new(channel, velocity)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# Can listen for and send MIDI messages
|
4
|
+
class Node
|
5
|
+
|
6
|
+
include API
|
7
|
+
|
8
|
+
attr_reader :input, :output
|
9
|
+
|
10
|
+
# @param [Hash] options
|
11
|
+
# @option options [Array<UniMIDI::Input>, UniMIDI::Input] :sources Inputs to listen for MIDI on
|
12
|
+
def initialize(options = {})
|
13
|
+
@input = Input.new(options)
|
14
|
+
@output = Output.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def inputs
|
18
|
+
@input.devices
|
19
|
+
end
|
20
|
+
|
21
|
+
def outputs
|
22
|
+
@output.devices
|
23
|
+
end
|
24
|
+
|
25
|
+
def receive_channel
|
26
|
+
@input.channel
|
27
|
+
end
|
28
|
+
alias_method :rx_channel, :receive_channel
|
29
|
+
|
30
|
+
def receive_channel=(channel)
|
31
|
+
@input.channel = channel
|
32
|
+
end
|
33
|
+
alias_method :rx_channel=, :receive_channel=
|
34
|
+
|
35
|
+
def transmit_channel
|
36
|
+
@output.channel
|
37
|
+
end
|
38
|
+
alias_method :tx_channel, :transmit_channel
|
39
|
+
|
40
|
+
def transmit_channel=(channel)
|
41
|
+
@output.channel = channel
|
42
|
+
end
|
43
|
+
alias_method :tx_channel=, :transmit_channel=
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# An NoteEvent is a pairing of a MIDI NoteOn and NoteOff message. Its duration can correspond to sequencer ticks
|
4
|
+
class NoteEvent
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :start,
|
9
|
+
:finish,
|
10
|
+
:length
|
11
|
+
|
12
|
+
alias_method :duration, :length
|
13
|
+
|
14
|
+
def_delegators :start, :note
|
15
|
+
|
16
|
+
# @param [MIDIMessage::NoteOn] start_message
|
17
|
+
# @param [Fixnum] duration
|
18
|
+
# @param [Hash] options
|
19
|
+
# @option options [MIDIMessage::NoteOff] :finish
|
20
|
+
def initialize(start_message, duration, options = {})
|
21
|
+
@start = start_message
|
22
|
+
@length = duration
|
23
|
+
|
24
|
+
@finish = options[:finish] || start_message.to_note_off
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module MIDIInstrument
|
2
|
+
|
3
|
+
# Send MIDI messages
|
4
|
+
class Output
|
5
|
+
|
6
|
+
attr_reader :devices, :channel
|
7
|
+
attr_writer :mute
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@channel = nil
|
11
|
+
@channel_filter = nil
|
12
|
+
@mute = false
|
13
|
+
@devices = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Set the output to convert all emitted notes to a specific channel
|
17
|
+
# @param [Fixnum, nil] channel
|
18
|
+
# @return [Boolean]
|
19
|
+
def channel=(channel)
|
20
|
+
@channel_filter = if channel.nil?
|
21
|
+
nil
|
22
|
+
else
|
23
|
+
MIDIFX::Limit.new(:channel, channel, :name => :output_channel)
|
24
|
+
end
|
25
|
+
@channel = channel
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Emit messages
|
30
|
+
# @param [*Fixnum, *MIDIMessage::NoteOn, *MIDIMessage::NoteOff, *String] args
|
31
|
+
# @return [Array<Fixnum>]
|
32
|
+
def puts(*args)
|
33
|
+
bytes = to_bytes(args)
|
34
|
+
if !@mute
|
35
|
+
@devices.each { |output| output.puts(*bytes) }
|
36
|
+
end
|
37
|
+
bytes
|
38
|
+
end
|
39
|
+
alias_method :<<, :puts
|
40
|
+
|
41
|
+
# Toggle muted output
|
42
|
+
# @return [Boolean]
|
43
|
+
def toggle_mute
|
44
|
+
@mute = !@mute
|
45
|
+
end
|
46
|
+
|
47
|
+
# Mute the output
|
48
|
+
# @return [TrueClass]
|
49
|
+
def mute
|
50
|
+
@mute = true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Un-mute the output
|
54
|
+
# @return [FalseClass]
|
55
|
+
def unmute
|
56
|
+
@mute = false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Is the output muted?
|
60
|
+
# @return [Boolean]
|
61
|
+
def mute?
|
62
|
+
@mute
|
63
|
+
end
|
64
|
+
|
65
|
+
# Replace the devices with the given devices
|
66
|
+
# @param [Array<UniMIDI::Outputs>] devices
|
67
|
+
# @return [OutputContainer]
|
68
|
+
def devices=(devices)
|
69
|
+
@devices.clear
|
70
|
+
@devices += devices
|
71
|
+
@devices
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Convert the input to MIDI message bytes
|
77
|
+
# @param [Array<Fixnum>, Array<MIDIMessage>] args
|
78
|
+
# @return [Array<Fixnum>]
|
79
|
+
def to_bytes(args)
|
80
|
+
data = [args.dup].flatten
|
81
|
+
messages = Message.to_messages(*data)
|
82
|
+
if !messages.nil?
|
83
|
+
messages = filter_output(messages)
|
84
|
+
messages.map(&:to_bytes).flatten
|
85
|
+
elsif Message.bytes?(data)
|
86
|
+
data
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Filter messages for output
|
91
|
+
# @param [Array<MIDIMessage::ChannelMessage>] messages
|
92
|
+
# @return [Array<MIDIMessage::ChannelMessage>]
|
93
|
+
def filter_output(messages)
|
94
|
+
if @channel_filter.nil?
|
95
|
+
messages
|
96
|
+
else
|
97
|
+
messages.map { |message| @channel_filter.process(message) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/test/device_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::DeviceTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Device" do
|
6
|
+
|
7
|
+
context ".partition" do
|
8
|
+
|
9
|
+
should "partition some devices" do
|
10
|
+
inputs = UniMIDI::Input.all
|
11
|
+
outputs = UniMIDI::Output.all
|
12
|
+
devices = inputs + outputs
|
13
|
+
result = MIDIInstrument::Device.partition(devices)
|
14
|
+
assert_not_nil result
|
15
|
+
assert result.kind_of?(Hash)
|
16
|
+
assert_not_nil result[:input]
|
17
|
+
assert_not_nil result[:output]
|
18
|
+
assert_equal inputs, result[:input]
|
19
|
+
assert_equal outputs, result[:output]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
2
|
+
$LOAD_PATH.unshift dir + "/../lib"
|
3
|
+
|
4
|
+
require "test/unit"
|
5
|
+
require "mocha/test_unit"
|
6
|
+
require "shoulda-context"
|
7
|
+
require "midi-instrument"
|
8
|
+
|
9
|
+
module TestHelper
|
10
|
+
|
11
|
+
def self.select_devices
|
12
|
+
$test_device ||= {}
|
13
|
+
{ :input => UniMIDI::Input, :output => UniMIDI::Output }.each do |type, klass|
|
14
|
+
$test_device[type] = klass.gets
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/input_test.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::InputTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include Mocha::ParameterMatchers
|
6
|
+
|
7
|
+
context "Input" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@input = MIDIInstrument::Input.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#receive" do
|
14
|
+
|
15
|
+
should "add callback" do
|
16
|
+
match = { :class => MIDIMessage::NoteOn }
|
17
|
+
block = proc { puts "hello" }
|
18
|
+
MIDIInstrument::Listener.any_instance.expects(:receive).once.with(match).yields(block)
|
19
|
+
result = @input.receive(match, &block)
|
20
|
+
assert_equal @input, result
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context "#add" do
|
26
|
+
|
27
|
+
should "accept strings" do
|
28
|
+
MIDIInstrument::Listener.any_instance.expects(:add).once.with(is_a(MIDIMessage::NoteOn), is_a(MIDIMessage::NoteOn), is_a(MIDIMessage::NoteOn))
|
29
|
+
@input.add("A3", "B4", "C5")
|
30
|
+
end
|
31
|
+
|
32
|
+
should "accept MIDI messages" do
|
33
|
+
MIDIInstrument::Listener.any_instance.expects(:add).once.with(is_a(MIDIMessage::NoteOn), is_a(MIDIMessage::NoteOn), is_a(MIDIMessage::NoteOn))
|
34
|
+
@input.add(MIDIMessage::NoteOn["C3"].new(3, 100), MIDIMessage::NoteOn["B4"].new(5, 100), MIDIMessage::NoteOn["D7"].new(7, 100))
|
35
|
+
end
|
36
|
+
|
37
|
+
should "not suppress unknown objects" do
|
38
|
+
MIDIInstrument::Listener.any_instance.expects(:add).never
|
39
|
+
assert_raise(NoMethodError) { @input.add("hello", "how", "are", "you") }
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context "#omni" do
|
45
|
+
|
46
|
+
should "not have a receive channel" do
|
47
|
+
assert_nil @input.instance_variable_get("@channel_filter")
|
48
|
+
@input.channel = 4
|
49
|
+
assert_equal 4, @input.channel
|
50
|
+
@input.omni
|
51
|
+
assert_nil @input.channel
|
52
|
+
assert_nil @input.instance_variable_get("@channel_filter")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#channel=" do
|
58
|
+
|
59
|
+
should "have filter when channel is specified" do
|
60
|
+
assert_nil @input.instance_variable_get("@channel_filter")
|
61
|
+
@input.channel = 3
|
62
|
+
assert_not_nil @input.instance_variable_get("@channel_filter")
|
63
|
+
assert_equal 3, @input.channel
|
64
|
+
end
|
65
|
+
|
66
|
+
should "not have filter when channel is nil" do
|
67
|
+
@input.channel = 3
|
68
|
+
assert_not_nil @input.instance_variable_get("@channel_filter")
|
69
|
+
assert_equal 3, @input.channel
|
70
|
+
@input.channel = nil
|
71
|
+
assert_nil @input.instance_variable_get("@channel_filter")
|
72
|
+
assert_nil @input.channel
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "#devices=" do
|
78
|
+
|
79
|
+
should "replace devices" do
|
80
|
+
assert_empty @input.devices
|
81
|
+
inputs = UniMIDI::Input.all
|
82
|
+
@input.devices = inputs
|
83
|
+
assert_equal inputs, @input.devices
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::ListenerTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Listener" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
MIDIInstrument::Listener.any_instance.unstub(:add)
|
9
|
+
@listener = MIDIInstrument::Listener.new(Object.new)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "#add" do
|
13
|
+
|
14
|
+
should "output hashes with timestamps" do
|
15
|
+
notes = [
|
16
|
+
MIDIMessage::NoteOn["C3"].new(3, 100),
|
17
|
+
MIDIMessage::NoteOn["B4"].new(5, 100),
|
18
|
+
MIDIMessage::NoteOn["D7"].new(7, 100)
|
19
|
+
]
|
20
|
+
result = @listener.add(*notes)
|
21
|
+
assert_not_nil result
|
22
|
+
assert_not_empty result
|
23
|
+
assert result.all? { |r| r.kind_of?(Hash) }
|
24
|
+
refute result.any? { |r| r[:timestamp].nil? }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::MessageTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Message" do
|
6
|
+
|
7
|
+
context ".to_messages" do
|
8
|
+
|
9
|
+
should "convert strings to note on messages" do
|
10
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
11
|
+
result = MIDIInstrument::Message.to_messages(*names)
|
12
|
+
assert_not_nil result
|
13
|
+
assert_not_empty result
|
14
|
+
assert_equal names.size, result.size
|
15
|
+
assert result.all? { |item| item.is_a?(MIDIMessage::NoteOn) }
|
16
|
+
names.each_with_index { |name, i| assert_equal name, result[i].name }
|
17
|
+
end
|
18
|
+
|
19
|
+
should "pass MIDI messages" do
|
20
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
21
|
+
messages = MIDIInstrument::Message.to_messages(*names)
|
22
|
+
result = MIDIInstrument::Message.to_messages(*messages)
|
23
|
+
assert_not_nil result
|
24
|
+
assert_not_empty result
|
25
|
+
messages.each_with_index { |message,i| assert_equal message.note, result[i].note }
|
26
|
+
end
|
27
|
+
|
28
|
+
should "return nil for unknowns" do
|
29
|
+
assert_nil MIDIInstrument::Message.to_messages("bla", "blah")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context ".to_bytes" do
|
35
|
+
|
36
|
+
should "convert messages to bytes" do
|
37
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
38
|
+
messages = MIDIInstrument::Message.to_messages(*names)
|
39
|
+
result = MIDIInstrument::Message.to_bytes(*messages)
|
40
|
+
assert_not_nil result
|
41
|
+
assert_not_empty result
|
42
|
+
assert result.all? { |item| item.is_a?(Fixnum) }
|
43
|
+
assert_equal messages.map(&:to_a).flatten, result
|
44
|
+
end
|
45
|
+
|
46
|
+
should "convert strings to bytes" do
|
47
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
48
|
+
messages = MIDIInstrument::Message.to_messages(*names)
|
49
|
+
result = MIDIInstrument::Message.to_bytes(*names)
|
50
|
+
assert_not_nil result
|
51
|
+
assert_not_empty result
|
52
|
+
assert result.all? { |item| item.is_a?(Fixnum) }
|
53
|
+
assert_equal messages.map(&:to_a).flatten, result
|
54
|
+
end
|
55
|
+
|
56
|
+
should "pass bytes" do
|
57
|
+
bytes = [144, 9, 100, 144, 61, 100, 144, 78, 100, 144, 86, 100]
|
58
|
+
result = MIDIInstrument::Message.to_bytes(*bytes)
|
59
|
+
assert_not_nil result
|
60
|
+
assert_not_empty result
|
61
|
+
assert result.all? { |item| item.is_a?(Fixnum) }
|
62
|
+
assert_equal bytes, result
|
63
|
+
end
|
64
|
+
|
65
|
+
should "return nil for unknown" do
|
66
|
+
assert_nil MIDIInstrument::Message.to_bytes("this", "is", "something", "weird", 4560)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
context ".to_note_ons" do
|
72
|
+
|
73
|
+
should "convert strings to note ons" do
|
74
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
75
|
+
result = MIDIInstrument::Message.to_note_ons(*names)
|
76
|
+
assert_not_nil result
|
77
|
+
assert_not_empty result
|
78
|
+
assert_equal names.size, result.size
|
79
|
+
assert result.all? { |item| item.is_a?(MIDIMessage::NoteOn) }
|
80
|
+
names.each_with_index { |name, i| assert_equal name, result[i].name }
|
81
|
+
end
|
82
|
+
|
83
|
+
should "pass note on messages" do
|
84
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
85
|
+
messages = MIDIInstrument::Message.to_messages(*names)
|
86
|
+
result = MIDIInstrument::Message.to_note_ons(*messages)
|
87
|
+
assert_not_nil result
|
88
|
+
assert_not_empty result
|
89
|
+
messages.each_with_index { |message,i| assert_equal message.note, result[i].note }
|
90
|
+
end
|
91
|
+
|
92
|
+
should "return nil for unknowns" do
|
93
|
+
result = MIDIInstrument::Message.to_note_ons("blah", "blah", 56904)
|
94
|
+
assert_not_nil result
|
95
|
+
assert_equal 3, result.size
|
96
|
+
assert_empty result.compact
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::NoteEventTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "NoteEvent" do
|
6
|
+
|
7
|
+
context ".new" do
|
8
|
+
|
9
|
+
should "create a note event" do
|
10
|
+
msg = MIDIMessage::NoteOn.new(0, 0x40, 0x40)
|
11
|
+
event = MIDIInstrument::NoteEvent.new(msg, 10)
|
12
|
+
assert_equal(msg, event.start)
|
13
|
+
assert_equal(10, event.duration)
|
14
|
+
assert_equal(MIDIMessage::NoteOff, event.finish.class)
|
15
|
+
assert_equal(msg.note, event.finish.note)
|
16
|
+
assert_equal(msg.note, event.note)
|
17
|
+
end
|
18
|
+
|
19
|
+
should "override finish" do
|
20
|
+
msg = MIDIMessage::NoteOn.new(0, 0x40, 0x40)
|
21
|
+
msg2 = MIDIMessage::NoteOff.new(0, 0x40, 127)
|
22
|
+
event = MIDIInstrument::NoteEvent.new(msg, 5, :finish => msg2)
|
23
|
+
assert_equal(msg, event.start)
|
24
|
+
assert_equal(5, event.duration)
|
25
|
+
assert_equal(0x40, event.start.velocity)
|
26
|
+
assert_equal(MIDIMessage::NoteOff, event.finish.class)
|
27
|
+
assert_equal(0x40, event.finish.note)
|
28
|
+
assert_equal(127, event.finish.velocity)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/test/output_test.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class MIDIInstrument::OutputTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include Mocha::ParameterMatchers
|
6
|
+
|
7
|
+
context "Output" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@output = MIDIInstrument::Output.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#puts" do
|
14
|
+
|
15
|
+
setup do
|
16
|
+
@output.devices << Object.new
|
17
|
+
end
|
18
|
+
|
19
|
+
should "output string notes" do
|
20
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
21
|
+
@output.devices.first.expects(:puts).times(1)
|
22
|
+
result = @output.puts(*names)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "output MIDI messages" do
|
26
|
+
@output.devices.first.expects(:puts).times(1)
|
27
|
+
result = @output.puts(MIDIMessage::NoteOn["C3"].new(0, 100), MIDIMessage::NoteOn["D3"].new(0, 110))
|
28
|
+
end
|
29
|
+
|
30
|
+
should "output to multiple devices" do
|
31
|
+
4.times do
|
32
|
+
@output.devices << Object.new
|
33
|
+
end
|
34
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
35
|
+
@output.devices.each { |device| device.expects(:puts).once }
|
36
|
+
result = @output.puts(*names)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#mute" do
|
42
|
+
|
43
|
+
setup do
|
44
|
+
@output.devices << Object.new
|
45
|
+
end
|
46
|
+
|
47
|
+
should "output when not muted" do
|
48
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
49
|
+
refute @output.mute?
|
50
|
+
@output.devices.first.expects(:puts).times(1)
|
51
|
+
result = @output.puts(*names)
|
52
|
+
end
|
53
|
+
|
54
|
+
should "not output when muted" do
|
55
|
+
names = ["A-1", "C#4", "F#5", "D6"]
|
56
|
+
@output.mute
|
57
|
+
assert @output.mute?
|
58
|
+
@output.devices.first.expects(:puts).never
|
59
|
+
result = @output.puts(*names)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#channel=" do
|
65
|
+
|
66
|
+
should "have filter when channel is specified" do
|
67
|
+
assert_nil @output.instance_variable_get("@channel_filter")
|
68
|
+
@output.channel = 3
|
69
|
+
assert_not_nil @output.instance_variable_get("@channel_filter")
|
70
|
+
assert_equal 3, @output.channel
|
71
|
+
end
|
72
|
+
|
73
|
+
should "not have filter when channel is nil" do
|
74
|
+
@output.channel = 3
|
75
|
+
assert_not_nil @output.instance_variable_get("@channel_filter")
|
76
|
+
assert_equal 3, @output.channel
|
77
|
+
@output.channel = nil
|
78
|
+
assert_nil @output.instance_variable_get("@channel_filter")
|
79
|
+
assert_nil @output.channel
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "#devices=" do
|
85
|
+
|
86
|
+
should "replace devices" do
|
87
|
+
assert_empty @output.devices
|
88
|
+
outputs = UniMIDI::Output.all
|
89
|
+
@output.devices = outputs
|
90
|
+
assert_equal outputs, @output.devices
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: midi-instrument
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ari Russo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: midi-eye
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: midi-fx
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.0.3
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.0.3
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: midi-message
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.3.1
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.3.1
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: unimidi
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.2.5
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 0.2.5
|
93
|
+
description: Core MIDI instrument functionality in Ruby
|
94
|
+
email:
|
95
|
+
- ari.russo@gmail.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- LICENSE
|
101
|
+
- README.md
|
102
|
+
- lib/midi-instrument.rb
|
103
|
+
- lib/midi-instrument/api.rb
|
104
|
+
- lib/midi-instrument/device.rb
|
105
|
+
- lib/midi-instrument/input.rb
|
106
|
+
- lib/midi-instrument/listener.rb
|
107
|
+
- lib/midi-instrument/message.rb
|
108
|
+
- lib/midi-instrument/node.rb
|
109
|
+
- lib/midi-instrument/note_event.rb
|
110
|
+
- lib/midi-instrument/output.rb
|
111
|
+
- test/device_test.rb
|
112
|
+
- test/helper.rb
|
113
|
+
- test/input_test.rb
|
114
|
+
- test/listener_test.rb
|
115
|
+
- test/message_test.rb
|
116
|
+
- test/note_event_test.rb
|
117
|
+
- test/output_test.rb
|
118
|
+
homepage: http://github.com/arirusso/midi-instrument
|
119
|
+
licenses:
|
120
|
+
- Apache 2.0
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 1.3.6
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project: midi-instrument
|
138
|
+
rubygems_version: 2.2.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Core MIDI instrument functionality
|
142
|
+
test_files: []
|