micromidi 0.1.3 → 0.1.4
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 +4 -4
- data/lib/micromidi.rb +5 -5
- data/lib/micromidi/context.rb +38 -19
- data/lib/micromidi/device.rb +37 -0
- data/lib/micromidi/instructions/composite.rb +28 -17
- data/lib/micromidi/instructions/input.rb +81 -38
- data/lib/micromidi/instructions/message.rb +65 -18
- data/lib/micromidi/instructions/output.rb +5 -4
- data/lib/micromidi/instructions/process.rb +53 -9
- data/lib/micromidi/instructions/shorthand.rb +16 -15
- data/lib/micromidi/instructions/sticky.rb +53 -24
- data/lib/micromidi/instructions/sysex.rb +39 -18
- data/lib/micromidi/module_methods.rb +17 -19
- data/lib/micromidi/state.rb +84 -41
- data/lib/midi.rb +6 -5
- data/test/composite_test.rb +23 -28
- data/test/context_test.rb +13 -18
- data/test/effect_test.rb +9 -13
- data/test/helper.rb +16 -9
- data/test/input_test.rb +4 -5
- data/test/message_test.rb +30 -35
- data/test/output_test.rb +3 -8
- data/test/state_test.rb +7 -12
- data/test/sticky_test.rb +37 -42
- data/test/sysex_test.rb +14 -17
- metadata +103 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d899af108dba8fc0eb7b830cdc53ecf65f8a9946
|
4
|
+
data.tar.gz: 947eb84dfbba8084a55359b460effd7da0bfa881
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 738e6469b03feff4fdf5f64027ca32f2d6341c18f0f2767b0dcc810b265c16cf4290fc13ac862ec0eeedc339ef43d6379ba590c93612fccce2d68e15450a697e
|
7
|
+
data.tar.gz: c9b87df40f972394d975d23d3ba35de7b8bf145efce20857504f069d02857e4c53401ce9358a7e88d1b401c09d1d536734a7f5efc84360db143ad0c4430586a4
|
data/lib/micromidi.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
#
|
4
4
|
# A Ruby DSL for MIDI
|
5
5
|
#
|
6
|
-
# (c)2011-2014 Ari Russo
|
7
|
-
#
|
6
|
+
# (c)2011-2014 Ari Russo
|
7
|
+
# Apache 2.0 License
|
8
8
|
#
|
9
9
|
|
10
10
|
# libs
|
@@ -15,6 +15,7 @@ require "midi-message"
|
|
15
15
|
require "unimidi"
|
16
16
|
|
17
17
|
# modules
|
18
|
+
require "micromidi/device"
|
18
19
|
require "micromidi/instructions/composite"
|
19
20
|
require "micromidi/module_methods"
|
20
21
|
|
@@ -32,9 +33,8 @@ require "micromidi/instructions/sysex"
|
|
32
33
|
require "micromidi/instructions/shorthand"
|
33
34
|
|
34
35
|
module MicroMIDI
|
35
|
-
|
36
|
-
VERSION = "0.1.
|
36
|
+
|
37
|
+
VERSION = "0.1.4"
|
37
38
|
|
38
39
|
end
|
39
40
|
MIDI = MicroMIDI
|
40
|
-
|
data/lib/micromidi/context.rb
CHANGED
@@ -1,41 +1,54 @@
|
|
1
1
|
module MicroMIDI
|
2
|
-
|
2
|
+
|
3
|
+
# The DSL context
|
3
4
|
class Context
|
4
|
-
|
5
|
+
|
5
6
|
include Instructions::Composite
|
6
7
|
extend Forwardable
|
7
|
-
|
8
|
+
|
8
9
|
attr_reader :state
|
9
|
-
|
10
|
+
|
10
11
|
def_delegator :state, :output_cache, :cache
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
|
13
|
+
# @param [Array<UniMIDI::Input>, UniMIDI::Input] inputs
|
14
|
+
# @param [Array<UniMIDI::Output, IO>, IO, UniMIDI::Output] outputs
|
15
|
+
# @param [Proc] block
|
16
|
+
def initialize(inputs, outputs, &block)
|
17
|
+
|
18
|
+
@state = State.new(inputs, outputs)
|
19
|
+
|
16
20
|
@instructions = {
|
17
21
|
:process => Instructions::Process.new(@state),
|
18
|
-
:input => Instructions::Input.new(@state),
|
22
|
+
:input => Instructions::Input.new(@state),
|
19
23
|
:message => Instructions::Message.new(@state),
|
20
24
|
:output => Instructions::Output.new(@state),
|
21
25
|
:sticky => Instructions::Sticky.new(@state),
|
22
26
|
:sysex => Instructions::SysEx.new(@state)
|
23
27
|
}
|
24
|
-
|
25
|
-
edit(&block)
|
28
|
+
|
29
|
+
edit(&block) if block_given?
|
26
30
|
end
|
27
|
-
|
28
|
-
#
|
31
|
+
|
32
|
+
# Eval a block for editing/live coding in this context
|
33
|
+
# @param [Proc] block
|
34
|
+
# @return [Object]
|
29
35
|
def edit(&block)
|
30
36
|
instance_eval(&block)
|
31
37
|
end
|
32
|
-
|
38
|
+
|
33
39
|
# Repeat the last instruction in the history
|
40
|
+
# @return [Object]
|
34
41
|
def repeat
|
35
|
-
|
42
|
+
unless @state.last_command.nil?
|
43
|
+
send(@state.last_command[:method], *@state.last_command[:args])
|
44
|
+
end
|
36
45
|
end
|
37
|
-
|
46
|
+
|
38
47
|
# Delegates a command to one of the instruction classes
|
48
|
+
# @param [Symbol] method
|
49
|
+
# @param [*Object] args
|
50
|
+
# @param [Proc] block
|
51
|
+
# @return [Object]
|
39
52
|
def method_missing(method, *args, &block)
|
40
53
|
results = delegate(method, args, &block)
|
41
54
|
if results.empty?
|
@@ -53,11 +66,17 @@ module MicroMIDI
|
|
53
66
|
private
|
54
67
|
|
55
68
|
# Should a message that resulted from the given instruction type be outputted?
|
69
|
+
# @param [Symbol] instruction_type
|
70
|
+
# @return [Boolean]
|
56
71
|
def output?(instruction_type)
|
57
72
|
@state.auto_output && [:sysex, :message, :process].include?(instruction_type)
|
58
73
|
end
|
59
74
|
|
60
|
-
# Delegate a command
|
75
|
+
# Delegate a command
|
76
|
+
# @param [Symbol] method
|
77
|
+
# @param [*Object] args
|
78
|
+
# @param [Proc] block
|
79
|
+
# @return [Object]
|
61
80
|
def delegate(method, args, &block)
|
62
81
|
results = @instructions.map do |key, instruction|
|
63
82
|
if instruction.respond_to?(method)
|
@@ -70,6 +89,6 @@ module MicroMIDI
|
|
70
89
|
end
|
71
90
|
results.compact
|
72
91
|
end
|
73
|
-
|
92
|
+
|
74
93
|
end
|
75
94
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MicroMIDI
|
2
|
+
|
3
|
+
# Deal with MIDI devices
|
4
|
+
module Device
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Is the given device a MIDI input?
|
9
|
+
# @param [Object] device
|
10
|
+
# @return [Boolean]
|
11
|
+
def input?(device)
|
12
|
+
device.respond_to?(:type) && device.type == :input && device.respond_to?(:gets)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Is the given device a MIDI output?
|
16
|
+
# @param [Object] device
|
17
|
+
# @return [Boolean]
|
18
|
+
def output?(device)
|
19
|
+
device.respond_to?(:puts)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Select the MIDI inputs from the given objects
|
23
|
+
# @params [*Object] args
|
24
|
+
# @return [Array<UniMIDI::Input>]
|
25
|
+
def get_inputs(*args)
|
26
|
+
[args].flatten.select { |device| input?(device) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Select the MIDI outputs from the given objects
|
30
|
+
# @params [*Object] args
|
31
|
+
# @return [Array<UniMIDI::Output, IO>]
|
32
|
+
def get_outputs(*args)
|
33
|
+
[args].flatten.select { |device| output?(device) }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -1,31 +1,35 @@
|
|
1
1
|
module MicroMIDI
|
2
2
|
|
3
3
|
module Instructions
|
4
|
-
|
4
|
+
|
5
|
+
# Commands that are composites of other commands
|
5
6
|
module Composite
|
6
|
-
|
7
|
+
|
7
8
|
#
|
8
|
-
#
|
9
|
+
# Play a note or notes, for the given duration.
|
9
10
|
#
|
10
|
-
# The first argument must be
|
11
|
-
#
|
11
|
+
# The first argument must be: [Array<String>, Array<MIDIMessage::NoteOn>, String, MIDIMessage::NoteOn]
|
12
|
+
# The last argument must be [Numeric] representing the duration
|
12
13
|
#
|
13
|
-
# Additional arguments
|
14
|
-
# be played as a chord with the first argument.
|
14
|
+
# Additional arguments can be [Array<String>, Array<MIDIMessage::NoteOn>, String, MIDIMessage::NoteOn] and will
|
15
|
+
# be played as a chord simultaneously with the first argument.
|
15
16
|
#
|
17
|
+
# @param [*Object] args
|
18
|
+
# @return [Array<MIDIMessage::NoteOn>, MIDIMessage::NoteOn]
|
16
19
|
def play(*args)
|
17
20
|
raise "Last argument must be a Numeric duration" unless args.last.kind_of?(Numeric)
|
18
|
-
|
21
|
+
args = args.dup
|
19
22
|
duration = args.pop
|
20
23
|
note_or_notes = [args].flatten
|
21
24
|
messages = as_note_messages(note_or_notes)
|
22
25
|
sleep(duration)
|
23
26
|
send_note_offs(messages)
|
24
|
-
|
27
|
+
|
25
28
|
messages.count > 1 ? messages : messages.first
|
26
29
|
end
|
27
|
-
|
28
|
-
#
|
30
|
+
|
31
|
+
# Send a note off message for every note on every channel
|
32
|
+
# @return [Boolean]
|
29
33
|
def all_off
|
30
34
|
(0..15).each do |channel|
|
31
35
|
(0..127).each do |note_num|
|
@@ -38,21 +42,28 @@ module MicroMIDI
|
|
38
42
|
|
39
43
|
private
|
40
44
|
|
45
|
+
# Send note off messages for the given note messages
|
46
|
+
# @param [Array<MIDIMessage::NoteOn, MIDIMessage::NoteOff>] messages
|
47
|
+
# @return [Boolean]
|
41
48
|
def send_note_offs(messages)
|
42
|
-
messages.each do |message|
|
49
|
+
messages.each do |message|
|
43
50
|
note_off(message.note, :channel => message.channel, :velocity => message.velocity)
|
44
51
|
end
|
52
|
+
true
|
45
53
|
end
|
46
54
|
|
55
|
+
# MIDI note message objects for the given arguments
|
56
|
+
# @param [Array<MIDIMessage::NoteOn, MIDIMessage::NoteOff, Fixnum, String>] note_or_notes
|
57
|
+
# @return [Array<MIDIMessage::NoteOn>]
|
47
58
|
def as_note_messages(note_or_notes)
|
48
|
-
note_or_notes.map do |
|
49
|
-
case
|
50
|
-
|
51
|
-
|
59
|
+
note_or_notes.map do |item|
|
60
|
+
case item
|
61
|
+
when Fixnum, String then note(item)
|
62
|
+
when MIDIMessage then item
|
52
63
|
end
|
53
64
|
end
|
54
65
|
end
|
55
|
-
|
66
|
+
|
56
67
|
end
|
57
68
|
|
58
69
|
end
|
@@ -1,23 +1,29 @@
|
|
1
1
|
module MicroMIDI
|
2
2
|
|
3
3
|
module Instructions
|
4
|
-
|
4
|
+
|
5
|
+
# Commands dealing with MIDI input
|
5
6
|
class Input
|
6
|
-
|
7
|
+
|
8
|
+
# @param [State] state
|
7
9
|
def initialize(state)
|
8
10
|
@state = state
|
9
11
|
end
|
10
12
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Bind an event that will be fired when a message is received
|
14
|
+
# @param [*Object] args Types of messages to filter on eg :note_on, :control_change
|
15
|
+
# @param [Proc] callback The event callback
|
16
|
+
# @return [Boolean]
|
17
|
+
def receive(*args, &callback)
|
18
|
+
args = args.dup
|
19
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
20
|
+
unless args.empty?
|
21
|
+
match = { :class => message_classes(args) }
|
17
22
|
end
|
18
|
-
listener(match, options) do |event|
|
23
|
+
listener(match, options) do |event|
|
19
24
|
yield(event[:message], event[:timestamp])
|
20
25
|
end
|
26
|
+
true
|
21
27
|
end
|
22
28
|
alias_method :gets, :receive
|
23
29
|
alias_method :handle, :receive
|
@@ -25,11 +31,15 @@ module MicroMIDI
|
|
25
31
|
alias_method :listen_for, :receive
|
26
32
|
alias_method :when_receive, :receive
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
# Bind an event that will be fired when a message is received
|
35
|
+
# @param [*Object] args Types of messages to filter out eg :note_on, :control_change
|
36
|
+
# @param [Proc] callback The event callback
|
37
|
+
# @return [Boolean]
|
38
|
+
def receive_unless(*args, &callback)
|
39
|
+
args = args.dup
|
40
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
41
|
+
match = message_classes(args)
|
42
|
+
listener(nil, options) do |event|
|
33
43
|
yield(event[:message], event[:timestamp]) unless match.include?(event[:message].class)
|
34
44
|
end
|
35
45
|
end
|
@@ -38,45 +48,66 @@ module MicroMIDI
|
|
38
48
|
alias_method :listen_for_unless, :receive_unless
|
39
49
|
alias_method :unless_receive, :receive_unless
|
40
50
|
|
41
|
-
#
|
51
|
+
# Send input messages thru to the outputs
|
42
52
|
def thru
|
43
53
|
thru_if
|
44
54
|
end
|
45
55
|
|
46
|
-
#
|
56
|
+
# Send input messages thru to the outputs if they have a specified class
|
57
|
+
# @param [*Object] args
|
58
|
+
# @return [Boolean]
|
47
59
|
def thru_if(*args)
|
48
|
-
receive_options =
|
60
|
+
receive_options = thru_arguments(args)
|
49
61
|
receive(*receive_options) { |message, timestamp| output(message) }
|
62
|
+
true
|
50
63
|
end
|
51
64
|
|
52
|
-
#
|
65
|
+
# Send input messages thru to the outputs unless they're of the specified class
|
66
|
+
# @param [*Object] args
|
67
|
+
# @return [Boolean]
|
53
68
|
def thru_unless(*args)
|
54
|
-
receive_options =
|
69
|
+
receive_options = thru_arguments(args)
|
55
70
|
receive_unless(*receive_options) { |message, timestamp| output(message) }
|
56
71
|
end
|
57
|
-
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
72
|
+
|
73
|
+
# Similar to Input#thru_unless except a callback can be passed that will be fired when notes specified arrive
|
74
|
+
# @param [*Object] args
|
75
|
+
# @param [Proc] callback
|
76
|
+
# @return [Boolean]
|
77
|
+
def thru_except(*args, &callback)
|
61
78
|
thru_unless(*args)
|
62
|
-
receive(*args, &
|
79
|
+
receive(*args, &callback)
|
63
80
|
end
|
64
|
-
|
65
|
-
#
|
66
|
-
#
|
81
|
+
|
82
|
+
# Wait for input on the last input passed in (blocking)
|
83
|
+
# Can pass the option :from => [an input] to specify which one to wait on
|
84
|
+
# @param [Hash] options
|
85
|
+
# @option options [UniMIDI::Input] :from
|
86
|
+
# @return [Boolean]
|
67
87
|
def wait_for_input(options = {})
|
68
88
|
listener = options[:from] || @state.listeners.last || @state.thru_listeners.last
|
69
89
|
listener.join
|
90
|
+
true
|
70
91
|
end
|
71
|
-
|
92
|
+
|
93
|
+
# Join the listener thread
|
94
|
+
# @return [Boolean]
|
72
95
|
def join
|
73
96
|
loop { wait_for_input }
|
97
|
+
true
|
74
98
|
end
|
75
99
|
|
76
100
|
protected
|
77
101
|
|
102
|
+
# Initialize input event listeners for the given inputs and options
|
103
|
+
# @param [Hash] match
|
104
|
+
# @param [Hash] options
|
105
|
+
# option options [Array<UniMIDI::Input>, UniMIDI::Input] :from
|
106
|
+
# option options [Boolean] :start
|
107
|
+
# option options [Boolean] :thru
|
78
108
|
def listener(match, options = {}, &block)
|
79
109
|
inputs = options[:from] || @state.inputs
|
110
|
+
inputs = [inputs].flatten
|
80
111
|
do_thru = options.fetch(:thru, false)
|
81
112
|
should_start = options.fetch(:start, true)
|
82
113
|
match ||= {}
|
@@ -89,21 +120,29 @@ module MicroMIDI
|
|
89
120
|
|
90
121
|
private
|
91
122
|
|
92
|
-
|
123
|
+
# Initialize an input event listener
|
124
|
+
# @param [UniMIDI::Input] input
|
125
|
+
# @param [Hash] match
|
126
|
+
# @param [Boolean] do_thru
|
127
|
+
# @param [Proc] callback
|
128
|
+
# @return [MIDIEye::Listener]
|
129
|
+
def initialize_listener(input, match, do_thru, &callback)
|
93
130
|
listener = MIDIEye::Listener.new(input)
|
94
|
-
listener.listen_for(match, &
|
131
|
+
listener.listen_for(match, &callback)
|
95
132
|
if do_thru
|
96
133
|
@state.thru_listeners.each(&:stop)
|
97
134
|
@state.thru_listeners.clear
|
98
135
|
@state.thru_listeners << listener
|
99
|
-
else
|
136
|
+
else
|
100
137
|
@state.listeners << listener
|
101
|
-
end
|
138
|
+
end
|
102
139
|
listener
|
103
140
|
end
|
104
141
|
|
105
|
-
# The
|
106
|
-
|
142
|
+
# The arguments for using thru
|
143
|
+
# @param [Array<Object>] args
|
144
|
+
# @return [Array<Object, Hash>]
|
145
|
+
def thru_arguments(args)
|
107
146
|
receive_options = args.dup
|
108
147
|
if receive_options.last.kind_of?(Hash)
|
109
148
|
receive_options.last[:thru] = true
|
@@ -113,9 +152,12 @@ module MicroMIDI
|
|
113
152
|
receive_options
|
114
153
|
end
|
115
154
|
|
116
|
-
|
117
|
-
|
118
|
-
|
155
|
+
# Get the MIDIMessage class for the given note name
|
156
|
+
# @param [Array<String, Symbol>] type_list
|
157
|
+
# @return [Array<Class>]
|
158
|
+
def message_classes(type_list)
|
159
|
+
classes = type_list.map do |type|
|
160
|
+
case type.to_sym
|
119
161
|
when :aftertouch, :pressure, :aft then [MIDIMessage::ChannelAftertouch, MIDIMessage::PolyphonicAftertouch]
|
120
162
|
when :channel_aftertouch, :channel_pressure, :ca, :cp then MIDIMessage::ChannelAftertouch
|
121
163
|
when :control_change, :cc, :c then MIDIMessage::ControlChange
|
@@ -126,7 +168,8 @@ module MicroMIDI
|
|
126
168
|
when :polyphonic_aftertouch, :poly_aftertouch, :poly_pressure, :polyphonic_pressure, :pa, :pp then MIDIMessage::PolyphonicAftertouch
|
127
169
|
when :program_change, :pc, :p then MIDIMessage::ProgramChange
|
128
170
|
end
|
129
|
-
end
|
171
|
+
end
|
172
|
+
classes.flatten.compact
|
130
173
|
end
|
131
174
|
|
132
175
|
end
|