tef-animation 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ||= "";