MusicMaster 0.0.1.20101110
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/ChangeLog +5 -0
- data/Credits +6 -0
- data/LICENSE +31 -0
- data/README +18 -0
- data/ReleaseInfo +8 -0
- data/TODO +3 -0
- data/album.conf.rb.example +78 -0
- data/bin/Album.rb +81 -0
- data/bin/AnalyzeAlbum.rb +66 -0
- data/bin/DBConvert.rb +36 -0
- data/bin/Deliver.rb +42 -0
- data/bin/DeliverAlbum.rb +68 -0
- data/bin/Fct2Wave.rb +58 -0
- data/bin/Master.rb +60 -0
- data/bin/Mix.rb +99 -0
- data/bin/PrepareMix.rb +422 -0
- data/bin/Record.rb +145 -0
- data/lib/MusicMaster/Common.rb +197 -0
- data/lib/MusicMaster/ConfLoader.rb +56 -0
- data/lib/MusicMaster/Processes/AddSilence.rb +27 -0
- data/lib/MusicMaster/Processes/ApplyVolumeFct.rb +46 -0
- data/lib/MusicMaster/Processes/Compressor.rb +246 -0
- data/lib/MusicMaster/Processes/Custom.rb +31 -0
- data/lib/MusicMaster/Processes/CutFirstSignal.rb +27 -0
- data/lib/MusicMaster/Processes/GVerb.rb +30 -0
- data/lib/MusicMaster/Processes/Normalize.rb +51 -0
- data/lib/MusicMaster/Processes/VolCorrection.rb +30 -0
- data/lib/MusicMaster/musicmaster.conf.rb +96 -0
- data/master.conf.rb.example +13 -0
- data/musicmaster.conf.rb.example +95 -0
- data/record.conf.rb.example +45 -0
- metadata +104 -0
@@ -0,0 +1,197 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require 'rational'
|
8
|
+
|
9
|
+
module MusicMaster
|
10
|
+
|
11
|
+
# Call WSK
|
12
|
+
#
|
13
|
+
# Parameters:
|
14
|
+
# * *iInputFile* (_String_): The input file
|
15
|
+
# * *iOutputFile* (_String_): The output file
|
16
|
+
# * *iAction* (_String_): The action
|
17
|
+
# * *iParams* (_String_): Action parameters [optional = '']
|
18
|
+
def self.wsk(iInputFile, iOutputFile, iAction, iParams = '')
|
19
|
+
logInfo ''
|
20
|
+
logInfo "========== Processing #{iInputFile} ==#{iAction}==> #{iOutputFile} | #{iParams} ..."
|
21
|
+
system("#{$MusicMasterConf[:WSKCmdLine]} --input \"#{iInputFile}\" --output \"#{iOutputFile}\" --action #{iAction} -- #{iParams}")
|
22
|
+
lErrorCode = $?.exitstatus
|
23
|
+
if (lErrorCode == 0)
|
24
|
+
logInfo "========== Processing #{iInputFile} ==#{iAction}==> #{iOutputFile} | #{iParams} ... OK"
|
25
|
+
else
|
26
|
+
logErr "========== Processing #{iInputFile} ==#{iAction}==> #{iOutputFile} | #{iParams} ... ERROR #{lErrorCode}"
|
27
|
+
raise RuntimeError, "Processing #{iInputFile} ==#{iAction}==> #{iOutputFile} | #{iParams} ... ERROR #{lErrorCode}"
|
28
|
+
end
|
29
|
+
logInfo ''
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse plugins
|
33
|
+
def self.parsePlugins
|
34
|
+
require 'rUtilAnts/Plugins'
|
35
|
+
RUtilAnts::Plugins::initializePlugins
|
36
|
+
lLibDir = File.expand_path(File.dirname(__FILE__))
|
37
|
+
parsePluginsFromDir('Processes', "#{lLibDir}/Processes", 'MusicMaster::Processes')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Apply given record effects on a Wave file.
|
41
|
+
# It modifies the given Wave file.
|
42
|
+
# It saves original and intermediate Wave files before modifications.
|
43
|
+
#
|
44
|
+
# Parameters:
|
45
|
+
# * *iEffects* (<em>list<map<Symbol,Object>></em>): List of effects to apply
|
46
|
+
# * *iFileName* (_String_): File name to apply effects to
|
47
|
+
# * *iDir* (_String_): The directory where temporary files are stored
|
48
|
+
def self.applyProcesses(iEffects, iFileName, iDir)
|
49
|
+
lFileNameNoExt = File.basename(iFileName[0..-5])
|
50
|
+
iEffects.each_with_index do |iEffectInfo, iIdxEffect|
|
51
|
+
begin
|
52
|
+
accessPlugin('Processes', iEffectInfo[:Name]) do |ioActionPlugin|
|
53
|
+
# Save the file before using the plugin
|
54
|
+
lSave = true
|
55
|
+
lSaveFileName = "#{iDir}/#{lFileNameNoExt}.Before_#{iIdxEffect}_#{iEffectInfo[:Name]}.wav"
|
56
|
+
if (File.exists?(lSaveFileName))
|
57
|
+
puts "!!! File #{lSaveFileName} already exists. Overwrite and apply effect ? [y='yes']"
|
58
|
+
lSave = ($stdin.gets.chomp == 'y')
|
59
|
+
end
|
60
|
+
if (lSave)
|
61
|
+
logInfo "Saving file #{iFileName} to #{lSaveFileName} ..."
|
62
|
+
FileUtils::mv(iFileName, lSaveFileName)
|
63
|
+
logInfo "===== Apply Effect #{iEffectInfo[:Name]} to #{iFileName} ====="
|
64
|
+
ioActionPlugin.execute(lSaveFileName, iFileName, iDir, iEffectInfo.clone.delete_if{|iKey, iValue| next (iKey == :Name)})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue Exception
|
68
|
+
logErr "An error occurred while processing #{iFileName} with process #{iEffectInfo[:Name]}: #{$!}."
|
69
|
+
raise
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Read record configuration.
|
75
|
+
# Perform basic checks on it.
|
76
|
+
#
|
77
|
+
# Parameters:
|
78
|
+
# * *iConfFile* (_String_): Configuration file
|
79
|
+
# Return:
|
80
|
+
# * _Exception_: Error, or nil in case of success
|
81
|
+
# * <em>map<Symbol,Object></em>: The configuration
|
82
|
+
def self.readRecordConf(iConfFile)
|
83
|
+
rError = nil
|
84
|
+
rConf = nil
|
85
|
+
|
86
|
+
if (!File.exists?(iConfFile))
|
87
|
+
rError = RuntimeError.new("Missing configuration file: #{iConfFile}")
|
88
|
+
else
|
89
|
+
File.open(iConfFile, 'r') do |iFile|
|
90
|
+
rConf = eval(iFile.read)
|
91
|
+
end
|
92
|
+
# Check that all tracks are assigned somewhere, just once
|
93
|
+
lLstTracks = nil
|
94
|
+
if (rConf[:Patches] != nil)
|
95
|
+
lLstTracks = rConf[:Patches].keys.clone
|
96
|
+
else
|
97
|
+
lLstTracks = []
|
98
|
+
end
|
99
|
+
if (rConf[:Performs] != nil)
|
100
|
+
rConf[:Performs].each do |iLstPerform|
|
101
|
+
lLstTracks.concat(iLstPerform)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
lAssignedTracks = {}
|
105
|
+
lLstTracks.each do |iIdxTrack|
|
106
|
+
if (lAssignedTracks.has_key?(iIdxTrack))
|
107
|
+
rError = RuntimeError.new("Track #{iIdxTrack} is recorded twice.")
|
108
|
+
break
|
109
|
+
else
|
110
|
+
lAssignedTracks[iIdxTrack] = nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
if (rError == nil)
|
114
|
+
if (rConf[:Patches] != nil)
|
115
|
+
rConf[:Patches].each do |iIdxTrack, iTrackConf|
|
116
|
+
if ((iTrackConf.has_key?(:VolCorrection)) and
|
117
|
+
(iTrackConf.has_key?(:VolCompareCuts)))
|
118
|
+
rError = RuntimeError.new("Patch track #{iIdxTrack} has both :VolCorrection and :VolCompareCuts values defined.")
|
119
|
+
elsif ((!iTrackConf.has_key?(:VolCorrection)) and
|
120
|
+
(!iTrackConf.has_key?(:VolCompareCuts)))
|
121
|
+
rError = RuntimeError.new("Patch track #{iIdxTrack} has neither :VolCorrection nor :VolCompareCuts values defined.")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
if (rError == nil)
|
126
|
+
lAssignedTracks.size.times do |iIdxTrack|
|
127
|
+
if (!lAssignedTracks.has_key?(iIdxTrack+1))
|
128
|
+
logWarn "Track #{iIdxTrack+1} is never recorded."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
return rError, rConf
|
136
|
+
end
|
137
|
+
|
138
|
+
# Convert a Wave file to another
|
139
|
+
#
|
140
|
+
# Parameters:
|
141
|
+
# * *iSrcFile* (_String_): Source WAVE file
|
142
|
+
# * *iDstFile* (_String_): Destination WAVE file
|
143
|
+
# * *iParams* (<em>map<Symbol,Object></em>): The parameters:
|
144
|
+
# ** *:SampleRate* (_Integer_): The new sample rate in Hz
|
145
|
+
# ** *:BitDepth* (_Integer_): The new bit depth (only for Wave) [optional = nil]
|
146
|
+
# ** *:Dither* (_Boolean_): Do we apply dither (only for Wave) ? [optional = false]
|
147
|
+
# ** *:BitRate* (_Integer_): Bit rate in kbps (only for MP3) [optional = 320]
|
148
|
+
# ** *:FileFormat* (_Symbol_); File format. Here are the possible values: [optional = :Wave]
|
149
|
+
# *** *:Wave*: Uncompressed PCM Wave file
|
150
|
+
# *** *:MP3*: MP3 file
|
151
|
+
def self.src(iSrcFile, iDstFile, iParams)
|
152
|
+
if ((iParams[:FileFormat] != nil) and
|
153
|
+
(iParams[:FileFormat] == :MP3))
|
154
|
+
# MP3 conversion
|
155
|
+
lTranslatedParams = []
|
156
|
+
iParams.each do |iParam, iValue|
|
157
|
+
case iParam
|
158
|
+
when :SampleRate
|
159
|
+
lTranslatedParams << "Sample rate: #{iValue} Hz"
|
160
|
+
when :BitRate
|
161
|
+
lTranslatedParams << "Bit rate: #{iValue} kbps"
|
162
|
+
when :FileFormat
|
163
|
+
# Nothing to do
|
164
|
+
else
|
165
|
+
logErr "Unknown MP3 parameter: #{iParam} (value #{iValue.inspect}). Ignoring it."
|
166
|
+
end
|
167
|
+
end
|
168
|
+
puts "Convert file #{iSrcFile} into file #{iDstFile} in MP3 format with following parameters: #{lTranslatedParams.join(', ')}"
|
169
|
+
puts 'Press Enter when done.'
|
170
|
+
$stdin.gets
|
171
|
+
else
|
172
|
+
# Wave conversion
|
173
|
+
lTranslatedParams = [ '--profile standard', '--twopass' ]
|
174
|
+
iParams.each do |iParam, iValue|
|
175
|
+
case iParam
|
176
|
+
when :SampleRate
|
177
|
+
lTranslatedParams << "--rate #{iValue}"
|
178
|
+
when :BitDepth
|
179
|
+
lTranslatedParams << "--bits #{iValue}"
|
180
|
+
when :Dither
|
181
|
+
if (iValue == true)
|
182
|
+
lTranslatedParams << '--dither 4'
|
183
|
+
end
|
184
|
+
when :FileFormat
|
185
|
+
# Nothing to do
|
186
|
+
else
|
187
|
+
logErr "Unknown Wave parameter: #{iParam} (value #{iValue.inspect}). Ignoring it."
|
188
|
+
end
|
189
|
+
end
|
190
|
+
FileUtils::mkdir_p(File.dirname(iDstFile))
|
191
|
+
lCmd = "#{$MusicMasterConf[:SRCCmdLine]} #{lTranslatedParams.join(' ')} \"#{iSrcFile}\" \"#{iDstFile}\""
|
192
|
+
logInfo "=> #{lCmd}"
|
193
|
+
system(lCmd)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
# This file is required by MusicMaster binaries to load the MusicMaster configuration.
|
7
|
+
# This sets the $MusicMasterConf object that can then be accessed from anywhere.
|
8
|
+
|
9
|
+
module MusicMaster
|
10
|
+
|
11
|
+
def self.loadConf
|
12
|
+
$MusicMasterConf = nil
|
13
|
+
lConfSource = nil
|
14
|
+
# 1. Find from the environment
|
15
|
+
lConfigFileName = ENV['MUSICMASTER_CONF_PATH']
|
16
|
+
if (lConfigFileName == nil)
|
17
|
+
# 2. Find from the MusicMaster directory
|
18
|
+
lConfigFileName = "#{File.dirname(__FILE__)}/musicmaster.conf.rb"
|
19
|
+
if (File.exists?(lConfigFileName))
|
20
|
+
lConfSource = 'MusicMaster package local libraries'
|
21
|
+
else
|
22
|
+
# 3. Find from the current directory
|
23
|
+
lConfigFileName = "musicmaster.conf.rb"
|
24
|
+
if (File.exists?(lConfigFileName))
|
25
|
+
lConfSource = 'current directory'
|
26
|
+
else
|
27
|
+
# 4. Failure
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
lConfSource = 'MUSICMASTER_CONF_PATH environment variable'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check the configuration
|
35
|
+
if (lConfSource == nil)
|
36
|
+
logErr "No MusicMaster configuration file could be found. You can set it by setting MUSICMASTER_CONF_PATH environment variable, or create a musicmaster.conf.rb file either in #{File.dirname(__FILE__)} or the current directory."
|
37
|
+
else
|
38
|
+
if (File.exists?(lConfigFileName))
|
39
|
+
File.open(lConfigFileName, 'r') do |iFile|
|
40
|
+
begin
|
41
|
+
$MusicMasterConf = eval(iFile.read)
|
42
|
+
rescue Exception
|
43
|
+
logErr "Invalid configuration file #{lConfigFileName} specified in #{lConfSource}: #{$!}"
|
44
|
+
$MusicMasterConf = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
logErr "Missing file #{lConfigFileName}, specified in #{lConfSource}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
MusicMaster::loadConf
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module MusicMaster
|
7
|
+
|
8
|
+
module Processes
|
9
|
+
|
10
|
+
class AddSilence
|
11
|
+
|
12
|
+
# Execute the process
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
16
|
+
# * *iOutputFileName* (_String_): File name to write
|
17
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
18
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
19
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
20
|
+
MusicMaster::wsk(iInputFileName, iOutputFileName, 'SilenceInserter', "--begin \"#{iParams[:Begin]}\" --end \"#{iParams[:End]}\"")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'WSK/Common'
|
7
|
+
|
8
|
+
module MusicMaster
|
9
|
+
|
10
|
+
module Processes
|
11
|
+
|
12
|
+
class ApplyVolumeFct
|
13
|
+
|
14
|
+
# Parameters of this process:
|
15
|
+
# * *:Function* (<em>map<Symbol,Object></em>): The function definition
|
16
|
+
# * *:Begin* (_String_): Position to apply volume transformation from. Can be specified as a sample number or a float seconds (ie. 12.3s).
|
17
|
+
# * *:End* (_String_): Position to apply volume transformation to. Can be specified as a sample number or a float seconds (ie. 12.3s). -1 means to the end of file.
|
18
|
+
# * *:DBUnits* (_Boolean_): Are the units of the function in DB scale ? (else they are in a ratio scale).
|
19
|
+
|
20
|
+
include WSK::Common
|
21
|
+
|
22
|
+
# Execute the process
|
23
|
+
#
|
24
|
+
# Parameters:
|
25
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
26
|
+
# * *iOutputFileName* (_String_): File name to write
|
27
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
28
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
29
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
30
|
+
# Create the file that will store the Function for WSK
|
31
|
+
lFunctionFile = "#{iTempDir}/#{File.basename(iInputFileName)[0..-5]}.fct.rb"
|
32
|
+
lFunction = WSK::Functions::Function.new
|
33
|
+
lFunction.set(iParams[:Function])
|
34
|
+
lFunction.writeToFile(lFunctionFile)
|
35
|
+
lStrUnitDB = '0'
|
36
|
+
if (iParams[:DBUnits])
|
37
|
+
lStrUnitDB = '1'
|
38
|
+
end
|
39
|
+
MusicMaster::wsk(iInputFileName, iOutputFileName, 'ApplyVolumeFct', "--function \"#{lFunctionFile}\" --begin \"#{iParams[:Begin]}\" --end \"#{iParams[:End]}\" --unitdb #{lStrUnitDB}")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'WSK/Common'
|
7
|
+
|
8
|
+
module MusicMaster
|
9
|
+
|
10
|
+
module Processes
|
11
|
+
|
12
|
+
class Compressor
|
13
|
+
|
14
|
+
# Parameters of this process:
|
15
|
+
# * *:DBUnits* (_Boolean_): Are units in DB format ? [optional = false]
|
16
|
+
# * *:Threshold* (_Float_): The threshold below which there is no compression (in DB if :DBUnit is true, else in a [0..1] scale)
|
17
|
+
# * *:Ratio* (_Float_): Compression ratio to apply above threshold.
|
18
|
+
# * *:AttackDuration* (_String_): The attack duration (either in seconds or in samples)
|
19
|
+
# * *:AttackDamping* (_String_): The attack damping value in the duration previously defined (in DB if :DBUnit is true, else in a [0..1] scale). The compressor will never attack more than :AttackDamping values during a duration of :AttackDuration.
|
20
|
+
# * *:AttackLookAhead* (_Boolean_): Is the attack to be forecast before it happens ?
|
21
|
+
# * *:ReleaseDuration* (_String_): The release duration (either in seconds or in samples)
|
22
|
+
# * *:ReleaseDamping* (_String_): The release damping value in the duration previously defined (in DB if :DBUnit is true, else in a [0..1] scale). The compressor will never release more than :ReleaseDamping values during a duration of :ReleaseDuration.
|
23
|
+
# * *:ReleaseLookAhead* (_Boolean_): Is the attack to be forecast before it happens ?
|
24
|
+
# * *:MinChangeDuration* (_String_): The minimal duration a change in volume should have (either in seconds or in samples)
|
25
|
+
# * *:RMSRatio* (_Float_): Ratio of RMS vs Peak level measurement used when profiling Wave files volumes. 0.0 = Use only Peak level. 1.0 = Use only RMS level. Other values in-between will produce a mix of both.
|
26
|
+
|
27
|
+
include WSK::Common
|
28
|
+
|
29
|
+
# -Infinity
|
30
|
+
MINUS_INFINITY = -1.0/0.0
|
31
|
+
|
32
|
+
# Execute the process
|
33
|
+
#
|
34
|
+
# Parameters:
|
35
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
36
|
+
# * *iOutputFileName* (_String_): File name to write
|
37
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
38
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
39
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
40
|
+
# Check parameters
|
41
|
+
lDBUnits = iParams[:DBUnits]
|
42
|
+
if (lDBUnits == nil)
|
43
|
+
lDBUnits = false
|
44
|
+
end
|
45
|
+
lParamsOK = true
|
46
|
+
if (lDBUnits)
|
47
|
+
# Threshold must be < 0
|
48
|
+
if (iParams[:Threshold] >= 0)
|
49
|
+
logErr "Threshold (#{iParams[:Threshold]}db) has to be < 0db"
|
50
|
+
lParamsOK = false
|
51
|
+
end
|
52
|
+
else
|
53
|
+
# Threshold must be < 1
|
54
|
+
if (iParams[:Threshold] >= 1)
|
55
|
+
logErr "Threshold (#{iParams[:Threshold]}) has to be < 1"
|
56
|
+
lParamsOK = false
|
57
|
+
end
|
58
|
+
# Threshold must be > 0
|
59
|
+
if (iParams[:Threshold] <= 0)
|
60
|
+
logErr "Threshold (#{iParams[:Threshold]}) has to be > 0"
|
61
|
+
lParamsOK = false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# Ratio must be > 1
|
65
|
+
if (iParams[:Ratio] <= 1)
|
66
|
+
logErr "Ratio (#{iParams[:Ratio]}) has to be > 1"
|
67
|
+
lParamsOK = false
|
68
|
+
end
|
69
|
+
if (lParamsOK)
|
70
|
+
# Get the volume profile of the Wave file
|
71
|
+
lTempVolProfileFile = "#{iTempDir}/#{File.basename(iInputFileName)[0..-5]}.ProfileFct.rb"
|
72
|
+
if (File.exists?(lTempVolProfileFile))
|
73
|
+
logWarn "File #{lTempVolProfileFile} already exists. Will not overwrite it."
|
74
|
+
else
|
75
|
+
lTempWaveFile = "#{iTempDir}/Dummy.wav"
|
76
|
+
# Get the volume profile
|
77
|
+
MusicMaster::wsk(iInputFileName, lTempWaveFile, 'VolumeProfile', "--function \"#{lTempVolProfileFile}\" --begin 0 --end -1 --interval \"#{$MusicMasterConf[:Compressor][:Interval]}\" --rmsratio #{iParams[:RMSRatio]}")
|
78
|
+
File::unlink(lTempWaveFile)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the file header for various measures later
|
82
|
+
lHeader = nil
|
83
|
+
File.open(iInputFileName, 'rb') do |iFile|
|
84
|
+
lError, lHeader = readHeader(iFile)
|
85
|
+
if (lError != nil)
|
86
|
+
logErr "An error occurred while reading header: #{lError}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create the Compressor's function based on the parameters
|
91
|
+
# Minimal value represented in DB.
|
92
|
+
# This value will be used to replace -Infinity
|
93
|
+
lMinimalDBValue = nil
|
94
|
+
lCompressorFunction = WSK::Functions::Function.new
|
95
|
+
lBDThreshold = Rational(iParams[:Threshold])
|
96
|
+
if (lDBUnits)
|
97
|
+
# The minimal DB value is the smallest ratio possible for RMS values of this file (1/2^(BPS-1)) converted in DB and minus 1 to not mix it with the ratio 1/2^(BPS-1)
|
98
|
+
lMinimalDBValue = lCompressorFunction.valueVal2db(Rational(1), Rational(2)**(lHeader.NbrBitsPerSample-1)) - 1
|
99
|
+
lCompressorFunction.set( {
|
100
|
+
:FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR,
|
101
|
+
:Points => [
|
102
|
+
[lMinimalDBValue, lMinimalDBValue],
|
103
|
+
[lBDThreshold, lBDThreshold],
|
104
|
+
[0, lBDThreshold - lBDThreshold/Rational(iParams[:Ratio]) ]
|
105
|
+
]
|
106
|
+
} )
|
107
|
+
else
|
108
|
+
lCompressorFunction.set( {
|
109
|
+
:FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR,
|
110
|
+
:Points => [
|
111
|
+
[0, 0],
|
112
|
+
[lBDThreshold, lBDThreshold],
|
113
|
+
[1, lBDThreshold + (1-lBDThreshold)/Rational(iParams[:Ratio]) ]
|
114
|
+
]
|
115
|
+
} )
|
116
|
+
end
|
117
|
+
logInfo "Compressor transfer function: #{lCompressorFunction.functionData[:Points].map{ |p| next [ sprintf('%.2f', p[0]), sprintf('%.2f', p[1]) ] }.inspect}"
|
118
|
+
|
119
|
+
# Compute the volume transformation function based on the profile function and the Compressor's parameters
|
120
|
+
lTempVolTransformFile = "#{iTempDir}/#{File.basename(iInputFileName)[0..-5]}.VolumeFct.rb"
|
121
|
+
if (File.exists?(lTempVolTransformFile))
|
122
|
+
logWarn "File #{lTempVolTransformFile} already exists. Will not overwrite it."
|
123
|
+
else
|
124
|
+
# Read the Profile function
|
125
|
+
logInfo 'Create volume profile function ...'
|
126
|
+
lProfileFunction = WSK::Functions::Function.new
|
127
|
+
lProfileFunction.readFromFile(lTempVolProfileFile)
|
128
|
+
if (lDBUnits)
|
129
|
+
# Convert the Profile function in DB units
|
130
|
+
lProfileFunction.convertToDB(Rational(1))
|
131
|
+
# Replace -Infinity with lMinimalDBValue
|
132
|
+
lProfileFunction.functionData[:Points].each do |ioPoint|
|
133
|
+
if (ioPoint[1] == MINUS_INFINITY)
|
134
|
+
ioPoint[1] = lMinimalDBValue
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#dumpDebugFct(iInputFileName, lProfileFunction, 'ProfileDB', lDBUnits, iTempDir)
|
140
|
+
|
141
|
+
# Clone the profile function before applying the map
|
142
|
+
lNewProfileFunction = WSK::Functions::Function.new
|
143
|
+
lNewProfileFunction.set(lProfileFunction.functionData.clone)
|
144
|
+
|
145
|
+
# Transform the Profile function with the Compressor function
|
146
|
+
logInfo 'Apply compressor transfer function ...'
|
147
|
+
lNewProfileFunction.applyMapFunction(lCompressorFunction)
|
148
|
+
|
149
|
+
#dumpDebugFct(iInputFileName, lNewProfileFunction, 'NewProfileDB', lDBUnits, iTempDir)
|
150
|
+
|
151
|
+
# The difference of the functions will give the volume transformation profile
|
152
|
+
logInfo 'Compute differing function ...'
|
153
|
+
lDiffProfileFunction = WSK::Functions::Function.new
|
154
|
+
lDiffProfileFunction.set(lNewProfileFunction.functionData.clone)
|
155
|
+
if (lDBUnits)
|
156
|
+
# The volume transformation will be a DB difference
|
157
|
+
lDiffProfileFunction.substractFunction(lProfileFunction)
|
158
|
+
else
|
159
|
+
# The volume transformation will be a ratio
|
160
|
+
lDiffProfileFunction.divideByFunction(lProfileFunction)
|
161
|
+
end
|
162
|
+
|
163
|
+
#dumpDebugFct(iInputFileName, lDiffProfileFunction, 'RawDiffProfileDB', lDBUnits, iTempDir)
|
164
|
+
|
165
|
+
# Apply damping for attack and release times
|
166
|
+
logInfo 'Damp differing function with attack and release ...'
|
167
|
+
lAttackDuration = Rational(readDuration(iParams[:AttackDuration], lHeader.SampleRate))
|
168
|
+
lAttackSlope = iParams[:AttackDamping].to_f.to_r/lAttackDuration
|
169
|
+
lReleaseDuration = Rational(readDuration(iParams[:ReleaseDuration], lHeader.SampleRate))
|
170
|
+
lReleaseSlope = iParams[:ReleaseDamping].to_f.to_r/lReleaseDuration
|
171
|
+
if (lDBUnits)
|
172
|
+
lAttackSlope = -lAttackSlope
|
173
|
+
lReleaseSlope = -lReleaseSlope
|
174
|
+
end
|
175
|
+
# Take care of look-aheads
|
176
|
+
if ((iParams[:AttackLookAhead] == true) or
|
177
|
+
(iParams[:ReleaseLookAhead] == true))
|
178
|
+
# Look-Aheads are implemented by applying damping on the reverted function
|
179
|
+
lDiffProfileFunction.invertAbscisses
|
180
|
+
if (iParams[:AttackLookAhead] == false)
|
181
|
+
lDiffProfileFunction.applyDamping(nil, -lReleaseSlope)
|
182
|
+
elsif (iParams[:ReleaseLookAhead] == false)
|
183
|
+
lDiffProfileFunction.applyDamping(lAttackSlope, nil)
|
184
|
+
else
|
185
|
+
lDiffProfileFunction.applyDamping(lAttackSlope, -lReleaseSlope)
|
186
|
+
end
|
187
|
+
lDiffProfileFunction.invertAbscisses
|
188
|
+
end
|
189
|
+
if (iParams[:AttackLookAhead] == true)
|
190
|
+
if (iParams[:ReleaseLookAhead] == false)
|
191
|
+
lDiffProfileFunction.applyDamping(lReleaseSlope, nil)
|
192
|
+
end
|
193
|
+
elsif (iParams[:ReleaseLookAhead] == true)
|
194
|
+
if (iParams[:AttackLookAhead] == false)
|
195
|
+
lDiffProfileFunction.applyDamping(nil, -lAttackSlope)
|
196
|
+
end
|
197
|
+
else
|
198
|
+
lDiffProfileFunction.applyDamping(lReleaseSlope, -lAttackSlope)
|
199
|
+
end
|
200
|
+
|
201
|
+
#dumpDebugFct(iInputFileName, lDiffProfileFunction, 'DampedDiffProfileDB', lDBUnits, iTempDir)
|
202
|
+
|
203
|
+
# Eliminate glitches in the function.
|
204
|
+
# This is done by deleting intermediate abscisses that are too close to each other
|
205
|
+
|
206
|
+
logInfo 'Smooth differing function ...'
|
207
|
+
lDiffProfileFunction.removeNoiseAbscisses(Rational(readDuration(iParams[:MinChangeDuration], lHeader.SampleRate)))
|
208
|
+
|
209
|
+
#dumpDebugFct(iInputFileName, lDiffProfileFunction, 'SmoothedDiffProfileDB', lDBUnits, iTempDir)
|
210
|
+
|
211
|
+
# Save the volume transformation file
|
212
|
+
lDiffProfileFunction.writeToFile(lTempVolTransformFile)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Apply the volume transformation to the Wave file
|
216
|
+
lStrUnitDB = 0
|
217
|
+
if (lDBUnits)
|
218
|
+
lStrUnitDB = 1
|
219
|
+
end
|
220
|
+
MusicMaster::wsk(iInputFileName, iOutputFileName, 'ApplyVolumeFct', "--function \"#{lTempVolTransformFile}\" --begin 0 --end -1 --unitdb #{lStrUnitDB}")
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Dump a function into a Wave file.
|
225
|
+
# This is used for debugging purposes only.
|
226
|
+
#
|
227
|
+
# Parameters:
|
228
|
+
# * *iInputFileName* (_String_): Name of the input file
|
229
|
+
# * *iFunction* (<em>WSK::Functions::Function</em>): The function to dump
|
230
|
+
# * *iName* (_String_): Name given to this function
|
231
|
+
# * *iDBUnits* (_Boolean_): Is the function in DB units ?
|
232
|
+
# * *iTempDir* (_String_): Temporary directory to use
|
233
|
+
def dumpDebugFct(iInputFileName, iFunction, iName, iDBUnits, iTempDir)
|
234
|
+
lBaseFileName = File.basename(iInputFileName)[0..-5]
|
235
|
+
# Clone the function to round it first
|
236
|
+
lRoundedFunction = WSK::Functions::Function.new
|
237
|
+
lRoundedFunction.set(iFunction.functionData.clone)
|
238
|
+
lRoundedFunction.writeToFile("#{iTempDir}/_#{lBaseFileName}_#{iName}.fct.rb", :Floats => true)
|
239
|
+
MusicMaster::wsk(iInputFileName, "#{iTempDir}/_#{lBaseFileName}_#{iName}.wav", 'DrawFct', "--function \"#{iTempDir}/_#{lBaseFileName}_#{iName}.fct.rb\" --unitdb #{iDBUnits ? '1' : '0'}")
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module MusicMaster
|
7
|
+
|
8
|
+
module Processes
|
9
|
+
|
10
|
+
class Custom
|
11
|
+
|
12
|
+
# Execute the process
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
16
|
+
# * *iOutputFileName* (_String_): File name to write
|
17
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
18
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
19
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
20
|
+
logInfo "Copying #{iInputFileName} => #{iOutputFileName} ..."
|
21
|
+
FileUtils::cp(iInputFileName, iOutputFileName)
|
22
|
+
puts "Apply custom process on file #{iOutputFileName}. Parameters: #{iParams.inspect}"
|
23
|
+
puts 'Press Enter when done ...'
|
24
|
+
$stdin.gets
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module MusicMaster
|
7
|
+
|
8
|
+
module Processes
|
9
|
+
|
10
|
+
class CutFirstSignal
|
11
|
+
|
12
|
+
# Execute the process
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
16
|
+
# * *iOutputFileName* (_String_): File name to write
|
17
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
18
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
19
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
20
|
+
MusicMaster::wsk(iInputFileName, iOutputFileName, 'CutFirstSignal', "--silencethreshold 0 --noisefft none --silencemin \"#{$MusicMasterConf[:NoiseGate][:SilenceMin]}\"")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module MusicMaster
|
7
|
+
|
8
|
+
module Processes
|
9
|
+
|
10
|
+
class GVerb
|
11
|
+
|
12
|
+
# Execute the process
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iInputFileName* (_String_): File name we want to apply effects to
|
16
|
+
# * *iOutputFileName* (_String_): File name to write
|
17
|
+
# * *iTempDir* (_String_): Temporary directory that can be used
|
18
|
+
# * *iParams* (<em>map<Symbol,Object></em>): Parameters
|
19
|
+
def execute(iInputFileName, iOutputFileName, iTempDir, iParams)
|
20
|
+
puts "===> Apply GVerb from Audacity to file #{iInputFileName} and write file #{iOutputFileName}"
|
21
|
+
puts "===> Parameters: #{iParams.inspect}"
|
22
|
+
puts 'Press Enter when done.'
|
23
|
+
$stdin.gets
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|