xi-lang 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +11 -0
 - data/.travis.yml +5 -0
 - data/CODE_OF_CONDUCT.md +74 -0
 - data/Gemfile +4 -0
 - data/Guardfile +5 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +70 -0
 - data/Rakefile +10 -0
 - data/bin/xi +8 -0
 - data/lib/xi/clock.rb +81 -0
 - data/lib/xi/core_ext/enumerable.rb +25 -0
 - data/lib/xi/core_ext/fixnum.rb +11 -0
 - data/lib/xi/core_ext/numeric.rb +34 -0
 - data/lib/xi/core_ext/simple.rb +15 -0
 - data/lib/xi/core_ext.rb +4 -0
 - data/lib/xi/error_log.rb +45 -0
 - data/lib/xi/event.rb +41 -0
 - data/lib/xi/pattern/generators.rb +141 -0
 - data/lib/xi/pattern/transforms.rb +302 -0
 - data/lib/xi/pattern.rb +150 -0
 - data/lib/xi/repl.rb +66 -0
 - data/lib/xi/stream.rb +203 -0
 - data/lib/xi/version.rb +3 -0
 - data/lib/xi.rb +26 -0
 - data/xi.gemspec +33 -0
 - metadata +170 -0
 
| 
         @@ -0,0 +1,302 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Xi
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Pattern
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Transforms
         
     | 
| 
      
 4 
     | 
    
         
            +
                  # Negates every number in the pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                  #
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Non-numeric values are ignored.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 9 
     | 
    
         
            +
                  #   peek -[10, 20, 30].p    #=> [-10, -20, -30]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  #   peek -[1, -2, 3].p      #=> [-1, 2, -3]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def -@
         
     | 
| 
      
 15 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 16 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:-@) ? -v : v) }
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  # Concatenate +object+ pattern or perform a scalar sum with +object+
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # If +object+ is a Pattern, concatenate the two patterns.
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # Else, for each value from pattern, sum with +object+.
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # Values that do not respond to #+ are ignored.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @example Concatenation of patterns
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #   peek [1, 2, 3].p + [4, 5, 6].p    #=> [1, 2, 3, 4, 5, 6]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @example Scalar sum
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #   peek [1, 2, 3].p + 60             #=> [61, 62, 63]
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #   peek [0.25, 0.5].p + 0.125        #=> [0.375, 0.625]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  #   peek [0, :foo, 2].p + 1           #=> [1, :foo, 3]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # @param object [Pattern, Numeric] pattern or numeric
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def +(object)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if object.is_a?(Pattern)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      Pattern.new(self, size: size + object.size) do |y|
         
     | 
| 
      
 40 
     | 
    
         
            +
                        each { |v| y << v }
         
     | 
| 
      
 41 
     | 
    
         
            +
                        object.each { |v| y << v }
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    else
         
     | 
| 
      
 44 
     | 
    
         
            +
                      Pattern.new(self) do |y|
         
     | 
| 
      
 45 
     | 
    
         
            +
                        each { |v| y << (v.respond_to?(:+) ? v + object : v) }
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  # Performs a scalar substraction with +numeric+
         
     | 
| 
      
 51 
     | 
    
         
            +
                  #
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # For each value from pattern, substract with +numeric+.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # Values that do not respond to #- are ignored.
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 56 
     | 
    
         
            +
                  #   peek [1, 2, 3].p - 10       #=> [-9, -8, -7]
         
     | 
| 
      
 57 
     | 
    
         
            +
                  #   peek [1, :foo, 3].p - 10    #=> [-9, :foo, -7]
         
     | 
| 
      
 58 
     | 
    
         
            +
                  #
         
     | 
| 
      
 59 
     | 
    
         
            +
                  # @param numeric [Numeric]
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  #
         
     | 
| 
      
 62 
     | 
    
         
            +
                  def -(numeric)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 64 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:-) ? v - numeric : v) }
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  # Performs a scalar multiplication with +numeric+
         
     | 
| 
      
 69 
     | 
    
         
            +
                  #
         
     | 
| 
      
 70 
     | 
    
         
            +
                  # For each value from pattern, multiplicate with +numeric+.
         
     | 
| 
      
 71 
     | 
    
         
            +
                  # Values that do not respond to #* are ignored.
         
     | 
| 
      
 72 
     | 
    
         
            +
                  #
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 74 
     | 
    
         
            +
                  #   peek [1, 2, 4].p * 2      #=> [2, 4, 8]
         
     | 
| 
      
 75 
     | 
    
         
            +
                  #   peek [1, :foo].p * 2      #=> [2, :foo]
         
     | 
| 
      
 76 
     | 
    
         
            +
                  #
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # @param numeric [Numeric]
         
     | 
| 
      
 78 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 79 
     | 
    
         
            +
                  #
         
     | 
| 
      
 80 
     | 
    
         
            +
                  def *(numeric)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 82 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:*) ? v * numeric : v) }
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  # Performs a scalar division by +numeric+
         
     | 
