midi-message 0.2.2
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.
- data/LICENSE +13 -0
- data/README.rdoc +100 -0
- data/TODO +0 -0
- data/lib/midi-message/channel_message.rb +179 -0
- data/lib/midi-message/constant.rb +61 -0
- data/lib/midi-message/context.rb +104 -0
- data/lib/midi-message/note_message.rb +70 -0
- data/lib/midi-message/parser.rb +48 -0
- data/lib/midi-message/process/filter.rb +58 -0
- data/lib/midi-message/process/limit.rb +31 -0
- data/lib/midi-message/process/processor.rb +30 -0
- data/lib/midi-message/process/transpose.rb +30 -0
- data/lib/midi-message/short_message.rb +119 -0
- data/lib/midi-message/system_exclusive.rb +202 -0
- data/lib/midi-message/system_message.rb +47 -0
- data/lib/midi-message/type_conversion.rb +47 -0
- data/lib/midi-message.rb +37 -0
- data/lib/midi.yml +337 -0
- data/test/helper.rb +11 -0
- data/test/test_constants.rb +48 -0
- data/test/test_context.rb +86 -0
- data/test/test_filter.rb +95 -0
- data/test/test_limit.rb +39 -0
- data/test/test_mutability.rb +28 -0
- data/test/test_parser.rb +72 -0
- data/test/test_processor.rb +19 -0
- data/test/test_short_message.rb +123 -0
- data/test/test_system_exclusive.rb +100 -0
- data/test/test_transpose.rb +39 -0
- metadata +74 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2010-2011 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.rdoc
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
= MIDI Message
|
2
|
+
|
3
|
+
{pic}[http://images.treetrouble.net/images/mks80_small.jpg]
|
4
|
+
|
5
|
+
MIDI messages, objectified in Ruby
|
6
|
+
|
7
|
+
== Features
|
8
|
+
|
9
|
+
* Flexible API to accommodate various sources and destinations of MIDI data
|
10
|
+
* Simple OO approach to System Exclusive data and devices
|
11
|
+
* {YAML dictionary of MIDI constants}[https://github.com/arirusso/midi-message/blob/master/lib/midi.yml]
|
12
|
+
|
13
|
+
== Install
|
14
|
+
|
15
|
+
gem install midi-message
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
require 'midi-message'
|
20
|
+
|
21
|
+
include MIDIMessage
|
22
|
+
|
23
|
+
==== Basic Messages
|
24
|
+
|
25
|
+
There are a few ways to create a new MIDI message. Here are some examples
|
26
|
+
|
27
|
+
NoteOn.new(0, 64, 64)
|
28
|
+
|
29
|
+
NoteOn["E4"].new(0, 100)
|
30
|
+
|
31
|
+
with(:channel => 0, :velocity => 100) { note_on("E4") }
|
32
|
+
|
33
|
+
Those expressions all evaluate to the same object
|
34
|
+
|
35
|
+
#<MIDIMessage::NoteOn:0x9c1c240
|
36
|
+
@channel=0,
|
37
|
+
@data=[64, 64],
|
38
|
+
@name="E4",
|
39
|
+
@note=64,
|
40
|
+
@status=[9, 0],
|
41
|
+
@velocity=64,
|
42
|
+
@verbose_name="Note On: E4">
|
43
|
+
|
44
|
+
==== SysEx Messages
|
45
|
+
|
46
|
+
As with any kind of message, you can begin with raw data
|
47
|
+
|
48
|
+
SystemExclusive.new(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
49
|
+
|
50
|
+
Or in a more object oriented way
|
51
|
+
|
52
|
+
synth = SystemExclusive::Node.new(0x41, :model_id => 0x42, :device_id => 0x10)
|
53
|
+
|
54
|
+
SystemExclusive::Command.new([0x40, 0x7F, 0x00], 0x00, :node => synth)
|
55
|
+
|
56
|
+
A Node represents a device that you're sending a message to (eg. your Yamaha DX7 is a Node). Sysex messages can either be a Command or Request
|
57
|
+
|
58
|
+
You can use the Node to instantiate a message
|
59
|
+
|
60
|
+
synth.command([0x40, 0x7F, 0x00], 0x00)
|
61
|
+
|
62
|
+
One way or another, you will wind up with a pair of objects like this
|
63
|
+
|
64
|
+
#<MIDIMessage::SystemExclusive::Command:0x9c1e57c
|
65
|
+
@address=[64, 0, 127],
|
66
|
+
@checksum=[65],
|
67
|
+
@data=[0],
|
68
|
+
@node=
|
69
|
+
#<MIDIMessage::SystemExclusive::Node:0x9c1e5a4
|
70
|
+
@device_id=16,
|
71
|
+
@manufacturer_id=65,
|
72
|
+
@model_id=66>>
|
73
|
+
|
74
|
+
==== Parsing
|
75
|
+
|
76
|
+
The parse method will take any valid message data and return the object representation
|
77
|
+
|
78
|
+
MIDIMessage.parse(0x90, 0x40, 0x40)
|
79
|
+
|
80
|
+
#<MIDIMessage::NoteOn:0x9c1c240 ..>
|
81
|
+
|
82
|
+
MIDIMessage.parse(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
83
|
+
|
84
|
+
#<MIDIMessage::SystemExclusive::Command:0x9c1e57c ..>
|
85
|
+
|
86
|
+
Check out {nibbler}[http://github.com/arirusso/nibbler] for more advanced parsing
|
87
|
+
|
88
|
+
== API Documentation
|
89
|
+
|
90
|
+
* {rdoc}[http://rubydoc.info/github/arirusso/midi-message]
|
91
|
+
|
92
|
+
== Author
|
93
|
+
|
94
|
+
* {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
|
95
|
+
|
96
|
+
== License
|
97
|
+
|
98
|
+
Apache 2.0, See the file LICENSE
|
99
|
+
|
100
|
+
Copyright (c) 2011 Ari Russo
|
data/TODO
ADDED
File without changes
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
module MIDIMessage
|
4
|
+
|
5
|
+
# common behavior amongst Channel Message types
|
6
|
+
module ChannelMessage
|
7
|
+
|
8
|
+
attr_reader :data,
|
9
|
+
:name
|
10
|
+
|
11
|
+
def self.new(*a, &block)
|
12
|
+
RawChannelMessage.new(*a, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*a)
|
16
|
+
options = a.last.kind_of?(Hash) ? a.pop : {}
|
17
|
+
@const = options[:const]
|
18
|
+
unless @const.nil?
|
19
|
+
key = self.class.map_constants_to
|
20
|
+
ind = self.class.properties.index(key)
|
21
|
+
ind ||= 0
|
22
|
+
a.insert(ind, @const.value)
|
23
|
+
end
|
24
|
+
initialize_channel_message(self.class.type_for_status, *a)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_properties
|
28
|
+
props = [
|
29
|
+
{ :name => :status, :index => 1 },
|
30
|
+
{ :name => :data, :index => 0 },
|
31
|
+
{ :name => :data, :index => 1 }
|
32
|
+
]
|
33
|
+
properties = self.class.properties
|
34
|
+
unless properties.nil?
|
35
|
+
properties.each_with_index do |prop,i|
|
36
|
+
self.class.send(:attr_reader, prop)
|
37
|
+
self.class.send(:define_method, "#{prop}=") do |val|
|
38
|
+
send(:instance_variable_set, "@#{prop.to_s}", val)
|
39
|
+
send(props[i][:name])[props[i][:index]] = val
|
40
|
+
update
|
41
|
+
return self
|
42
|
+
end
|
43
|
+
instance_variable_set("@#{prop}", send(props[i][:name])[props[i][:index]])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def self.included(base)
|
51
|
+
base.extend(ClassMethods)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def initialize_channel_message(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
57
|
+
@status = [status_nibble_1, status_nibble_2]
|
58
|
+
@data = [data_byte_1]
|
59
|
+
@data[1] = data_byte_2 if self.class.second_data_byte?
|
60
|
+
initialize_properties
|
61
|
+
initialize_short_message(status_nibble_1, status_nibble_2)
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
|
66
|
+
attr_reader :properties
|
67
|
+
|
68
|
+
def type_for_status
|
69
|
+
@display_name.nil? ? nil : Status[@display_name]
|
70
|
+
end
|
71
|
+
|
72
|
+
def schema(*args)
|
73
|
+
@properties = args
|
74
|
+
end
|
75
|
+
alias_method :layout, :schema
|
76
|
+
|
77
|
+
def second_data_byte?
|
78
|
+
@properties.nil? || (@properties.length-1) > 1
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
# use this if you want to instantiate a raw channel message
|
86
|
+
#
|
87
|
+
# example = ChannelMessage.new(0x9, 0x0, 0x40, 0x57) # creates a raw note-on message
|
88
|
+
#
|
89
|
+
class RawChannelMessage
|
90
|
+
|
91
|
+
include ShortMessage
|
92
|
+
include ChannelMessage
|
93
|
+
|
94
|
+
use_display_name 'Channel Message'
|
95
|
+
|
96
|
+
def initialize(*a)
|
97
|
+
initialize_channel_message(*a)
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_type
|
101
|
+
status = (@status[0] << 4) + (@status[1])
|
102
|
+
MIDIMessage.parse(status, *@data)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# MIDI Channel Aftertouch message
|
109
|
+
#
|
110
|
+
class ChannelAftertouch
|
111
|
+
|
112
|
+
include ShortMessage
|
113
|
+
include ChannelMessage
|
114
|
+
|
115
|
+
schema :channel, :value
|
116
|
+
use_display_name 'Channel Aftertouch'
|
117
|
+
|
118
|
+
end
|
119
|
+
ChannelPressure = ChannelAftertouch
|
120
|
+
|
121
|
+
#
|
122
|
+
# MIDI Control Change message
|
123
|
+
#
|
124
|
+
class ControlChange
|
125
|
+
|
126
|
+
include ShortMessage
|
127
|
+
include ChannelMessage
|
128
|
+
|
129
|
+
schema :channel, :index, :value
|
130
|
+
use_display_name 'Control Change'
|
131
|
+
use_constants 'Control Change', :for => :index
|
132
|
+
|
133
|
+
end
|
134
|
+
Controller = ControlChange #shortcut
|
135
|
+
|
136
|
+
#
|
137
|
+
# MIDI Pitch Bend message
|
138
|
+
#
|
139
|
+
class PitchBend
|
140
|
+
|
141
|
+
include ShortMessage
|
142
|
+
include ChannelMessage
|
143
|
+
|
144
|
+
schema :channel, :low, :high
|
145
|
+
use_display_name 'Pitch Bend'
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# MIDI Polyphonic (note specific) Aftertouch message
|
151
|
+
#
|
152
|
+
class PolyphonicAftertouch
|
153
|
+
|
154
|
+
include ShortMessage
|
155
|
+
include ChannelMessage
|
156
|
+
|
157
|
+
schema :channel, :note, :value
|
158
|
+
use_display_name 'Polyphonic Aftertouch'
|
159
|
+
use_constants 'Note', :for => :note
|
160
|
+
|
161
|
+
end
|
162
|
+
PolyAftertouch = PolyphonicAftertouch
|
163
|
+
PolyPressure = PolyphonicAftertouch
|
164
|
+
PolyphonicPressure = PolyphonicAftertouch
|
165
|
+
|
166
|
+
#
|
167
|
+
# MIDI Program Change message
|
168
|
+
#
|
169
|
+
class ProgramChange
|
170
|
+
|
171
|
+
include ShortMessage
|
172
|
+
include ChannelMessage
|
173
|
+
|
174
|
+
schema :channel, :program
|
175
|
+
use_display_name 'Program Change'
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
|
4
|
+
module MIDIMessage
|
5
|
+
|
6
|
+
# MIDI Constants
|
7
|
+
class ConstantGroup
|
8
|
+
|
9
|
+
attr_reader :key, :value
|
10
|
+
|
11
|
+
def initialize(key, constants)
|
12
|
+
@key = key
|
13
|
+
@constants = constants.map { |k, v| Constant.new(k, v) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(name)
|
17
|
+
@constants.find { |const| const.key.to_s.downcase.eql?(name.to_s.downcase) }
|
18
|
+
end
|
19
|
+
alias_method :[], :find
|
20
|
+
|
21
|
+
def find_by_value(value)
|
22
|
+
@constants.find { |const| const.value.to_s.downcase.eql?(value.to_s.downcase) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.all
|
26
|
+
ensure_initialized
|
27
|
+
@groups
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.[](key)
|
31
|
+
ensure_initialized
|
32
|
+
@groups.find { |g| g.key.to_s.downcase.eql?(key.to_s.downcase) }
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# lazy initialize
|
38
|
+
def self.ensure_initialized
|
39
|
+
@dict ||= YAML.load_file(File.expand_path('../../midi.yml', __FILE__))
|
40
|
+
@groups ||= @dict.map { |k, v| new(k, v) }
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class Constant
|
46
|
+
|
47
|
+
attr_reader :key, :value
|
48
|
+
|
49
|
+
def initialize(key, value)
|
50
|
+
@key = key
|
51
|
+
@value = value
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.find(group_name, const_name)
|
55
|
+
group = ConstantGroup[group_name]
|
56
|
+
group.find(const_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
module MIDIMessage
|
6
|
+
|
7
|
+
class Context
|
8
|
+
|
9
|
+
attr_accessor :channel,
|
10
|
+
:velocity
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@channel = options[:channel]
|
14
|
+
@velocity = options[:velocity]
|
15
|
+
end
|
16
|
+
|
17
|
+
def note_off(note, options = {})
|
18
|
+
channel = options[:channel] || @channel
|
19
|
+
velocity = options[:velocity] || @velocity
|
20
|
+
raise 'note_off requires both channel and velocity' if channel.nil? || velocity.nil?
|
21
|
+
if note.kind_of?(String)
|
22
|
+
NoteOff[note].new(channel, velocity, options)
|
23
|
+
else
|
24
|
+
NoteOff.new(channel, note, velocity, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias_method :NoteOff, :note_off
|
28
|
+
|
29
|
+
def note_on(note, options = {})
|
30
|
+
channel = options[:channel] || @channel
|
31
|
+
velocity = options[:velocity] || @velocity
|
32
|
+
raise 'note_on requires both channel and velocity' if channel.nil? || velocity.nil?
|
33
|
+
if note.kind_of?(String)
|
34
|
+
NoteOn[note].new(channel, velocity, options)
|
35
|
+
else
|
36
|
+
NoteOn.new(channel, note, velocity, options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
alias_method :NoteOn, :note_on
|
40
|
+
|
41
|
+
def program_change(program, options = {})
|
42
|
+
channel = options[:channel] || @channel
|
43
|
+
raise 'program_change requires channel' if channel.nil?
|
44
|
+
if program.kind_of?(String)
|
45
|
+
ProgramChange[program].new(channel, options)
|
46
|
+
else
|
47
|
+
ProgramChange.new(channel, program, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :ProgramChange, :program_change
|
51
|
+
|
52
|
+
def control_change(index, value, options = {})
|
53
|
+
channel = options[:channel] || @channel
|
54
|
+
raise 'control_change requires channel' if channel.nil?
|
55
|
+
if index.kind_of?(String)
|
56
|
+
ControlChange[index].new(channel, value, options)
|
57
|
+
else
|
58
|
+
ControlChange.new(channel, index, value, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias_method :ControlChange, :control_change
|
62
|
+
alias_method :Controller, :control_change
|
63
|
+
alias_method :controller, :control_change
|
64
|
+
|
65
|
+
def polyphonic_aftertouch(note, value, options = {})
|
66
|
+
channel = options[:channel] || @channel
|
67
|
+
raise 'channel_aftertouch requires a channel' if channel.nil?
|
68
|
+
if note.kind_of?(String)
|
69
|
+
PolyphonicAftertouch[note].new(channel, value, options)
|
70
|
+
else
|
71
|
+
PolyphonicAftertouch.new(channel, note, value, options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
alias_method :PolyphonicAftertouch, :polyphonic_aftertouch
|
75
|
+
alias_method :PolyAftertouch, :polyphonic_aftertouch
|
76
|
+
alias_method :PolyphonicPressure, :polyphonic_aftertouch
|
77
|
+
alias_method :PolyPressure, :polyphonic_aftertouch
|
78
|
+
alias_method :poly_aftertouch, :polyphonic_aftertouch
|
79
|
+
alias_method :poly_pressure, :polyphonic_aftertouch
|
80
|
+
|
81
|
+
def channel_aftertouch(value, options = {})
|
82
|
+
channel = options[:channel] || @channel
|
83
|
+
raise 'channel_aftertouch requires a channel' if channel.nil?
|
84
|
+
ChannelAftertouch.new(channel, value, options)
|
85
|
+
end
|
86
|
+
alias_method :ChannelAftertouch, :channel_aftertouch
|
87
|
+
alias_method :ChannelPressure, :channel_aftertouch
|
88
|
+
alias_method :channel_pressure, :channel_aftertouch
|
89
|
+
|
90
|
+
def pitch_bend(low, high, options = {})
|
91
|
+
channel = options[:channel] || @channel
|
92
|
+
raise 'channel_aftertouch requires a channel' if channel.nil?
|
93
|
+
PitchBend.new(channel, low, high, options)
|
94
|
+
end
|
95
|
+
alias_method :PitchBend, :pitch_bend
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def with_context(options = {}, &block)
|
100
|
+
Context.new(options, &block).instance_eval(&block)
|
101
|
+
end
|
102
|
+
alias_method :with, :with_context
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
module MIDIMessage
|
4
|
+
|
5
|
+
#
|
6
|
+
# Common Note Message Behavior
|
7
|
+
#
|
8
|
+
module NoteMessage
|
9
|
+
|
10
|
+
# the octave number of the note
|
11
|
+
def octave
|
12
|
+
(note / 12) -1
|
13
|
+
end
|
14
|
+
alias_method :oct, :octave
|
15
|
+
|
16
|
+
# set the octave number of the note
|
17
|
+
def octave=(val)
|
18
|
+
self.note = ((val + 1) * 12) + abs_note
|
19
|
+
self
|
20
|
+
end
|
21
|
+
alias_method :oct=, :octave=
|
22
|
+
|
23
|
+
# how many half-steps is this note above the closest C
|
24
|
+
def abs_note
|
25
|
+
note - ((note / 12) * 12)
|
26
|
+
end
|
27
|
+
|
28
|
+
# the name of the note without its octave e.g. F#
|
29
|
+
def note_name
|
30
|
+
name.split(/-?\d\z/).first
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# MIDI Note-Off message
|
37
|
+
#
|
38
|
+
class NoteOff
|
39
|
+
|
40
|
+
include NoteMessage
|
41
|
+
include ShortMessage
|
42
|
+
include ChannelMessage
|
43
|
+
|
44
|
+
schema :channel, :note, :velocity
|
45
|
+
use_display_name 'Note Off'
|
46
|
+
use_constants 'Note', :for => :note
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# MIDI Note-On message
|
52
|
+
#
|
53
|
+
class NoteOn
|
54
|
+
|
55
|
+
include NoteMessage
|
56
|
+
include ShortMessage
|
57
|
+
include ChannelMessage
|
58
|
+
|
59
|
+
schema :channel, :note, :velocity
|
60
|
+
use_display_name 'Note On'
|
61
|
+
use_constants 'Note', :for => :note
|
62
|
+
|
63
|
+
# returns the NoteOff equivalent of this object
|
64
|
+
def to_note_off
|
65
|
+
NoteOff.new(channel, note, velocity)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
|
4
|
+
module MIDIMessage
|
5
|
+
|
6
|
+
# very simple parsing
|
7
|
+
# for more advanced parsing check out {nibbler}[http://github.com/arirusso/nibbler]
|
8
|
+
class Parser
|
9
|
+
|
10
|
+
# can take either a hex string eg Parser.new("904040")
|
11
|
+
# or bytes eg Parser.new(0x90, 0x40, 0x40)
|
12
|
+
# or an array of bytes eg Parser.new([0x90, 0x40, 0x40])
|
13
|
+
def initialize(*a)
|
14
|
+
@data = case a.first
|
15
|
+
when Array then a.first
|
16
|
+
when Numeric then a
|
17
|
+
when String then TypeConversion.hex_string_to_numeric_byte_array(a.first)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse
|
22
|
+
first_nibble = ((@data.first & 0xF0) >> 4)
|
23
|
+
second_nibble = (@data.first & 0x0F)
|
24
|
+
case first_nibble
|
25
|
+
when 0x8 then NoteOff.new(second_nibble, @data[1], @data[2])
|
26
|
+
when 0x9 then NoteOn.new(second_nibble, @data[1], @data[2])
|
27
|
+
when 0xA then PolyphonicAftertouch.new(second_nibble, @data[1], @data[2])
|
28
|
+
when 0xB then ControlChange.new(second_nibble, @data[1], @data[2])
|
29
|
+
when 0xC then ProgramChange.new(second_nibble, @data[1])
|
30
|
+
when 0xD then ChannelAftertouch.new(second_nibble, @data[1])
|
31
|
+
when 0xE then PitchBend.new(second_nibble, @data[1], @data[2])
|
32
|
+
when 0xF then case second_nibble
|
33
|
+
when 0x0 then SystemExclusive.new(*@data)
|
34
|
+
when 0x1..0x6 then SystemCommon.new(second_nibble, @data[1], @data[2])
|
35
|
+
when 0x8..0xF then SystemRealtime.new(second_nibble)
|
36
|
+
else nil
|
37
|
+
end
|
38
|
+
else nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.parse(*a)
|
45
|
+
Parser.new(*a).parse
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module MIDIMessage
|
4
|
+
|
5
|
+
module Process
|
6
|
+
|
7
|
+
# Use the Filter superclass when you need a multi-band filter
|
8
|
+
class Filter
|
9
|
+
|
10
|
+
include Processor
|
11
|
+
|
12
|
+
attr_reader :bandwidth, :property, :reject
|
13
|
+
|
14
|
+
def initialize(message, prop, bandwidth, options = {})
|
15
|
+
@bandwidth = [bandwidth].flatten
|
16
|
+
@message = message
|
17
|
+
@property = prop
|
18
|
+
@reject = options[:reject] || false
|
19
|
+
initialize_processor(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def process
|
23
|
+
val = @message.send(@property)
|
24
|
+
result = @bandwidth.map { |bw| val >= bw.min && val <= bw.max ? @message : nil }
|
25
|
+
result.include?(@message) ^ @reject ? @message : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class LowPassFilter < Filter
|
31
|
+
def initialize(message, prop, max, options = {})
|
32
|
+
super(message, prop, (0..max), options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class HighPassFilter < Filter
|
37
|
+
def initialize(message, prop, min, options = {})
|
38
|
+
super(message, prop, (min..127), options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class BandPassFilter < Filter
|
43
|
+
def initialize(message, prop, accept_range, options = {})
|
44
|
+
options[:reject] = false
|
45
|
+
super(message, prop, accept_range, options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class BandRejectFilter < Filter
|
50
|
+
def initialize(message, prop, reject_range, options = {})
|
51
|
+
options[:reject] = true
|
52
|
+
super(message, prop, reject_range, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
NotchFilter = BandRejectFilter
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module MIDIMessage
|
4
|
+
|
5
|
+
module Process
|
6
|
+
|
7
|
+
class Limit
|
8
|
+
|
9
|
+
include Processor
|
10
|
+
|
11
|
+
attr_reader :property, :range
|
12
|
+
|
13
|
+
def initialize(message, prop, range, options = {})
|
14
|
+
@range = range
|
15
|
+
@message = message
|
16
|
+
@property = prop
|
17
|
+
initialize_processor(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
def process
|
21
|
+
val = @message.send(@property)
|
22
|
+
@message.send("#{@property}=", @range.min) if val < @range.min
|
23
|
+
@message.send("#{@property}=", @range.max) if val > @range.max
|
24
|
+
@message
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module MIDIMessage
|
4
|
+
|
5
|
+
module Process
|
6
|
+
|
7
|
+
module Processor
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
base.send(:attr_reader, :message)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def process(*a, &block)
|
16
|
+
new(*a).process(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def initialize_processor(message)
|
23
|
+
@message = message
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|