midi-instrument 0.4.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.
- 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: []
|