tef-animation 0.1.0

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