| 
      
 87 
     | 
    
         
            +
                  #
         
     | 
| 
      
 88 
     | 
    
         
            +
                  # For each value from pattern, divide by +numeric+.
         
     | 
| 
      
 89 
     | 
    
         
            +
                  # Values that do not respond to #/ are ignored.
         
     | 
| 
      
 90 
     | 
    
         
            +
                  #
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 92 
     | 
    
         
            +
                  #   peek [1, 2, 4].p / 2      #=> [(1/2), (1/1), (2/1)]
         
     | 
| 
      
 93 
     | 
    
         
            +
                  #   peek [0.5, :foo].p / 2    #=> [0.25, :foo]
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #
         
     | 
| 
      
 95 
     | 
    
         
            +
                  # @param numeric [Numeric]
         
     | 
| 
      
 96 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 97 
     | 
    
         
            +
                  #
         
     | 
| 
      
 98 
     | 
    
         
            +
                  def /(numeric)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 100 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:/) ? v / numeric : v) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                    end
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  # Performs a scalar modulo against +numeric+
         
     | 
| 
      
 105 
     | 
    
         
            +
                  #
         
     | 
| 
      
 106 
     | 
    
         
            +
                  # For each value from pattern, return modulo of value divided by +numeric+.
         
     | 
| 
      
 107 
     | 
    
         
            +
                  # Values from pattern that do not respond to #% are ignored.
         
     | 
| 
      
 108 
     | 
    
         
            +
                  #
         
     | 
| 
      
 109 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 110 
     | 
    
         
            +
                  #   peek (1..5).p % 2                     #=> [1, 0, 1, 0, 1]
         
     | 
| 
      
 111 
     | 
    
         
            +
                  #   peek [0, 1, 2, :bar, 4, 5, 6].p % 3   #=> [0, 1, 2, :bar, l, 2, 0]
         
     | 
| 
      
 112 
     | 
    
         
            +
                  #
         
     | 
| 
      
 113 
     | 
    
         
            +
                  # @param numeric [Numeric]
         
     | 
| 
      
 114 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 115 
     | 
    
         
            +
                  #
         
     | 
| 
      
 116 
     | 
    
         
            +
                  def %(numeric)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 118 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:%) ? v % numeric : v) }
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  # Raises each value to the power of +numeric+, which may be negative or
         
     | 
| 
      
 123 
     | 
    
         
            +
                  # fractional.
         
     | 
| 
      
 124 
     | 
    
         
            +
                  #
         
     | 
| 
      
 125 
     | 
    
         
            +
                  # Values from pattern that do not respond to #** are ignored.
         
     | 
| 
      
 126 
     | 
    
         
            +
                  #
         
     | 
| 
      
 127 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 128 
     | 
    
         
            +
                  #   peek (0..5).p ** 2        #=> [0, 1, 4, 9, 16, 25]
         
     | 
| 
      
 129 
     | 
    
         
            +
                  #   peek [1, 2, 3].p ** -2    #=> [1, (1/4), (1/9)]
         
     | 
| 
      
 130 
     | 
    
         
            +
                  #
         
     | 
| 
      
 131 
     | 
    
         
            +
                  # @param numeric [Numeric]
         
     | 
| 
      
 132 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 133 
     | 
    
         
            +
                  #
         
     | 
| 
      
 134 
     | 
    
         
            +
                  def **(numeric)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 136 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:**) ? v ** numeric : v) }
         
     | 
| 
      
 137 
     | 
    
         
            +
                    end
         
     | 
| 
      
 138 
     | 
    
         
            +
                  end
         
     | 
| 
      
 139 
     | 
    
         
            +
                  alias_method :^, :**
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  # Cycles pattern +repeats+ number of times, shifted by +offset+
         
     | 
| 
      
 142 
     | 
    
         
            +
                  #
         
     | 
| 
      
 143 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 144 
     | 
    
         
            +
                  #   peek [1, 2, 3].p.seq              #=> [1, 2, 3]
         
     | 
| 
      
 145 
     | 
    
         
            +
                  #   peek [1, 2, 3].p.seq(2)           #=> [1, 2, 3, 1, 2, 3]
         
     | 
| 
      
 146 
     | 
    
         
            +
                  #   peek [1, 2, 3].p.seq(1, 1)        #=> [2, 3, 1]
         
     | 
| 
      
 147 
     | 
    
         
            +
                  #   peek [1, 2, 3].p.seq(2, 2)        #=> [3, 2, 1, 3, 2, 1]
         
     | 
| 
      
 148 
     | 
    
         
            +
                  #   peek [1, 2].p.seq(inf, 1)         #=> [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
         
     | 
| 
      
 149 
     | 
    
         
            +
                  #
         
     | 
| 
      
 150 
     | 
    
         
            +
                  # @param repeats [Fixnum, Symbol] number or inf (defaut: 1)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  # @param offset [Fixnum] (default: 0)
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 153 
     | 
    
         
            +
                  #
         
     | 
