vissen-input 0.2.2 → 0.3.0

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