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