<commit_before>require 'launchpad/device'
require 'launchpad/logging'

module Launchpad

  # This class provides advanced interaction features.
  #
  # Example:
  #
  #   require 'launchpad'
  #
  #   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
  class Interaction

    include Logging

    # Returns the Launchpad::Device the Launchpad::Interaction acts on.
    attr_reader :device

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

    # Initializes the interaction.
    #
    # Optional options hash:
    #
    # [<tt>:device</tt>]            Launchpad::Device to act on,
    #                               optional, <tt>:input_device_id/:output_device_id</tt> will be used if omitted
    # [<tt>:input_device_id</tt>]   ID of the MIDI input device to use,
    #                               optional, <tt>:device_name</tt> will be used if omitted
    # [<tt>:output_device_id</tt>]  ID of the MIDI output device to use,
    #                               optional, <tt>:device_name</tt> will be used if omitted
    # [<tt>:device_name</tt>]       Name of the MIDI device to use,
    #                               optional, defaults to "Launchpad"
    # [<tt>:latency</tt>]           delay (in s, fractions allowed) between MIDI pulls,
    #                               optional, defaults to 0.001 (1ms)
    # [<tt>:logger</tt>]            [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
    def initialize(opts = 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

    # Sets the logger to be used by the current instance and the device.
    #
    # [+logger+]  the [Logger] instance
    def logger=(logger)
      @logger = logger
      @device.logger = logger if @device
    end

    # 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
    def close
      logger.debug "closing Launchpad::Interaction##{object_id}"
      stop
      @device.close
    end

    # Determines whether this interaction's device has been closed.
    def closed?
      @device.closed?
    end

    # Starts interacting with the launchpad. Resets the device when
    # the interaction was properly stopped via stop or close.
    #
    # Optional options hash:
    #
    # [<tt>:detached</tt>]  <tt>true/false</tt>,
    #                       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
    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 |action|
              action_thread = Thread.new(action) do |act|
                respond_to_action(act)
              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}"<commit_msg>Allow the option of synchronous input handling.<commit_after>require 'launchpad/device'
require 'launchpad/logging'

module Launchpad

  # This class provides advanced interaction features.
  #
  # Example:
  #
  #   require 'launchpad'
  #
  #   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
  class Interaction

    include Logging

    # Returns the Launchpad::Device the Launchpad::Interaction acts on.
    attr_reader :device

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

    # Initializes the interaction.
    #
    # Optional options hash:
    #
    # [<tt>:device</tt>]            Launchpad::Device to act on,
    #                               optional, <tt>:input_device_id/:output_device_id</tt> will be used if omitted
    # [<tt>:input_device_id</tt>]   ID of the MIDI input device to use,
    #                               optional, <tt>:device_name</tt> will be used if omitted
    # [<tt>:output_device_id</tt>]  ID of the MIDI output device to use,
    #                               optional, <tt>:device_name</tt> will be used if omitted
    # [<tt>:device_name</tt>]       Name of the MIDI device to use,
    #                               optional, defaults to "Launchpad"
    # [<tt>:latency</tt>]           delay (in s, fractions allowed) between MIDI pulls,
    #                               optional, defaults to 0.001 (1ms)
    # [<tt>:logger</tt>]            [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
    def initialize(opts = nil)
      opts ||= {}

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

      @device       = opts[:device]
      @use_threads  = opts[:use_threads] || true
      @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

    # Sets the logger to be used by the current instance and the device.
    #
    # [+logger+]  the [Logger] instance
    def logger=(logger)
      @logger = logger
      @device.logger = logger if @device
    end

    # 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
    def close
      logger.debug "closing Launchpad::Interaction##{object_id}"
      stop
      @device.close
    end

    # Determines whether this interaction's device has been closed.
    def closed?
      @device.closed?
    end

    # Starts interacting with the launchpad. Resets the device when
    # the interaction was properly stopped via stop or close.
    #
    # Optional options hash:
    #
    # [<tt>:detached</tt>]  <tt>true/false</tt>,
    #                       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
    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 |action|
              if @use_threads
                action_thread = Thread.new(action) do |act|
                  respond_to_action(act)
                end
                @action_threads.add(action_thread)
              else
                respond_to_action(action)
              end
            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}"
{'fraction_non_alphanumeric': 0.09457794208256315, 'fraction_numerical': 0.002259190798932019, 'mean_word_length': 2.2452515828057313, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 60, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6117318', 'n_tokens_mistral': 2739, 'n_tokens_neox': 2597, 'n_words': 828}