MusicMaster 0.0.1.20101110 → 1.0.0.20120307
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.
- data/AUTHORS +4 -1
- data/ChangeLog +28 -0
- data/LICENSE +1 -1
- data/README +2 -5
- data/ReleaseInfo +8 -8
- data/bin/Calibrate.rb +55 -0
- data/bin/Clean.rb +55 -0
- data/bin/DBConvert.rb +3 -1
- data/bin/Deliver.rb +73 -42
- data/bin/Mix.rb +39 -78
- data/bin/Process.rb +55 -0
- data/bin/Record.rb +63 -116
- data/bin/{Album.rb → old/Album.rb} +18 -18
- data/bin/{AnalyzeAlbum.rb → old/AnalyzeAlbum.rb} +11 -11
- data/bin/{DeliverAlbum.rb → old/DeliverAlbum.rb} +12 -12
- data/bin/{Fct2Wave.rb → old/Fct2Wave.rb} +11 -11
- data/{album.conf.rb.example → bin/old/album.conf.rb.example} +0 -0
- data/lib/MusicMaster/FilesNamer.rb +253 -0
- data/lib/MusicMaster/Formats/MP3.rb +50 -0
- data/lib/MusicMaster/Formats/Test.rb +44 -0
- data/lib/MusicMaster/Formats/Wave.rb +80 -0
- data/lib/MusicMaster/Hash.rb +20 -0
- data/lib/MusicMaster/Launcher.rb +241 -0
- data/lib/MusicMaster/Processes/ApplyVolumeFct.rb +4 -8
- data/lib/MusicMaster/Processes/Compressor.rb +50 -47
- data/lib/MusicMaster/Processes/Custom.rb +3 -3
- data/lib/MusicMaster/Processes/{AddSilence.rb → Cut.rb} +8 -4
- data/lib/MusicMaster/Processes/CutFirstSignal.rb +6 -3
- data/lib/MusicMaster/Processes/DCShifter.rb +30 -0
- data/lib/MusicMaster/Processes/GVerb.rb +3 -2
- data/lib/MusicMaster/Processes/Normalize.rb +7 -6
- data/lib/MusicMaster/Processes/SilenceInserter.rb +31 -0
- data/lib/MusicMaster/Processes/Test.rb +39 -0
- data/lib/MusicMaster/Processes/VolCorrection.rb +3 -3
- data/lib/MusicMaster/RakeProcesses.rb +1014 -0
- data/lib/MusicMaster/Symbol.rb +12 -0
- data/lib/MusicMaster/Task.rb +29 -0
- data/lib/MusicMaster/Utils.rb +467 -0
- data/lib/MusicMaster/old/Common.rb +101 -0
- data/musicmaster.conf.rb.example +42 -59
- data/record.conf.rb.example +374 -30
- metadata +91 -41
- data/TODO +0 -3
- data/bin/Master.rb +0 -60
- data/bin/PrepareMix.rb +0 -422
- data/lib/MusicMaster/Common.rb +0 -197
- data/lib/MusicMaster/ConfLoader.rb +0 -56
- data/lib/MusicMaster/musicmaster.conf.rb +0 -96
- data/master.conf.rb.example +0 -13
@@ -0,0 +1,1014 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011 - 2012 Muriel Salvan (muriel@x-aeon.com)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
gem 'rake', '>= 0.9'
|
7
|
+
require 'rake'
|
8
|
+
|
9
|
+
require 'rUtilAnts/Platform'
|
10
|
+
RUtilAnts::Platform.install_platform_on_object
|
11
|
+
require 'rUtilAnts/Misc'
|
12
|
+
RUtilAnts::Misc.install_misc_on_object
|
13
|
+
require 'MusicMaster/Symbol'
|
14
|
+
require 'MusicMaster/Task'
|
15
|
+
|
16
|
+
module MusicMaster
|
17
|
+
|
18
|
+
module RakeProcesses
|
19
|
+
|
20
|
+
class UnknownTrackIDError < RuntimeError
|
21
|
+
end
|
22
|
+
|
23
|
+
include Rake::DSL
|
24
|
+
|
25
|
+
# Initialize variables used by rake processes
|
26
|
+
#
|
27
|
+
# Parameters::
|
28
|
+
# * *iOptions* (<em>map<Symbol,Object></em>): First set of options [optional = {}]
|
29
|
+
def initialize_RakeProcesses(iOptions = {})
|
30
|
+
# The context: this will be shared across Rake tasks and this code.
|
31
|
+
# map< Symbol, Object >
|
32
|
+
# * *:EnvsToCalibrate* (<em>map< [Symbol,Symbol],nil ></em>): The set of environments pairs to calibrate
|
33
|
+
# * *:CleanFiles* (<em>map<String,map<Symbol,Object>></em>: Data associated to a recorded file that will be cleaned, per recorded file base name (without extension):
|
34
|
+
# * *:FramedFileName* (_String_): Name of the recorded file once framed
|
35
|
+
# * *:DCRemovedFileName* (_String_): Name of the file without DC offset
|
36
|
+
# * *:NoiseGatedFileName* (_String_): Name of the file with noise gate applied
|
37
|
+
# * *:SilenceAnalysisFileName* (_String_): Name of the file containing analysis of the corresponding silence recording
|
38
|
+
# * *:SilenceFFTProfileFileName* (_String_): Name of the file containing FFT profile of the corresponding silence recording
|
39
|
+
# * *:RakeSetupFor_GenerateSourceFiles* (_Boolean_): Have the rules for GenerateSourceFiles been created ?
|
40
|
+
# * *:RakeSetupFor_CleanRecordings* (_Boolean_): Have the rules for CleanRecordings been created ?
|
41
|
+
# * *:RakeSetupFor_CalibrateRecordings* (_Boolean_): Have the rules for CalibrateRecordings been created ?
|
42
|
+
# * *:RakeSetupFor_ProcessSourceFiles* (_Boolean_): Have the rules for ProcessSourceFiles been created ?
|
43
|
+
# * *:RakeSetupFor_Mix* (_Boolean_): Have the rules for Mix been created ?
|
44
|
+
# * *:Calibrate* (<em>map<String,map<Symbol,Object>></em>): Data associated to a calibrated file, per recorded file base name (without extension):
|
45
|
+
# * *:FinalTask* (_Symbol_): Name of the final calibration task
|
46
|
+
# * *:CalibratedFileName* (_String_): Name of the calibrated file
|
47
|
+
# * *:CalibrationAnalysisFiles* (<em>map< [Symbol,Symbol],String ></em>): Name of the calibration analysis files, per environment pair [ReferenceEnv, RecordingEnv]
|
48
|
+
# * *:Processes* (<em>map<String,map<Symbol,Object>></em>): Data associated to a process chain, per recorded file base name (without extension):
|
49
|
+
# * *:LstProcesses* (<em>list<map<Symbol,Object>></em>): List of processes to apply to this recording
|
50
|
+
# * *:FinalTask* (_Symbol_): Name of the final process task
|
51
|
+
# * *:WaveProcesses* (<em>map<String,map<Symbol,Object>></em>): Data associated to a process chain, per Wave name (from the config file):
|
52
|
+
# * *:LstProcesses* (<em>list<map<Symbol,Object>></em>): List of processes to apply to this Wave file
|
53
|
+
# * *:FinalTask* (_Symbol_): Name of the final process task
|
54
|
+
# * *:RecordedFilesPrepared* (_Boolean_): Recorded files are already prepared: no need to wait for user input while recording.
|
55
|
+
# * *:LstEnvToRecord* (<em>list<Symbol></em>): The list of recording environments to record. An empty list means all environments.
|
56
|
+
# * *:LstMixNames* (_String_): Names of the mix to produce. Can be empty to produce all mixes.
|
57
|
+
# * *:LstDeliverNames* (_String_): Names of the deliverables to produce. Can be empty to produce all deliverables.
|
58
|
+
# * *:FinalMixSources* (<em>map<Object,Symbol></em>): List of all tasks defining source files, per mix TrackID
|
59
|
+
# * *:DeliverableConf* (<em>map<String,[map<Symbol,Object>,map<Symbol,Object>]></em>): The deliverable information, per deliverable file name: [FormatConfig, Metadata]
|
60
|
+
# * *:Deliverables* (<em>map<String,map<Symbol,Object>></em>): Data associated to a deliverable, per deliverable name (from the config file):
|
61
|
+
# * *:FileName* (_String_): The real deliverable file name
|
62
|
+
@Context = {
|
63
|
+
:EnvsToCalibrate => {},
|
64
|
+
:CleanFiles => {},
|
65
|
+
:RakeSetupFor_GenerateSourceFiles => false,
|
66
|
+
:RakeSetupFor_CleanRecordings => false,
|
67
|
+
:RakeSetupFor_CalibrateRecordings => false,
|
68
|
+
:RakeSetupFor_ProcessSourceFiles => false,
|
69
|
+
:Calibrate => {},
|
70
|
+
:CalibrationAnalysisFiles => {},
|
71
|
+
:Processes => {},
|
72
|
+
:WaveProcesses => {},
|
73
|
+
:RecordedFilesPrepared => false,
|
74
|
+
:LstEnvToRecord => [],
|
75
|
+
:LstMixNames => [],
|
76
|
+
:LstDeliverableNames => [],
|
77
|
+
:FinalMixSources => {},
|
78
|
+
:DeliverableConf => {},
|
79
|
+
:Deliverables => {}
|
80
|
+
}.merge(iOptions)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Display rake tasks
|
84
|
+
# This is useful for debugging purposes
|
85
|
+
def displayRakeTasks
|
86
|
+
Rake.application.tasks.each do |iTask|
|
87
|
+
log_info "+-#{iTask.name}: #{iTask.comment}"
|
88
|
+
iTask.prerequisites.each do |iPrerequisiteTaskName|
|
89
|
+
log_info "| +-#{iPrerequisiteTaskName}"
|
90
|
+
end
|
91
|
+
log_info '|'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generate rake targets for generating source files
|
96
|
+
def generateRakeFor_GenerateSourceFiles
|
97
|
+
lLstGlobalRecordTasks = []
|
98
|
+
|
99
|
+
# 1. Recordings
|
100
|
+
lRecordingsConf = @RecordConf[:Recordings]
|
101
|
+
if (lRecordingsConf != nil)
|
102
|
+
# Generate recordings rules
|
103
|
+
# Gather the recording environments and their respective file names to produce
|
104
|
+
# map< Symbol, list< String > >
|
105
|
+
lRecordings = {}
|
106
|
+
lTracksConf = lRecordingsConf[:Tracks]
|
107
|
+
if (lTracksConf != nil)
|
108
|
+
lTracksConf.each do |iLstTracks, iRecordingConf|
|
109
|
+
lEnv = iRecordingConf[:Env]
|
110
|
+
lRecordedFileName = getRecordedFileName(lEnv, iLstTracks)
|
111
|
+
|
112
|
+
desc "Raw recording of tracks #{iLstTracks.sort.join(', ')} in recording environment #{lEnv}"
|
113
|
+
file lRecordedFileName do |iTask|
|
114
|
+
# Raw recording task
|
115
|
+
record(iTask.name, @Context[:RecordedFilesPrepared])
|
116
|
+
end
|
117
|
+
|
118
|
+
if (lRecordings[lEnv] == nil)
|
119
|
+
lRecordings[lEnv] = []
|
120
|
+
end
|
121
|
+
lRecordings[lEnv] << lRecordedFileName
|
122
|
+
# If there is a need of calibration, record also the calibration files
|
123
|
+
if (iRecordingConf[:CalibrateWithEnv] != nil)
|
124
|
+
lReferenceEnv = iRecordingConf[:CalibrateWithEnv]
|
125
|
+
[
|
126
|
+
[ lReferenceEnv, lEnv ],
|
127
|
+
[ lEnv, lReferenceEnv ]
|
128
|
+
].each do |iEnvPair|
|
129
|
+
iRefEnv, iRecEnv = iEnvPair
|
130
|
+
lCalibrationFileName = getRecordedCalibrationFileName(iRefEnv, iRecEnv)
|
131
|
+
if (lRecordings[iRecEnv] == nil)
|
132
|
+
lRecordings[iRecEnv] = []
|
133
|
+
end
|
134
|
+
if (!lRecordings[iRecEnv].include?(lCalibrationFileName))
|
135
|
+
|
136
|
+
desc "Calibration recording in recording environment #{iRecEnv} to be compared with reference environment #{iRefEnv}"
|
137
|
+
file lCalibrationFileName do |iTask|
|
138
|
+
record(iTask.name, @Context[:RecordedFilesPrepared])
|
139
|
+
end
|
140
|
+
|
141
|
+
lRecordings[iRecEnv] << lCalibrationFileName
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@Context[:EnvsToCalibrate][[ lReferenceEnv, lEnv ].sort] = nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
# Make the task recording in the correct order
|
149
|
+
lSortedEnv = lRecordingsConf[:EnvRecordingOrder] || []
|
150
|
+
lRecordings.sort do
|
151
|
+
|iElem1, iElem2|
|
152
|
+
if (iElem2[1].size == iElem1[1].size)
|
153
|
+
next iElem1[0] <=> iElem2[0]
|
154
|
+
else
|
155
|
+
next iElem2[1].size <=> iElem1[1].size
|
156
|
+
end
|
157
|
+
end.each do |iElem|
|
158
|
+
if (!lSortedEnv.include?(iElem[0]))
|
159
|
+
lSortedEnv << iElem[0]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
lLstTasks = []
|
163
|
+
lSortedEnv.each do |iEnv|
|
164
|
+
lLstFiles = lRecordings[iEnv]
|
165
|
+
if (lLstFiles != nil)
|
166
|
+
# Record a silence file
|
167
|
+
lSilenceFile = getRecordedSilenceFileName(iEnv)
|
168
|
+
|
169
|
+
desc "Record silence file for recording environment #{iEnv}"
|
170
|
+
file lSilenceFile do |iTask|
|
171
|
+
# Raw recording task
|
172
|
+
record(iTask.name, @Context[:RecordedFilesPrepared])
|
173
|
+
end
|
174
|
+
|
175
|
+
lSymTask = "Record_#{iEnv}".to_sym
|
176
|
+
|
177
|
+
desc "Record all files for recording environment #{iEnv}"
|
178
|
+
task lSymTask => lLstFiles + [lSilenceFile]
|
179
|
+
|
180
|
+
lLstTasks << lSymTask if (@Context[:LstEnvToRecord].empty?) or (@Context[:LstEnvToRecord].include?(iEnv))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
desc 'Record all files'
|
185
|
+
task :Record => lLstTasks
|
186
|
+
|
187
|
+
lLstGlobalRecordTasks << :Record
|
188
|
+
end
|
189
|
+
|
190
|
+
# 2. Wave files
|
191
|
+
lWaveFilesConf = @RecordConf[:WaveFiles]
|
192
|
+
if (lWaveFilesConf != nil)
|
193
|
+
# Generate wave files rules
|
194
|
+
lLstWaveFiles = []
|
195
|
+
lWaveFilesConf[:FilesList].map { |iFileInfo| iFileInfo[:Name] }.each do |iFileName|
|
196
|
+
lWaveFileName = getWaveSourceFileName(iFileName)
|
197
|
+
if (!File.exists?(iFileName))
|
198
|
+
|
199
|
+
desc "Generate wave file #{iFileName}"
|
200
|
+
file lWaveFileName do |iTask|
|
201
|
+
puts "Create Wave file #{iTask.name}, and press Enter when done."
|
202
|
+
$stdin.gets
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
lLstWaveFiles << lWaveFileName
|
207
|
+
end
|
208
|
+
|
209
|
+
desc 'Generate all wave files'
|
210
|
+
task :GenerateWaveFiles => lLstWaveFiles
|
211
|
+
|
212
|
+
lLstGlobalRecordTasks << :GenerateWaveFiles
|
213
|
+
end
|
214
|
+
|
215
|
+
desc 'Generate source files (both recording and Wave files)'
|
216
|
+
task :GenerateSourceFiles => lLstGlobalRecordTasks
|
217
|
+
|
218
|
+
@Context[:RakeSetupFor_GenerateSourceFiles] = true
|
219
|
+
end
|
220
|
+
|
221
|
+
# Generate rake targets for cleaning recorded files
|
222
|
+
def generateRakeFor_CleanRecordings
|
223
|
+
if (!@Context[:RakeSetupFor_GenerateSourceFiles])
|
224
|
+
generateRakeFor_GenerateSourceFiles
|
225
|
+
end
|
226
|
+
|
227
|
+
# List of cleaning tasks
|
228
|
+
# list< Symbol >
|
229
|
+
lLstCleanTasks = []
|
230
|
+
lRecordingsConf = @RecordConf[:Recordings]
|
231
|
+
if (lRecordingsConf != nil)
|
232
|
+
lTracksConf = lRecordingsConf[:Tracks]
|
233
|
+
if (lTracksConf != nil)
|
234
|
+
# Look for recorded files
|
235
|
+
lTracksConf.each do |iLstTracks, iRecordingConf|
|
236
|
+
lEnv = iRecordingConf[:Env]
|
237
|
+
lRecordedFileName = getRecordedFileName(lEnv, iLstTracks)
|
238
|
+
lRecordedBaseName = File.basename(lRecordedFileName[0..-5])
|
239
|
+
# Clean the recorded file itself
|
240
|
+
lLstCleanTasks << generateRakeForCleaningRecordedFile(lRecordedBaseName, lEnv)
|
241
|
+
end
|
242
|
+
# Look for calibration files
|
243
|
+
@Context[:EnvsToCalibrate].each do |iEnvToCalibratePair, iNil|
|
244
|
+
iEnv1, iEnv2 = iEnvToCalibratePair
|
245
|
+
# Read the cutting values if any from the conf
|
246
|
+
lCutInfo = nil
|
247
|
+
if (lRecordingsConf[:EnvCalibration] != nil)
|
248
|
+
lRecordingsConf[:EnvCalibration].each do |iEnvPair, iCalibrationInfo|
|
249
|
+
if (iEnvPair.sort == iEnvToCalibratePair)
|
250
|
+
# Found it
|
251
|
+
lCutInfo = iCalibrationInfo[:CompareCuts]
|
252
|
+
break
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
# Clean the calibration files
|
257
|
+
lReferenceFileName = getRecordedCalibrationFileName(iEnv1, iEnv2)
|
258
|
+
lLstCleanTasks << generateRakeForCleaningRecordedFile(File.basename(lReferenceFileName)[0..-5], iEnv2, lCutInfo)
|
259
|
+
lRecordingFileName = getRecordedCalibrationFileName(iEnv2, iEnv1)
|
260
|
+
lLstCleanTasks << generateRakeForCleaningRecordedFile(File.basename(lRecordingFileName)[0..-5], iEnv1, lCutInfo)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
desc 'Clean all recorded files: remove silences, cut them, remove DC offset and apply noise gate'
|
266
|
+
task :CleanRecordings => lLstCleanTasks.sort.uniq
|
267
|
+
|
268
|
+
@Context[:RakeSetupFor_CleanRecordings] = true
|
269
|
+
end
|
270
|
+
|
271
|
+
# Generate rake targets for calibrating recorded files
|
272
|
+
def generateRakeFor_CalibrateRecordings
|
273
|
+
if (!@Context[:RakeSetupFor_CleanRecordings])
|
274
|
+
generateRakeFor_CleanRecordings
|
275
|
+
end
|
276
|
+
|
277
|
+
# List of calibrating tasks
|
278
|
+
# list< Symbol >
|
279
|
+
lLstCalibrateTasks = []
|
280
|
+
lRecordingsConf = @RecordConf[:Recordings]
|
281
|
+
if (lRecordingsConf != nil)
|
282
|
+
lTracksConf = lRecordingsConf[:Tracks]
|
283
|
+
if (lTracksConf != nil)
|
284
|
+
# Generate analysis files for calibration files
|
285
|
+
@Context[:EnvsToCalibrate].each do |iEnvToCalibratePair, iNil|
|
286
|
+
[
|
287
|
+
[ iEnvToCalibratePair[0], iEnvToCalibratePair[1] ],
|
288
|
+
[ iEnvToCalibratePair[1], iEnvToCalibratePair[0] ]
|
289
|
+
].each do |iEnvPair|
|
290
|
+
iEnv1, iEnv2 = iEnvPair
|
291
|
+
lCalibrationFileName = getRecordedCalibrationFileName(iEnv1, iEnv2)
|
292
|
+
lNoiseGatedFileName = @Context[:CleanFiles][File.basename(lCalibrationFileName)[0..-5]][:NoiseGatedFileName]
|
293
|
+
lAnalysisFileName = getRecordedAnalysisFileName(File.basename(lNoiseGatedFileName)[0..-5])
|
294
|
+
@Context[:CalibrationAnalysisFiles][iEnvPair] = lAnalysisFileName
|
295
|
+
|
296
|
+
desc "Generate analysis for framed calibration file #{lNoiseGatedFileName}"
|
297
|
+
file lAnalysisFileName => lNoiseGatedFileName do |iTask|
|
298
|
+
analyzeFile(iTask.prerequisites[0], iTask.name)
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Generate calibrated files
|
305
|
+
lTracksConf.each do |iLstTracks, iRecordingConf|
|
306
|
+
if (iRecordingConf[:CalibrateWithEnv] != nil)
|
307
|
+
# Need calibration
|
308
|
+
lRecEnv = iRecordingConf[:Env]
|
309
|
+
lRefEnv = iRecordingConf[:CalibrateWithEnv]
|
310
|
+
lRecordedBaseName = File.basename(getRecordedFileName(lRecEnv, iLstTracks))[0..-5]
|
311
|
+
# Create the data target that stores the comparison of analysis files for calibration
|
312
|
+
lCalibrationInfoTarget = "#{lRecordedBaseName}.Calibration.info".to_sym
|
313
|
+
|
314
|
+
desc "Compare the analysis of calibration files for recording #{lRecordedBaseName}"
|
315
|
+
task lCalibrationInfoTarget => [
|
316
|
+
@Context[:CalibrationAnalysisFiles][[lRefEnv,lRecEnv]],
|
317
|
+
@Context[:CalibrationAnalysisFiles][[lRecEnv,lRefEnv]]
|
318
|
+
] do |iTask|
|
319
|
+
iRecordingCalibrationAnalysisFileName, iReferenceCalibrationAnalysisFileName = iTask.prerequisites
|
320
|
+
# Compute the distance between the 2 average RMS values
|
321
|
+
lRMSReference = getRMSValue(iReferenceCalibrationAnalysisFileName)
|
322
|
+
lRMSRecording = getRMSValue(iRecordingCalibrationAnalysisFileName)
|
323
|
+
log_info "Reference environment #{lRefEnv} - RMS: #{lRMSReference}"
|
324
|
+
log_info "Recording environment #{lRecEnv} - RMS: #{lRMSRecording}"
|
325
|
+
iTask.data = {
|
326
|
+
:RMSReference => lRMSReference,
|
327
|
+
:RMSRecording => lRMSRecording,
|
328
|
+
:MaxValue => getAnalysis(iRecordingCalibrationAnalysisFileName)[:MinPossibleValue].abs
|
329
|
+
}
|
330
|
+
end
|
331
|
+
|
332
|
+
# Create the dependency task
|
333
|
+
lDependenciesTask = "Dependencies_Calibration_#{lRecordedBaseName}".to_sym
|
334
|
+
|
335
|
+
desc "Compute dependencies to know if we need to calibrate tracks [#{iLstTracks.join(', ')}] recording."
|
336
|
+
task lDependenciesTask => lCalibrationInfoTarget do |iTask|
|
337
|
+
lCalibrationInfo = Rake::Task[iTask.prerequisites.first].data
|
338
|
+
# If the RMS values are different, we need to generate the calibrated file
|
339
|
+
lRecordedBaseName2 = iTask.name.match(/^Dependencies_Calibration_(.*)$/)[1]
|
340
|
+
lCalibrateContext = @Context[:Calibrate][lRecordedBaseName2]
|
341
|
+
lLstPrerequisitesFinalTask = [iTask.name]
|
342
|
+
if (lCalibrationInfo[:RMSRecording] != lCalibrationInfo[:RMSReference])
|
343
|
+
# Make the final task depend on the calibrated file
|
344
|
+
lLstPrerequisitesFinalTask << lCalibrateContext[:CalibratedFileName]
|
345
|
+
# Create the target that will generate the calibrated file.
|
346
|
+
|
347
|
+
desc "Generate calibrated recording for #{lRecordedBaseName2}"
|
348
|
+
file @Context[:Calibrate][lRecordedBaseName2][:CalibratedFileName] => [
|
349
|
+
@Context[:CleanFiles][lRecordedBaseName2][:NoiseGatedFileName],
|
350
|
+
lCalibrationInfoTarget
|
351
|
+
] do |iTask2|
|
352
|
+
iRecordedFileName, iCalibrationInfoTarget = iTask2.prerequisites
|
353
|
+
lCalibrationInfo = Rake::Task[iCalibrationInfoTarget].data
|
354
|
+
# If the Recording is louder, apply a volume reduction
|
355
|
+
if (lCalibrationInfo[:RMSRecording] < lCalibrationInfo[:RMSReference])
|
356
|
+
# Here we are loosing quality: we need to increase the recording volume.
|
357
|
+
# Notify the user about it.
|
358
|
+
lDBValue, lPCValue = val2db(lCalibrationInfo[:RMSReference]-lCalibrationInfo[:RMSRecording], lCalibrationInfo[:MaxValue])
|
359
|
+
log_warn "Tracks [#{iLstTracks.join(', ')}] should be recorded louder (at least #{lDBValue} db <=> #{lPCValue} %)."
|
360
|
+
end
|
361
|
+
wsk(iRecordedFileName, iTask2.name, 'Multiply', "--coeff \"#{lCalibrationInfo[:RMSReference]}/#{lCalibrationInfo[:RMSRecording]}\"")
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
Rake::Task[lCalibrateContext[:FinalTask]].prerequisites.replace(lLstPrerequisitesFinalTask)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Make the final task depend on this dependency task
|
369
|
+
lCalibrateFinalTask = "Calibrate_#{iLstTracks.join('_')}".to_sym
|
370
|
+
lLstCalibrateTasks << lCalibrateFinalTask
|
371
|
+
@Context[:Calibrate][lRecordedBaseName] = {
|
372
|
+
:FinalTask => lCalibrateFinalTask,
|
373
|
+
:CalibratedFileName => getCalibratedFileName(lRecordedBaseName)
|
374
|
+
}
|
375
|
+
|
376
|
+
desc "Calibrate tracks [#{iLstTracks.join(', ')}] recording."
|
377
|
+
task lCalibrateFinalTask => lDependenciesTask
|
378
|
+
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
end
|
383
|
+
end
|
384
|
+
# Generate global task
|
385
|
+
|
386
|
+
desc 'Calibrate recordings needing it'
|
387
|
+
task :CalibrateRecordings => lLstCalibrateTasks
|
388
|
+
|
389
|
+
@Context[:RakeSetupFor_CalibrateRecordings] = true
|
390
|
+
end
|
391
|
+
|
392
|
+
# Generate rake targets for processing source files
|
393
|
+
def generateRakeFor_ProcessSourceFiles
|
394
|
+
if (!@Context[:RakeSetupFor_CalibrateRecordings])
|
395
|
+
generateRakeFor_CalibrateRecordings
|
396
|
+
end
|
397
|
+
|
398
|
+
# List of process tasks
|
399
|
+
# list< Symbol >
|
400
|
+
lLstProcessTasks = []
|
401
|
+
|
402
|
+
# 1. Handle recordings
|
403
|
+
lRecordingsConf = @RecordConf[:Recordings]
|
404
|
+
if (lRecordingsConf != nil)
|
405
|
+
# Read global processes and environment processes to be applied before and after recordings
|
406
|
+
lGlobalProcesses_Before = lRecordingsConf[:GlobalProcesses_Before] || []
|
407
|
+
lGlobalProcesses_After = lRecordingsConf[:GlobalProcesses_After] || []
|
408
|
+
lEnvProcesses_Before = lRecordingsConf[:EnvProcesses_Before] || {}
|
409
|
+
lEnvProcesses_After = lRecordingsConf[:EnvProcesses_After] || {}
|
410
|
+
lTracksConf = lRecordingsConf[:Tracks]
|
411
|
+
if (lTracksConf != nil)
|
412
|
+
lTracksConf.each do |iLstTracks, iRecordingConf|
|
413
|
+
lRecEnv = iRecordingConf[:Env]
|
414
|
+
# Compute the list of processes to apply
|
415
|
+
lEnvProcesses_RecordingBefore = lEnvProcesses_Before[lRecEnv] || []
|
416
|
+
lEnvProcesses_RecordingAfter = lEnvProcesses_After[lRecEnv] || []
|
417
|
+
lRecordingProcesses = iRecordingConf[:Processes] || []
|
418
|
+
# Optimize the list
|
419
|
+
lLstProcesses = optimizeProcesses(lGlobalProcesses_Before + lEnvProcesses_RecordingBefore + lRecordingProcesses + lEnvProcesses_RecordingAfter + lGlobalProcesses_After)
|
420
|
+
# Get the file name to apply processes to
|
421
|
+
lRecordedBaseName = File.basename(getRecordedFileName(lRecEnv, iLstTracks))[0..-5]
|
422
|
+
# Create the target that gives the name of the final wave file, and make it depend on the Calibration.Info target only if calibration might be needed
|
423
|
+
lPrerequisites = []
|
424
|
+
lPrerequisites << "#{lRecordedBaseName}.Calibration.info".to_sym if (iRecordingConf[:CalibrateWithEnv] != nil)
|
425
|
+
lFinalBeforeMixTarget = "FinalBeforeMix_Recording_#{lRecordedBaseName}".to_sym
|
426
|
+
|
427
|
+
desc "Get final wave file name for recording #{lRecordedBaseName}"
|
428
|
+
task lFinalBeforeMixTarget => lPrerequisites do |iTask|
|
429
|
+
lRecordedBaseName2 = iTask.name.match(/^FinalBeforeMix_Recording_(.*)$/)[1]
|
430
|
+
# Get the name of the file that may be processed
|
431
|
+
# Set the cleaned file as a default
|
432
|
+
lFileNameToProcess = getNoiseGateFileName(lRecordedBaseName2)
|
433
|
+
if (!iTask.prerequisites.empty?)
|
434
|
+
lCalibrationInfo = Rake::Task[iTask.prerequisites.first].data
|
435
|
+
if (lCalibrationInfo[:RMSReference] != lCalibrationInfo[:RMSRecording])
|
436
|
+
# Apply processes on the calibrated file
|
437
|
+
lFileNameToProcess = getCalibratedFileName(lRecordedBaseName2)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
# By default, the final name is the one to be processed
|
441
|
+
lFinalFileName = lFileNameToProcess
|
442
|
+
# Get the list of processes from the context
|
443
|
+
if (@Context[:Processes][lRecordedBaseName2] != nil)
|
444
|
+
# Processing has to be performed
|
445
|
+
# Now generate the whole branch of targets to process the choosen file
|
446
|
+
lFinalFileName = generateRakeForProcesses(@Context[:Processes][lRecordedBaseName2][:LstProcesses], lFileNameToProcess, getProcessesRecordDir)
|
447
|
+
end
|
448
|
+
iTask.data = {
|
449
|
+
:FileName => lFinalFileName
|
450
|
+
}
|
451
|
+
end
|
452
|
+
|
453
|
+
if (!lLstProcesses.empty?)
|
454
|
+
# Generate the Dependencies task, and make it depend on the target creating the processing chain
|
455
|
+
lDependenciesTask = "Dependencies_ProcessRecord_#{lRecordedBaseName}".to_sym
|
456
|
+
|
457
|
+
desc "Create the targets needed to process tracks [#{iLstTracks.join(', ')}]"
|
458
|
+
task lDependenciesTask => lFinalBeforeMixTarget do |iTask|
|
459
|
+
lRecordedBaseName2 = iTask.name.match(/^Dependencies_ProcessRecord_(.*)$/)[1]
|
460
|
+
# Make the final task depend on the processed file
|
461
|
+
Rake::Task[@Context[:Processes][lRecordedBaseName2][:FinalTask]].prerequisites.replace([
|
462
|
+
iTask.name,
|
463
|
+
Rake::Task[iTask.prerequisites.first].data[:FileName]
|
464
|
+
])
|
465
|
+
end
|
466
|
+
|
467
|
+
# Make the final task depend on the Dependencies task only for the beginning
|
468
|
+
lFinalTask = "ProcessRecord_#{iLstTracks.join('_')}".to_sym
|
469
|
+
lLstProcessTasks << lFinalTask
|
470
|
+
|
471
|
+
desc "Apply processes to recording #{lRecordedBaseName}"
|
472
|
+
task lFinalTask => lDependenciesTask
|
473
|
+
|
474
|
+
@Context[:Processes][lRecordedBaseName] = {
|
475
|
+
:LstProcesses => lLstProcesses,
|
476
|
+
:FinalTask => lFinalTask
|
477
|
+
}
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# 2. Handle Wave files
|
484
|
+
lWaveFilesConf = @RecordConf[:WaveFiles]
|
485
|
+
if (lWaveFilesConf != nil)
|
486
|
+
lGlobalProcesses_Before = lWaveFilesConf[:GlobalProcesses_Before] || []
|
487
|
+
lGlobalProcesses_After = lWaveFilesConf[:GlobalProcesses_After] || []
|
488
|
+
lLstWaveInfo = lWaveFilesConf[:FilesList]
|
489
|
+
if (lLstWaveInfo != nil)
|
490
|
+
lLstWaveInfo.each do |iWaveInfo|
|
491
|
+
lWaveProcesses = iWaveInfo[:Processes] || []
|
492
|
+
if (iWaveInfo[:Position] != nil)
|
493
|
+
lWaveProcesses << {
|
494
|
+
:Name => 'SilenceInserter',
|
495
|
+
:Begin => iWaveInfo[:Position],
|
496
|
+
:End => 0
|
497
|
+
}
|
498
|
+
end
|
499
|
+
# Optimize the list
|
500
|
+
lLstProcesses = optimizeProcesses(lGlobalProcesses_Before + lWaveProcesses + lGlobalProcesses_After)
|
501
|
+
lFinalBeforeMixTarget = "FinalBeforeMix_Wave_#{iWaveInfo[:Name]}"
|
502
|
+
|
503
|
+
desc "Get final wave file name for Wave #{iWaveInfo[:Name]}"
|
504
|
+
task lFinalBeforeMixTarget do |iTask|
|
505
|
+
lWaveName = iTask.name.match(/^FinalBeforeMix_Wave_(.*)$/)[1]
|
506
|
+
# By default, use the original Wave file
|
507
|
+
lFinalFileName = getWaveSourceFileName(lWaveName)
|
508
|
+
if (@Context[:WaveProcesses][lWaveName] != nil)
|
509
|
+
# Generate rake tasks for processing the clean recorded file.
|
510
|
+
lFinalFileName = generateRakeForProcesses(@Context[:WaveProcesses][lWaveName][:LstProcesses], lFinalFileName, getProcessesWaveDir)
|
511
|
+
end
|
512
|
+
iTask.data = {
|
513
|
+
:FileName => lFinalFileName
|
514
|
+
}
|
515
|
+
end
|
516
|
+
|
517
|
+
if (!lLstProcesses.empty?)
|
518
|
+
# Generate the Dependencies task, and make it depend on the target creating the processing chain
|
519
|
+
lDependenciesTask = "Dependencies_ProcessWave_#{iWaveInfo[:Name]}".to_sym
|
520
|
+
|
521
|
+
desc "Create the targets needed to process Wave #{iWaveInfo[:Name]}"
|
522
|
+
task lDependenciesTask => lFinalBeforeMixTarget do |iTask|
|
523
|
+
lWaveName = iTask.name.match(/^Dependencies_ProcessWave_(.*)$/)[1]
|
524
|
+
# Make the final task depend on the processed file
|
525
|
+
Rake::Task[@Context[:WaveProcesses][lWaveName][:FinalTask]].prerequisites.replace([
|
526
|
+
iTask.name,
|
527
|
+
Rake::Task[iTask.prerequisites.first].data[:FileName]
|
528
|
+
])
|
529
|
+
end
|
530
|
+
|
531
|
+
# Make the final task depend on the Dependencies task only for the beginning
|
532
|
+
lFinalTask = "ProcessWave_#{iWaveInfo[:Name]}".to_sym
|
533
|
+
lLstProcessTasks << lFinalTask
|
534
|
+
|
535
|
+
desc "Apply processes to Wave #{iWaveInfo[:Name]}"
|
536
|
+
task lFinalTask => lDependenciesTask
|
537
|
+
|
538
|
+
@Context[:WaveProcesses][iWaveInfo[:Name]] = {
|
539
|
+
:LstProcesses => lLstProcesses,
|
540
|
+
:FinalTask => lFinalTask
|
541
|
+
}
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# 3. Generate global task
|
548
|
+
|
549
|
+
desc 'Process source files'
|
550
|
+
task :ProcessSourceFiles => lLstProcessTasks
|
551
|
+
|
552
|
+
@Context[:RakeSetupFor_ProcessSourceFiles] = true
|
553
|
+
end
|
554
|
+
|
555
|
+
# Generate rake targets for the mix
|
556
|
+
def generateRakeFor_Mix
|
557
|
+
if (!@Context[:RakeSetupFor_ProcessSourceFiles])
|
558
|
+
generateRakeFor_ProcessSourceFiles
|
559
|
+
end
|
560
|
+
|
561
|
+
lMixConf = @RecordConf[:Mix]
|
562
|
+
if (lMixConf != nil)
|
563
|
+
|
564
|
+
# Create a map of all possible TrackIDs, with their corresponding target containing the file name as part of its data
|
565
|
+
# map< Object, Symbol >
|
566
|
+
lFinalSources = {}
|
567
|
+
lRecordingsConf = @RecordConf[:Recordings]
|
568
|
+
if (lRecordingsConf != nil)
|
569
|
+
lTracksConf = lRecordingsConf[:Tracks]
|
570
|
+
if (lTracksConf != nil)
|
571
|
+
lTracksConf.each do |iLstTracks, iTrackInfo|
|
572
|
+
associateSourceTarget(iLstTracks, iTrackInfo, "FinalBeforeMix_Recording_#{File.basename(getRecordedFileName(iTrackInfo[:Env], iLstTracks))[0..-5]}".to_sym, lFinalSources)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
lWaveConf = @RecordConf[:WaveFiles]
|
577
|
+
if (lWaveConf != nil)
|
578
|
+
lFilesList = lWaveConf[:FilesList]
|
579
|
+
if (lFilesList != nil)
|
580
|
+
lFilesList.each do |iWaveInfo|
|
581
|
+
associateSourceTarget(iWaveInfo[:Name], iWaveInfo, "FinalBeforeMix_Wave_#{iWaveInfo[:Name]}".to_sym, lFinalSources)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
lMixConf.each do |iMixName, iMixInfo|
|
586
|
+
associateSourceTarget(iMixName, iMixInfo, "FinalMix_#{iMixName}".to_sym, lFinalSources)
|
587
|
+
end
|
588
|
+
log_debug "List of mix final sources:\n#{lFinalSources.pretty_inspect}"
|
589
|
+
|
590
|
+
# Use this info to generate needed targets
|
591
|
+
lLstTargets = []
|
592
|
+
lMixConf.keys.sort.each do |iMixName|
|
593
|
+
lLstTargets << generateRakeForMix(iMixName, lFinalSources) if (@Context[:LstMixNames].empty?) or (@Context[:LstMixNames].include?(iMixName))
|
594
|
+
end
|
595
|
+
|
596
|
+
desc 'Produce all mixes'
|
597
|
+
task :Mix => lLstTargets
|
598
|
+
|
599
|
+
@Context[:FinalMixSources] = lFinalSources
|
600
|
+
end
|
601
|
+
|
602
|
+
@Context[:RakeSetupFor_Mix] = true
|
603
|
+
end
|
604
|
+
|
605
|
+
# Generate rake targets for the deliverables
|
606
|
+
def generateRakeFor_Deliver
|
607
|
+
if (!@Context[:RakeSetupFor_Mix])
|
608
|
+
generateRakeFor_Mix
|
609
|
+
end
|
610
|
+
lLstTargets = []
|
611
|
+
lDeliverConf = @RecordConf[:Deliver]
|
612
|
+
if (lDeliverConf != nil)
|
613
|
+
lDeliverablesConf = lDeliverConf[:Deliverables]
|
614
|
+
if (lDeliverablesConf != nil)
|
615
|
+
# Use this info to generate needed targets
|
616
|
+
lDeliverablesConf.keys.sort.each do |iDeliverableName|
|
617
|
+
lLstTargets << generateRakeForDeliver(iDeliverableName) if (@Context[:LstDeliverableNames].empty?) or (@Context[:LstDeliverableNames].include?(iDeliverableName))
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
desc 'Produce all deliverables'
|
623
|
+
task :Deliver => lLstTargets
|
624
|
+
|
625
|
+
end
|
626
|
+
|
627
|
+
private
|
628
|
+
|
629
|
+
# Associate a given name and associated info to a given target.
|
630
|
+
# Take the map to complete as a parameter.
|
631
|
+
#
|
632
|
+
# Parameters::
|
633
|
+
# * *iInitialID* (_Object_): Initial ID to be associated
|
634
|
+
# * *iInfo* (<em>map<Symbol,Object></em>): Info associated to the initial ID, that can be used to get aliases
|
635
|
+
# * *iTargetName* (_Symbol_): Target to associate the ID and its aliases to
|
636
|
+
# * *oFinalSources* (<em>map<Object,Symbol></em>): The map to complete with the associations
|
637
|
+
def associateSourceTarget(iInitialID, iInfo, iTargetName, oFinalSources)
|
638
|
+
# Get aliases
|
639
|
+
lNames = [ iInitialID ]
|
640
|
+
if (iInfo[:Alias] != nil)
|
641
|
+
if (iInfo[:Alias].is_a?(Array))
|
642
|
+
lNames.concat(iInfo[:Alias])
|
643
|
+
else
|
644
|
+
lNames << iInfo[:Alias]
|
645
|
+
end
|
646
|
+
end
|
647
|
+
lNames.each do |iName|
|
648
|
+
oFinalSources[iName] = iTargetName
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
# Generate rake targets to clean a recorded file
|
653
|
+
#
|
654
|
+
# Parameters::
|
655
|
+
# * *iBaseName* (_String_): The base name (without extension) of the recorded file
|
656
|
+
# * *iEnv* (_Symbol_): The environment in which this file has been recorded
|
657
|
+
# * *iCutInfo* (<em>[String,String]</em>): The cut information, used to extract only a part of the file (begin and end markers, in seconds or samples) [optional = nil]
|
658
|
+
# Return::
|
659
|
+
# * _Symbol_: Name of the entering task for this generation process
|
660
|
+
def generateRakeForCleaningRecordedFile(iBaseName, iEnv, iCutInfo = nil)
|
661
|
+
# 1. Create all needed analysis files
|
662
|
+
lRecordedSilenceFileName = getRecordedSilenceFileName(iEnv)
|
663
|
+
lRecordedSilenceBaseName = File.basename(lRecordedSilenceFileName)[0..-5]
|
664
|
+
|
665
|
+
lAnalyzeSilenceFileName = getRecordedAnalysisFileName(lRecordedSilenceBaseName)
|
666
|
+
if (!Rake::Task.task_defined?(lAnalyzeSilenceFileName))
|
667
|
+
|
668
|
+
desc "Generate analysis of silence file for environment #{iEnv}"
|
669
|
+
file lAnalyzeSilenceFileName => lRecordedSilenceFileName do |iTask|
|
670
|
+
analyzeFile(iTask.prerequisites[0], iTask.name)
|
671
|
+
end
|
672
|
+
|
673
|
+
end
|
674
|
+
lFFTProfileSilenceFileName = getRecordedFFTProfileFileName(lRecordedSilenceBaseName)
|
675
|
+
if (!Rake::Task.task_defined?(lFFTProfileSilenceFileName))
|
676
|
+
|
677
|
+
desc "Generate FFT profile of silence file for environment #{iEnv}"
|
678
|
+
file lFFTProfileSilenceFileName => lRecordedSilenceFileName do |iTask|
|
679
|
+
fftProfileFile(iTask.prerequisites[0], iTask.name)
|
680
|
+
end
|
681
|
+
|
682
|
+
end
|
683
|
+
lAnalyzeRecordedFileName = getRecordedAnalysisFileName(iBaseName)
|
684
|
+
lRecordedFileName = "#{getRecordedDir}/#{iBaseName}.wav"
|
685
|
+
|
686
|
+
desc "Generate analysis of file #{lRecordedFileName}"
|
687
|
+
file lAnalyzeRecordedFileName => lRecordedFileName do |iTask|
|
688
|
+
analyzeFile(iTask.prerequisites[0], iTask.name)
|
689
|
+
end
|
690
|
+
|
691
|
+
# 2. Remove silences from the beginning and the end
|
692
|
+
lSilenceRemovedFileName = getSilenceRemovedFileName(iBaseName)
|
693
|
+
|
694
|
+
desc "Remove silences from beginning and end of file #{lRecordedFileName}"
|
695
|
+
file lSilenceRemovedFileName => [ lRecordedFileName, lAnalyzeRecordedFileName, lFFTProfileSilenceFileName, lAnalyzeSilenceFileName ] do |iTask|
|
696
|
+
iRecordedFileName, iAnalyzeRecordedFileName, iFFTProfileSilenceFileName, iAnalyzeSilenceFileName = iTask.prerequisites
|
697
|
+
|
698
|
+
# Get DC offset from the recorded file
|
699
|
+
lOffset, lDCOffsets = getDCOffsets(iAnalyzeRecordedFileName)
|
700
|
+
# Get thresholds (without DC offsets) from the silence file
|
701
|
+
lSilenceThresholds = getThresholds(iAnalyzeSilenceFileName, :margin => @MusicMasterConf[:Clean][:MarginSilenceThresholds])
|
702
|
+
# Get the thresholds with the recorded DC offset, and prepare them to be given to wsk
|
703
|
+
lLstStrSilenceThresholdsWithDC = shiftThresholdsByDCOffset(lSilenceThresholds, lDCOffsets).map { |iSilenceThresholdInfo| iSilenceThresholdInfo.join(',') }
|
704
|
+
|
705
|
+
# Call wsk
|
706
|
+
wsk(iRecordedFileName, iTask.name, 'SilenceRemover', "--silencethreshold \"#{lLstStrSilenceThresholdsWithDC.join('|')}\" --attack 0 --release #{@MusicMasterConf[:Clean][:SilenceMin]} --noisefft \"#{iFFTProfileSilenceFileName}\"")
|
707
|
+
end
|
708
|
+
|
709
|
+
# Cut the file if needed
|
710
|
+
lFramedFileName = nil
|
711
|
+
if (iCutInfo == nil)
|
712
|
+
lFramedFileName = lSilenceRemovedFileName
|
713
|
+
else
|
714
|
+
lFramedFileName = getCutFileName(iBaseName, iCutInfo)
|
715
|
+
|
716
|
+
desc "Extract sample [#{iCutInfo.join(', ')}] from file #{lSilenceRemovedFileName}"
|
717
|
+
file lFramedFileName => lSilenceRemovedFileName do |iTask|
|
718
|
+
wsk(iTask.prerequisites.first, iTask.name, 'Cut', "--begin \"#{iCutInfo[0]}\" --end \"#{iCutInfo[1]}\"")
|
719
|
+
end
|
720
|
+
|
721
|
+
end
|
722
|
+
lDCRemovedFileName = getDCRemovedFileName(iBaseName)
|
723
|
+
# Create a target that will change the dependencies of the noise gate dynamically
|
724
|
+
lNoiseGatedFileName = getNoiseGateFileName(iBaseName)
|
725
|
+
lDependenciesNoiseGateTaskName = "Dependencies_NoiseGate_#{iBaseName}".to_sym
|
726
|
+
|
727
|
+
desc "Compute NoiseGate dependencies for file #{lNoiseGatedFileName}"
|
728
|
+
task lDependenciesNoiseGateTaskName => lAnalyzeRecordedFileName do |iTask|
|
729
|
+
# Get the basename from the task name
|
730
|
+
lBaseName = iTask.name.match(/^Dependencies_NoiseGate_(.*)$/)[1]
|
731
|
+
lRecordedAnalysisFileName = iTask.prerequisites.first
|
732
|
+
# Get DC offset from the recorded file
|
733
|
+
lOffset, lDCOffsets = getDCOffsets(lRecordedAnalysisFileName)
|
734
|
+
lSourceFileName = nil
|
735
|
+
if (lOffset)
|
736
|
+
log_debug "Noise gated file #{lNoiseGatedFileName} will depend on a DC shifted recording."
|
737
|
+
lSourceFileName = @Context[:CleanFiles][lBaseName][:DCRemovedFileName]
|
738
|
+
# Create the corresponding task removing the DC offset
|
739
|
+
|
740
|
+
desc "Remove DC offset from file #{lRecordedAnalysisFileName}"
|
741
|
+
file lSourceFileName => [ lRecordedAnalysisFileName, @Context[:CleanFiles][lBaseName][:FramedFileName] ] do |iDCRemoveTask|
|
742
|
+
iRecordedAnalysisFileName, iFramedFileName = iDCRemoveTask.prerequisites
|
743
|
+
lOffset2, lDCOffsets2 = getDCOffsets(iRecordedAnalysisFileName)
|
744
|
+
wsk(iFramedFileName, iDCRemoveTask.name, 'DCShifter', "--offset \"#{lDCOffsets2.map { |iValue| -iValue }.join('|')}\"")
|
745
|
+
end
|
746
|
+
|
747
|
+
else
|
748
|
+
log_debug "Noise gated file #{lNoiseGatedFileName} does not depend on a DC shifted recording."
|
749
|
+
lSourceFileName = @Context[:CleanFiles][lBaseName][:FramedFileName]
|
750
|
+
end
|
751
|
+
# Set prerequisites for the task generating Noise Gate
|
752
|
+
Rake::Task[@Context[:CleanFiles][lBaseName][:NoiseGatedFileName]].prerequisites.replace([
|
753
|
+
iTask.name,
|
754
|
+
lSourceFileName,
|
755
|
+
lRecordedAnalysisFileName,
|
756
|
+
@Context[:CleanFiles][lBaseName][:SilenceAnalysisFileName],
|
757
|
+
@Context[:CleanFiles][lBaseName][:SilenceFFTProfileFileName]
|
758
|
+
])
|
759
|
+
end
|
760
|
+
|
761
|
+
# Create the Noise Gate file generation target.
|
762
|
+
# By default it depends only on the corresponding dependencies task, but its execution will modify its prerequisites.
|
763
|
+
|
764
|
+
desc "Apply Noise Gate to recorded file based on #{iBaseName}"
|
765
|
+
file lNoiseGatedFileName => lDependenciesNoiseGateTaskName do |iTask|
|
766
|
+
# Prerequisites list has been setup by the first prerequisite execution
|
767
|
+
iSourceFileName, iRecordedAnalysisFileName, iSilenceAnalysisFileName, iSilenceFFTProfileFileName = iTask.prerequisites[1..4]
|
768
|
+
# Get thresholds (without DC offsets) from the silence file
|
769
|
+
lSilenceThresholds = getThresholds(iSilenceAnalysisFileName, :margin => @MusicMasterConf[:Clean][:MarginSilenceThresholds])
|
770
|
+
lLstStrSilenceThresholds = lSilenceThresholds.map { |iThreshold| iThreshold.join(',') }
|
771
|
+
wsk(iSourceFileName, iTask.name, 'NoiseGate', "--silencethreshold \"#{lLstStrSilenceThresholds.join('|')}\" --attack #{@MusicMasterConf[:Clean][:Attack]} --release #{@MusicMasterConf[:Clean][:Release]} --silencemin #{@MusicMasterConf[:Clean][:SilenceMin]} --noisefft \"#{iSilenceFFTProfileFileName}\"")
|
772
|
+
end
|
773
|
+
|
774
|
+
# Create embracing task
|
775
|
+
rFinalTaskName = "CleanRecord_#{iBaseName}".to_sym
|
776
|
+
|
777
|
+
desc "Clean recorded file #{iBaseName}"
|
778
|
+
task rFinalTaskName => lNoiseGatedFileName
|
779
|
+
|
780
|
+
# Set context for this file to be cleaned
|
781
|
+
@Context[:CleanFiles][iBaseName] = {
|
782
|
+
:SilenceAnalysisFileName => lAnalyzeSilenceFileName,
|
783
|
+
:SilenceFFTProfileFileName => lFFTProfileSilenceFileName,
|
784
|
+
:FramedFileName => lFramedFileName,
|
785
|
+
:DCRemovedFileName => lDCRemovedFileName,
|
786
|
+
:NoiseGatedFileName => lNoiseGatedFileName
|
787
|
+
}
|
788
|
+
|
789
|
+
return rFinalTaskName
|
790
|
+
end
|
791
|
+
|
792
|
+
# Generate rake rules to apply processes to a given Wave file.
|
793
|
+
# Return the name of the last file.
|
794
|
+
#
|
795
|
+
# Parameters::
|
796
|
+
# * *iProcesses* (<em>list<map<Symbol,Object>></em>): List of processes to apply
|
797
|
+
# * *iFileName* (_String_): File name to apply processes to
|
798
|
+
# * *iDir* (_String_): The directory where processed files are stored
|
799
|
+
def generateRakeForProcesses(iProcesses, iFileName, iDir)
|
800
|
+
rLastFileName = iFileName
|
801
|
+
|
802
|
+
lFileNameNoExt = File.basename(iFileName[0..-5])
|
803
|
+
iProcesses.each_with_index do |iProcessInfo, iIdxProcess|
|
804
|
+
lProcessName = iProcessInfo[:Name]
|
805
|
+
lProcessParams = iProcessInfo.clone.delete_if { |iKey, iValue| (iKey == :Name) }
|
806
|
+
access_plugin('Processes', lProcessName) do |ioActionPlugin|
|
807
|
+
# Set the MusicMaster configuration as an instance variable of the plugin also
|
808
|
+
ioActionPlugin.instance_variable_set(:@MusicMasterConf, @MusicMasterConf)
|
809
|
+
# Add Utils to the plugin namespace
|
810
|
+
ioActionPlugin.class.module_eval('include MusicMaster::Utils')
|
811
|
+
lCurrentFileName = rLastFileName
|
812
|
+
rLastFileName = getProcessedFileName(iDir, lFileNameNoExt, iIdxProcess, lProcessName, lProcessParams)
|
813
|
+
|
814
|
+
desc "Process file #{lCurrentFileName} with #{lProcessName}"
|
815
|
+
file rLastFileName => [lCurrentFileName] do |iTask|
|
816
|
+
log_info "===== Apply Process #{iProcessInfo[:Name]} to #{iTask.name} ====="
|
817
|
+
FileUtils::mkdir_p(File.dirname(iTask.name))
|
818
|
+
ioActionPlugin.execute(iTask.prerequisites.first, iTask.name, '.', lProcessParams)
|
819
|
+
end
|
820
|
+
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
return rLastFileName
|
825
|
+
end
|
826
|
+
|
827
|
+
# Generate all needed targets to produce a given mix
|
828
|
+
#
|
829
|
+
# Parameters::
|
830
|
+
# * *iMixName* (_String_): The name of the mix to produce targets for
|
831
|
+
# * *iFinalSources* (<em>map<Object,String></em>): The set of possible sources, per Track ID (can be alias, mix name, tracks list, wave name)
|
832
|
+
# Return::
|
833
|
+
# * _Symbol_: Name of the top-level target producing the mix
|
834
|
+
def generateRakeForMix(iMixName, iFinalSources)
|
835
|
+
rTarget = "Mix_#{iMixName}".to_sym
|
836
|
+
|
837
|
+
# If the target already exists, do nothing
|
838
|
+
if (!Rake::Task.task_defined?(rTarget))
|
839
|
+
lDependenciesTarget = "Dependencies_Mix_#{iMixName}".to_sym
|
840
|
+
lFinalMixTask = "FinalMix_#{iMixName}".to_sym
|
841
|
+
# Create the target being the symbolic link
|
842
|
+
lSymLinkFileName = getShortcutFileName(getFinalMixFileName(iMixName))
|
843
|
+
|
844
|
+
desc "Mix #{iMixName}"
|
845
|
+
task rTarget => lSymLinkFileName
|
846
|
+
|
847
|
+
desc "Symbolic link pointing to the mix file #{iMixName}"
|
848
|
+
file lSymLinkFileName => lDependenciesTarget do |iTask|
|
849
|
+
# Get the mix name from the name of the Dependencies target
|
850
|
+
lMixName = iTask.prerequisites[0].to_s.match(/^Dependencies_Mix_(.*)$/)[1]
|
851
|
+
FileUtils::mkdir_p(File.dirname(iTask.name))
|
852
|
+
createShortcut(iTask.prerequisites[1], getFinalMixFileName(lMixName))
|
853
|
+
end
|
854
|
+
|
855
|
+
desc "Dependencies needed to mix #{iMixName}"
|
856
|
+
task lDependenciesTarget => lFinalMixTask do |iTask|
|
857
|
+
lMixName = iTask.name.match(/^Dependencies_Mix_(.*)$/)[1]
|
858
|
+
|
859
|
+
# Modify the dependencies of the symbolic link
|
860
|
+
Rake::Task[getShortcutFileName(getFinalMixFileName(lMixName))].prerequisites.replace([
|
861
|
+
iTask.name,
|
862
|
+
Rake::Task[iTask.prerequisites.first].data[:FileName]
|
863
|
+
])
|
864
|
+
end
|
865
|
+
|
866
|
+
# Use the corresponding final mix task to create the whole processing chain
|
867
|
+
# First, compute dependencies of the final mix task
|
868
|
+
lLstDeps = []
|
869
|
+
@RecordConf[:Mix][iMixName][:Tracks].keys.sort.each do |iTrackID|
|
870
|
+
raise UnknownTrackIDError, "TrackID #{iTrackID} is not defined in the configuration for the mix." if (iFinalSources[iTrackID] == nil)
|
871
|
+
lLstDeps << iFinalSources[iTrackID]
|
872
|
+
end
|
873
|
+
|
874
|
+
desc "Create processing chain for mix #{iMixName}"
|
875
|
+
task lFinalMixTask => lLstDeps do |iTask|
|
876
|
+
# This task is responsible for creating the whole processing chain from the source files (taken from prerequisites' data), and storing the top-level file name as its data.
|
877
|
+
lMixName = iTask.name.match(/^FinalMix_(.*)$/)[1]
|
878
|
+
lMixConf = @RecordConf[:Mix][lMixName]
|
879
|
+
lFinalMixFileName = nil
|
880
|
+
if (lMixConf[:Tracks].size == 1)
|
881
|
+
# Just 1 source for this mix
|
882
|
+
lTrackID = lMixConf[:Tracks].keys.first
|
883
|
+
lTrackInfo = lMixConf[:Tracks][lTrackID]
|
884
|
+
lSourceFileName = Rake::Task[@Context[:FinalMixSources][lTrackID]].data[:FileName]
|
885
|
+
# Use all processes
|
886
|
+
lLstProcesses = []
|
887
|
+
lLstProcesses.concat(lTrackInfo[:Processes]) if (lTrackInfo[:Processes] != nil)
|
888
|
+
lLstProcesses.concat(lMixConf[:Processes]) if (lMixConf[:Processes] != nil)
|
889
|
+
lLstProcesses = optimizeProcesses(lLstProcesses)
|
890
|
+
if (lLstProcesses.empty?)
|
891
|
+
# Nothing to do
|
892
|
+
lFinalMixFileName = lSourceFileName
|
893
|
+
else
|
894
|
+
lFinalMixFileName = generateRakeForProcesses(lLstProcesses, lSourceFileName, getMixDir)
|
895
|
+
end
|
896
|
+
else
|
897
|
+
# Here, there will be a step of mixing files
|
898
|
+
# 1. Process source files if needed
|
899
|
+
lLstProcessedSourceFiles = []
|
900
|
+
lMixConf[:Tracks].keys.sort.each do |iTrackID|
|
901
|
+
lTrackInfo = lMixConf[:Tracks][iTrackID]
|
902
|
+
# Get the source file for this track ID
|
903
|
+
lSourceFileName = Rake::Task[@Context[:FinalMixSources][iTrackID]].data[:FileName]
|
904
|
+
# By default it will be the processed file name
|
905
|
+
lProcessedFileName = lSourceFileName
|
906
|
+
# Get the list of processes to apply to it
|
907
|
+
if (lTrackInfo[:Processes] != nil)
|
908
|
+
lLstProcesses = optimizeProcesses(lTrackInfo[:Processes])
|
909
|
+
if (!lLstProcesses.empty?)
|
910
|
+
lProcessedFileName = generateRakeForProcesses(lLstProcesses, lSourceFileName, getMixDir)
|
911
|
+
end
|
912
|
+
end
|
913
|
+
lLstProcessedSourceFiles << lProcessedFileName
|
914
|
+
end
|
915
|
+
# 2. Mix all resulting files
|
916
|
+
lFinalMixFileName = getMixFileName(getMixDir, lMixName, lMixConf[:Tracks])
|
917
|
+
|
918
|
+
desc "Mix all processed sources for mix #{lMixName}"
|
919
|
+
file lFinalMixFileName => lLstProcessedSourceFiles do |iTask2|
|
920
|
+
lMixInputFile = iTask2.prerequisites.first
|
921
|
+
lLstMixFiles = iTask2.prerequisites[1..-1]
|
922
|
+
wsk(lMixInputFile, iTask2.name, 'Mix', "--files \"#{lLstMixFiles.join('|1|')}|1\" ")
|
923
|
+
end
|
924
|
+
|
925
|
+
# 3. Process the mix result
|
926
|
+
if (lMixConf[:Processes] != nil)
|
927
|
+
lLstProcesses = optimizeProcesses(lMixConf[:Processes])
|
928
|
+
if (!lLstProcesses.empty?)
|
929
|
+
lFinalMixFileName = generateRakeForProcesses(lLstProcesses, lFinalMixFileName, getMixDir)
|
930
|
+
end
|
931
|
+
end
|
932
|
+
end
|
933
|
+
log_info "File produced from the mix #{lMixName}: #{lFinalMixFileName}"
|
934
|
+
iTask.data = {
|
935
|
+
:FileName => lFinalMixFileName
|
936
|
+
}
|
937
|
+
end
|
938
|
+
|
939
|
+
end
|
940
|
+
|
941
|
+
return rTarget
|
942
|
+
end
|
943
|
+
|
944
|
+
# Generate all needed targets to produce a given deliverable
|
945
|
+
#
|
946
|
+
# Parameters::
|
947
|
+
# * *iDeliverableName* (_String_): The name of the deliverable to produce targets for
|
948
|
+
# Return::
|
949
|
+
# * _Symbol_: Name of the top-level target producing the deliverable
|
950
|
+
def generateRakeForDeliver(iDeliverableName)
|
951
|
+
rTarget = "Deliver_#{iDeliverableName}".to_sym
|
952
|
+
|
953
|
+
lDeliverableConf = @RecordConf[:Deliver][:Deliverables][iDeliverableName]
|
954
|
+
|
955
|
+
# Get metadata
|
956
|
+
# Default values
|
957
|
+
lMetadata = {
|
958
|
+
:FileName => 'Track'
|
959
|
+
}
|
960
|
+
lMetadata.merge!(@RecordConf[:Deliver][:Metadata]) if (@RecordConf[:Deliver][:Metadata] != nil)
|
961
|
+
lMetadata.merge!(lDeliverableConf[:Metadata]) if (lDeliverableConf[:Metadata] != nil)
|
962
|
+
|
963
|
+
# Get the format
|
964
|
+
# Default values
|
965
|
+
lFormatConf = {
|
966
|
+
:FileFormat => 'Wave'
|
967
|
+
}
|
968
|
+
lFormatConf.merge!(@RecordConf[:Deliver][:Formats][lDeliverableConf[:Format]]) if (lDeliverableConf[:Format] != nil)
|
969
|
+
|
970
|
+
# Call the format plugin
|
971
|
+
access_plugin('Formats', lFormatConf[:FileFormat]) do |iFormatPlugin|
|
972
|
+
# Set the MusicMaster configuration as an instance variable of the plugin also
|
973
|
+
iFormatPlugin.instance_variable_set(:@MusicMasterConf, @MusicMasterConf)
|
974
|
+
# Create the final filename
|
975
|
+
# TODO: On Windows, when the format plugin creates a symbolic link, this target has a different name. Should create a virtual target storing the real name. Otherwise it will be always invoked every time.
|
976
|
+
lDeliverableFileName = "#{getDeliverDir}/#{get_valid_file_name(iDeliverableName)}/#{replace_vars(lMetadata[:FileName], lMetadata)}.#{iFormatPlugin.getFileExt}"
|
977
|
+
# Get the name of the mix file using the target computing it
|
978
|
+
lFinalMixTarget = "FinalMix_#{lDeliverableConf[:Mix]}".to_sym
|
979
|
+
# Use a dependency target to adapt the prerequisites of our deliverable
|
980
|
+
lDepTarget = "Dependencies_Deliver_#{iDeliverableName}".to_sym
|
981
|
+
|
982
|
+
desc "Compute dependencies for the deliverable #{iDeliverableName}"
|
983
|
+
task lDepTarget => lFinalMixTarget do |iTask|
|
984
|
+
lDeliverableName = iTask.name.match(/^Dependencies_Deliver_(.*)$/)[1]
|
985
|
+
Rake::Task[@Context[:Deliverables][lDeliverableName][:FileName]].prerequisites.replace([
|
986
|
+
iTask.name,
|
987
|
+
Rake::Task[iTask.prerequisites.first].data[:FileName]
|
988
|
+
])
|
989
|
+
end
|
990
|
+
|
991
|
+
desc "Produce file for deliverable #{iDeliverableName}"
|
992
|
+
file lDeliverableFileName => lDepTarget do |iTask|
|
993
|
+
FileUtils::mkdir_p(File.dirname(iTask.name))
|
994
|
+
iFormatPlugin.deliver(iTask.prerequisites[1], iTask.name, @Context[:DeliverableConf][iTask.name][0], @Context[:DeliverableConf][iTask.name][1])
|
995
|
+
end
|
996
|
+
|
997
|
+
desc "Deliver deliverable #{iDeliverableName}"
|
998
|
+
task rTarget => lDeliverableFileName
|
999
|
+
|
1000
|
+
@Context[:DeliverableConf][lDeliverableFileName] = [
|
1001
|
+
lFormatConf.delete_if { |iKey, iValue| iKey == :FileFormat },
|
1002
|
+
lMetadata
|
1003
|
+
]
|
1004
|
+
@Context[:Deliverables][iDeliverableName] = {
|
1005
|
+
:FileName => lDeliverableFileName
|
1006
|
+
}
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
return rTarget
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
end
|