Class: LaunchpadMk2::Interaction

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/launchpad_mk2/interaction.rb

Overview

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger

Constructor Details

#initialize(opts = nil) ⇒ Interaction

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



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 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

Instance Attribute Details

#activeObject (readonly)

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



29
30
31
# File 'lib/launchpad_mk2/interaction.rb', line 29

def active
  @active
end

#deviceObject (readonly)

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



26
27
28
# File 'lib/launchpad_mk2/interaction.rb', line 26

def device
  @device
end

Instance Method Details

#closeObject

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



85
86
87
88
89
# File 'lib/launchpad_mk2/interaction.rb', line 85

def close
  logger.debug "closing Launchpad::Interaction##{object_id}"
  stop
  @device.close
end

#closed?Boolean

Determines whether this interaction’s device has been closed.

Returns:

  • (Boolean)


92
93
94
# File 'lib/launchpad_mk2/interaction.rb', line 92

def closed?
  @device.closed?
end

#logger=(logger) ⇒ Object

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

logger

the [Logger] instance



74
75
76
77
# 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) ⇒ Object

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



225
226
227
228
229
230
231
232
233
234
235
236
# 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) ⇒ Object

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



250
251
252
# 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) ⇒ Object

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



193
194
195
196
197
198
199
200
201
202
203
204
205
206
# 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) ⇒ Object

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



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# 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

#stopObject

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



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 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