tef-animation 0.1.0 → 0.1.1

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.
@@ -4,7 +4,17 @@ $coordinate_def ||= {
4
4
  y: 1,
5
5
  }
6
6
 
7
+ # TheElectricFursuits module.
8
+ # @see https://github.com/TheElectricFursuits
7
9
  module TEF
10
+ # Animation-Related Module
11
+ #
12
+ # This module wraps all classes related to TEF 'Synth'-Line animation.
13
+ # They are meant to provide an abstraction layer over the hardware-implemented
14
+ # animations that run on slave devices, such as the FurComs-Connected Synth Bit,
15
+ # and give the user full access to high-level functions such as configuring
16
+ # named parameters, setting up value smoothing and transitions, and
17
+ # creating and deleting objects.
8
18
  module Animation
9
19
  class Coordinate
10
20
  $coordinate_def.each do |c, id|
@@ -1,6 +1,8 @@
1
1
 
2
2
  require_relative 'Animatable.rb'
3
3
 
4
+ # TheElectricFursuits module.
5
+ # @see https://github.com/TheElectricFursuits
4
6
  module TEF
5
7
  module Animation
6
8
  class Eye < Animatable
@@ -1,14 +1,64 @@
1
1
 
2
-
2
+ # TheElectricFursuits module.
3
+ # @see https://github.com/TheElectricFursuits
3
4
  module TEF
5
+ # Animation-Related Module
6
+ #
7
+ # This module wraps all classes related to TEF 'Synth'-Line animation.
8
+ # They are meant to provide an abstraction layer over the hardware-implemented
9
+ # animations that run on slave devices, such as the FurComs-Connected Synth Bit,
10
+ # and give the user full access to high-level functions such as configuring
11
+ # named parameters, setting up value smoothing and transitions, and
12
+ # creating and deleting objects.
4
13
  module Animation
5
14
  class Value
6
15
  PARAM_TYPES = [:add, :multiply, :dampen, :delay, :from, :jump, :velocity];
7
16
 
17
+ # @return [Integer] Hardware-Number of this Value
8
18
  attr_reader :ID
9
19
 
20
+ # @return [String, nil] Module-ID of the {Animatable} that this
21
+ # Value belongs to.
10
22
  attr_reader :module_id
11
23
 
24
+ # @!attribute [rw] add
25
+ # @return [Numeric] The add-offset to apply to this Value.
26
+ # Can be used either to set the absolute target (with {#from} being
27
+ # nil), or to specify an offset of the number grabbed from {#from}.
28
+
29
+ # @!attribute [rw] multiply
30
+ # @return [Numeric] Multiplication factor. Will be applied to
31
+ # ({#from} + {#add}) * {#multiply}. 0 means no multiplication.
32
+
33
+ # @!attribute [rw] dampen
34
+ # @return [Numeric] dampening factor. Causes the actual output value
35
+ # to smoothly transition to the target value ((from + add) * multiply).
36
+ # Larger values take longer (higher dampening)
37
+
38
+ # @!attribute [rw] delay
39
+ # @return [Numeric] delay factor. If it and {#dampen} are nonzero,
40
+ # will cause the actual output to oscillate slightly. Also
41
+ # causes {#velocity} to have an effect. Higher delays cause
42
+ # slower transitions, and may need more {#dampen}ing to mitigate
43
+ # overshoot.
44
+
45
+ # @!attribute [rw] from
46
+ # @return [String,nil] If not set to nil, defines the other {Value}
47
+ # that this Value will take the output from. Allows the user
48
+ # to follow some other parameter and create interesting linkages.
49
+
50
+ # @!attribute [w] jump
51
+ # @return [Numeric] Instantly jump to the given number, skipping
52
+ # animation and smoothing. Will not reconfigure the actual target,
53
+ # and can thusly be used to temporarily "bump" the value.
54
+
55
+ # @!attribute [w] velocity
56
+ # @return [Numeric] Set the velocity of the value. Only has an
57
+ # effect if {#dampen} and {#delay} are nonzero.
58
+
59
+ # Initialize a new Value.
60
+ # @param [Integer] value_num The hardware ID of this Value.
61
+ # must match the ID defined in the animation slaves.
12
62
  def initialize(value_num)
13
63
  @ID = value_num;
14
64
 
