midi-events 0.5.0
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/.gitignore +10 -0
- data/Gemfile +8 -0
- data/LICENSE +674 -0
- data/LICENSE.midi-message +13 -0
- data/README.md +170 -0
- data/Rakefile +10 -0
- data/examples/constants.rb +37 -0
- data/examples/context.rb +25 -0
- data/examples/melody.rb +27 -0
- data/examples/short_messages.rb +33 -0
- data/examples/sysex.rb +56 -0
- data/lib/midi-events/channel_message.rb +152 -0
- data/lib/midi-events/constant.rb +260 -0
- data/lib/midi-events/context.rb +161 -0
- data/lib/midi-events/message.rb +62 -0
- data/lib/midi-events/messages.rb +215 -0
- data/lib/midi-events/note_message.rb +40 -0
- data/lib/midi-events/system_exclusive.rb +244 -0
- data/lib/midi-events/system_message.rb +19 -0
- data/lib/midi-events/type_conversion.rb +79 -0
- data/lib/midi-events.rb +27 -0
- data/lib/midi.yml +338 -0
- data/midi-events.gemspec +22 -0
- metadata +67 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011-2015 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,170 @@
|
|
1
|
+
# MIDI Events
|
2
|
+
|
3
|
+
**Ruby MIDI Events Objects**
|
4
|
+
|
5
|
+
This library is part of a suite of Ruby libraries for MIDI:
|
6
|
+
|
7
|
+
| Function | Library |
|
8
|
+
| --- | --- |
|
9
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) |
|
10
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) |
|
11
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) |
|
12
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) |
|
13
|
+
| Low level MIDI interface to Linux | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [alsa-rawmidi](http://github.com/arirusso/alsa-rawmidi)) |
|
14
|
+
| Low level MIDI interface to JRuby | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-jruby](http://github.com/arirusso/midi-jruby))|
|
15
|
+
| Low level MIDI interface to Windows | **TO DO** (by now [MIDI Communications](https://github.com/javier-sy/midi-communications) uses [midi-winm](http://github.com/arirusso/midi-winmm)) |
|
16
|
+
|
17
|
+
This library is based on [Ari Russo's](http://github.com/arirusso) library [MIDI Message](https://github.com/arirusso/midi-message).
|
18
|
+
|
19
|
+
## Features
|
20
|
+
|
21
|
+
* Flexible API to accommodate various sources and destinations of MIDI data
|
22
|
+
* Simple approach to System Exclusive data and devices
|
23
|
+
* [YAML dictionary of MIDI constants](https://github.com/javier-sy/midi-events/blob/master/lib/midi.yml)
|
24
|
+
|
25
|
+
## Install
|
26
|
+
|
27
|
+
`gem install midi-events`
|
28
|
+
|
29
|
+
Or if you're using Bundler, add this to your Gemfile
|
30
|
+
|
31
|
+
`gem "midi-events"`
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require "midi-events"
|
37
|
+
```
|
38
|
+
|
39
|
+
#### Basic Messages
|
40
|
+
|
41
|
+
There are a few ways to create a new MIDI event. Here are some examples:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
MIDIEvents::NoteOn.new(0, 64, 64)
|
45
|
+
|
46
|
+
MIDIEvents::NoteOn["E4"].new(0, 100)
|
47
|
+
|
48
|
+
MIDIEvents.with(:channel => 0, :velocity => 100) { note_on("E4") }
|
49
|
+
```
|
50
|
+
|
51
|
+
Those expressions all evaluate to the same object:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
#<MIDIEvents::NoteOn:0x9c1c240
|
55
|
+
@channel=0,
|
56
|
+
@data=[64, 64],
|
57
|
+
@name="E4",
|
58
|
+
@note=64,
|
59
|
+
@status=[9, 0],
|
60
|
+
@velocity=64,
|
61
|
+
@verbose_name="Note On: E4">
|
62
|
+
```
|
63
|
+
|
64
|
+
#### SysEx Messages
|
65
|
+
|
66
|
+
As with any kind of message, you can begin with raw data:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
MIDIEvents::SystemExclusive.new(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
70
|
+
```
|
71
|
+
|
72
|
+
Or in a more object oriented way:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
synth = SystemExclusive::Node.new(0x41, model_id: 0x42, device_id: 0x10)
|
76
|
+
|
77
|
+
SystemExclusive::Command.new([0x40, 0x7F, 0x00], 0x00, node: synth)
|
78
|
+
```
|
79
|
+
|
80
|
+
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.
|
81
|
+
|
82
|
+
You can use the Node to instantiate a message:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
synth.command([0x40, 0x7F, 0x00], 0x00)
|
86
|
+
```
|
87
|
+
|
88
|
+
One way or another, you will wind up with a pair of objects like this:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
#<MIDIEvents::SystemExclusive::Command:0x9c1e57c
|
92
|
+
@address=[64, 0, 127],
|
93
|
+
@checksum=[65],
|
94
|
+
@data=[0],
|
95
|
+
@node=
|
96
|
+
#<MIDIMessage::SystemExclusive::Node:0x9c1e5a4
|
97
|
+
@device_id=16,
|
98
|
+
@manufacturer_id=65,
|
99
|
+
@model_id=66>>
|
100
|
+
```
|
101
|
+
|
102
|
+
## Documentation
|
103
|
+
|
104
|
+
* (**TO DO**) [rdoc](http://rubydoc.info/github/javier-sy/midi-events)
|
105
|
+
|
106
|
+
## Differences between [MIDI Events](https://github.com/javier-sy/midi-events) library and [MIDI Message](https://github.com/arirusso/midi-message) library
|
107
|
+
|
108
|
+
[MIDI Events](https://github.com/javier-sy/midi-events) is mostly a clone of [MIDI Message](https://github.com/arirusso/midi-message) with some modifications:
|
109
|
+
* Renamed gem to midi-events instead of midi-message
|
110
|
+
* Renamed module to MIDIEvents instead of MIDIMessage
|
111
|
+
* Removed parsing features (in favour of the more complete parser [MIDI Parser](https://github.com/javier-sy/midi-parser))
|
112
|
+
* TODO: update tests to use rspec instead of rake
|
113
|
+
* TODO: migrate to (or confirm it's working ok on) Ruby 3.0 and Ruby 3.1
|
114
|
+
|
115
|
+
## Then, why does exist this library if it is mostly a clone of another library?
|
116
|
+
|
117
|
+
The author has been developing since 2016 a Ruby project called
|
118
|
+
[Musa DSL](https://github.com/javier-sy/musa-dsl) that needs a way
|
119
|
+
of representing MIDI Events and a way of communicating with
|
120
|
+
MIDI Instruments and MIDI Control Surfaces.
|
121
|
+
|
122
|
+
[Ari Russo](https://github.com/arirusso) has done a great job creating
|
123
|
+
several interdependent Ruby libraries that allow
|
124
|
+
MIDI Events representation ([MIDI Message](https://github.com/arirusso/midi-message)
|
125
|
+
and [Nibbler](https://github.com/arirusso/nibbler))
|
126
|
+
and communication with MIDI Instruments and MIDI Control Surfaces
|
127
|
+
([unimidi](https://github.com/arirusso/unimidi),
|
128
|
+
[ffi-coremidi](https://github.com/arirusso/ffi-coremidi) and others)
|
129
|
+
that, **with some modifications**, I've been using in MusaDSL.
|
130
|
+
|
131
|
+
After thinking about the best approach to publish MusaDSL
|
132
|
+
I've decided to publish my own renamed version of the modified dependencies because:
|
133
|
+
|
134
|
+
* Some differences on the approach of the modifications vs the original library doesn't allow to merge the modifications on the original libraries.
|
135
|
+
* Then the renaming of the libraries is needed to avoid confusing existent users of the original libraries.
|
136
|
+
* Due to some of the interdependencies of Ari Russo libraries,
|
137
|
+
the modification and renaming on some of the low level libraries (ffi-coremidi, etc.)
|
138
|
+
forces to modify and rename unimidi library.
|
139
|
+
* The original libraries have features
|
140
|
+
(very detailed logging and processing history information, not locking behaviour when waiting input midi messages)
|
141
|
+
that are not needed in MusaDSL and, in fact,
|
142
|
+
can degrade the performance on some use case scenarios in MusaDSL.
|
143
|
+
|
144
|
+
All in all I have decided to publish a suite of libraries optimized for MusaDSL use case that also can be used by other people in their projects.
|
145
|
+
|
146
|
+
| Function | Library | Based on Ari Russo's| Difference |
|
147
|
+
| --- | --- | --- | --- |
|
148
|
+
| MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) | [MIDI Message](https://github.com/arirusso/midi-message) | removed parsing, small improvements |
|
149
|
+
| MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) | [Nibbler](https://github.com/arirusso/nibbler) | removed process history information, minor optimizations |
|
150
|
+
| MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) | [unimidi](https://github.com/arirusso/unimidi) | use of [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos)
|
151
|
+
| Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) | [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) | removed process history information, locking behaviour when waiting midi events, improved midi devices name detection, minor optimizations |
|
152
|
+
| Low level MIDI interface to Linux | **TO DO** | | |
|
153
|
+
| Low level MIDI interface to JRuby | **TO DO** | | |
|
154
|
+
| Low level MIDI interface to Windows | **TO DO** | | |
|
155
|
+
|
156
|
+
## Author
|
157
|
+
|
158
|
+
* [Javier Sánchez Yeste](https://github.com/javier-sy)
|
159
|
+
|
160
|
+
## Acknowledgements
|
161
|
+
|
162
|
+
Thanks to [Ari Russo](http://github.com/arirusso) for his ruby library [MIDI Message](https://github.com/arirusso/midi-message) licensed as Apache License 2.0.
|
163
|
+
|
164
|
+
## License
|
165
|
+
|
166
|
+
[MIDI Events](https://github.com/javier-sy/midi-events) Copyright (c) 2021 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
|
167
|
+
|
168
|
+
[MIDI Message](https://github.com/arirusso/midi-message) Copyright (c) 2011-2015 [Ari Russo](http://arirusso.com), licensed under Apache License 2.0 (see the file LICENSE.midi-message)
|
169
|
+
|
170
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# How to use constants
|
4
|
+
#
|
5
|
+
|
6
|
+
$:.unshift(File.join("..", "lib"))
|
7
|
+
|
8
|
+
require "midi-events"
|
9
|
+
|
10
|
+
# some messages for a sequencer
|
11
|
+
|
12
|
+
pp MIDIEvents::SystemRealtime["Start"].new
|
13
|
+
pp MIDIEvents::NoteOn["E4"].new(0, 100)
|
14
|
+
pp MIDIEvents::SystemRealtime["Stop"].new
|
15
|
+
|
16
|
+
# this should output something like:
|
17
|
+
|
18
|
+
#
|
19
|
+
# #<MIDIMessage::SystemRealtime:0x89fda3c
|
20
|
+
# @name="Start",
|
21
|
+
# @status=[15, 250],
|
22
|
+
# @verbose_name="System Realtime: Start">
|
23
|
+
#
|
24
|
+
# #<MIDIMessage::NoteOn:0x9363cac
|
25
|
+
# @channel=0,
|
26
|
+
# @data=[64, 100],
|
27
|
+
# @name="C3",
|
28
|
+
# @note=64,
|
29
|
+
# @status=[9, 0],
|
30
|
+
# @velocity=100,
|
31
|
+
# @verbose_name="Note On: C3">
|
32
|
+
#
|
33
|
+
# #<MIDIMessage::SystemRealtime:0x89fc600
|
34
|
+
# @name="Stop",
|
35
|
+
# @status=[15, 252],
|
36
|
+
# @verbose_name="System Realtime: Stop">
|
37
|
+
#
|
data/examples/context.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Use a block loaded with velocity and channel
|
4
|
+
#
|
5
|
+
|
6
|
+
$:.unshift(File.join("..", "lib"))
|
7
|
+
|
8
|
+
require "midi-events"
|
9
|
+
|
10
|
+
MIDIEvents.with(:channel => 0, :velocity => 100) do
|
11
|
+
|
12
|
+
note_on("E4")
|
13
|
+
note_off("E4")
|
14
|
+
|
15
|
+
note_on("C4")
|
16
|
+
note_off("C4")
|
17
|
+
|
18
|
+
control_change("Portamento", 64)
|
19
|
+
|
20
|
+
note_on("E4")
|
21
|
+
pp note_off("E4")
|
22
|
+
|
23
|
+
pp program_change(20)
|
24
|
+
|
25
|
+
end
|
data/examples/melody.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Construct a melody
|
4
|
+
#
|
5
|
+
|
6
|
+
$:.unshift(File.join("..", "lib"))
|
7
|
+
|
8
|
+
require "midi-events"
|
9
|
+
|
10
|
+
channel = 0
|
11
|
+
notes = [36, 40, 43] # C E G
|
12
|
+
octaves = 2
|
13
|
+
velocity = 100
|
14
|
+
|
15
|
+
melody = []
|
16
|
+
|
17
|
+
(0..((octaves-1)*12)).step(12) do |oct|
|
18
|
+
|
19
|
+
notes.each { |note| melody << MIDIEvents::NoteOn.new(channel, note + oct, velocity) }
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
pp melody
|
24
|
+
|
25
|
+
# this should output something like:
|
26
|
+
|
27
|
+
# (will add when I have the constants yaml more filled out)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Walk through of different ways to instantiate short (non-sysex) MIDI Messages
|
4
|
+
#
|
5
|
+
|
6
|
+
$:.unshift(File.join("..", "lib"))
|
7
|
+
|
8
|
+
require "midi-events"
|
9
|
+
|
10
|
+
# Here are examples of different ways to construct messages, going from low to high-level
|
11
|
+
|
12
|
+
pp MIDIEvents.parse(0x90, 0x40, 0x40)
|
13
|
+
|
14
|
+
channel_msg = MIDIEvents::ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
|
15
|
+
|
16
|
+
pp channel_msg
|
17
|
+
|
18
|
+
# this will return a NoteOn object with the properties of channel_msg
|
19
|
+
pp channel_msg.to_type
|
20
|
+
|
21
|
+
pp MIDIEvents::ChannelMessage.new(MIDIEvents::Constant::Status["Note On"], 0x0, 0x40, 0x40)
|
22
|
+
|
23
|
+
pp MIDIEvents::ChannelMessage.new(MIDIEvents::Constant::Status["Note On"], 0x0, 0x40, 0x40).to_type
|
24
|
+
|
25
|
+
pp MIDIEvents::NoteOn.new(0, 64, 64) # or NoteOn.new(0x0, 0x64, 0x64)
|
26
|
+
|
27
|
+
# some message properties are mutable
|
28
|
+
|
29
|
+
pp msg = MIDIEvents::NoteOn["E4"].new(0, 100)
|
30
|
+
|
31
|
+
msg.note += 5
|
32
|
+
|
33
|
+
pp msg
|
data/examples/sysex.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Walk through of different ways to instantiate System Exclusive (SysEx) messages
|
4
|
+
#
|
5
|
+
|
6
|
+
$:.unshift(File.join("..", "lib"))
|
7
|
+
|
8
|
+
require "midi-events"
|
9
|
+
|
10
|
+
# you can create a message by parsing bytes
|
11
|
+
|
12
|
+
pp MIDIEvents.parse(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
13
|
+
|
14
|
+
# or create a Node (destination) object and then send messages to that.
|
15
|
+
# a Node represents a device that you"re sending a message to
|
16
|
+
# (eg. your Yamaha DX7 is a Node).
|
17
|
+
|
18
|
+
node = MIDIEvents::SystemExclusive::Node.new(0x41, model_id: 0x42, device_id: 0x10)
|
19
|
+
|
20
|
+
# The following will create a command object for address "407F00" with value "00"
|
21
|
+
# associated with our node
|
22
|
+
#
|
23
|
+
# A command is a sysex message where the status (byte index 4) is 0x12
|
24
|
+
#
|
25
|
+
# A Request type message (SystemExclusive::Request) has a status byte
|
26
|
+
# equal to 0x11
|
27
|
+
|
28
|
+
pp MIDIEvents::SystemExclusive::Command.new([0x40, 0x7F, 0x00], 0x00, node: node)
|
29
|
+
|
30
|
+
# it is actually optional to pass a node to your message-- one case where not
|
31
|
+
# doing so is useful is when want to have a generic message prototype used with
|
32
|
+
# multiple nodes
|
33
|
+
|
34
|
+
prototype = MIDIEvents::SystemExclusive::Command.new([0x40, 0x7F, 0x00], 0x00)
|
35
|
+
|
36
|
+
pp node.new_message_from(prototype) # this will create a new message using the prototype"s data and the node"s information
|
37
|
+
|
38
|
+
# you can also generate a totally new message from the Node
|
39
|
+
|
40
|
+
pp node.command([0x40, 0x7F, 0x00], 0x00)
|
41
|
+
|
42
|
+
# each of the print statements in this example should output a message something like:
|
43
|
+
|
44
|
+
#
|
45
|
+
# #<MIDIMessage::SystemExclusive::Command:0x9c1e57c
|
46
|
+
# @address=[64, 0, 127],
|
47
|
+
# @checksum=[65],
|
48
|
+
# @data=[0],
|
49
|
+
# @node=
|
50
|
+
# #<MIDIMessage::SystemExclusive::Node:0x9c1e5a4
|
51
|
+
# @device_id=16,
|
52
|
+
# @manufacturer=65,
|
53
|
+
# @model_id=66>>
|
54
|
+
#
|
55
|
+
|
56
|
+
# read more about SysEx messages in general here: http://www.2writers.com/eddie/TutSysEx.htm
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module MIDIEvents
|
2
|
+
# Common behavior amongst Channel Message types
|
3
|
+
module ChannelMessage
|
4
|
+
include MIDIEvents # this enables ..kind_of?(MIDIEvents)
|
5
|
+
|
6
|
+
attr_reader :data, :name
|
7
|
+
|
8
|
+
# Shortcut to RawChannelMessage.new
|
9
|
+
# aka build a ChannelMessage from raw nibbles and bytes
|
10
|
+
# eg ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
|
11
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
12
|
+
# @return [RawChannelMessage] The resulting RawChannelMessage object
|
13
|
+
def self.new(*data, &block)
|
14
|
+
Message.new(*data, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
18
|
+
def initialize(*data)
|
19
|
+
data = data.dup
|
20
|
+
options = data.last.is_a?(Hash) ? data.pop : {}
|
21
|
+
add_constant_value(options[:const], data) unless options[:const].nil?
|
22
|
+
initialize_channel_message(self.class.type_for_status, *data)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.send(:include, ::MIDIEvents::Message)
|
29
|
+
base.send(:extend, ClassMethods)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add the given constant to message data
|
33
|
+
def add_constant_value(constant, data)
|
34
|
+
index = Constant::Loader.get_index(self)
|
35
|
+
data.insert(index, constant.value)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Assign the message data
|
39
|
+
def assign_data(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
40
|
+
@status = [status_nibble_1, status_nibble_2]
|
41
|
+
@data = [data_byte_1]
|
42
|
+
@data[1] = data_byte_2 if self.class.second_data_byte?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initialize the message: assign data, decorate with accessors
|
46
|
+
def initialize_channel_message(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
47
|
+
assign_data(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2)
|
48
|
+
Accessors.initialize(self) unless self.class.properties.nil?
|
49
|
+
initialize_message(status_nibble_1, status_nibble_2)
|
50
|
+
end
|
51
|
+
|
52
|
+
class Accessors
|
53
|
+
SCHEMA = [
|
54
|
+
{ name: :status, index: 1 }, # second status nibble
|
55
|
+
{ name: :data, index: 0 }, # first data byte
|
56
|
+
{ name: :data, index: 1 } # second data byte
|
57
|
+
].freeze
|
58
|
+
|
59
|
+
# @param [Class] klass
|
60
|
+
# @return [Class]
|
61
|
+
def self.decorate(klass)
|
62
|
+
decorator = new(klass)
|
63
|
+
decorator.decorate
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initialize a message object with it's properties
|
67
|
+
# @param [MIDIEvents] message
|
68
|
+
# @return [Boolean]
|
69
|
+
def self.initialize(message)
|
70
|
+
message.class.properties.each_with_index do |property, i|
|
71
|
+
data_mapping = SCHEMA[i]
|
72
|
+
container = message.send(data_mapping[:name])
|
73
|
+
index = data_mapping[:index]
|
74
|
+
message.send(:instance_variable_set, "@#{property.to_s}", container[index])
|
75
|
+
end
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Class] klass
|
80
|
+
def initialize(klass)
|
81
|
+
@klass = klass
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Class]
|
85
|
+
def decorate
|
86
|
+
@klass.properties.each_with_index do |property, i|
|
87
|
+
data_mapping = SCHEMA[i]
|
88
|
+
define_getter(property)
|
89
|
+
define_setter(property, data_mapping)
|
90
|
+
end
|
91
|
+
@klass
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# @param [Symbol, String] property
|
97
|
+
# @return [Boolean]
|
98
|
+
def define_getter(property)
|
99
|
+
@klass.send(:attr_reader, property)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param [Symbol, String] property
|
104
|
+
# @param [Hash] mapping
|
105
|
+
# @return [Boolean]
|
106
|
+
def define_setter(property, mapping)
|
107
|
+
index = mapping[:index]
|
108
|
+
@klass.send(:define_method, "#{property.to_s}=") do |value|
|
109
|
+
send(:instance_variable_set, "@#{property.to_s}", value)
|
110
|
+
send(mapping[:name])[index] = value
|
111
|
+
send(:update)
|
112
|
+
return self
|
113
|
+
end
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# For defining Channel Message class types
|
120
|
+
module ClassMethods
|
121
|
+
def properties
|
122
|
+
const_get('DATA') if const_defined?('DATA')
|
123
|
+
end
|
124
|
+
|
125
|
+
# Does the schema of this Channel Message carry a second data byte?
|
126
|
+
# eg. NoteMessage does, and ProgramChange doesn"t
|
127
|
+
# @return [Boolean] Is there a second data byte on this message type?
|
128
|
+
def second_data_byte?
|
129
|
+
properties.nil? || (properties.length - 1) > 1
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# Use this if you want to instantiate a raw channel message
|
135
|
+
#
|
136
|
+
# For example ChannelMessage::Message.new(0x9, 0x0, 0x40, 0x57)
|
137
|
+
# creates a raw note-on message
|
138
|
+
class Message
|
139
|
+
include ChannelMessage
|
140
|
+
|
141
|
+
DISPLAY_NAME = 'Channel Message'.freeze
|
142
|
+
|
143
|
+
# Build a Channel Message from raw nibbles and bytes
|
144
|
+
# eg ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
|
145
|
+
# @param [*Fixnum] data The status nibbles and data bytes
|
146
|
+
# @return [RawChannelMessage] The resulting RawChannelMessage object
|
147
|
+
def initialize(*data)
|
148
|
+
initialize_channel_message(*data)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|