vissen-input 0.2.2 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf9e2d7e4ab570645627dab31c509d35c94c1dd0fab403113505fcd807acf4ec
4
- data.tar.gz: 12845d0b04e56faf957a4780d0a04e671d304197a9d0b2040f7c5665ab1aa0fc
3
+ metadata.gz: ba0c28648eb117befa313aef9e8e83f2e1b75db97f2d5899f727368c9bd39926
4
+ data.tar.gz: c3207e55160d602acf04008df707fbf20388de7ac209be2e37b2cf37e88abef1
5
5
  SHA512:
6
- metadata.gz: 560ac7d4f5e7670e2ebf5d79fd8e4ce5ada839b61ab8ec30815bf44be5b6e5b5e683aac9f992f1e003bdc2c22947cb2db5a8a97e4209b87636391e88de46d30d
7
- data.tar.gz: ed3da5a2f4f479dcc9ef4fac688945ebdda4ec2b1a3c7e045ffb991babe330201f53e076c39e5783ead938e1cf1b2309bab84c6b70d59637fe5f5b6c3624330e
6
+ metadata.gz: cddb65a57ed9b49f7a6b9e367d133cd095586f6f05b6e75b975b8a9a2fdea31e49034bffebd7d2635447a743a498cfb4bfba51dc947003f24bb2dc1dab6fbddb
7
+ data.tar.gz: d88b5534407b282d7c9f35bf360d9a1f185a3a2871cd205125daeeaafac560b00799074700d799c9bb4edd26376c2c0712c4d638a65d12f4359797b5046d0116
@@ -46,3 +46,21 @@ Metrics/BlockLength:
46
46
  Metrics/ModuleLength:
47
47
  Exclude:
48
48
  - 'test/**/*.rb'
49
+
50
+ Naming/UncommunicativeMethodParamName:
51
+ AllowedNames:
52
+ - 'io'
53
+ - 'id'
54
+ - 'to'
55
+ - 'by'
56
+ - 'on'
57
+ - 'in'
58
+ - 'at'
59
+ - '_'
60
+
61
+ Style/MixinUsage:
62
+ Exclude:
63
+ - 'examples/*.rb'
64
+
65
+ Style/FormatStringToken:
66
+ Enabled: false
@@ -0,0 +1,2 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
@@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+ ### Added
9
+ - Message#to_h converts messages into a Hash format that include their data and timestamp.
10
+ - Matcher#match accepts a message and yields it in case of a match.
11
+
12
+ ### Changed
13
+ - Improved the documentation.
14
+ - Matcher#match? now also accepts a Hash.
15
+ - Broker#publish now accepts raw messages and handles the conversion into message objects.
16
+ - Broker#publish no longer accepts an array.
8
17
 
9
18
  ## [0.2.2] - 2018-04-18