@@ -26,11 +76,22 @@ module TEF
26
76
  end
27
77
  end
28
78
 
79
+ # @return [String] Total ID of this Value, in the form
80
+ # 'SxxMxxVxx'
29
81
  def total_id()
30
82
  "#{@module_id}V#{@ID.to_s(16)}"
31
83
  end
32
84
 
85
+ # Internal function to set any of the Value's parameters.
86
+ #
87
+ # This can be called by the user, but it is preferrable to use
88
+ # {#configure} or the matching parameter setter functions.
33
89
  def generic_set(key, value)
90
+ if key == :from
91
+ self.from = value
92
+ return
93
+ end
94
+
34
95
  raise ArgumentError, 'Key does not exist!' unless PARAM_TYPES.include? key
35
96
  raise ArgumentError, "Input must be numeric!" unless value.is_a? Numeric
36
97
 
@@ -54,6 +115,13 @@ module TEF
54
115
  end
55
116
  end
56
117
 
118
+ # Configure the Value with a Hash.
119
+ #
120
+ # This lets the user configure the Value by passing a Hash.
121
+ # The data will be passed into the six attributes of this Value
122
+ # according to their key.
123
+ # @example
124
+ # a_color.configure({ add: 2, dampen: 10 })
57
125
  def configure(data)
58
126
  if data.is_a? Numeric
59
127
  self.add = data
@@ -92,10 +160,16 @@ module TEF
92
160
  return !@changes.empty?
93
161
  end
94
162
 
163
+ # Internal function to strip trailing zeroes for floats
95
164
  private def rcut(value)
96
165
  value.to_s.gsub(/(\.)0+$/, '')
97
166
  end
98
167
 
168
+ # @private
169
+ # Internal function to retrieve the list of changes for this Value.
170
+ # @note Do not call this as user unless you know what you are doing!
171
+ # This will delete the retrieved changes, which may cause loss of
172
+ # data if they are not properly sent to the animation slaves!
99
173
  def set_string()
100
174
  return nil unless has_changes?
101
175
 
@@ -1,13 +1,39 @@
1
1
 
2
2
  require_relative 'Stack.rb'
3
3
 
4
+ # TheElectricFursuits module.
5
+ # @see https://github.com/TheElectricFursuits
4
6
  module TEF
5
7
  module ParameterStack
8
+ # Override class.
9
+ #
10
+ # This class represents one 'access point' for the software,
11
+ # with which it can override certain values of the {Stack}
12
+ # it belongs to.
13
+ #
14
+ # Using it is as simple as using a Hash:
15
+ # @example
16
+ # override = Override.new(central_stack, 10);
17
+ # override['SomeParam'] = 5
18
+ # override['SomeParam'] = nil # Relinquish control over the value.
6
19
  class Override
7
20
  include Comparable
8
21
 
22
+ # @return [Numeric] Level of this Override.
23
+ # Higher levels mean higher priority. The highest-level
24
+ # override that is active for any given key will determine
25
+ # that key's value!
9
26
  attr_reader :level
10
27
 
28
+ # Initialize an Override.
29
+ #
30
+ # Values can be written into the Override immediately after
31
+ # creation. When the Override is no longer used,
32
+ # make sure to call {#destroy!}
33
+ #
34
+ # @param [Stack] stack The stack to write to.
35
+ # @param [Numeric] init_level Level of this Override.
36
+ # Can not be changed, is always the same for all keys!
11
37
  def initialize(stack, init_level = 0)
12
38
  raise ArgumentError, 'Handler must be CoreStack!' unless stack.is_a? Stack
13
39
 
@@ -21,10 +47,17 @@ module TEF
21
47
  @stack.add_override self
22
48
  end
23
49
 
50
+ # @return The value of this Override for a given key.
51
+ # @note This will not be the global key. Use {Stack#[]} to retrieve
52
+ # the currently active key.
24
53
  def [](key)
25
54
  @overrides[key]
26
55
  end
27
56
 
57
+ # Set the value for the given key.
58
+ # Setting nil as value causes this Override to relinquish control
59
+ # @param key Hash-Key
60
+ # @param new_value User-defined value. If nil, relinquishes control.
28
61
  def []=(key, new_value)
29
62
  return if @overrides[key] == new_value
30
63
 
@@ -37,13 +70,18 @@ module TEF
37
70
  @stack.override_claims self, key
