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.
@@ -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