class LaunchpadMk2::Interaction

This class provides advanced interaction features.

Example:

require 'launchpad_mk2'

interaction = Launchpad::Interaction.new
interaction.response_to(:grid, :down) do |interaction, action|
  interaction.device.change(:grid, action.merge(:red => :high))
end
interaction.response_to(:mixer, :down) do |interaction, action|
  interaction.stop
end

interaction.start

Attributes

active[R]

Returns whether the Launchpad::Interaction is active or not.

device[R]

Returns the Launchpad::Device the Launchpad::Interaction acts on.

Public Class Methods

new(opts = nil) click to toggle source

Initializes the interaction.

Optional options hash:

:device

Launchpad::Device to act on, optional, :input_device_id/:output_device_id will be used if omitted

:input_device_id

ID of the MIDI input device to use, optional, :device_name will be used if omitted

:output_device_id

ID of the MIDI output device to use, optional, :device_name will be used if omitted

:device_name

Name of the MIDI device to use, optional, defaults to “Launchpad”

:latency

delay (in s, fractions allowed) between MIDI pulls, optional, defaults to 0.001 (1ms)

:logger
Logger

to be used by this interaction instance, can be changed afterwards

Errors raised:

Launchpad::NoSuchDeviceError

when device with ID or name specified does not exist

Launchpad::DeviceBusyError

when device with ID or name specified is busy

# File lib/launchpad_mk2/interaction.rb, line 51
def initialize(opts = nil)
  @reader_thread = nil
  @device = nil
  opts ||= {}

  self.logger = opts[:logger]
  logger.debug "initializing Launchpad::Interaction##{object_id} with #{opts.inspect}"

  @device ||= opts[:device]
  @device ||= Device.new(opts.merge(
    :input => true,
    :output => true,
    :logger => opts[:logger]
  ))
  @latency = (opts[:latency] || 0.001).to_f.abs
  @active = false

  @action_threads = ThreadGroup.new
end

Public Instance Methods

close() click to toggle source

Closes the interaction's device - nothing can be done with the interaction/device afterwards.

Errors raised:

Launchpad::NoInputAllowedError

when input is not enabled on the interaction's device

Launchpad::CommunicationError

when anything unexpected happens while communicating with the

# File lib/launchpad_mk2/interaction.rb, line 85
def close
  logger.debug "closing Launchpad::Interaction##{object_id}"
  stop
  @device.close
end
closed?() click to toggle source

Determines whether this interaction's device has been closed.

# File lib/launchpad_mk2/interaction.rb, line 92
def closed?
  @device.closed?
end
logger=(logger) click to toggle source

Sets the logger to be used by the current instance and the device.

logger

the [Logger] instance

# File lib/launchpad_mk2/interaction.rb, line 74
def logger=(logger)
  @logger = logger
  @device.logger = logger if @device
end
no_response_to(types = nil, state = :both, opts = nil) click to toggle source

Deregisters all responses to one or more actions.

Parameters (see Launchpad for values):

types

one or an array of button types to respond to, additional value :all for actions on all buttons (but not meaning “all responses”), optional, defaults to nil, meaning “all responses”

state

button state to respond to, additional value :both

Optional options hash:

:x

x coordinate(s), can contain arrays and ranges, when specified without y coordinate, it's interpreted as a whole column

:y

y coordinate(s), can contain arrays and ranges, when specified without x coordinate, it's interpreted as a whole row

# File lib/launchpad_mk2/interaction.rb, line 225
def no_response_to(types = nil, state = :both, opts = nil)
  logger.debug "removing response to #{types.inspect} for state #{state.inspect}"
  types = Array(types)
  Array(state == :both ? %w(down up) : state).each do |current_state|
    types.each do |type|
      combined_types(type, opts).each do |combined_type|
        responses[combined_type][current_state.to_sym].clear
      end
    end
  end
  nil
end
respond_to(type, state, opts = nil) click to toggle source

Responds to an action by executing all matching responses, effectively simulating a button press/release.

Parameters (see Launchpad for values):

type

type of the button to trigger

state

state of the button

Optional options hash (see Launchpad for values):

:x

x coordinate

:y

y coordinate

# File lib/launchpad_mk2/interaction.rb, line 250
def respond_to(type, state, opts = nil)
  respond_to_action((opts || {}).merge(:type => type, :state => state))
end
response_to(types = :all, state = :both, opts = nil, &block) click to toggle source

Registers a response to one or more actions.

Parameters (see Launchpad for values):

types

one or an array of button types to respond to, additional value :all for all buttons

state

button state to respond to, additional value :both

Optional options hash:

:exclusive

true/false, whether to deregister all other responses to the specified actions, optional, defaults to false

:x

x coordinate(s), can contain arrays and ranges, when specified without y coordinate, it's interpreted as a whole column

:y

y coordinate(s), can contain arrays and ranges, when specified without x coordinate, it's interpreted as a whole row

Takes a block which will be called when an action matching the parameters occurs.

Block parameters:

interaction

the interaction object that received the action

action

the action received from Launchpad::Device.read_pending_actions