38
71
  end
39
72
 
73
+ # @return [true,false] Whether this Override includes a given key
40
74
  def include?(key)
41
75
  @overrides.include? key
42
76
  end
77
+
78
+ # @return [Array] List of keys
43
79
  def keys
44
80
  @overrides.keys
45
81
  end
46
82
 
83
+ # Delete a value for the given key.
84
+ # Equivalent to calling {#[]=} nil
47
85
  def delete(key)
48
86
  return unless @overrides.include? key
49
87
  @overrides.delete key
@@ -53,6 +91,15 @@ module TEF
53
91
  @stack.recompute_single key
54
92
  end
55
93
 
94
+ # Destroy this Override.
95
+ #
96
+ # This function MUST be called if the user no longer
97
+ # needs this object. It relinquishes control over all the
98
+ # values that this Override formerly held.
99
+ #
100
+ # Note that nothing else is modified, and
101
+ # {Stack#add_override} could be used to re-add
102
+ # this Override.
56
103
  def destroy!
57
104
  @stack.remove_override self
58
105
  end
@@ -1,10 +1,29 @@
1
1
 
2
2
  require_relative 'Override.rb'
3
3
 
4
+ # TheElectricFursuits module.
5
+ # @see https://github.com/TheElectricFursuits
4
6
  module TEF
7
+ # Module for the ParameterStack TEF code.
8
+ #
9
+ # The ParameterStack system is a module designed to let multiple different
10
+ # subsystems of code interact and configure certain core parameters.
11
+ # Each subystem is assigned its own {ParameterStack::Override}, and can
12
+ # seamlessly take and relinquish control over parameters at any time.
5
13
  module ParameterStack
14
+ # Parameter Stack class.
15
+ #
16
+ # This class contains all parameters that have been configured by
17
+ # {Override}s. It provides a way to retrieve the currently valid
18
+ # coniguration, as well as letting the user (de)regsiter new {Override}s.
6
19
  class Stack
20
+
21
+ # @return [Hash] Hash whose keys specify which parameters have been
22
+ # changed since the last recompute cycle.
7
23
  attr_reader :changes
24
+
25
+ # @return [Hash] Hash of the overrides that have control over
26
+ # a given parameter key.
8
27
  attr_reader :active_overrides
9
28
 
10
29
  def initialize()
@@ -22,16 +41,32 @@ module TEF
22
41
  @default_override = Override.new(self, -1);
23
42
  end
24
43
 
44
+ # @return Returns the currently configured parameter for the given key.
25
45
  def [](key)
26
46
  @current_values[key]
27
47
  end
48
+
49
+ # Set the default parameter for a given key.
50
+ #
51
+ # Default parameters have a level of -1, meaning that any
52
+ # {Override} with priority >= 0 (the default) will claim the value.
28
53
  def []=(key, value)
29
54
  @default_override[key] = value
30
55
  end
56
+
31
57
  def keys
32
58
  @current_values.keys
33
59
  end
34
60
 
61
+ # Add a callback for immediate parameter changes.
62
+ #
63
+ # The given block will be called for any change of a parameter,
64
+ # so should be used sparingly. Recursive parameter setting should also
65
+ # be carefully avoided!
66
+ #
67
+ # @param [Array] keys Key whitelist to trigger on.
68
+ # @yieldparam key Key that was changed.
69
+ # @yieldparam value New value of the key
35
70
  def on_recompute(*keys, &block)
36
71
  @recompute_blocks << {
37
72
  block: block,
@@ -39,6 +74,16 @@ module TEF
39
74
  }
40
75
  end
41
76
 
77
+ # Add a callback to be called during {#process_changes}.
78
+ #
79
+ # The given block will be called if a change in a key specified by
80
+ # the filters occoured, and will be called during {#process_changes}.
81
+ #
82
+ # This lets the user only act on changes, and act on them in bunches,
83
+ # rather than on every individual change.
84
+ #
85
+ # @param [String, Regexp] filters List of filters to use. Acts like
86
+ # a whitelist, can be a Regexp
42
87
  def on_change(*filters, &block)
43
88
  filters = nil if filters.empty?
44
89
 
@@ -57,6 +102,11 @@ module TEF
57
102
  end
58
103
  end
59
104
 
