require 'forwardable' require 'xi/event' require 'xi/pattern/transforms' require 'xi/pattern/generators' module Xi class Pattern include Enumerable include Transforms include Generators extend Forwardable attr_reader :source, :event_duration, :metadata, :total_duration alias_method :dur, :event_duration def_delegators :@source, :size def initialize(source=nil, size: nil, **metadata) if source.nil? && !block_given? fail ArgumentError, 'must provide source or block' end size ||= source.size if source.respond_to?(:size) @source = if block_given? Enumerator.new(size) { |y| yield y } else source end @is_infinite = @source.size.nil? || @source.size == Float::INFINITY @event_duration = metadata.delete(:dur) || metadata.delete(:event_duration) @event_duration ||= source.event_duration if source.respond_to?(:event_duration) @event_duration ||= 1 @metadata = source.respond_to?(:metadata) ? source.metadata : {} @metadata.merge!(metadata) if @is_infinite @total_duration = @event_duration else last_ev = each_event.take(@source.size).last @total_duration = last_ev ? last_ev.start + last_ev.duration : 0 end end def self.[](*args, **metadata) new(args, **metadata) end def infinite? @is_infinite end def finite? !infinite? end def ==(o) self.class == o.class && source == o.source && event_duration == o.event_duration && metadata == o.metadata end def p(dur=nil, **metadata) Pattern.new(@source, dur: dur || @event_duration, size: size, **@metadata.merge(metadata)) end def each_event return enum_for(__method__) unless block_given? dur = @event_duration pos = 0 @source.each do |value| if value.is_a?(Pattern) value.each do |v| yield Event.new(v, pos, dur) pos += dur end elsif value.is_a?(Event) yield value pos += value.duration else yield Event.new(value, pos, dur) pos += dur end end end def each return enum_for(__method__) unless block_given? each_event { |e| yield e.value } end def inspect ss = if @source.respond_to?(:join) @source.map(&:inspect).join(', ') elsif @source.is_a?(Enumerator) "?enum" else @source.inspect end ms = @metadata.reject { |_, v| v.nil? } ms.merge!(dur: dur) if dur != 1 ms = ms.map { |k, v| "#{k}: #{v.inspect}" }.join(', ') "P[#{ss}#{", #{ms}" unless ms.empty?}]" end alias_method :to_s, :inspect def map_events return enum_for(__method__) unless block_given? Pattern.new(self) { |y| each_event { |e| y << yield(e) } } end alias_method :collect_events, :map_events def select_events return enum_for(__method__) unless block_given? Pattern.new(self) { |y| each_event { |e| y << e if yield(e) } } end alias_method :find_all_events, :select_events def reject_events return enum_for(__method__) unless block_given? Pattern.new(self) { |y| each_event { |e| y << e unless yield(e) } } end def to_events each_event.to_a end def peek(limit=10) values = take(limit + 1) puts "There are more than #{limit} values..." if values.size > limit values.take(limit) end def peek_events(limit=10) events = each_event.take(limit + 1) puts "There are more than #{limit} events..." if events.size > limit events.take(limit) end end end P = Xi::Pattern