# File lib/launchpad_mk2/interaction.rb, line 193
def response_to(types = :all, state = :both, opts = nil, &block)
  logger.debug "setting response to #{types.inspect} for state #{state.inspect} with #{opts.inspect}"
  types = Array(types)
  opts ||= {}
  no_response_to(types, state) if opts[:exclusive] == true
  Array(state == :both ? %w(down up) : state).each do |current_state|
    types.each do |type|
      combined_types(type, opts).each do |combined_type|
        responses[combined_type][current_state.to_sym] << block
      end
    end
  end
  nil
end
start(opts = nil) click to toggle source

Starts interacting with the launchpad. Resets the device when the interaction was properly stopped via stop or close.

Optional options hash:

:detached

true/false, whether to detach the interaction, method is blocking when false, optional, defaults to false

Errors raised:

Launchpad::NoInputAllowedError

when input is not enabled on the interaction's device

Launchpad::NoOutputAllowedError

when output is not enabled on the interaction's device

Launchpad::CommunicationError

when anything unexpected happens while communicating with the launchpad

# File lib/launchpad_mk2/interaction.rb, line 110
def start(opts = nil)
  logger.debug "starting Launchpad::Interaction##{object_id}"

  opts = {
    :detached => false
  }.merge(opts || {})

  @active = true

  @reader_thread ||= Thread.new do
    begin
      while @active do
        @device.read_pending_actions.each do |pending_action|
          action_thread = Thread.new(pending_action) do |action|
            respond_to_action(action)
          end
          @action_threads.add(action_thread)
        end
        sleep @latency# if @latency > 0.0
      end
    rescue Portmidi::DeviceError => e
      logger.fatal "could not read from device, stopping to read actions"
      raise CommunicationError.new(e)
    rescue Exception => e
      logger.fatal "error causing action reading to stop: #{e.inspect}"
      raise e
    end
  end
  @reader_thread.join unless opts[:detached]
end
stop() click to toggle source

Stops interacting with the launchpad.

Errors raised:

Launchpad::NoInputAllowedError

when input is not enabled on the interaction's device

Launchpad::CommunicationError

when anything unexpected happens while communicating with the

# File lib/launchpad_mk2/interaction.rb, line 147
def stop
  logger.debug "stopping Launchpad::Interaction##{object_id}"
  @active = false
  if @reader_thread
    # run (resume from sleep) and wait for @reader_thread to end
    @reader_thread.run if @reader_thread.alive?
    @reader_thread.join
    @reader_thread = nil
  end
ensure
  @action_threads.list.each do |thread|
    begin
      thread.kill
      thread.join
    rescue Exception => e
      logger.error "error when killing action thread: #{e.inspect}"
    end
  end
  nil
end

Private Instance Methods

combined_types(type, opts = nil) click to toggle source

Returns a list of combined types for the type and opts specified. Combined types are just the type, except for grid, where the opts are interpreted and all combinations of x and y coordinates are added as a position suffix.

Example:

combined_types(:grid, :x => 1..2, y => 2) => [:grid12, :grid22]

Parameters (see Launchpad for values):

type

type of the button

Optional options hash:

:x

x coordinate(s), can contain arrays and ranges, when specified without y coordinate, it's interpreted as a whole column

:y

y coordinate(s), can contain arrays and ranges, when specified without x coordinate, it's interpreted as a whole row

# File lib/launchpad_mk2/interaction.rb, line 295
def combined_types(type, opts = nil)
  if type.to_sym == :grid && opts
    x = grid_range(opts[:x])
    y = grid_range(opts[:y])
    return [:grid] if x.nil? && y.nil?  # whole grid
    x ||= ['-']                         # whole row
    y ||= ['-']                         # whole column
    x.product(y).map {|current_x, current_y| :"grid#{current_x}#{current_y}"}
  else
    [type.to_sym]
  end
end
grid_range(range) click to toggle source

Returns an array of grid positions for a range.

Parameters:

range

the range definitions, can be

  • a Fixnum

  • a Range

  • an Array of Fixnum, Range or Array objects

# File lib/launchpad_mk2/interaction.rb, line 270
def grid_range(range)
  return nil if range.nil?
  Array(range).flatten.map do |pos|
    pos.respond_to?(:to_a) ? pos.to_a : pos
  end.flatten.uniq
end
respond_to_action(action) click to toggle source

Reponds to an action by executing all matching responses.

Parameters:

action

hash containing an action from Launchpad::Device.read_pending_actions

# File lib/launchpad_mk2/interaction.rb, line 313
def respond_to_action(action)
  type = action[:type].to_sym
  state = action[:state].to_sym
  actions = []
  if type == :grid
    actions += responses[:"grid#{action[:x]}#{action[:y]}"][state]
    actions += responses[:"grid#{action[:x]}-"][state]
    actions += responses[:"grid-#{action[:y]}"][state]
  end
  actions += responses[type][state]
  actions += responses[:all][state]
  actions.compact.each {|block| block.call(self, action)}
  nil
rescue Exception => e
  logger.error "error when responding to action #{action.inspect}: #{e.inspect}"
  raise e
end
responses() click to toggle source

Returns the hash storing all responses. Keys are button types, values are hashes themselves, keys are :down/:up, values are arrays of responses.

# File lib/launchpad_mk2/interaction.rb, line 258
def responses
  @responses ||= Hash.new {|hash, key| hash[key] = {:down => [], :up => []}}
end