105
+ # Check if an {Override} can claim a key.
106
+ #
107
+ # This is mainly an internal function. It will check if the passed
108
+ # {Override} has permission to claim the given key, and will
109
+ # change the value accordingly.
60
110
  def override_claims(override, key)
61
111
  return if !(@active_overrides[key].nil?) && (@active_overrides[key] > override)
62
112
 
@@ -69,6 +119,12 @@ module TEF
69
119
  mark_key_change key
70
120
  end
71
121
 
122
+ # Re-Calculate which {Override} is currently claiming a given key.
123
+ #
124
+ # This method is mainly for internal work. It will iterate through
125
+ # the list of {Overrides} to find the next in line that wants
126
+ # to claim the given key.
127
+ # May be slow!
72
128
  def recompute_single(key)
73
129
  old_value = @current_values[key]
74
130
 
@@ -95,6 +151,9 @@ module TEF
95
151
  mark_key_change key if old_value != @current_values[key]
96
152
  end
97
153
 
154
+ # Recompute the owner of the list of keys.
155
+ # Mainly an internal function, used by an {Override} that is
156
+ # de-registering itself.
98
157
  def recompute(keys)
99
158
  keys.each do |key|
100
159
  recompute_single key
@@ -116,6 +175,11 @@ module TEF
116
175
  return false
117
176
  end
118
177
 
178
+ # Trigger processing of all {#on_change} callbacks.
179
+ #
180
+ # Works best when triggered after all changes for a given
181
+ # time-tick have been performed, such as during a
182
+ # {Sequencing::Player#after_exec} callback.
119
183
  def process_changes()
120
184
  change_list = @changes.keys
121
185
 
@@ -1,12 +1,47 @@
1
1
 
2
2
 
3
+
4
+ # TheElectricFursuits module.
5
+ # @see https://github.com/TheElectricFursuits
3
6
  module TEF
7
+ # Program Selection related module
8
+ #
9
+ # This module is meant to wrap around the functions that define and help with
10
+ # program selection.
11
+ # Its main purpose is to provide an easy to use identification and selection
12
+ # system for the various animations and effects of a fursuit. Often, different
13
+ # effects can have the same name (such as a 'hello' animation), and
14
+ # even within a certain type of animation (a group), variations of the same
15
+ # animaton can occour.
16
+ #
17
+ # The code here thusly provides a {ProgramSelection::ID} for identification,
18
+ # as well as a {ProgramSelection::Selector} that eases selection of a fitting
19
+ # program in certain situations.
4
20
  module ProgramSelection
21
+ # Program ID class.
22
+ #
23
+ # This class is meant to uniquely identify a specific program.
24
+ # It also provides a {#hash} and {#==} operator, allowing the use
25
+ # as hash key.
5
26
  class ID
27
+ # @return [String] Main title of the program.
28
+ # Often defines the action the program will execute, i.e. 'hello'
29
+ # or 'red alert'
6
30
  attr_reader :title
31
+ # @return [Array<String>] List of groups this program belongs to.
32
+ # Further defines the program by providing a bit of context, such as
33
+ # 'portal', 'glad os', etc.
7
34
  attr_reader :groups
35
+
36
+ # @return [String] The variant of this program.
37
+ # Its main purpose is to separate different variations of the same
38
+ # general program, such as when there are different 'hello's from
39
+ # the same groups. Has no effect on the actual selection.
8
40
  attr_reader :variant
9
41
 
42
+ # @return [Numeric] The hash key of this ID. Allows
43
+ # identification and comparison of keys in a Hash. Two keys match if
44
+ # they have the same groups, title and are of the same variant.
10
45
  attr_reader :hash
11
46
 
12
47
  def initialize(title, groups, variant)
@@ -17,6 +52,8 @@ module TEF
17
52
  @hash = @title.hash ^ @groups.hash ^ @variant.hash
18
53
  end
19
54
 
55
+ # @return [true, false] Two keys match if
56
+ # they have the same groups, title and are of the same variant.
20
57
  def ==(other)
21
58
  if other.is_a? String
22
59
  @title == other
@@ -30,6 +67,8 @@ module TEF
30
67
  end
31
68
  alias eql? ==
32
69
 
70
+ # @return [-1..1] Sorting operator, sorts alphabetically by title,
71
+ # then group, then variant.
33
72
  def <=>(other)