| 
      
 154 
     | 
    
         
            +
                  def seq(repeats=1, offset=0)
         
     | 
| 
      
 155 
     | 
    
         
            +
                    unless (repeats.is_a?(Fixnum) && repeats >= 0) || repeats == inf
         
     | 
| 
      
 156 
     | 
    
         
            +
                      fail ArgumentError, "repeats must be a non-negative Fixnum or inf"
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    unless offset.is_a?(Fixnum) && offset >= 0
         
     | 
| 
      
 159 
     | 
    
         
            +
                      fail ArgumentError, "offset must be a non-negative Fixnum"
         
     | 
| 
      
 160 
     | 
    
         
            +
                    end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                    Pattern.new(self, size: size * repeats) do |y|
         
     | 
| 
      
 163 
     | 
    
         
            +
                      rep = repeats
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                      loop do
         
     | 
| 
      
 166 
     | 
    
         
            +
                        if rep != inf
         
     | 
| 
      
 167 
     | 
    
         
            +
                          rep -= 1
         
     | 
| 
      
 168 
     | 
    
         
            +
                          break if rep < 0
         
     | 
| 
      
 169 
     | 
    
         
            +
                        end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                        c = offset
         
     | 
| 
      
 172 
     | 
    
         
            +
                        offset_items = []
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                        is_empty = true
         
     | 
| 
      
 175 
     | 
    
         
            +
                        each do |v|
         
     | 
| 
      
 176 
     | 
    
         
            +
                          is_empty = false
         
     | 
| 
      
 177 
     | 
    
         
            +
                          if c > 0
         
     | 
| 
      
 178 
     | 
    
         
            +
                            offset_items << v
         
     | 
| 
      
 179 
     | 
    
         
            +
                            c -= 1
         
     | 
| 
      
 180 
     | 
    
         
            +
                          else
         
     | 
| 
      
 181 
     | 
    
         
            +
                            y << v
         
     | 
| 
      
 182 
     | 
    
         
            +
                          end
         
     | 
| 
      
 183 
     | 
    
         
            +
                        end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                        offset_items.each { |v| y << v }
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                        break if is_empty
         
     | 
| 
      
 188 
     | 
    
         
            +
                      end
         
     | 
| 
      
 189 
     | 
    
         
            +
                    end
         
     | 
| 
      
 190 
     | 
    
         
            +
                  end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                  # Traverses the pattern in order and then in reverse order
         
     | 
| 
      
 193 
     | 
    
         
            +
                  #
         
     | 
| 
      
 194 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 195 
     | 
    
         
            +
                  #   peek (0..3).p.bounce   #=> [0, 1, 2, 3, 3, 2, 1, 0]
         
     | 
| 
      
 196 
     | 
    
         
            +
                  #
         
     | 
| 
      
 197 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 198 
     | 
    
         
            +
                  #
         
     | 
| 
      
 199 
     | 
    
         
            +
                  def bounce
         
     | 
| 
      
 200 
     | 
    
         
            +
                    Pattern.new(self, size: size * 2 - 1) do |y|
         
     | 
| 
      
 201 
     | 
    
         
            +
                      each.with_index { |v, i| y << v if i > 0 }
         
     | 
| 
      
 202 
     | 
    
         
            +
                      reverse_each.with_index { |v, i| y << v if i > 0 }
         
     | 
| 
      
 203 
     | 
    
         
            +
                    end
         
     | 
| 
      
 204 
     | 
    
         
            +
                  end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                  # Normalizes a pattern of values that range from +min+ to +max+ to 0..1
         
     | 
| 
      
 207 
     | 
    
         
            +
                  #
         
     | 
| 
      
 208 
     | 
    
         
            +
                  # Values from pattern that do not respond to #- are ignored.
         
     | 
| 
      
 209 
     | 
    
         
            +
                  #
         
     | 
| 
      
 210 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 211 
     | 
    
         
            +
                  #   peek (1..5).p.normalize(0, 100)
         
     | 
| 
      
 212 
     | 
    
         
            +
                  #     #=> [(1/100), (1/50), (3/100), (1/25), (1/20)]
         
     | 
| 
      
 213 
     | 
    
         
            +
                  #   peek [0, 0x40, 0x80, 0xc0].p.normalize(0, 0x100)
         
     | 
| 
      
 214 
     | 
    
         
            +
                  #     #=> [(0/1), (1/4), (1/2), (3/4)]
         
     | 
| 
      
 215 
     | 
    
         
            +
                  #
         
     | 
| 
      
 216 
     | 
    
         
            +
                  # @param min [Numeric]
         
     | 
| 
      
 217 
     | 
    
         
            +
                  # @param max [Numeric]
         
     | 
| 
      
 218 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 219 
     | 
    
         
            +
                  #
         
     | 
| 
      
 220 
     | 
    
         
            +
                  def normalize(min, max)
         
     | 
| 
      
 221 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 222 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:-) ? (v - min) / (max - min) : v) }
         
     | 
