MusicMaster 0.0.1.20101110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +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
|