From 19972b31763a58827f2a6a7e115d793dbf82f48d Mon Sep 17 00:00:00 2001 From: Ari Russo Date: Thu, 17 Feb 2022 22:54:03 -0500 Subject: [PATCH 1/4] Add Rspec, Rubocop, move sleep timer for rapid message test --- Gemfile | 19 ++-- Rakefile | 17 +-- examples/monitor.rb | 10 +- examples/synched_arpeggios.rb | 14 +-- examples/thru.rb | 46 ++++++++ examples/transpose.rb | 14 +-- lib/midi-eye.rb | 20 ++-- lib/midi-eye/event.rb | 28 +++-- lib/midi-eye/listener.rb | 35 +++--- lib/midi-eye/source.rb | 12 +- spec/helper.rb | 20 ++++ spec/listener_spec.rb | 164 +++++++++++++++++++++++++++ test/helper.rb | 25 ---- test/listener_test.rb | 207 ---------------------------------- 14 files changed, 312 insertions(+), 319 deletions(-) create mode 100644 examples/thru.rb create mode 100644 spec/helper.rb create mode 100644 spec/listener_spec.rb delete mode 100644 test/helper.rb delete mode 100644 test/listener_test.rb diff --git a/Gemfile b/Gemfile index 775b795..441c047 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,11 @@ -source "https://rubygems.org" +# frozen_string_literal: true -group :test do - gem "minitest", "~> 5.5", ">= 5.5.0" - gem "mocha", "~> 1.1", ">= 1.1.0" - gem "rake", "~> 10.4", ">= 10.4.2" - gem "shoulda-context", "~> 1.2", ">= 1.2.1" -end +source 'https://rubygems.org' -gem "midi-message", "~> 0.4", ">= 0.4.9" -gem "midi-nibbler", "~> 0.2", ">= 0.2.4" -gem "unimidi", "~> 0.4", ">= 0.4.8" +gem 'rake', '~> 13.0', '>= 13.0.6', groups: %i[development test] +gem 'rspec', '~> 3.11', '>= 3.11.0', groups: %i[test] +gem 'rubocop', '~> 1.10', '>= 1.10.0', groups: %i[development test], require: false + +gem 'midi-message', '~> 0.4', '>= 0.4.9' +gem 'midi-nibbler', '~> 0.2', '>= 0.2.4' +gem 'unimidi', '~> 0.5', '>= 0.5.1' diff --git a/Rakefile b/Rakefile index 2548653..e7a3d9f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,11 @@ -require "rake" -require "rake/testtask" +# frozen_string_literal: true -Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.test_files = FileList["test/**/*_test.rb"] - t.verbose = true -end +begin + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) -task :default => [:test] + task default: :spec +rescue LoadError + # no rspec available +end diff --git a/examples/monitor.rb b/examples/monitor.rb index e99389f..348246e 100644 --- a/examples/monitor.rb +++ b/examples/monitor.rb @@ -1,7 +1,9 @@ #!/usr/bin/env ruby -$:.unshift(File.join("..", "lib")) +# frozen_string_literal: true -require "midi-eye" +$LOAD_PATH.unshift(File.join('..', 'lib')) + +require 'midi-eye' # This example takes any note messages received from a UniMIDI input, # and prints them to the console @@ -15,15 +17,13 @@ # Bind an event to the listener using Listener#listen_for # transpose.listen_for do |event| - message = event[:message] p message - end # Start the listener -p "Control-C to quit..." +p 'Control-C to quit...' transpose.run diff --git a/examples/synched_arpeggios.rb b/examples/synched_arpeggios.rb index 313ab13..4acda94 100755 --- a/examples/synched_arpeggios.rb +++ b/examples/synched_arpeggios.rb @@ -1,7 +1,9 @@ #!/usr/bin/env ruby -$:.unshift(File.join("..", "lib")) +# frozen_string_literal: true -require "midi-eye" +$LOAD_PATH.unshift(File.join('..', 'lib')) + +require 'midi-eye' # # This example plays arpeggios in sync with MIDI clock ticks that are received on an input @@ -25,8 +27,7 @@ is_note_on = true # Listen for clock messages -@clock.listen_for(:name => "Clock") do |event| - +@clock.listen_for(name: 'Clock') do |_event| # Should it output a note on this click? if message_counter.eql?(@ticks_per_note) @@ -46,15 +47,14 @@ # Once its finished with both note on and off for this particular note, # increment the note counter - note_counter = (note_counter < (@notes.length-1) ? note_counter + 1 : 0) if is_note_on + note_counter = (note_counter < (@notes.length - 1) ? note_counter + 1 : 0) if is_note_on message_counter = 0 else message_counter += 1 end - end -p "Control-C to quit..." +p 'Control-C to quit...' # Start the listener @clock.run diff --git a/examples/thru.rb b/examples/thru.rb new file mode 100644 index 0000000..331ddd6 --- /dev/null +++ b/examples/thru.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.join('..', 'lib')) + +require 'midi-eye' + +# This example takes any note messages received from a UniMIDI input, +# transposes them up one octave and sends them to an output + +# First, initialize the MIDI io ports +@input = UniMIDI::Input.gets +@output = UniMIDI::Output.gets + +# Create a listener for the input port +transpose = MIDIEye::Listener.new(@input) + +# Bind an event to the listener using Listener#listen_for +# +# The listener will try to positively match the parameters you pass in to the properties of +# the messages it receives +# +# This example looks for note on/off messages +# +# You also have the option of leaving out the parameters altogether and including a conditional +# in your callback (eg if event[:message].class.eql?(NoteOn) do... etc) +# +# There's no limit to how many events can be binded to a listener +# +transpose.on_message do |event| + # Send the altered note message to the output + @output.puts(event[:message]) +end + +# Start the listener + +p 'Control-C to quit...' + +transpose.run + +# You can also have the listener run only in a background thread by using +# +# Transpose.run(:background => true) +# +# This will allow you to run multiple listeners at the same time for example, if you +# want to listen on multiple input ports diff --git a/examples/transpose.rb b/examples/transpose.rb index 442b86b..cb9f5a0 100755 --- a/examples/transpose.rb +++ b/examples/transpose.rb @@ -1,7 +1,9 @@ #!/usr/bin/env ruby -$:.unshift(File.join("..", "lib")) +# frozen_string_literal: true -require "midi-eye" +$LOAD_PATH.unshift(File.join('..', 'lib')) + +require 'midi-eye' # This example takes any note messages received from a UniMIDI input, # transposes them up one octave and sends them to an output @@ -25,21 +27,19 @@ # # There's no limit to how many events can be binded to a listener # -transpose.listen_for(:class => [MIDIMessage::NoteOn, MIDIMessage::NoteOff]) do |event| - +transpose.listen_for(class: [MIDIMessage::NoteOn, MIDIMessage::NoteOff]) do |event| # Raise the note value by an octave new_note = event[:message].note + 12 - puts "Transposing from note #{event[:message].note} to note #{(new_note)}" + puts "Transposing from note #{event[:message].note} to note #{new_note}" event[:message].note = new_note # Send the altered note message to the output @output.puts(event[:message]) - end # Start the listener -p "Control-C to quit..." +p 'Control-C to quit...' transpose.run diff --git a/lib/midi-eye.rb b/lib/midi-eye.rb index f4bcf12..0712ba6 100644 --- a/lib/midi-eye.rb +++ b/lib/midi-eye.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # midi-eye # MIDI input event listener for Ruby @@ -8,18 +10,16 @@ # # libs -require "forwardable" -require "midi-message" -require "nibbler" -require "unimidi" +require 'forwardable' +require 'midi-message' +require 'nibbler' +require 'unimidi' # classes -require "midi-eye/event" -require "midi-eye/listener" -require "midi-eye/source" +require 'midi-eye/event' +require 'midi-eye/listener' +require 'midi-eye/source' module MIDIEye - - VERSION = "0.3.10" - + VERSION = '0.3.10' end diff --git a/lib/midi-eye/event.rb b/lib/midi-eye/event.rb index 3de9d22..12c764e 100644 --- a/lib/midi-eye/event.rb +++ b/lib/midi-eye/event.rb @@ -1,8 +1,8 @@ -module MIDIEye +# frozen_string_literal: true +module MIDIEye # User defined callbacks for input events class Event - extend Forwardable def_delegators :@event, :count @@ -34,9 +34,9 @@ def add(options = {}, &callback) name = options[:listener_name] options.delete(:listener_name) event = { - :conditions => options, - :proc => callback, - :listener_name => name + conditions: options, + proc: callback, + listener_name: name } @event << event event @@ -46,9 +46,9 @@ def add(options = {}, &callback) # @return [Fixnum] The number of triggered events def trigger_enqueued counter = 0 - while !@queue.empty? do + until @queue.empty? counter += 1 - trigger(@queue.shift) + trigger_event(@queue.shift) end counter end @@ -63,8 +63,8 @@ def enqueue_all(message) # @return [Hash] def enqueue(action, message) event = { - :action => action, - :message => message + action: action, + message: message } @queue << event event @@ -76,7 +76,7 @@ def enqueue(action, message) def meets_conditions?(conditions, message) results = conditions.map do |key, value| if message.respond_to?(key) - if value.kind_of?(Array) + if value.is_a?(Enumerable) value.include?(message.send(key)) else value.eql?(message.send(key)) @@ -89,18 +89,16 @@ def meets_conditions?(conditions, message) end # Trigger an event - def trigger(event) + def trigger_event(event) action = event[:action] conditions = action[:conditions] if conditions.nil? || meets_conditions?(conditions, event[:message][:message]) begin action[:proc].call(event[:message]) - rescue Exception => exception - Thread.main.raise(exception) + rescue Exception => e + Thread.main.raise(e) end end end - end - end diff --git a/lib/midi-eye/listener.rb b/lib/midi-eye/listener.rb index 4ef7c18..a808f49 100644 --- a/lib/midi-eye/listener.rb +++ b/lib/midi-eye/listener.rb @@ -1,8 +1,8 @@ -module MIDIEye +# frozen_string_literal: true +module MIDIEye class Listener - - LISTEN_INTERVAL = 1.0 / 1000 + LISTEN_INTERVAL = 1.0 / 10000 attr_reader :event attr_accessor :sources @@ -31,7 +31,7 @@ def add_input(inputs) @sources += input_sources.map { |input| Source.new(input) } @sources end - alias_method :add_inputs, :add_input + alias add_inputs add_input # Remove a MIDI source # @param [Array, UniMIDI::Input] inputs Input(s) to remove from the list of sources for this listener @@ -43,7 +43,7 @@ def remove_input(inputs) end @sources end - alias_method :remove_inputs, :remove_input + alias remove_inputs remove_input # Start listening for MIDI messages # @params [Hash] options @@ -51,10 +51,10 @@ def remove_input(inputs) # @return [MIDIEye::Listener] self def run(options = {}) listen - join unless !!options[:background] + join if options[:background].nil? self end - alias_method :start, :run + alias start run # Stop listening for MIDI messages. # @return [MIDIEye::Listener] self @@ -64,7 +64,7 @@ def close @sources.clear self end - alias_method :stop, :close + alias stop close # Is the listener running? # @return [Boolean] @@ -77,9 +77,9 @@ def running? def join begin @listener.join - rescue Exception => exception + rescue Exception => e @listener.kill - Thread.main.raise(exception) + Thread.main.raise(e) end self end @@ -95,11 +95,12 @@ def delete_event(event_name) # @param [Hash] options # @return [MIDIEye::Listener] self def listen_for(options = {}, &callback) - raise "Listener must have a block" if callback.nil? + raise 'Listener must have a block' if callback.nil? + @event.add(options, &callback) self end - alias_method :on_message, :listen_for + alias on_message listen_for # Poll the input source for new input. This will normally be done by the background thread def poll @@ -109,8 +110,8 @@ def poll messages = [batch[:messages]].flatten.compact messages.each do |message| data = { - :message => message, - :timestamp => batch[:timestamp] + message: message, + timestamp: batch[:timestamp] } @event.enqueue_all(data) end @@ -135,14 +136,12 @@ def listen @listener = Thread.new do begin listen_loop - rescue Exception => exception - Thread.main.raise(exception) + rescue Exception => e + Thread.main.raise(e) end end @listener.abort_on_exception = true true end - end - end diff --git a/lib/midi-eye/source.rb b/lib/midi-eye/source.rb index a73acc4..1beb5a6 100644 --- a/lib/midi-eye/source.rb +++ b/lib/midi-eye/source.rb @@ -1,8 +1,8 @@ -module MIDIEye +# frozen_string_literal: true +module MIDIEye # Retrieves new messages from a unimidi input buffer class Source - attr_reader :device, :pointer # Whether the given object is a UniMIDI input @@ -20,13 +20,13 @@ def initialize(input) end # Grabs new messages from the input buffer - def poll(&block) + def poll messages = @device.buffer.slice(@pointer, @device.buffer.length - @pointer) @pointer = @device.buffer.length messages.compact.each do |raw_message| parsed_messages = begin - @parser.parse(raw_message[:data], :timestamp => raw_message[:timestamp]) - rescue + @parser.parse(raw_message[:data], timestamp: raw_message[:timestamp]) + rescue StandardError nil end objects = [parsed_messages].flatten.compact @@ -40,7 +40,5 @@ def poll(&block) def uses?(input) @device == input end - end - end diff --git a/spec/helper.rb b/spec/helper.rb new file mode 100644 index 0000000..7662f29 --- /dev/null +++ b/spec/helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +dir = File.dirname(File.expand_path(__FILE__)) +$LOAD_PATH.unshift("#{dir}/../lib") + +require 'rspec' +require 'midi-eye' + +module SpecHelper + module_function + + def devices + if @devices.nil? + @devices = {} + { input: UniMIDI::Input, output: UniMIDI::Output }.each do |type, klass| + @devices[type] = klass.gets + end + end + @devices + end +end diff --git a/spec/listener_spec.rb b/spec/listener_spec.rb new file mode 100644 index 0000000..8bf63df --- /dev/null +++ b/spec/listener_spec.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true +require 'helper' + +describe MIDIEye::Listener do + let(:input) { SpecHelper.devices[:input] } + let(:output) { SpecHelper.devices[:output] } + let(:listener) { MIDIEye::Listener.new(input) } + before { sleep 0.2 } + + after do + listener.close + input.clear_buffer + input.close + output.close + sleep 0.5 + end + + describe '#listen_for' do + describe 'no filter' do + it 'receivs messages' do + i = 0 + listener.listen_for do |_event| + i += 1 + end + listener.start(background: true) + + sleep 0.5 + + output.puts(0x90, 0x40, 0x10) + sleep(0.2) + expect(i).to eq(1) + end + end + + describe 'filter on control change' do + describe 'rapid messages' do + it 'receives messages' do + i = 0 + listener.listen_for(class: MIDIMessage::ControlChange) do + i += 1 + end + listener.start(background: true) + + 5.times do + 126.times do |i2| + output.puts(176, 1, i2 + 1) + sleep 0.001 + end + end + expect(i).to eq(5 * 126) + end + end + + describe 'normal messages' do + it 'receives messages' do + event = nil + listener.listen_for(class: MIDIMessage::ControlChange) do |e| + event = e + end + listener.start(background: true) + sleep 0.5 + input.clear_buffer + + output.puts(176, 1, 35) + sleep 0.2 + expect(event).to_not be_nil + expect(event[:message]).to be_a(MIDIMessage::ControlChange) + expect(event[:message].index).to eq(1) + expect(event[:message].value).to eq(35) + expect(event[:message].to_bytes).to eq([176, 1, 35]) + end + end + end + + describe 'filter on sysex' do + it 'receives messages' do + event = nil + listener.listen_for(class: MIDIMessage::SystemExclusive::Command) do |e| + event = e + end + listener.start(background: true) + sleep 0.5 + + output.puts(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7) + sleep 0.2 + expect(event).to_not be_nil + expect(event[:message]).to be_a(MIDIMessage::SystemExclusive::Command) + expect(event[:message].to_byte_array).to eq([0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7]) + end + end + + describe 'filter on note on' do + it 'receives messages' do + event = nil + listener.listen_for(class: MIDIMessage::NoteOff) do |e| + event = e + end + listener.start(background: true) + sleep 0.5 + + output.puts(0x80, 0x50, 0x40) + sleep 0.2 + expect(event).to_not be_nil + expect(event[:message]).to be_a(MIDIMessage::NoteOff) + expect(event[:message].note).to eq(0x50) + expect(event[:message].velocity).to eq(0x40) + expect(event[:message].to_bytes).to eq([0x80, 0x50, 0x40]) + end + end + end + + describe '#delete_event' do + it 'deletes event' do + event = nil + listener.listen_for(listener_name: :test) do |e| + event = e + end + output.puts(0x90, 0x70, 0x20) + listener.start(background: true) + sleep 0.5 + + expect(listener.event.count).to eq(1) + listener.delete_event(:test) + expect(listener.event.count).to eq(0) + end + end + + describe '#uses_input?' do + it 'acknowledges input' do + expect(listener.uses_input?(input)).to be(true) + end + end + + describe '#add_input' do + it 'ignores redundant input' do + num_sources = listener.sources.size + listener.add_input(input) + expect(listener.sources.size).to eq(num_sources) + expect(listener.sources.last).to be_a(MIDIEye::Source) + end + end + + describe '#remove_input' do + it 'removes input' do + num_sources = listener.sources.size + expect(num_sources).to be > 0 + listener.remove_input(input) + expect(listener.sources.size).to eq(num_sources - 1) + end + end + + describe '#close' do + before do + listener.start(background: true) + output.puts(0x80, 0x50, 0x40) + end + + it 'closes' do + expect(listener.close).to be_truthy + sleep 0.5 + expect(listener.running?).to be(false) + end + end +end diff --git a/test/helper.rb b/test/helper.rb deleted file mode 100644 index 07bcfc0..0000000 --- a/test/helper.rb +++ /dev/null @@ -1,25 +0,0 @@ -dir = File.dirname(File.expand_path(__FILE__)) -$LOAD_PATH.unshift(dir + "/../lib") - -require "minitest/autorun" -require "mocha/test_unit" -require "shoulda-context" - -require "midi-eye" - -module TestHelper - - extend self - - attr_reader :device - - def select_devices - @device ||= {} - { :input => UniMIDI::Input, :output => UniMIDI::Output }.each do |type, klass| - @device[type] = klass.gets - end - end - -end - -TestHelper.select_devices diff --git a/test/listener_test.rb b/test/listener_test.rb deleted file mode 100644 index 2f44991..0000000 --- a/test/listener_test.rb +++ /dev/null @@ -1,207 +0,0 @@ -require "helper" - -class ListenerTest < Minitest::Test - - context "Listener" do - - setup do - sleep(0.2) - @output = TestHelper.device[:output] - @input = TestHelper.device[:input] - @listener = MIDIEye::Listener.new(@input) - end - - teardown do - @listener.close - @input.clear_buffer - @input.close - @output.close - sleep(0.5) - end - - context "#listen_for" do - - context "no filter" do - - setup do - @i = 0 - @listener.listen_for do |event| - @i += 1 - end - @listener.start(:background => true) - sleep(0.5) - end - - should "receive messages" do - @output.puts(0x90, 0x40, 0x10) - sleep(0.2) - assert_equal 1, @i - end - - end - - context "filter on control change" do - - context "rapid messages" do - - setup do - @i = 0 - @listener.listen_for(:class => MIDIMessage::ControlChange) do |event| - @i += 1 - end - @listener.start(:background => true) - sleep(0.5) - end - - should "receive messages" do - 5.times do - 126.times do |i| - @output.puts(176, 1, i+1) - end - end - sleep(1) - assert_equal(5 * 126, @i) - end - - end - - context "normal messages" do - - setup do - @event = nil - @listener.listen_for(:class => MIDIMessage::ControlChange) do |event| - @event = event - end - @listener.start(:background => true) - sleep(0.5) - @input.clear_buffer - end - - should "receive messages" do - @output.puts(176, 1, 35) - sleep(0.2) - refute_nil @event - assert_equal(MIDIMessage::ControlChange, @event[:message].class) - assert_equal(1, @event[:message].index) - assert_equal(35, @event[:message].value) - assert_equal([176, 1, 35], @event[:message].to_bytes) - end - - end - - end - - context "filter on sysex" do - - setup do - @event = nil - @listener.listen_for(:class => MIDIMessage::SystemExclusive::Command) do |event| - @event = event - end - @listener.start(:background => true) - sleep(0.5) - end - - should "receive messages" do - @output.puts(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7) - sleep(0.2) - refute_nil @event - assert_equal(MIDIMessage::SystemExclusive::Command, @event[:message].class) - assert_equal([0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], @event[:message].to_byte_array) - end - - end - - context "filter on note on" do - - setup do - @event = nil - @listener.listen_for(:class => MIDIMessage::NoteOff) do |event| - @event = event - end - @listener.start(:background => true) - sleep(0.5) - end - - should "receive messages" do - @output.puts(0x80, 0x50, 0x40) - sleep(0.2) - refute_nil @event - assert_equal(MIDIMessage::NoteOff, @event[:message].class) - assert_equal(0x50, @event[:message].note) - assert_equal(0x40, @event[:message].velocity) - assert_equal([0x80, 0x50, 0x40], @event[:message].to_bytes) - end - - end - - end - - context "#delete_event" do - - setup do - @event = nil - @listener.listen_for(:listener_name => :test) do |event| - @event = event - end - @output.puts(0x90, 0x70, 0x20) - @listener.start(:background => true) - sleep(0.5) - end - - should "delete event" do - assert_equal(1, @listener.event.count) - @listener.delete_event(:test) - assert_equal(0, @listener.event.count) - end - - end - - context "#uses_input?" do - - should "acknowledge input" do - assert @listener.uses_input?(@input) - end - - end - - context "#add_input" do - - should "ignore redundant input" do - num_sources = @listener.sources.size - @listener.add_input(@input) - assert_equal num_sources, @listener.sources.size - assert_equal MIDIEye::Source, @listener.sources.last.class - end - - end - - context "#remove_input" do - - should "remove input" do - num_sources = @listener.sources.size - assert num_sources > 0 - @listener.remove_input(@input) - assert_equal num_sources - 1, @listener.sources.size - end - - end - - context "#close" do - - setup do - @listener.start(:background => true) - @output.puts(0x80, 0x50, 0x40) - end - - should "close" do - assert @listener.close - sleep(0.5) - refute @listener.running? - end - - end - - end - -end From 404a898b59b357fd6ecf57ec63255b120379e45e Mon Sep 17 00:00:00 2001 From: Ari Russo Date: Tue, 9 Aug 2022 22:53:14 -0400 Subject: [PATCH 2/4] Passing Rubocop --- .gitignore | 3 +++ .rubocop.yml | 4 ++++ Gemfile | 3 ++- README.md | 2 +- examples/monitor.rb | 0 examples/thru.rb | 0 lib/midi-eye/event.rb | 35 ++++++++++++++++++----------------- lib/midi-eye/listener.rb | 32 ++++++++++++++++++-------------- lib/midi-eye/source.rb | 24 ++++++++++++++---------- spec/helper.rb | 1 + spec/listener_spec.rb | 1 + 11 files changed, 62 insertions(+), 43 deletions(-) create mode 100644 .gitignore create mode 100644 .rubocop.yml mode change 100644 => 100755 examples/monitor.rb mode change 100644 => 100755 examples/thru.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58592d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Gemfile.lock +.idea/**/* +*.gemspec diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..ee14425 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,4 @@ +Metrics/BlockLength: + IgnoredMethods: ['describe', 'context'] +Naming/FileName: + Enabled: false \ No newline at end of file diff --git a/Gemfile b/Gemfile index 441c047..b0e5bea 100644 --- a/Gemfile +++ b/Gemfile @@ -7,5 +7,6 @@ gem 'rspec', '~> 3.11', '>= 3.11.0', groups: %i[test] gem 'rubocop', '~> 1.10', '>= 1.10.0', groups: %i[development test], require: false gem 'midi-message', '~> 0.4', '>= 0.4.9' -gem 'midi-nibbler', '~> 0.2', '>= 0.2.4' +gem 'midi-nibbler' +# gem 'midi-nibbler', path: '/Users/russo/workspace/nibbler' gem 'unimidi', '~> 0.5', '>= 0.5.1' diff --git a/README.md b/README.md index 061cc95..2ce40fe 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ transpose.run A listener can also be run in a background thread by passing in `:background => true`. ```ruby -transpose.run(:background => true) +transpose.run(background: true) transpose.join # join the background thread later ``` diff --git a/examples/monitor.rb b/examples/monitor.rb old mode 100644 new mode 100755 diff --git a/examples/thru.rb b/examples/thru.rb old mode 100644 new mode 100755 diff --git a/lib/midi-eye/event.rb b/lib/midi-eye/event.rb index 12c764e..75ea363 100644 --- a/lib/midi-eye/event.rb +++ b/lib/midi-eye/event.rb @@ -74,30 +74,31 @@ def enqueue(action, message) # Does the given message meet the given conditions? def meets_conditions?(conditions, message) - results = conditions.map do |key, value| - if message.respond_to?(key) - if value.is_a?(Enumerable) - value.include?(message.send(key)) - else - value.eql?(message.send(key)) - end - else - false - end - end - results.all? + conditions.map { |key, value| condition_met?(message, key, value) }.all? end # Trigger an event def trigger_event(event) action = event[:action] conditions = action[:conditions] - if conditions.nil? || meets_conditions?(conditions, event[:message][:message]) - begin - action[:proc].call(event[:message]) - rescue Exception => e - Thread.main.raise(e) + return unless conditions.nil? || meets_conditions?(conditions, event[:message][:message]) + + begin + action[:proc].call(event[:message]) + rescue StandardError => e + Thread.main.raise(e) + end + end + + def condition_met?(message, key, value) + if message.respond_to?(key) + if value.is_a?(Enumerable) + value.include?(message.send(key)) + else + value.eql?(message.send(key)) end + else + false end end end diff --git a/lib/midi-eye/listener.rb b/lib/midi-eye/listener.rb index a808f49..99f1fe0 100644 --- a/lib/midi-eye/listener.rb +++ b/lib/midi-eye/listener.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true module MIDIEye + # Listens for MIDI Messages class Listener - LISTEN_INTERVAL = 1.0 / 10000 + LISTEN_INTERVAL = 1.0 / 10_000 attr_reader :event attr_accessor :sources @@ -34,7 +35,8 @@ def add_input(inputs) alias add_inputs add_input # Remove a MIDI source - # @param [Array, UniMIDI::Input] inputs Input(s) to remove from the list of sources for this listener + # @param [Array, UniMIDI::Input] inputs Input(s) to remove from + # the list of sources for this listener # @return [Array] The updated list of sources for this listener def remove_input(inputs) inputs = [inputs].flatten.compact @@ -77,7 +79,7 @@ def running? def join begin @listener.join - rescue Exception => e + rescue StandardError => e @listener.kill Thread.main.raise(e) end @@ -106,22 +108,24 @@ def listen_for(options = {}, &callback) def poll @sources.each do |input| input.poll do |objs| - objs.each do |batch| - messages = [batch[:messages]].flatten.compact - messages.each do |message| - data = { - message: message, - timestamp: batch[:timestamp] - } - @event.enqueue_all(data) - end - end + objs.each { |batch| input_to_messages(batch) } end end end private + def input_to_messages(batch) + messages = [batch[:messages]].flatten.compact + messages.each do |message| + data = { + message: message, + timestamp: batch[:timestamp] + } + @event.enqueue_all(data) + end + end + # A loop that runs while the listener is active def listen_loop loop do @@ -136,7 +140,7 @@ def listen @listener = Thread.new do begin listen_loop - rescue Exception => e + rescue StandardError => e Thread.main.raise(e) end end diff --git a/lib/midi-eye/source.rb b/lib/midi-eye/source.rb index 1beb5a6..f7d9796 100644 --- a/lib/midi-eye/source.rb +++ b/lib/midi-eye/source.rb @@ -20,18 +20,10 @@ def initialize(input) end # Grabs new messages from the input buffer - def poll + def poll(&block) messages = @device.buffer.slice(@pointer, @device.buffer.length - @pointer) @pointer = @device.buffer.length - messages.compact.each do |raw_message| - parsed_messages = begin - @parser.parse(raw_message[:data], timestamp: raw_message[:timestamp]) - rescue StandardError - nil - end - objects = [parsed_messages].flatten.compact - yield(objects) - end + messages.compact.each { |raw_message| handle_message(raw_message, &block) } end # If this source was created from the given input @@ -40,5 +32,17 @@ def poll def uses?(input) @device == input end + + private + + def handle_message(raw_message) + parsed_messages = begin + @parser.parse(raw_message[:data], timestamp: raw_message[:timestamp]) + rescue StandardError + nil + end + objects = [parsed_messages].flatten.compact + yield(objects) + end end end diff --git a/spec/helper.rb b/spec/helper.rb index 7662f29..8e3834a 100644 --- a/spec/helper.rb +++ b/spec/helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + dir = File.dirname(File.expand_path(__FILE__)) $LOAD_PATH.unshift("#{dir}/../lib") diff --git a/spec/listener_spec.rb b/spec/listener_spec.rb index 8bf63df..35300d7 100644 --- a/spec/listener_spec.rb +++ b/spec/listener_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'helper' describe MIDIEye::Listener do From 654c4d500418cec5eb6ea6f831901a149ebc5287 Mon Sep 17 00:00:00 2001 From: Ari Russo Date: Tue, 9 Aug 2022 22:54:37 -0400 Subject: [PATCH 3/4] Add Rubocop to github actions --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ Gemfile | 3 +-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..48079e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI +on: [push, pull_request] +jobs: + lint: + strategy: + matrix: + ruby-version: + - "2.4" + name: ${{ format('Lint (Ruby {0})', matrix.ruby-version) }} + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v1 + + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Run Linter + run: | + bundle exec rubocop diff --git a/Gemfile b/Gemfile index b0e5bea..441c047 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,5 @@ gem 'rspec', '~> 3.11', '>= 3.11.0', groups: %i[test] gem 'rubocop', '~> 1.10', '>= 1.10.0', groups: %i[development test], require: false gem 'midi-message', '~> 0.4', '>= 0.4.9' -gem 'midi-nibbler' -# gem 'midi-nibbler', path: '/Users/russo/workspace/nibbler' +gem 'midi-nibbler', '~> 0.2', '>= 0.2.4' gem 'unimidi', '~> 0.5', '>= 0.5.1' From 27a82ca6fa12054544e63496bf2ff99c0dffdce3 Mon Sep 17 00:00:00 2001 From: Ari Russo Date: Wed, 10 Aug 2022 18:59:32 -0400 Subject: [PATCH 4/4] Modern notation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ce40fe..1132850 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The listener will try to positively match the parameters you pass in to the prop In this example, we specify that the listener listens for note on/off messages, which are identifiable by their class. ```ruby -transpose.listen_for(:class => [MIDIMessage::NoteOn, MIDIMessage::NoteOff]) do |event| +transpose.listen_for(class: [MIDIMessage::NoteOn, MIDIMessage::NoteOff]) do |event| # raise the note value by an octave event[:message].note += 12 @@ -59,7 +59,7 @@ Once all the events are bound, start the listener transpose.run ``` -A listener can also be run in a background thread by passing in `:background => true`. +A listener can also be run in a background thread by passing in `background: true`. ```ruby transpose.run(background: true)