| 
      
 223 
     | 
    
         
            +
                    end
         
     | 
| 
      
 224 
     | 
    
         
            +
                  end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                  # Scales a pattern of normalized values (0..1) to a custom range
         
     | 
| 
      
 227 
     | 
    
         
            +
                  # +min+..+max+
         
     | 
| 
      
 228 
     | 
    
         
            +
                  #
         
     | 
| 
      
 229 
     | 
    
         
            +
                  # This is inverse of {#normalize}
         
     | 
| 
      
 230 
     | 
    
         
            +
                  # Values from pattern that do not respond to #* are ignored.
         
     | 
| 
      
 231 
     | 
    
         
            +
                  #
         
     | 
| 
      
 232 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 233 
     | 
    
         
            +
                  #   peek [0.01, 0.02, 0.03, 0.04, 0.05].p.denormalize(0, 100)
         
     | 
| 
      
 234 
     | 
    
         
            +
                  #     #=> [1.0, 2.0, 3.0, 4.0, 5.0]
         
     | 
| 
      
 235 
     | 
    
         
            +
                  #   peek [0, 0.25, 0.50, 0.75].p.denormalize(0, 0x100)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  #     #=> [0, 64.0, 128.0, 192.0]
         
     | 
| 
      
 237 
     | 
    
         
            +
                  #
         
     | 
| 
      
 238 
     | 
    
         
            +
                  # @param min [Numeric]
         
     | 
| 
      
 239 
     | 
    
         
            +
                  # @param max [Numeric]
         
     | 
| 
      
 240 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 241 
     | 
    
         
            +
                  #
         
     | 
| 
      
 242 
     | 
    
         
            +
                  def denormalize(min, max)
         
     | 
| 
      
 243 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 244 
     | 
    
         
            +
                      each { |v| y << (v.respond_to?(:*) ? (max - min) * v + min : v) }
         
     | 
| 
      
 245 
     | 
    
         
            +
                    end
         
     | 
| 
      
 246 
     | 
    
         
            +
                  end
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                  # Scale from one range of values to another range of values
         
     | 
| 
      
 249 
     | 
    
         
            +
                  #
         
     | 
| 
      
 250 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 251 
     | 
    
         
            +
                  #   peek [0,2,4,1,3,6].p.scale(0, 6, 0, 0x7f)
         
     | 
| 
      
 252 
     | 
    
         
            +
                  #
         
     | 
| 
      
 253 
     | 
    
         
            +
                  # @param min_from [Numeric]
         
     | 
| 
      
 254 
     | 
    
         
            +
                  # @param max_from [Numeric]
         
     | 
| 
      
 255 
     | 
    
         
            +
                  # @param min_to [Numeric]
         
     | 
| 
      
 256 
     | 
    
         
            +
                  # @param max_to [Numeric]
         
     | 
| 
      
 257 
     | 
    
         
            +
                  # @return [Pattern]
         
     | 
| 
      
 258 
     | 
    
         
            +
                  #
         
     | 
| 
      
 259 
     | 
    
         
            +
                  def scale(min_from, max_from, min_to, max_to)
         
     | 
| 
      
 260 
     | 
    
         
            +
                    normalize(min_from, max_from).denormalize(min_to, max_to)
         
     | 
| 
      
 261 
     | 
    
         
            +
                  end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                  # TODO Document
         
     | 
| 
      
 264 
     | 
    
         
            +
                  def decelerate(num)
         
     | 
| 
      
 265 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 266 
     | 
    
         
            +
                      each_event { |e| y << E[e.value, e.start * num, e.duration * num] }
         
     | 
| 
      
 267 
     | 
    
         
            +
                    end
         
     | 
| 
      
 268 
     | 
    
         
            +
                  end
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                  # TODO Document
         
     | 
| 
      
 271 
     | 
    
         
            +
                  def accelerate(num)
         
     | 
| 
      
 272 
     | 
    
         
            +
                    Pattern.new(self) do |y|
         
     | 
| 
      
 273 
     | 
    
         
            +
                      each_event { |e| y << E[e.value, e.start / num, e.duration / num] }
         
     | 
| 
      
 274 
     | 
    
         
            +
                    end
         
     | 
| 
      
 275 
     | 
    
         
            +
                  end
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
                  # Based on +probability+, it yields original value or nil
         
     | 
| 
      
 278 
     | 
    
         
            +
                  # TODO Document
         
     | 
| 
      
 279 
     | 
    
         
            +
                  #
         
     | 
| 
      
 280 
     | 
    
         
            +
                  def sometimes(probability=0.5)
         
     | 
| 
      
 281 
     | 
    
         
            +
                    prob_pat = probability.p
         
     | 
| 
      
 282 
     | 
    
         
            +
                    Pattern.new(self, size: size * prob_pat.size) do |y|
         
     | 
| 
      
 283 
     | 
    
         
            +
                      prob_pat.each do |prob|
         
     | 
