MusicMaster 0.0.1.20101110 → 1.0.0.20120307

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/AUTHORS +4 -1
  2. data/ChangeLog +28 -0
  3. data/LICENSE +1 -1
  4. data/README +2 -5
  5. data/ReleaseInfo +8 -8
  6. data/bin/Calibrate.rb +55 -0
  7. data/bin/Clean.rb +55 -0
  8. data/bin/DBConvert.rb +3 -1
  9. data/bin/Deliver.rb +73 -42
  10. data/bin/Mix.rb +39 -78
  11. data/bin/Process.rb +55 -0
  12. data/bin/Record.rb +63 -116
  13. data/bin/{Album.rb → old/Album.rb} +18 -18
  14. data/bin/{AnalyzeAlbum.rb → old/AnalyzeAlbum.rb} +11 -11
  15. data/bin/{DeliverAlbum.rb → old/DeliverAlbum.rb} +12 -12
  16. data/bin/{Fct2Wave.rb → old/Fct2Wave.rb} +11 -11
  17. data/{album.conf.rb.example → bin/old/album.conf.rb.example} +0 -0
  18. data/lib/MusicMaster/FilesNamer.rb +253 -0
  19. data/lib/MusicMaster/Formats/MP3.rb +50 -0
  20. data/lib/MusicMaster/Formats/Test.rb +44 -0
  21. data/lib/MusicMaster/Formats/Wave.rb +80 -0
  22. data/lib/MusicMaster/Hash.rb +20 -0
  23. data/lib/MusicMaster/Launcher.rb +241 -0
  24. data/lib/MusicMaster/Processes/ApplyVolumeFct.rb +4 -8
  25. data/lib/MusicMaster/Processes/Compressor.rb +50 -47
  26. data/lib/MusicMaster/Processes/Custom.rb +3 -3
  27. data/lib/MusicMaster/Processes/{AddSilence.rb → Cut.rb} +8 -4
  28. data/lib/MusicMaster/Processes/CutFirstSignal.rb +6 -3
  29. data/lib/MusicMaster/Processes/DCShifter.rb +30 -0
  30. data/lib/MusicMaster/Processes/GVerb.rb +3 -2
  31. data/lib/MusicMaster/Processes/Normalize.rb +7 -6
  32. data/lib/MusicMaster/Processes/SilenceInserter.rb +31 -0
  33. data/lib/MusicMaster/Processes/Test.rb +39 -0
  34. data/lib/MusicMaster/Processes/VolCorrection.rb +3 -3
  35. data/lib/MusicMaster/RakeProcesses.rb +1014 -0
  36. data/lib/MusicMaster/Symbol.rb +12 -0
  37. data/lib/MusicMaster/Task.rb +29 -0
  38. data/lib/MusicMaster/Utils.rb +467 -0
  39. data/lib/MusicMaster/old/Common.rb +101 -0
  40. data/musicmaster.conf.rb.example +42 -59
  41. data/record.conf.rb.example +374 -30
  42. metadata +91 -41
  43. data/TODO +0 -3
  44. data/bin/Master.rb +0 -60
  45. data/bin/PrepareMix.rb +0 -422
  46. data/lib/MusicMaster/Common.rb +0 -197
  47. data/lib/MusicMaster/ConfLoader.rb +0 -56
  48. data/lib/MusicMaster/musicmaster.conf.rb +0 -96
  49. 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