midi-message 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|