| 
      
 284 
     | 
    
         
            +
                        each { |v| y << (rand < prob ? v : nil) }
         
     | 
| 
      
 285 
     | 
    
         
            +
                      end
         
     | 
| 
      
 286 
     | 
    
         
            +
                    end
         
     | 
| 
      
 287 
     | 
    
         
            +
                  end
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                  # Repeats each value +times+
         
     | 
| 
      
 290 
     | 
    
         
            +
                  # TODO Document
         
     | 
| 
      
 291 
     | 
    
         
            +
                  #
         
     | 
| 
      
 292 
     | 
    
         
            +
                  def repeat_each(times)
         
     | 
| 
      
 293 
     | 
    
         
            +
                    times_pat = times.p
         
     | 
| 
      
 294 
     | 
    
         
            +
                    Pattern.new(self, size: size * times_pat.size) do |y|
         
     | 
| 
      
 295 
     | 
    
         
            +
                      times_pat.each do |t|
         
     | 
| 
      
 296 
     | 
    
         
            +
                        each { |v| t.times { y << v } }
         
     | 
| 
      
 297 
     | 
    
         
            +
                      end
         
     | 
| 
      
 298 
     | 
    
         
            +
                    end
         
     | 
| 
      
 299 
     | 
    
         
            +
                  end
         
     | 
| 
      
 300 
     | 
    
         
            +
                end
         
     | 
| 
      
 301 
     | 
    
         
            +
              end
         
     | 
| 
      
 302 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/xi/pattern.rb
    ADDED
    
    | 
         @@ -0,0 +1,150 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'forwardable'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'xi/event'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'xi/pattern/transforms'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'xi/pattern/generators'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Xi
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Pattern
         
     | 
| 
      
 8 
     | 
    
         
            +
                include Enumerable
         
     | 
| 
      
 9 
     | 
    
         
            +
                include Transforms
         
     | 
| 
      
 10 
     | 
    
         
            +
                include Generators
         
     | 
| 
      
 11 
     | 
    
         
            +
                extend  Forwardable
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :source, :event_duration, :metadata, :total_duration
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                alias_method :dur, :event_duration
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def_delegators :@source, :size
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def initialize(enum=nil, size: nil, **metadata)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  size ||= enum.size if enum.respond_to?(:size)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  @source = if block_given?
         
     | 
| 
      
 23 
     | 
    
         
            +
                    Enumerator.new(size) { |y| yield y }
         
     | 
| 
      
 24 
     | 
    
         
            +
                  elsif enum
         
     | 
| 
      
 25 
     | 
    
         
            +
                    enum
         
     | 
| 
      
 26 
     | 
    
         
            +
                  else
         
     | 
| 
      
 27 
     | 
    
         
            +
                    fail ArgumentError, 'must provide source or block'
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  @is_infinite = @source.size.nil? || @source.size == Float::INFINITY
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  @event_duration = metadata.delete(:dur) || metadata.delete(:event_duration)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @event_duration ||= enum.event_duration if enum.respond_to?(:event_duration)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @event_duration ||= 1
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  @metadata = enum.respond_to?(:metadata) ? enum.metadata : {}
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @metadata.merge!(metadata)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  if @is_infinite
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @total_duration = @event_duration
         
     | 
| 
      
 41 
     | 
    
         
            +
                  else
         
     | 
| 
      
 42 
     | 
    
         
            +
                    last_ev = each_event.take(@source.size).last
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @total_duration = last_ev ? last_ev.start + last_ev.duration : 0
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def self.[](*args, **metadata)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  new(args, **metadata)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def infinite?
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @is_infinite
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def finite?
         
     | 
| 
      
 56 
     | 
    
         
            +
                  !infinite?
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def ==(o)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  self.class == o.class &&
         
     | 
| 
      
 61 
     | 
    
         
            +
                    source == o.source &&
         
     | 
| 
      
 62 
     | 
    
         
            +
                    event_duration == o.event_duration &&
         
     | 
| 
      
 63 
     | 
    
         
            +
                    metadata == o.metadata
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def p(dur=nil, **metadata)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  Pattern.new(@source, dur: dur || @event_duration,
         
     | 
| 
      
 68 
     | 
    
         
            +
                              **@metadata.merge(metadata))
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def each_event
         
     | 
| 
      
 72 
     | 
    
         
            +
                  return enum_for(__method__) unless block_given?
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  dur = @event_duration
         
     | 
| 
      
 75 
     | 
    
         
            +
                  pos = 0
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  @source.each do |value|
         
     | 
| 
      
 78 
     | 
    
         
            +
                    if value.is_a?(Pattern)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      value.each do |v|
         
     | 
| 
      
 80 
     | 
    
         
            +
                        yield Event.new(v, pos, dur)
         
     | 
| 
      
 81 
     | 
    
         
            +
                        pos += dur
         
     | 
| 
      
 82 
     | 
    
         
            +
                      end
         
     | 
| 
      
 83 
     | 
    
         
            +
                    elsif value.is_a?(Event)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      yield value
         
     | 
