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 +4 -4
- data/.rubocop.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +5 -1
- data/README.md +24 -2
- data/Rakefile +12 -1
- data/examples/raw_input_stream.rb +63 -0
- data/lib/vissen/input.rb +0 -2
- data/lib/vissen/input/broker.rb +11 -6
- data/lib/vissen/input/matcher.rb +34 -3
- data/lib/vissen/input/message.rb +36 -2
- data/lib/vissen/input/message/aftertouch.rb +4 -2
- data/lib/vissen/input/message/base.rb +69 -6
- data/lib/vissen/input/message/channel_mode.rb +19 -16
- data/lib/vissen/input/message/channel_pressure.rb +8 -5
- data/lib/vissen/input/message/control_change.rb +5 -3
- data/lib/vissen/input/message/note.rb +13 -4
- data/lib/vissen/input/message/pitch_bend_change.rb +9 -4
- data/lib/vissen/input/message/program_change.rb +4 -1
- data/lib/vissen/input/message_factory.rb +2 -13
- data/lib/vissen/input/subscription.rb +5 -2
- data/lib/vissen/input/version.rb +1 -1
- data/vissen-input.gemspec +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba0c28648eb117befa313aef9e8e83f2e1b75db97f2d5899f727368c9bd39926
|
4
|
+
data.tar.gz: c3207e55160d602acf04008df707fbf20388de7ac209be2e37b2cf37e88abef1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cddb65a57ed9b49f7a6b9e367d133cd095586f6f05b6e75b975b8a9a2fdea31e49034bffebd7d2635447a743a498cfb4bfba51dc947003f24bb2dc1dab6fbddb
|
7
|
+
data.tar.gz: d88b5534407b282d7c9f35bf360d9a1f185a3a2871cd205125daeeaafac560b00799074700d799c9bb4edd26376c2c0712c4d638a65d12f4359797b5046d0116
|
data/.rubocop.yml
CHANGED
@@ -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
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -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
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
vissen-input (0.
|
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
|
1
|
+
# 🥀 Vissen Input
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/vissen-input)
|
3
4
|
[](https://travis-ci.org/midi-visualizer/vissen-input)
|
4
5
|
[](http://inch-ci.org/github/midi-visualizer/vissen-input)
|
6
|
+
[](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
|
-
|
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
|
-
|
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)
|
data/lib/vissen/input.rb
CHANGED
@@ -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.
|
data/lib/vissen/input/broker.rb
CHANGED
@@ -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
|
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.
|
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
|
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
|
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
|
data/lib/vissen/input/matcher.rb
CHANGED
@@ -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
|
-
# @
|
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
|
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
|
data/lib/vissen/input/message.rb
CHANGED
@@ -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
|
-
#
|
8
|
-
#
|
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
|
-
#
|
62
|
+
# rubocop:enable Metrics/AbcSize
|
63
|
+
|
64
|
+
# @!method match?(obj)
|
65
|
+
# Accessor to `Matcher#match?` on the default class matcher.
|
58
66
|
#
|
59
|
-
# @
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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
|
-
|
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
-
#
|
11
|
-
#
|
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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
|
-
#
|
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
|
-
|
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 `
|
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
|
-
# @
|
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.
|
data/lib/vissen/input/version.rb
CHANGED
data/vissen-input.gemspec
CHANGED
@@ -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.
|
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-
|
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
|