class TEF::ProgramSelection::SoundCollection
Sound-File collection.
This class is meant as convenient container for sound-filenames. It will automatically scan the current directory for any file ending with '.mp3', '.ogg' or '.wav', and will construct a unique {ID} for it based on the filepath. Paths can then be retrieved by using {#soundmap}.
Additionally, it keeps a list of silences, the {#silence_maps}. They are auto-generated lists of silences or loud sections of the file, useful for auto-generating programs. They can be retrieved via {#silence_maps}
Note that to play a sound, this should be done via {Sequencing::SheetSequence#play}, to ensure that the sound is killed in synch with the program.
Attributes
@return [Hash] Config loaded from './sounds/soundconfig.yml'
@return [Hash<String, Hash<Numeric, Numeric>>] List of noise levels
for a given file-path. Is in the format { timestamp (in s) => 0 to 1 } It is ensured that a 0 is inserted at the beginning and end of the sound track, and it is ensured that the hash keys are sorted.
@see silences_for
@return [Hash<ID, String>] Map of {ID}s to matching file-paths
Public Class Methods
Initialize a SoundCollection
This will scan the current directory for any sound files. Found files will auto-generate a ID
based on their path, and are registered with the passed {Selector}.
Paths are deconstructed as follows:
-
The path is split along any '/' or '-'. Each element up to the last is taken as a group. The last element in the list is taken as title.
-
The variant is generated by taking the sequence (-d+)?.(mp3|ogg|wav) from the end. This means that variants can be specified by appending a '-1234' to the title.
'./sounds/portal/announcer-hello-4.mp3' is registered as:
{Selector#register_ID}('hello', ['sounds', 'portal', 'announcer'], '-4.mp3');
Also note that a custom config file, {#load_config}, is loaded from
a YAML file './sounds/soundconfig.yml', if present.
Additionally, the {#silence_maps} are loaded from, and saved to,
'./sounds/silence_maps.yml'
# File lib/tef/ProgramSelection/SoundCollection.rb, line 61 def initialize(program_handler) @handler = program_handler @soundmap = {} @load_config = {}; @silence_maps = {} if File.file? './sounds/soundconfig.yml' @load_config = YAML.load File.read './sounds/soundconfig.yml' end if File.file? './sounds/silence_maps.yml' @silence_maps = YAML.load File.read './sounds/silence_maps.yml' end `find ./`.split("\n").each { |fn| add_file fn }; File.write('./sounds/silence_maps.yml', YAML.dump(@silence_maps)); end
Public Instance Methods
Add a file to the collection of files.
Will auto-generate silences and a matching {ID} as described in {#initialize}.
# File lib/tef/ProgramSelection/SoundCollection.rb, line 120 def add_file(fname) rMatch = /^\.\/sounds\/(?<groups>(?:[a-z_]+[\/-])*)(?<title>[a-z_]+)(?<variant>(?:-\d+)?\.(?:ogg|mp3|wav))/.match fname; return unless rMatch; title = rMatch[:title].gsub('_', ' '); groups = rMatch[:groups].gsub('_', ' ').gsub('-','/').split('/'); groups = ["default"] if groups.empty? id = @handler.register_ID(title, groups, rMatch[:variant]) @soundmap[id] = fname generate_silences fname end
@return [Hash<Numeric, Numeric>, nil] The silence map for
the passed {ID}, or nil if none was found.
@see silence_maps
# File lib/tef/ProgramSelection/SoundCollection.rb, line 140 def silences_for(key) @silence_maps[@soundmap[key]] end
Private Instance Methods
Internal function. Generates a list of silences/loud sections with ffmpeg.
# File lib/tef/ProgramSelection/SoundCollection.rb, line 83 def generate_silences(fname) return if @silence_maps[fname] ffmpeg_str = `ffmpeg -i #{fname} -af silencedetect=n=0.1:d=0.1 -f null - 2>&1` out_event = {} ffmpeg_str.split("\n").each do |line| if line =~ /silence_start: ([\d\.-]*)/ out_event[$1.to_f] = 0 elsif line =~ /silence_end: ([\d\.-]*)/ out_event[$1.to_f] = 1 elsif line =~ /Duration: (\d+):(\d+):([\d\.]+)/ out_event[$1.to_f * 3600 + $2.to_f * 60 + $3.to_f] = 0 end end out_event = out_event.sort.to_h if(out_event.empty?) out_event[0.01] = 1 elsif (k = out_event.keys[0]) < 0 out_event.delete k out_event[0.01] = 0 else out_event[0.01] = 1 end out_event = out_event.sort.to_h @silence_maps[fname] = out_event end