| 
      
 85 
     | 
    
         
            +
                      pos += value.duration
         
     | 
| 
      
 86 
     | 
    
         
            +
                    else
         
     | 
| 
      
 87 
     | 
    
         
            +
                      yield Event.new(value, pos, dur)
         
     | 
| 
      
 88 
     | 
    
         
            +
                      pos += dur
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def each
         
     | 
| 
      
 94 
     | 
    
         
            +
                  return enum_for(__method__) unless block_given?
         
     | 
| 
      
 95 
     | 
    
         
            +
                  each_event { |e| yield e.value }
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 99 
     | 
    
         
            +
                  ss = if @source.respond_to?(:join)
         
     | 
| 
      
 100 
     | 
    
         
            +
                         @source.map(&:inspect).join(', ')
         
     | 
| 
      
 101 
     | 
    
         
            +
                       elsif @source.is_a?(Enumerator)
         
     | 
| 
      
 102 
     | 
    
         
            +
                         "?enum"
         
     | 
| 
      
 103 
     | 
    
         
            +
                       else
         
     | 
| 
      
 104 
     | 
    
         
            +
                         @source.inspect
         
     | 
| 
      
 105 
     | 
    
         
            +
                       end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  ms = @metadata.reject { |_, v| v.nil? }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  ms.merge!(dur: dur) if dur != 1
         
     | 
| 
      
 109 
     | 
    
         
            +
                  ms = ms.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  "P[#{ss}#{", #{ms}" unless ms.empty?}]"
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
                alias_method :to_s, :inspect
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                def map_events
         
     | 
| 
      
 116 
     | 
    
         
            +
                  return enum_for(__method__) unless block_given?
         
     | 
| 
      
 117 
     | 
    
         
            +
                  Pattern.new(dur: dur, **metadata) { |y| each_event { |e| y << yield(e) } }
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
                alias_method :collect_events, :map_events
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                def select_events
         
     | 
| 
      
 122 
     | 
    
         
            +
                  return enum_for(__method__) unless block_given?
         
     | 
| 
      
 123 
     | 
    
         
            +
                  Pattern.new { |y| each_event { |e| y << e if yield(e) } }
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
                alias_method :find_all_events, :select_events
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                def reject_events
         
     | 
| 
      
 128 
     | 
    
         
            +
                  return enum_for(__method__) unless block_given?
         
     | 
| 
      
 129 
     | 
    
         
            +
                  Pattern.new { |y| each_event { |e| y << e unless yield(e) } }
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def to_events
         
     | 
| 
      
 133 
     | 
    
         
            +
                  each_event.to_a
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                def peek(limit=10)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  values = take(limit + 1)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  puts "There are more than #{limit} values..." if values.size > limit
         
     | 
| 
      
 139 
     | 
    
         
            +
                  values.take(limit)
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                def peek_events(limit=10)
         
     | 
| 
      
 143 
     | 
    
         
            +
                  events = each_event.take(limit + 1)
         
     | 
| 
      
 144 
     | 
    
         
            +
                  puts "There are more than #{limit} events..." if events.size > limit
         
     | 
| 
      
 145 
     | 
    
         
            +
                  events.take(limit)
         
     | 
| 
      
 146 
     | 
    
         
            +
                end
         
     | 
| 
      
 147 
     | 
    
         
            +
              end
         
     | 
| 
      
 148 
     | 
    
         
            +
            end
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
            P = Xi::Pattern
         
     | 
    
        data/lib/xi/repl.rb
    ADDED
    
    | 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "pry"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'io/console'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "xi/error_log"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Xi
         
     | 
| 
      
 6 
     | 
    
         
            +
              module REPL
         
     | 
| 
      
 7 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                CONFIG_PATH = File.expand_path("~/.config/xi")
         
     | 
| 
      
 10 
     | 
    
         
            +
                HISTORY_FILE = "history"
         
     | 
| 
      
 11 
     | 
    
         
            +
                INIT_SCRIPT_FILE = "init.rb"
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                DEFAULT_INIT_SCRIPT =
         
     | 
| 
      
 14 
     | 
    
         
            +
                  "# Here you can customize or define functions that will be available in\n" \
         
     | 
| 
      
 15 
     | 
    
         
            +
                  "# Xi, e.g. new streams or a custom clock."
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def start
         
     | 
| 
      
 18 
     | 
    
         
            +
                  configure
         
     | 
| 
      
 19 
     | 
    
         
            +
                  load_init_script
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  Pry.start
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def configure
         
     | 
| 
      
 25 
     | 
    
         
            +
                  prepare_config_dir
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  if ENV["INSIDE_EMACS"]
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Pry.config.correct_indent = false
         
     | 
| 
      
 29 
     | 
    
         
            +
                    Pry.config.pager = false
         
     | 
| 
      
 30 
     | 
    
         
            +
                    Pry.config.prompt = [ proc { "" }, proc { "" }]
         
     | 