10
19
  ### Changed
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vissen-input (0.2.2)
4
+ vissen-input (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -16,6 +16,7 @@ GEM
16
16
  powerpack (0.1.1)
17
17
  rainbow (3.0.0)
18
18
  rake (10.5.0)
19
+ redcarpet (3.4.0)
19
20
  rubocop (0.55.0)
20
21
  parallel (~> 1.10)
21
22
  parser (>= 2.5)
@@ -30,6 +31,7 @@ GEM
30
31
  simplecov-html (~> 0.10.0)
31
32
  simplecov-html (0.10.2)
32
33
  unicode-display_width (1.3.0)
34
+ yard (0.9.12)
33
35
 
34
36
  PLATFORMS
35
37
  ruby
@@ -38,9 +40,11 @@ DEPENDENCIES
38
40
  bundler (~> 1.16)
39
41
  minitest (~> 5.0)
40
42
  rake (~> 10.0)
43
+ redcarpet (~> 3.4)
41
44
  rubocop (~> 0.52)
42
45
  simplecov (~> 0.16)
43
46
  vissen-input!
47
+ yard (~> 0.9)
44
48
 
45
49
  BUNDLED WITH
46
50
  1.16.1
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
- # Vissen::Input
1
+ # 🥀 Vissen Input
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/vissen-input.svg)](https://badge.fury.io/rb/vissen-input)
3
4
  [![Build Status](https://travis-ci.org/midi-visualizer/vissen-input.svg?branch=master)](https://travis-ci.org/midi-visualizer/vissen-input)
4
5
  [![Inline docs](http://inch-ci.org/github/midi-visualizer/vissen-input.svg?branch=master)](http://inch-ci.org/github/midi-visualizer/vissen-input)
6
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/vissen-input/)
5
7
 
6
8
  ## Installation
7
9
 
@@ -21,7 +23,27 @@ Or install it yourself as:
21
23
 
22
24
  ## Usage
23
25
 
24
- TODO: Write usage instructions here
26
+ ```ruby
27
+ include Vissen::Input
28
+
29
+ # First we setup a broker.
30
+ broker = Broker.new
31
+
32
+ # We then subscribe to a message type and provide a callback.
33
+ broker.subscribe Message::Note[0] do |msg|
34
+ play msg.note if msg.on?
35
+ end
36
+
37
+ # We simulate a raw note on message arriving to the broker
38
+ # at time 4.2.
39
+ broker.publish data: [0x90, 42, 0], timestamp: 4.2
40
+
41
+ # Finally we let the broker process the next message in its
42
+ # queue. The callback should now have been called.
43
+ broker.run_once
44
+ ```
45
+
46
+ Please see the [documentation](http://www.rubydoc.info/gems/vissen-input/) for more details.
25
47
 
26
48
  ## Development
27
49
 
data/Rakefile CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+ require 'yard'
5
7
 
6
8
  Rake::TestTask.new(:test) do |t|
7
9
  t.libs << 'test'
@@ -9,4 +11,13 @@ Rake::TestTask.new(:test) do |t|
9
11
  t.test_files = FileList['test/**/*_test.rb']
10
12
  end
11
13
 
12
- task default: :test
14
+ RuboCop::RakeTask.new(:rubocop)
15
+
16
+ YARD::Rake::YardocTask.new(:yard) do |t|
17
+ t.stats_options = %w[--list-undoc]
18
+ end
19
+
20
+ desc 'Generate Ruby documentation'
21
+ task doc: %w[yard]
22
+
23
+ task default: %w[test rubocop:auto_correct]
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'vissen/input'
5
+ include Vissen::Input
6
+
7
+ T = 4.2
8
+ N = 100_000
9
+
10
+ # First we setup a broker.
11
+ broker = Broker.new
12
+
13
+ Counter = Struct.new :count, :limit do
14
+ def call(_, _)
15
+ self.count += 1
16
+ end
17
+
18
+ def percent
19
+ count.to_f / limit * 100
20
+ end
21
+ end
22
+
23
+ counter = Counter.new 0, N
24
+
25
+ # Subscribe to some messages
26
+ broker.subscribe(Message::Note[5, 1], counter)
27
+ broker.subscribe(Message::ControlChange[8], counter)
28
+ broker.subscribe(Message::ProgramChange[15], counter)
29
+ broker.subscribe(Message::PitchBendChange, counter)
30
+ broker.subscribe(Message::Aftertouch, counter)
31
+
32
+ # We then define the message generation process.
33
+ stream_generator = proc { [rand(0..255), rand(0..127), 0] }
34
+
35
+ puts 'Benchmark results:'
36
+
37
+ res =
38
+ Benchmark.bm(10, 'Real') do |x|
39
+ # We measure just the random message generation as a
40
+ # baseline.
41
+ bl = x.report('Baseline') do
42
+ N.times { { data: stream_generator.call, timestamp: T } }
43
+ end
44
+
45
+ # We then let the factory build N messages from the
46
+ # random message stream.
47
+ fa = x.report('Factory') do
48
+ N.times do
49
+ broker.publish(data: stream_generator.call, timestamp: T)
50
+ broker.run_once
51
+ end
52
+ end
53
+
54
+ # We remove the baseline from the result.
55
+ re = fa - bl
56
+
57
+ [re]
58
+ end
59
+
60
+ puts
61
+ puts format('%d messages processed (%0.1f%%)', counter.count, counter.percent)
62
+ puts format('%.2f us per message', res.last.utime * (1_000_000 / N))
63
+ puts format('%.0f messages per second', N / res.last.utime)
@@ -19,8 +19,6 @@ require 'vissen/input/message/program_change'
19
19
  require 'vissen/input/message/unknown'
20
20
 
21
21
  module Vissen
22
- # Input
23
- #
24
22
  # This module includes all the input messages that can be sent to the vissen
25
23
  # engine, as well as some facilities for converting them to and from their
26
24
  # binary form.
@@ -74,9 +74,11 @@ module Vissen
74
74
  # Insert a new message into the message queue. The message is handled at a
75
75
  # later time in `#run_once`.
76
76
  #
77
- # @param message [Message] the message(s) to handle.
77
+ # @param message [Message, Hash] the message(s) to handle.
78
78
  def publish(*message)
79
- message.each { |m| @message_queue.push m }
79
+ message.each do |m|
80
+ @message_queue.push m
81
+ end
80
82
  end
81
83
 
82
84
  # Takes one message from the message queue and handles it.
@@ -100,16 +102,18 @@ module Vissen
100
102
  # TODO: Remap the message if needed.
101
103
  @subscriptions.each do |subscription|
102
104
  break if ctrl.stop?(subscription.priority)
103
- next unless subscription.match? message
104
105
 
105
- subscription.handle message, ctrl
106
+ subscription.match message do |msg|
107
+ subscription.handle msg, ctrl
108
+ message = msg
109
+ end
106
110
  end
107
111
  nil
108
112
  end
109
113
 
110
114
  private
111
115
 
112
- # Insert of append a new subscription to the list. The subscription will
116
+ # Insert or append a new subscription to the list. The subscription will
113
117
  # be placed before the first subscription that is found to have a lower
114
118
  # priority, or last.
115
119
  #
@@ -152,7 +156,8 @@ module Vissen
152
156
  @current_priority = 0
153
157
  end
154
158
 
155
- # @param [Integer] the priority to test if
159
+ # @param priority [Integer] the priority to check out stopping
160
+ # condition against.
156
161
  # @return [true, false] whether to stop or not.
157
162
  def stop?(priority)
158
163
  return true if priority < @stop_at_priority
@@ -17,6 +17,8 @@ module Vissen
17
17
  # matcher.match? [0xA0, 0, 0] # => true
18
18
  #
19
19
  class Matcher
20
+ # @return [Message] the message class responsible for the messages that
21
+ # this matcher matches.
20
22
  attr_reader :klass
21
23
 
22
24
  # @param message_klass [Message] the message class that should be used to
@@ -36,14 +38,43 @@ module Vissen
36
38
  # Match either a byte array or a `Message` against the rule stored in the
37
39
  # matcher.
38
40
  #
39
- # @param obj [#to_a] the message data to match.
41
+ # @raise [KeyError] if `obj` is a `Hash` but does not include the `:data`
42
+ # key.
43
+ #
44
+ # @param obj [Hash, #to_a] the message data to match.
40
45
  # @return [true, false] true if the data matches.
41
46
  def match?(obj)
42
- data = obj.to_a
47
+ data = obj.is_a?(Hash) ? obj.fetch(:data) : obj.to_a
43
48
 
44
- return false unless data.length >= @klass::DATA_LENGTH
49
+ return false if data.length < @klass::DATA_LENGTH
45
50
  @rule.call data
46
51
  end
52
+
53
+ # Matches either a hash or a `Message` against the internal matching rule.
54
+ # If the object matches and is not a `Message` object a new instance will
55
+ # be created.
56
+ #
57
+ # If a block is given the message will be yielded to it.
58
+ #
59
+ # @param obj [Message, Hash] the message to be matched.
60
+ # @return [false] if the object does not match.
61
+ # @return [Object] the return value of the given block, if a block was
62
+ # given.
63
+ # @return [Message] the given message if it is a `Message` or a new
64
+ # instance.
65
+ def match(obj)
66
+ data = obj.fetch :data
67
+ return false if data.length < @klass::DATA_LENGTH || !@rule.call(data)
68
+
69
+ message =
70
+ case obj
71
+ when Message then obj
72
+ when Hash then @klass.new data, obj.fetch(:timestamp)
73
+ end
74
+
75
+ return message unless block_given?
76
+ yield message
77
+ end
47
78
  end
48
79
  end
49
80
  end
@@ -22,14 +22,22 @@ module Vissen
22
22
  # term _status_ is, howerver, sometimes also used to name only the upper
23
23
  # nibble of the status field.
24
24
  module Message
25
+ # The status mask determines which bits of the first byte belong to the
26
+ # status code.
25
27
  STATUS_MASK = 0xF0
28
+
29
+ # The channel mask determines which bits of the first byte belong to the
30
+ # channel value.
26
31
  CHANNEL_MASK = 0x0F
32
+
33
+ # Data length specifies what number of bytes must be present in the raw
34
+ # message for it to be valid.
27
35
  DATA_LENGTH = 1
28
36
 
29
- # @return [Array<Integer>]
37
+ # @return [Array<Integer>] the raw message data.
30
38
  attr_reader :data
31
39
 
32
- # @return [Float]
40
+ # @return [Float] the time of arrival for the message.
33
41
  attr_reader :timestamp
34
42
 
35
43
  # Allow a message to pass for the raw byte array
@@ -52,6 +60,32 @@ module Vissen
52
60
  true
53
61
  end
54
62
 
63
+ # Converts the message back into a raw hash representation. The format is
64
+ # intentionally similar to the output of the `Unimidi` gem.
65
+ #
66
+ # @return [Hash] a hash containing both the message data and the timestamp
67
+ # marking when it arrived.
68
+ def to_h
69
+ { data: @data, timestamp: @timestamp }.freeze
70
+ end
71
+
72
+ # Allows every message instance to pass for a `Hash` object on the same
73
+ # form returned by #to_h.
74
+ #
75
+ # @raise [KeyError] unless the given key is `:data` or `:timestamp`.
76
+ #
77
+ # @param key [Symbol] the field to fetch (either `:data` or
78
+ # `:timestamp`).
79
+ # @return [Array<Integer>] when key is `:data`.
80
+ # @return [Float] when key is `:timestamp`.
81
+ def fetch(key)
82
+ case key
83
+ when :data then @data
84
+ when :timestamp then @timestamp
85
+ else raise KeyError
86
+ end
87
+ end
88
+
55
89
  # @return [Integer] the message status.
56
90
  def status
57
91
  @data[0] & self.class::STATUS_MASK
@@ -4,9 +4,11 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # Polyphonic Key Pressure (Aftertouch). This message is most often sent
8
- # by pressing down on the key after it "bottoms out".
7
+ #
8
+ # > Polyphonic Key Pressure (Aftertouch). This message is most often sent
9
+ # > by pressing down on the key after it "bottoms out".
9
10
  class Aftertouch < Base
11
+ # @see Message
10
12
  STATUS = 0xA0
11
13
 
12
14
  # @return [Integer] the note value.
@@ -13,7 +13,9 @@ module Vissen
13
13
  class Base
14
14
  include Message
15
15
 
16
+ # @see Message
16
17
  DATA_LENGTH = 3
18
+ # @see Message
17
19
  STATUS = 0
18
20
 
19
21
  # Checks message data consistency with the class default matcher.
@@ -25,6 +27,9 @@ module Vissen
25
27
  end
26
28
 
27
29
  class << self
30
+ extend Forwardable
31
+ # rubocop:disable Metrics/AbcSize
32
+
28
33
  # Returns a new instance of a Matcher, configured to match this
29
34
  # particular Message class. Subclasses of Base can utilize the same
30
35
  # functionality by simply redefining STATUS and, if necessary,
@@ -54,13 +59,25 @@ module Vissen
54
59
  end
55
60
  end
56
61
 
57
- # Accessor for the class default matcher.
62
+ # rubocop:enable Metrics/AbcSize
63
+
64
+ # @!method match?(obj)
65
+ # Accessor to `Matcher#match?` on the default class matcher.
58
66
  #
59
- # @param message [#to_a] the message or data to match.
60
- # @return [true, false] see `Matcher#match?`.
61
- def match?(message)
62
- matcher.match? message
63
- end
67
+ # @see Matcher#match?
68
+ #
69
+ # @param obj [#to_a] the message or data to match.
70
+ # @return [true, false] (see Matcher#match?)
71
+ def_delegator :klass_matcher, :match?, :match?
72
+
73
+ # @!method match(obj)
74
+ # Accessor to `Matcher#match` on the default class matcher.
75
+ #
76
+ # @see Matcher#match
77
+ #
78
+ # @param obj [Hash, Message] the message to match.
79
+ # @return [false, Object, Message] (see Matcher#match)
80
+ def_delegator :klass_matcher, :match, :match
64
81
 
65
82
  # Creates a new factory with all the subclasses of base added to it as
66
83
  # matchers.
@@ -74,6 +91,8 @@ module Vissen
74
91
 
75
92
  # Alias to `#matcher` that swaps named arguments for positional ones.
76
93
  #
94
+ # @see #matcher
95
+ #
77
96
  # @param (see #matcher)
78
97
  # @return (see #matcher)
79
98
  def [](channel, number = nil)
@@ -116,18 +135,52 @@ module Vissen
116
135
 
117
136
  protected
118
137
 
138
+ # Called automatically by inheriting classes.
139
+ #
140
+ # @param subclass [Base] the inheriting class.
141
+ # @return [nil]
119
142
  def inherited(subclass)
120
143
  (@subclasses ||= []) << subclass
144
+ nil
121
145
  end
122
146
 
147
+ # Helper method to validate a status value.
148
+ #
149
+ # @raise [RangeError] if the status is outside its allowable range.
150
+ #
151
+ # @param status [Integer] the status to validate.
152
+ # @return [true] when the status is valid.
123
153
  def validate_status(status)
124
154
  raise RangeError unless (status & ~STATUS_MASK).zero?
155
+ true
125
156
  end
126
157
 
158
+ # Helper method to validate a channel value.
159
+ #
160
+ # @raise [RangeError] if the channel is outside its allowable range.
161
+ #
162
+ # @param channel [Integer] the channel to validate.
163
+ # @return [true] when the channel is valid.
127
164
  def validate_channel(channel)
128
165
  raise RangeError unless (channel & ~CHANNEL_MASK).zero?
166
+ true
129
167
  end
130
168
 
169
+ # Creates a value and mask that can be used to match the first byte of
170
+ # a message.
171
+ #
172
+ # == Usage
173
+ # The following example illustrates how the value and mask are
174
+ # intended to be used.
175
+ #
176
+ # value, mask = status_value_and_mask
177
+ # 0x42 & mask == value # => true if STATUS == 0x40
178
+ #
179
+ # value, mask = status_value_and_mask 3
180
+ # 0x42 & mask == value # => false since channel is 2
181
+ #
182
+ # @param channel [Integer] the channel to match.
183
+ # @return [Array<Integer>] a value and a mask.
131
184
  def status_value_and_mask(channel = nil)
132
185
  if channel
133
186
  validate_channel channel
@@ -138,6 +191,16 @@ module Vissen
138
191
  end
139
192
  end
140
193
 
194
+ # The klass matcher is the most generic `Matcher` for the message
195
+ # class and is cached to avoid duplication. By default the default
196
+ # `Matcher` uses the value and mask returned by
197
+ # `#status_value_and_mask` to match messages. Subclasses that need
198
+ # different behaviour can pass a block to be forwarded directly to the
199
+ # matcher (see Matcher.new).
200
+ #
201
+ # @param block [Proc] the block that should be passed to
202
+ # `Matcher.new` when first creating the matcher.
203
+ # @return [Matcher] a matcher that matches all messages of this type.
141
204
  def klass_matcher(&block)
142
205
  return @klass_matcher if defined?(@klass_matcher)
143
206
 
@@ -4,26 +4,29 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # This the same code as the Control Change (above), but implements Mode
8
- # control and special message by using reserved controller numbers
9
- # 120-127. The commands are:
10
7
  #
11
- # - All Sound Off (120)
12
- # When All Sound Off is received all oscillators will turn off, and
13
- # their volume envelopes are set to zero as soon as possible.
14
- # - Reset All Controllers (121)
15
- # When Reset All Controllers is received, all controller values are
16
- # reset to their default values.
17
- # - Local Control (122)
18
- # When Local Control is Off, all devices on a given channel will
19
- # respond only to data received over MIDI. Played data, etc. will be
20
- # ignored. Local Control On restores the functions of the normal
21
- # controllers.
22
- # - All Notes Off (123..127)
23
- # When an All Notes Off is received, all oscillators will turn off.
8
+ # > This the same code as the Control Change (above), but implements Mode
9
+ # > control and special message by using reserved controller numbers
10
+ # > 120-127. The commands are:
11
+ #
12
+ # > - All Sound Off (120)
13
+ # > When All Sound Off is received all oscillators will turn off, and
14
+ # > their volume envelopes are set to zero as soon as possible.
15
+ # > - Reset All Controllers (121)
16
+ # > When Reset All Controllers is received, all controller values are
17
+ # > reset to their default values.
18
+ # > - Local Control (122)
19
+ # > When Local Control is Off, all devices on a given channel will
20
+ # > respond only to data received over MIDI. Played data, etc. will be
21
+ # > ignored. Local Control On restores the functions of the normal
22
+ # > controllers.
23
+ # > - All Notes Off (123..127)
24
+ # > When an All Notes Off is received, all oscillators will turn off.
24
25
  #
25
26
  class ChannelMode < Base
27
+ # @see Message
26
28
  DATA_LENGTH = 2
29
+ # @see Message
27
30
  STATUS = 0xB0
28
31
 
29
32
  # @return [Integer] the control number.
@@ -4,13 +4,16 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # Channel Pressure (After-touch). This message is most often sent by
8
- # pressing down on the key after it "bottoms out". This message is
9
- # different from polyphonic after-touch. Use this message to send the
10
- # single greatest pressure value (of all the current depressed keys)
7
+ #
8
+ # > Channel Pressure (After-touch). This message is most often sent by
9
+ # > pressing down on the key after it "bottoms out". This message is
10
+ # > different from polyphonic after-touch. Use this message to send the
11
+ # > single greatest pressure value (of all the current depressed keys)
11
12
  class ChannelPressure < Base
12
- STATUS = 0xD0
13
+ # @see Message
13
14
  DATA_LENGTH = 2
15
+ # @see Message
16
+ STATUS = 0xD0
14
17
 
15
18
  # @return [Integer] the pressure value.
16
19
  def pressure
@@ -4,10 +4,12 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # This message is sent when a controller value changes. Controllers
8
- # include devices such as pedals and levers. Controller numbers 120-127
9
- # are reserved as "Channel Mode Messages".
7
+ #
8
+ # > This message is sent when a controller value changes. Controllers
9
+ # > include devices such as pedals and levers. Controller numbers 120-127
10
+ # > are reserved as "Channel Mode Messages".
10
11
  class ControlChange < Base
12
+ # @see Message
11
13
  STATUS = 0xB0
12
14
 
13
15
  # @return [Integer] the control number.
@@ -4,15 +4,24 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # Note On event.
8
- # This message is sent when a note is depressed (start).
9
7
  #
10
- # Note Off event.
11
- # This message is sent when a note is released (ended).
8
+ # > Note On event.
9
+ # > This message is sent when a note is depressed (start).
10
+ #
11
+ # > Note Off event.
12
+ # > This message is sent when a note is released (ended).
12
13
  class Note < Base
14
+ # @see Message
13
15
  STATUS_MASK = 0xE0
16
+ # @see Message
14
17
  STATUS = 0x80
18
+
19
+ # Note On specifies the value of the lowest status bit for note on
20
+ # messages.
15
21
  NOTE_ON = 0x10
22
+
23
+ # Note On specifies the value of the lowest status bit for note off
24
+ # messages.
16
25
  NOTE_OFF = 0x00
17
26
 
18
27
  # @return [Integer] the note value.
@@ -4,12 +4,17 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # This message is sent to indicate a change in the pitch bender (wheel
8
- # or lever, typically). The pitch bender is measured by a fourteen bit
9
- # value. Center (no pitch change) is 2000H. Sensitivity is a function of
10
- # the receiver, but may be set using RPN 0.
7
+ #
8
+ # > This message is sent to indicate a change in the pitch bender (wheel
9
+ # > or lever, typically). The pitch bender is measured by a fourteen bit
10
+ # > value. Center (no pitch change) is 2000H. Sensitivity is a function of
11
+ # > the receiver, but may be set using RPN 0.
11
12
  class PitchBendChange < Base
13
+ # @see Message
12
14
  STATUS = 0xE0
15
+
16
+ # Center value is defined as the the offset that should be removed from
17
+ # the 14 bit pitch bend value to center it around zero.
13
18
  CENTER_VALUE = 0x2000
14
19
 
15
20
  # @return [Integer] the integer pitch bend value.
@@ -4,9 +4,12 @@ module Vissen
4
4
  module Input
5
5
  module Message
6
6
  # From the MIDI Association:
7
- # This message sent when the patch number changes.
7
+ #
8
+ # > This message sent when the patch number changes.
8
9
  class ProgramChange < Base
10
+ # @see Message
9
11
  DATA_LENGTH = 2
12
+ # @see Message
10
13
  STATUS = 0xC0
11
14
 
12
15
  # @return [Integer] the program number.
@@ -23,7 +23,7 @@ module Vissen
23
23
  # @param matchers [nil, Array<Matcher>] the matchers to use when building
24
24
  # messages. If provided the factory will be frozen after creation.
25
25
  def initialize(matchers = nil)
26
- @lookup_table = Array.new(16)
26
+ @lookup_table = Array.new(16) { [] }
27
27
  @matchers = []
28
28
 
29
29
  return unless matchers
@@ -79,22 +79,11 @@ module Vissen
79
79
 
80
80
  unless matcher
81
81
  matcher = @matchers.find { |m| m.match? data }
82
- add_to_lookup matcher, data if matcher
82
+ @lookup_table[status] << matcher if matcher
83
83
  end
84
84
 
85
85
  matcher
86
86
  end
87
-
88
- def add_to_lookup(matcher, data)
89
- status = data[0] >> 4
90
- entry = @lookup_table[status]
91
-
92
- if entry
93
- entry << matcher
94
- else
95
- @lookup_table[status] = [matcher]
96
- end
97
- end
98
87
  end
99
88
  end
100
89
  end
@@ -15,15 +15,18 @@ module Vissen
15
15
  attr_reader :priority
16
16
 
17
17
  # @!method match?(message)
18
- # This method is forwarded to `Message#match?`.
18
+ # This method is forwarded to `Matcher#match?`.
19
19
  #
20
+ # @param message (see Matcher#match?)
20
21
  # @return [true, false] (see Matcher#match?).
21
22
  def_delegator :@matcher, :match?, :match?
22
23
 
24
+ def_delegator :@matcher, :match, :match
25
+
23
26
  # @!method handle(message)
24
27
  # Calls the registered handler with the given message.
25
28
  #
26
- # @return (see Matcher#match?)
29
+ # @param message [Message] the message that the subscriber should handle.
27
30
  def_delegator :@handler, :call, :handle
28
31
 
29
32
  # @param matcher [#match?] the matcher to use when filtering messages.
@@ -3,6 +3,6 @@
3
3
  module Vissen
4
4
  module Input
5
5
  # The Vissen Input library version number.
6
- VERSION = '0.2.2'
6
+ VERSION = '0.3.0'
7
7
  end
8
8
  end
@@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'bundler', '~> 1.16'
28
28
  spec.add_development_dependency 'minitest', '~> 5.0'
29
29
  spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'redcarpet', '~> 3.4'
30
31
  spec.add_development_dependency 'rubocop', '~> 0.52'
31
32
  spec.add_development_dependency 'simplecov', '~> 0.16'
33
+ spec.add_development_dependency 'yard', '~> 0.9'
32
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vissen-input
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Lindberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-18 00:00:00.000000000 Z
11
+ date: 2018-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rubocop
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.16'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
83
111
  description: This gem implements the input messages and message matching engine used
84
112
  in the vissen project.
85
113
  email:
@@ -91,6 +119,7 @@ files:
91
119
  - ".gitignore"
92
120
  - ".rubocop.yml"
93
121
  - ".travis.yml"
122
+ - ".yardopts"
94
123
  - CHANGELOG.md
95
124
  - Gemfile
96
125
  - Gemfile.lock
@@ -99,6 +128,7 @@ files:
99
128
  - Rakefile
100
129
  - bin/console
101
130
  - bin/setup
131
+ - examples/raw_input_stream.rb
102
132
  - lib/vissen/input.rb
103
133
  - lib/vissen/input/broker.rb
104
134
  - lib/vissen/input/matcher.rb