tef-animation 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/README.md +6 -0
- data/lib/tef/Animation/Animatable.rb +213 -0
- data/lib/tef/Animation/Animation_Handler.rb +180 -0
- data/lib/tef/Animation/Color.rb +112 -0
- data/lib/tef/Animation/Coordinate.rb +49 -0
- data/lib/tef/Animation/Eyes.rb +21 -0
- data/lib/tef/Animation/Value.rb +133 -0
- data/lib/tef/ParameterStack/Override.rb +66 -0
- data/lib/tef/ParameterStack/Stack.rb +149 -0
- data/lib/tef/ProgramSelection/ProgramID.rb +56 -0
- data/lib/tef/ProgramSelection/ProgramSelector.rb +76 -0
- data/lib/tef/ProgramSelection/SequenceCollection.rb +75 -0
- data/lib/tef/ProgramSelection/SoundCollection.rb +108 -0
- data/lib/tef/Sequencing/BaseSequence.rb +72 -0
- data/lib/tef/Sequencing/EventCollector.rb +127 -0
- data/lib/tef/Sequencing/SequencePlayer.rb +91 -0
- data/lib/tef/Sequencing/Sheet.rb +33 -0
- data/lib/tef/Sequencing/SheetSequence.rb +138 -0
- data/lib/tef/animation.rb +11 -0
- metadata +105 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
require_relative 'ProgramID.rb'
|
3
|
+
|
4
|
+
module TEF
|
5
|
+
module ProgramSelection
|
6
|
+
class Selector
|
7
|
+
attr_accessor :group_weights
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@known_programs = {} # Hash based on titles
|
11
|
+
@known_groups = {}
|
12
|
+
|
13
|
+
@group_weights = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def register_ID(program, pgroups = [], pvariant = nil)
|
17
|
+
if program.is_a? String
|
18
|
+
program = ID.new(program, pgroups, pvariant)
|
19
|
+
end
|
20
|
+
|
21
|
+
proglist = (@known_programs[program.title] ||= [])
|
22
|
+
|
23
|
+
if found_prog = proglist.find { |prg| prg == program }
|
24
|
+
return found_prog
|
25
|
+
end
|
26
|
+
|
27
|
+
proglist << program
|
28
|
+
|
29
|
+
program.groups.each { |group| @known_groups[group] = true }
|
30
|
+
|
31
|
+
program
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_ID(title, group_weights = {})
|
35
|
+
return nil if @known_programs[title].nil?
|
36
|
+
|
37
|
+
weights = @group_weights.merge(group_weights)
|
38
|
+
|
39
|
+
current_best = nil;
|
40
|
+
current_list = [];
|
41
|
+
|
42
|
+
@known_programs[title].each do |prg|
|
43
|
+
p_score = prg.get_scoring(weights);
|
44
|
+
current_best ||= p_score
|
45
|
+
|
46
|
+
next if current_best > p_score
|
47
|
+
|
48
|
+
if current_best == p_score
|
49
|
+
current_list << prg
|
50
|
+
else
|
51
|
+
current_best = p_score
|
52
|
+
current_list = [prg]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
current_list.sample
|
57
|
+
end
|
58
|
+
|
59
|
+
def fetch_string(str)
|
60
|
+
title, groups = str.split(" from ");
|
61
|
+
groups ||= "";
|
62
|
+
groups = groups.split(" and ").map { |g| [g, 2] }.to_h
|
63
|
+
|
64
|
+
fetch_ID(title, groups)
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_titles()
|
68
|
+
@known_programs.keys
|
69
|
+
end
|
70
|
+
|
71
|
+
def all_groups()
|
72
|
+
@known_groups.keys
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
require_relative '../Sequencing/SheetSequence.rb'
|
3
|
+
require_relative 'ProgramID.rb'
|
4
|
+
|
5
|
+
module TEF
|
6
|
+
module ProgramSelection
|
7
|
+
class SequenceCollection
|
8
|
+
attr_reader :sheet_opts
|
9
|
+
|
10
|
+
def self.current_collection
|
11
|
+
@current_collection
|
12
|
+
end
|
13
|
+
def self.current_collection=(n_collection)
|
14
|
+
@current_collection = n_collection
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(program_selector, sequence_runner)
|
18
|
+
@program_selector = program_selector
|
19
|
+
@sequence_runner = sequence_runner
|
20
|
+
|
21
|
+
@known_programs = {}
|
22
|
+
@sheet_opts = {}
|
23
|
+
|
24
|
+
self.class.current_collection = self
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
@known_programs[key]
|
29
|
+
end
|
30
|
+
def []=(key, n_program)
|
31
|
+
key = @program_selector.register_ID key
|
32
|
+
@known_programs[key] = n_program
|
33
|
+
end
|
34
|
+
|
35
|
+
def play(key)
|
36
|
+
prog = @known_programs[key]
|
37
|
+
return unless prog
|
38
|
+
|
39
|
+
prog_key = prog.program_key if prog.is_a? ProgramSheet
|
40
|
+
|
41
|
+
if prog.is_a? Sequencing::Sheet
|
42
|
+
opts = @sheet_opts[key] || {}
|
43
|
+
opts[:sheet] = prog
|
44
|
+
opts[:program_key] = key
|
45
|
+
|
46
|
+
prog = Sequencing::SheetSequence.new(Time.now(), 1, **opts)
|
47
|
+
end
|
48
|
+
prog_key ||= 'default'
|
49
|
+
|
50
|
+
@sequence_runner[prog_key] = prog
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ProgramSheet < TEF::Sequencing::Sheet
|
55
|
+
attr_accessor :program_key
|
56
|
+
|
57
|
+
def initialize()
|
58
|
+
super()
|
59
|
+
|
60
|
+
yield(self) if block_given?
|
61
|
+
end
|
62
|
+
|
63
|
+
## TODO Give option to add multiple keys with options-hash
|
64
|
+
|
65
|
+
def add_key(title, groups = [], variation = '.mp3')
|
66
|
+
prog_collection = SequenceCollection.current_collection
|
67
|
+
raise "No program collection was instantiated yet!" unless prog_collection
|
68
|
+
|
69
|
+
id = ID.new(title, groups, variation)
|
70
|
+
|
71
|
+
prog_collection[id] = self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
require_relative 'ProgramSelector.rb'
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module TEF
|
7
|
+
module ProgramSelection
|
8
|
+
class SoundCollection
|
9
|
+
attr_reader :silence_maps
|
10
|
+
attr_reader :soundmap
|
11
|
+
attr_reader :load_config
|
12
|
+
|
13
|
+
def initialize(program_handler)
|
14
|
+
@handler = program_handler
|
15
|
+
@soundmap = {}
|
16
|
+
|
17
|
+
@load_config = {};
|
18
|
+
@silence_maps = {}
|
19
|
+
|
20
|
+
if File.file? './sounds/soundconfig.yml'
|
21
|
+
@load_config = YAML.load File.read './sounds/soundconfig.yml'
|
22
|
+
end
|
23
|
+
|
24
|
+
if File.file? './sounds/silence_maps.yml'
|
25
|
+
@silence_maps = YAML.load File.read './sounds/silence_maps.yml'
|
26
|
+
end
|
27
|
+
|
28
|
+
`find ./`.split("\n").each { |fn| add_file fn };
|
29
|
+
|
30
|
+
File.write('./sounds/silence_maps.yml', YAML.dump(@silence_maps));
|
31
|
+
|
32
|
+
@play_pids = {};
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_silences(fname)
|
36
|
+
return if @silence_maps[fname]
|
37
|
+
|
38
|
+
ffmpeg_str = `ffmpeg -i #{fname} -af silencedetect=n=0.1:d=0.1 -f null - 2>&1`
|
39
|
+
|
40
|
+
out_event = {}
|
41
|
+
|
42
|
+
ffmpeg_str.split("\n").each do |line|
|
43
|
+
if line =~ /silence_start: ([\d\.-]*)/
|
44
|
+
out_event[$1.to_f] = 0
|
45
|
+
elsif line =~ /silence_end: ([\d\.-]*)/
|
46
|
+
out_event[$1.to_f] = 1
|
47
|
+
elsif line =~ /Duration: (\d+):(\d+):([\d\.]+)/
|
48
|
+
out_event[$1.to_f * 3600 + $2.to_f * 60 + $3.to_f] = 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
out_event = out_event.sort.to_h
|
53
|
+
|
54
|
+
if(out_event.empty?)
|
55
|
+
out_event[0.01] = 1
|
56
|
+
elsif (k = out_event.keys[0]) < 0
|
57
|
+
out_event.delete k
|
58
|
+
out_event[0.01] = 0
|
59
|
+
else
|
60
|
+
out_event[0.01] = 1
|
61
|
+
end
|
62
|
+
|
63
|
+
out_event = out_event.sort.to_h
|
64
|
+
|
65
|
+
@silence_maps[fname] = out_event
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_file(fname)
|
69
|
+
rMatch = /^\.\/sounds\/(?<groups>(?:[a-z_]+[\/-])*)(?<title>[a-z_]+)(?<variant>(?:-\d+)?\.(?:ogg|mp3|wav))/.match fname;
|
70
|
+
return unless rMatch;
|
71
|
+
|
72
|
+
title = rMatch[:title].gsub('_', ' ');
|
73
|
+
groups = rMatch[:groups].gsub('_', ' ').gsub('-','/').split('/');
|
74
|
+
|
75
|
+
groups = ["default"] if groups.empty?
|
76
|
+
|
77
|
+
id = @handler.register_ID(title, groups, rMatch[:variant])
|
78
|
+
|
79
|
+
@soundmap[id] = fname
|
80
|
+
|
81
|
+
generate_silences fname
|
82
|
+
end
|
83
|
+
|
84
|
+
def silences_for(key)
|
85
|
+
@silence_maps[@soundmap[key]]
|
86
|
+
end
|
87
|
+
|
88
|
+
def play(id, collection_id = 'default')
|
89
|
+
sound_name = @soundmap[id]
|
90
|
+
return if sound_name.nil?
|
91
|
+
|
92
|
+
collection_id ||= id;
|
93
|
+
|
94
|
+
if old_pid = @play_pids[collection_id]
|
95
|
+
Process.kill('QUIT', old_pid)
|
96
|
+
end
|
97
|
+
|
98
|
+
Thread.new do
|
99
|
+
fork_pid = spawn(*%W{play -q --volume 0.3 #{sound_name}});
|
100
|
+
|
101
|
+
@play_pids[collection_id] = fork_pid;
|
102
|
+
Process.waitpid fork_pid;
|
103
|
+
@play_pids.delete collection_id
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require_relative 'EventCollector.rb'
|
3
|
+
|
4
|
+
module TEF
|
5
|
+
module Sequencing
|
6
|
+
class BaseSequence
|
7
|
+
attr_reader :start_time
|
8
|
+
attr_reader :end_time
|
9
|
+
|
10
|
+
attr_reader :offset
|
11
|
+
attr_reader :slope
|
12
|
+
|
13
|
+
attr_reader :state
|
14
|
+
|
15
|
+
def initialize(offset, slope, **options)
|
16
|
+
@start_time ||= options[:start_time] || 0;
|
17
|
+
@end_time ||= options[:end_time];
|
18
|
+
|
19
|
+
@offset = offset;
|
20
|
+
@slope = slope;
|
21
|
+
|
22
|
+
@state = :uninitialized
|
23
|
+
|
24
|
+
@opts_hash = options;
|
25
|
+
end
|
26
|
+
|
27
|
+
def parent_start_time
|
28
|
+
@offset + @start_time / @slope
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup()
|
32
|
+
raise 'Program had to be uninitialized!' unless @state == :uninitialized
|
33
|
+
@state = :running
|
34
|
+
end
|
35
|
+
|
36
|
+
def teardown()
|
37
|
+
return unless @state == :running
|
38
|
+
@state = :torn_down
|
39
|
+
|
40
|
+
@opts_hash = nil;
|
41
|
+
end
|
42
|
+
|
43
|
+
def append_events(collector)
|
44
|
+
local_collector = collector.offset_collector(@offset, @slope);
|
45
|
+
|
46
|
+
return if local_collector.has_events? &&
|
47
|
+
local_collector.event_time < @start_time
|
48
|
+
return if @state == :torn_down
|
49
|
+
|
50
|
+
if @state == :uninitialized
|
51
|
+
local_collector.add_event({
|
52
|
+
time: @start_time,
|
53
|
+
code: proc { self.setup() }
|
54
|
+
});
|
55
|
+
end
|
56
|
+
|
57
|
+
if @state == :running
|
58
|
+
overload_append_events(local_collector)
|
59
|
+
end
|
60
|
+
|
61
|
+
if !@end_time.nil?
|
62
|
+
local_collector.add_event({
|
63
|
+
time: @end_time,
|
64
|
+
code: proc { self.teardown }
|
65
|
+
})
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def overload_append_events(_collector) end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
require 'xasin_logger'
|
3
|
+
|
4
|
+
module TEF
|
5
|
+
module Sequencing
|
6
|
+
class OffsetCollector
|
7
|
+
attr_reader :parent
|
8
|
+
|
9
|
+
attr_reader :total_offset
|
10
|
+
attr_reader :total_slope
|
11
|
+
|
12
|
+
def initialize(parent, total_offset, total_slope)
|
13
|
+
@parent = parent
|
14
|
+
@total_offset = total_offset
|
15
|
+
@total_slope = total_slope
|
16
|
+
end
|
17
|
+
|
18
|
+
def convert_to_local(global_time)
|
19
|
+
return nil if global_time.nil?
|
20
|
+
|
21
|
+
(global_time - @total_offset) * @total_slope
|
22
|
+
end
|
23
|
+
def convert_to_global(local_time)
|
24
|
+
return nil if local_time.nil?
|
25
|
+
|
26
|
+
@total_offset + (local_time.to_f / @total_slope)
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_time
|
30
|
+
convert_to_local @parent.start_time
|
31
|
+
end
|
32
|
+
|
33
|
+
def event_time
|
34
|
+
convert_to_local @parent.event_time
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_events?
|
38
|
+
return @parent.has_events?
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_event(event)
|
42
|
+
event = event.clone
|
43
|
+
|
44
|
+
event[:time] = convert_to_global event[:time]
|
45
|
+
|
46
|
+
@parent.add_event event
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_events(list)
|
50
|
+
list.each { |event| add_event event }
|
51
|
+
end
|
52
|
+
|
53
|
+
def offset_collector(offset, slope)
|
54
|
+
OffsetCollector.new(@parent, convert_to_global(offset), @total_slope * slope)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class EventCollector
|
59
|
+
include XasLogger::Mix
|
60
|
+
|
61
|
+
attr_accessor :start_time
|
62
|
+
attr_reader :event_time
|
63
|
+
|
64
|
+
attr_reader :current_events
|
65
|
+
|
66
|
+
def initialize()
|
67
|
+
@current_events = []
|
68
|
+
@start_time = Time.at(0);
|
69
|
+
@event_time = nil;
|
70
|
+
|
71
|
+
init_x_log("Sequence Player")
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_event(event)
|
75
|
+
return if event[:time] <= @start_time
|
76
|
+
return if (!@event_time.nil?) && (event[:time] > @event_time)
|
77
|
+
|
78
|
+
if (!@event_time.nil?) && (event[:time] == @event_time)
|
79
|
+
@current_events << event
|
80
|
+
else
|
81
|
+
@current_events = [event]
|
82
|
+
@event_time = event[:time]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def has_events?
|
87
|
+
!@current_events.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_until_event
|
91
|
+
return unless has_events?
|
92
|
+
|
93
|
+
t_diff = @event_time - Time.now();
|
94
|
+
|
95
|
+
if t_diff < -0.5
|
96
|
+
x_logf('Sequence long overdue!')
|
97
|
+
elsif t_diff < -0.1
|
98
|
+
x_logw('Sequencing overdue')
|
99
|
+
end
|
100
|
+
|
101
|
+
sleep t_diff if t_diff > 0
|
102
|
+
end
|
103
|
+
|
104
|
+
def execute!
|
105
|
+
return unless has_events?
|
106
|
+
|
107
|
+
wait_until_event
|
108
|
+
|
109
|
+
@current_events.each do |event|
|
110
|
+
event[:code].call()
|
111
|
+
end
|
112
|
+
|
113
|
+
@start_time = @event_time
|
114
|
+
restart();
|
115
|
+
end
|
116
|
+
|
117
|
+
def restart()
|
118
|
+
@current_events = []
|
119
|
+
@event_time = nil;
|
120
|
+
end
|
121
|
+
|
122
|
+
def offset_collector(offset, slope)
|
123
|
+
OffsetCollector.new(self, offset, slope);
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|