| 
      
 31 
     | 
    
         
            +
                  else
         
     | 
| 
      
 32 
     | 
    
         
            +
                    Pry.config.prompt = [ proc { "xi> " }, proc { "..> " }]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  Pry.config.history.file = history_path
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  Pry.hooks.add_hook(:after_eval, "check_for_errors") do |result, pry|
         
     | 
| 
      
 38 
     | 
    
         
            +
                    more_errors = ErrorLog.instance.more_errors?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    ErrorLog.instance.each do |msg|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      puts "(╯°□°)╯︵ ɹoɹɹǝ #{msg}"
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                    puts "(⌣_⌣”) There were more errors..." if more_errors
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def load_init_script
         
     | 
| 
      
 47 
     | 
    
         
            +
                  require(init_script_path)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def prepare_config_dir
         
     | 
| 
      
 51 
     | 
    
         
            +
                  FileUtils.mkdir_p(CONFIG_PATH)
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  unless File.exists?(init_script_path)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    File.write(init_script_path, DEFAULT_INIT_SCRIPT)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def history_path
         
     | 
| 
      
 59 
     | 
    
         
            +
                  File.join(CONFIG_PATH, HISTORY_FILE)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def init_script_path
         
     | 
| 
      
 63 
     | 
    
         
            +
                  File.join(CONFIG_PATH, INIT_SCRIPT_FILE)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/xi/stream.rb
    ADDED
    
    | 
         @@ -0,0 +1,203 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xi
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Stream
         
     | 
| 
      
 5 
     | 
    
         
            +
                WINDOW_SEC = 0.05
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :clock, :source, :source_patterns, :state, :event_duration, :gate
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(clock)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @mutex = Mutex.new
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @playing = false
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @state = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @new_sound_object_id = 0
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @changed_params = [].to_set
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  self.clock = clock
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def set(event_duration: nil, gate: nil, **source)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @source = source
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @gate = gate if gate
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @event_duration = event_duration if event_duration
         
     | 
| 
      
 24 
     | 
    
         
            +
                    update_internal_structures
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  play
         
     | 
| 
      
 27 
     | 
    
         
            +
                  self
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
                alias_method :<<, :set
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def event_duration=(new_value)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @event_duration = new_value
         
     | 
| 
      
 34 
     | 
    
         
            +
                    update_internal_structures
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def gate=(new_value)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @gate = new_value
         
     | 
| 
      
 41 
     | 
    
         
            +
                    update_internal_structures
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def clock=(new_clock)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @clock.unsubscribe(self) if @clock
         
     | 
| 
      
 47 
     | 
    
         
            +
                  new_clock.subscribe(self) if playing?
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @clock = new_clock
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def playing?
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @mutex.synchronize { @playing }
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def stopped?
         
     | 
| 
      
 56 
     | 
    
         
            +
                  !playing?
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def play
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @playing = true
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @clock.subscribe(self)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  self
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
                alias_method :start, :play
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @playing = false
         
     | 
| 
      
 71 
     | 
    
         
            +
                    @state.clear
         
     | 
| 
      
 72 
     | 
    
         
            +
                    @clock.unsubscribe(self)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                  self
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
                alias_method :pause, :play
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 79 
     | 
    
         
            +
                  "#<#{self.class.name}:#{"0x%014x" % object_id} clock=#{@clock.inspect} #{playing? ? :playing : :stopped}>"
         
     | 
| 
      
 80 
     | 
    
         
            +
                rescue => err
         
     | 
| 
      
 81 
     | 
    
         
            +
                  logger.error(err)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def notify(now)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  return unless playing? && @source
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @changed_params.clear
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    forward_enums(now) if @must_forward
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    gate_on, gate_off = play_enums(now)
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    do_gate_off_change(gate_off) unless gate_off.empty?
         
     | 
| 
      
 95 
     | 
    
         
            +
                    do_state_change if state_changed?
         
     | 
| 
      
 96 
     | 
    
         
            +
                    do_gate_on_change(gate_on) unless gate_on.empty?
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                private
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                def changed_state
         
     | 
| 
      
 103 
     | 
    
         
            +
                  @state.select { |k, _| @changed_params.include?(k) }
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                def forward_enums(now)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  @enums.each do |p, (enum, total_dur)|
         
     | 
| 
      
 108 
     | 
    
         
            +
                    cur_pos = now % total_dur
         
     | 
| 
      
 109 
     | 
    
         
            +
                    next_ev = enum.peek
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    while distance = (cur_pos - next_ev.start) % total_dur do
         
     | 
| 
      
 112 
     | 
    
         
            +
                      enum.next
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                      break if distance <= next_ev.duration
         
     | 
| 
      
 115 
     | 
    
         
            +
                      next_ev = enum.peek
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
                  @must_forward = false
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                def play_enums(now)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  gate_off = []
         
     | 
| 
      
 123 
     | 
    
         
            +
                  gate_on = []
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  @enums.each do |p, (enum, total_dur)|
         
     | 