34
73
  tsort = (@title <=> other.title)
35
74
  return tsort unless tsort.zero?
@@ -40,6 +79,13 @@ module TEF
40
79
  @variant <=> other.variant
41
80
  end
42
81
 
82
+ # Compute the selection score for this ID.
83
+ # Used in {Selector} to determine the best-matching program ID
84
+ # for a given group scoring.
85
+ #
86
+ # @param [Hash<String, Numeric>] Hash of group weights. Any group
87
+ # of this ID that has a weight will be added to the score.
88
+ # @return [Numeric] Sum of the selected group weights.
43
89
  def get_scoring(group_weights)
44
90
  score = 0;
45
91
 
@@ -3,7 +3,16 @@ require_relative 'ProgramID.rb'
3
3
 
4
4
  module TEF
5
5
  module ProgramSelection
6
+ # Program Selector class.
7
+ #
8
+ # This class's purpose is to provide a central list of all known
9
+ # programs, as well as to give the user a method of easily selecting
10
+ # a matching program for a given situation or query.
6
11
  class Selector
12
+
13
+ # @return [Hash<String, Numeric>] Weights of groups.
14
+ # Used when selecing a program via {#fetch_ID} or {#fetch_string}.
15
+ # Higher weights mean higher preference
7
16
  attr_accessor :group_weights
8
17
 
9
18
  def initialize()
@@ -13,6 +22,22 @@ module TEF
13
22
  @group_weights = {}
14
23
  end
15
24
 
25
+ # Register a new ID to the list of known {ID}s.
26
+ #
27
+ # This will ensure that the given {ID} is present in the list
28
+ # of known IDs. It will then return either the given {ID} or an equivalent
29
+ # ID already present in the list. Either can be used for identification.
30
+ #
31
+ # This function can be used in two ways, either by passing a {ID} as
32
+ # only argument, or by passing a String as first argument and optional
33
+ # group and variant specifiers.
34
+ #
35
+ # @param [ID, String] program Program {ID} OR a String to use
36
+ # as program title.
37
+ # @param [Array<String>] pgroups Optional list of groups to construct
38
+ # a new {ID} with.
39
+ # @param [String, nil] pvariant Optional variant specification to
40
+ # construct a new {ID} with.
16
41
  def register_ID(program, pgroups = [], pvariant = nil)
17
42
  if program.is_a? String
18
43
  program = ID.new(program, pgroups, pvariant)
@@ -31,6 +56,21 @@ module TEF
31
56
  program
32
57
  end
33
58
 
59
+ # Fetch an {ID} from the list of known IDs.
60
+ #
61
+ # This will fetch a specific {ID} from the list of IDs, based on
62
+ # its title and group weights.
63
+ # The title MUST match the given title. Then, the group scoring
64
+ # of each matching ID will be calculated, based on {#group_weights} and
65
+ # the given group weights. From all IDs with maximum group weight,
66
+ # a random variant will be selected, and will be returned.
67
+ #
68
+ # @param [String] title Title to look for. Must exactly match the
69
+ # title of the {ID}.
70
+ # @param [Hash<String, Numeric>] group_weights Group weights. Higher
71
+ # group score means a specific {ID} will be preferred.
72
+ # @return [nil, ID] Either nil if no {ID} with matching title was found,
73
+ # or the ID with best group score.
34
74
  def fetch_ID(title, group_weights = {})
35
75
  return nil if @known_programs[title].nil?
36
76
 
@@ -56,6 +96,20 @@ module TEF
56
96
  current_list.sample
57
97
  end
58
98
 
99
+ # Fetch an {ID} based on a string.
100
+ #
101
+ # This function performs similarly to {#fetch_ID}, but is based on
102
+ # a more human readable string.
103
+ # The input is parsed as follows:
104
+ # - It is split at the first ' from '
105
+ # - The first part is taken as a title.
106
+ # - The second part will be split through ' and '. Each resulting
107
+ # array element is taken as group weight with a score of 2.
108
+ #
109
+ # @example
110
+ # selector.fetch_string('hello from portal and turret')
111
+ # # This is equivalent to:
112
+ # selector.fetch_ID('hello', { 'portal' => 2, 'turret' => 2})
59
113
  def fetch_string(str)
60
114
  title, groups = str.split(" from ");
61
115
  groups ||= "";