| 
      
 126 
     | 
    
         
            +
                    cur_pos = now % total_dur
         
     | 
| 
      
 127 
     | 
    
         
            +
                    next_ev = enum.peek
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                    # Check if there are any currently playing sound objects that
         
     | 
| 
      
 130 
     | 
    
         
            +
                    # must be gated off
         
     | 
| 
      
 131 
     | 
    
         
            +
                    @playing_sound_objects.dup.each do |end_pos, so_ids|
         
     | 
| 
      
 132 
     | 
    
         
            +
                      if (cur_pos - end_pos) % total_dur <= WINDOW_SEC
         
     | 
| 
      
 133 
     | 
    
         
            +
                        gate_off = so_ids
         
     | 
| 
      
 134 
     | 
    
         
            +
                        @playing_sound_objects.delete(end_pos)
         
     | 
| 
      
 135 
     | 
    
         
            +
                      end
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                    # Do we need to play next event now? If not, skip this parameter
         
     | 
| 
      
 139 
     | 
    
         
            +
                    if (cur_pos - next_ev.start) % total_dur <= WINDOW_SEC
         
     | 
| 
      
 140 
     | 
    
         
            +
                      # Update state based on pattern value
         
     | 
| 
      
 141 
     | 
    
         
            +
                      update_state(p, next_ev.value)
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                      # If this parameter is a gate, mark it as gate on as
         
     | 
| 
      
 144 
     | 
    
         
            +
                      # a new sound object
         
     | 
| 
      
 145 
     | 
    
         
            +
                      if p == @gate
         
     | 
| 
      
 146 
     | 
    
         
            +
                        new_so_ids = Array(next_ev.value).size.times.map do
         
     | 
| 
      
 147 
     | 
    
         
            +
                          so_id = @new_sound_object_id
         
     | 
| 
      
 148 
     | 
    
         
            +
                          @new_sound_object_id += 1
         
     | 
| 
      
 149 
     | 
    
         
            +
                          so_id
         
     | 
| 
      
 150 
     | 
    
         
            +
                        end
         
     | 
| 
      
 151 
     | 
    
         
            +
                        gate_on = new_so_ids
         
     | 
| 
      
 152 
     | 
    
         
            +
                        @playing_sound_objects[next_ev.end] = new_so_ids
         
     | 
| 
      
 153 
     | 
    
         
            +
                      end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                      # Because we already processed event, advance enumerator
         
     | 
| 
      
 156 
     | 
    
         
            +
                      enum.next
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                  [gate_on, gate_off]
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def update_internal_structures
         
     | 
| 
      
 164 
     | 
    
         
            +
                  @playing_sound_objects ||= {}
         
     | 
| 
      
 165 
     | 
    
         
            +
                  @must_forward = true
         
     | 
| 
      
 166 
     | 
    
         
            +
                  @enums = @source.map { |k, v|
         
     | 
| 
      
 167 
     | 
    
         
            +
                    pat = v.p(@event_duration)
         
     | 
| 
      
 168 
     | 
    
         
            +
                    [k, [infinite_enum(pat), pat.total_duration]]
         
     | 
| 
      
 169 
     | 
    
         
            +
                  }.to_h
         
     | 
| 
      
 170 
     | 
    
         
            +
                end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                def do_gate_on_change(ss)
         
     | 
| 
      
 173 
     | 
    
         
            +
                  logger.info "Gate on change: #{ss}"
         
     | 
| 
      
 174 
     | 
    
         
            +
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def do_gate_off_change(ss)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  logger.info "Gate off change: #{ss}"
         
     | 
| 
      
 178 
     | 
    
         
            +
                end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                def do_state_change
         
     | 
| 
      
 181 
     | 
    
         
            +
                  logger.info "State change: #{@state.select { |k, v| @changed_params.include?(k) }.to_h}"
         
     | 
| 
      
 182 
     | 
    
         
            +
                end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                def update_state(p, v)
         
     | 
| 
      
 185 
     | 
    
         
            +
                  logger.debug "Update state of :#{p}: #{v}"
         
     | 
| 
      
 186 
     | 
    
         
            +
                  @changed_params << p if v != @state[p]
         
     | 
| 
      
 187 
     | 
    
         
            +
                  @state[p] = v
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                def state_changed?
         
     | 
| 
      
 191 
     | 
    
         
            +
                  !@changed_params.empty?
         
     | 
| 
      
 192 
     | 
    
         
            +
                end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                def infinite_enum(p)
         
     | 
| 
      
 195 
     | 
    
         
            +
                  Enumerator.new { |y| loop { p.each_event { |e| y << e } } }
         
     | 
| 
      
 196 
     | 
    
         
            +
                end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 199 
     | 
    
         
            +
                  # FIXME this should be configurable
         
     | 
| 
      
 200 
     | 
    
         
            +
                  @logger ||= Logger.new("/tmp/xi.log")
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
              end
         
     | 
| 
      
 203 
     | 
    
         
            +
            end
         
     |