Class: MusicMaster::Processes::Compressor
- Inherits:
-
Object
- Object
- MusicMaster::Processes::Compressor
- Includes:
- WSK::Common
- Defined in:
- lib/MusicMaster/Processes/Compressor.rb
Constant Summary collapse
- MINUS_INFINITY =
-Infinity
-1.0/0.0
Instance Method Summary collapse
-
#dumpDebugFct(iInputFileName, iFunction, iName, iDBUnits, iTempDir) ⇒ Object
Dump a function into a Wave file.
-
#execute(iInputFileName, iOutputFileName, iTempDir, iParams) ⇒ Object
Execute the process.
Instance Method Details
#dumpDebugFct(iInputFileName, iFunction, iName, iDBUnits, iTempDir) ⇒ Object
Dump a function into a Wave file. This is used for debugging purposes only.
Parameters:
-
iInputFileName (String): Name of the input file
-
iFunction (WSK::Functions::Function): The function to dump
-
iName (String): Name given to this function
-
iDBUnits (Boolean): Is the function in DB units ?
-
iTempDir (String): Temporary directory to use
233 234 235 236 237 238 239 240 |
# File 'lib/MusicMaster/Processes/Compressor.rb', line 233 def dumpDebugFct(iInputFileName, iFunction, iName, iDBUnits, iTempDir) lBaseFileName = File.basename(iInputFileName)[0..-5] # Clone the function to round it first lRoundedFunction = WSK::Functions::Function.new lRoundedFunction.set(iFunction.functionData.clone) lRoundedFunction.writeToFile("#{iTempDir}/_#{lBaseFileName}_#{iName}.fct.rb", :Floats => true) MusicMaster::wsk(iInputFileName, "#{iTempDir}/_#{lBaseFileName}_#{iName}.wav", 'DrawFct', "--function \"#{iTempDir}/_#{lBaseFileName}_#{iName}.fct.rb\" --unitdb #{iDBUnits ? '1' : '0'}") end |
#execute(iInputFileName, iOutputFileName, iTempDir, iParams) ⇒ Object
Execute the process
Parameters:
-
iInputFileName (String): File name we want to apply effects to
-
iOutputFileName (String): File name to write
-
iTempDir (String): Temporary directory that can be used
-
iParams (map<Symbol,Object>): Parameters
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/MusicMaster/Processes/Compressor.rb', line 39 def execute(iInputFileName, iOutputFileName, iTempDir, iParams) # Check parameters lDBUnits = iParams[:DBUnits] if (lDBUnits == nil) lDBUnits = false end lParamsOK = true if (lDBUnits) # Threshold must be < 0 if (iParams[:Threshold] >= 0) logErr "Threshold (#{iParams[:Threshold]}db) has to be < 0db" lParamsOK = false end else # Threshold must be < 1 if (iParams[:Threshold] >= 1) logErr "Threshold (#{iParams[:Threshold]}) has to be < 1" lParamsOK = false end # Threshold must be > 0 if (iParams[:Threshold] <= 0) logErr "Threshold (#{iParams[:Threshold]}) has to be > 0" lParamsOK = false end end # Ratio must be > 1 if (iParams[:Ratio] <= 1) logErr "Ratio (#{iParams[:Ratio]}) has to be > 1" lParamsOK = false end if (lParamsOK) # Get the volume profile of the Wave file lTempVolProfileFile = "#{iTempDir}/#{File.basename(iInputFileName)[0..-5]}.ProfileFct.rb" if (File.exists?(lTempVolProfileFile)) logWarn "File #{lTempVolProfileFile} already exists. Will not overwrite it." else lTempWaveFile = "#{iTempDir}/Dummy.wav" # Get the volume profile MusicMaster::wsk(iInputFileName, lTempWaveFile, 'VolumeProfile', "--function \"#{lTempVolProfileFile}\" --begin 0 --end -1 --interval \"#{$MusicMasterConf[:Compressor][:Interval]}\" --rmsratio #{iParams[:RMSRatio]}") File::unlink(lTempWaveFile) end # Get the file header for various measures later lHeader = nil File.open(iInputFileName, 'rb') do |iFile| lError, lHeader = readHeader(iFile) if (lError != nil) logErr "An error occurred while reading header: #{lError}" end end # Create the Compressor's function based on the parameters # Minimal value represented in DB. # This value will be used to replace -Infinity lMinimalDBValue = nil lCompressorFunction = WSK::Functions::Function.new lBDThreshold = Rational(iParams[:Threshold]) if (lDBUnits) # 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) lMinimalDBValue = lCompressorFunction.valueVal2db(Rational(1), Rational(2)**(lHeader.NbrBitsPerSample-1)) - 1 lCompressorFunction.set( { :FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR, :Points => [ [lMinimalDBValue, lMinimalDBValue], [lBDThreshold, lBDThreshold], [0, lBDThreshold - lBDThreshold/Rational(iParams[:Ratio]) ] ] } ) else lCompressorFunction.set( { :FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR, :Points => [ [0, 0], [lBDThreshold, lBDThreshold], [1, lBDThreshold + (1-lBDThreshold)/Rational(iParams[:Ratio]) ] ] } ) end logInfo "Compressor transfer function: #{lCompressorFunction.functionData[:Points].map{ |p| next [ sprintf('%.2f', p[0]), sprintf('%.2f', p[1]) ] }.inspect}" # Compute the volume transformation function based on the profile function and the Compressor's parameters lTempVolTransformFile = "#{iTempDir}/#{File.basename(iInputFileName)[0..-5]}.VolumeFct.rb" if (File.exists?(lTempVolTransformFile)) logWarn "File #{lTempVolTransformFile} already exists. Will not overwrite it." else # Read the Profile function logInfo 'Create volume profile function ...' lProfileFunction = WSK::Functions::Function.new lProfileFunction.readFromFile(lTempVolProfileFile) if (lDBUnits) # Convert the Profile function in DB units lProfileFunction.convertToDB(Rational(1)) # Replace -Infinity with lMinimalDBValue lProfileFunction.functionData[:Points].each do |ioPoint| if (ioPoint[1] == MINUS_INFINITY) ioPoint[1] = lMinimalDBValue end end end #dumpDebugFct(iInputFileName, lProfileFunction, 'ProfileDB', lDBUnits, iTempDir) # Clone the profile function before applying the map lNewProfileFunction = WSK::Functions::Function.new lNewProfileFunction.set(lProfileFunction.functionData.clone) # Transform the Profile function with the Compressor function logInfo 'Apply compressor transfer function ...' lNewProfileFunction.applyMapFunction(lCompressorFunction) #dumpDebugFct(iInputFileName, lNewProfileFunction, 'NewProfileDB', lDBUnits, iTempDir) # The difference of the functions will give the volume transformation profile logInfo 'Compute differing function ...' lDiffProfileFunction = WSK::Functions::Function.new lDiffProfileFunction.set(lNewProfileFunction.functionData.clone) if (lDBUnits) # The volume transformation will be a DB difference lDiffProfileFunction.substractFunction(lProfileFunction) else # The volume transformation will be a ratio lDiffProfileFunction.divideByFunction(lProfileFunction) end #dumpDebugFct(iInputFileName, lDiffProfileFunction, 'RawDiffProfileDB', lDBUnits, iTempDir) # Apply damping for attack and release times logInfo 'Damp differing function with attack and release ...' lAttackDuration = Rational(readDuration(iParams[:AttackDuration], lHeader.SampleRate)) lAttackSlope = iParams[:AttackDamping].to_f.to_r/lAttackDuration lReleaseDuration = Rational(readDuration(iParams[:ReleaseDuration], lHeader.SampleRate)) lReleaseSlope = iParams[:ReleaseDamping].to_f.to_r/lReleaseDuration if (lDBUnits) lAttackSlope = -lAttackSlope lReleaseSlope = -lReleaseSlope end # Take care of look-aheads if ((iParams[:AttackLookAhead] == true) or (iParams[:ReleaseLookAhead] == true)) # Look-Aheads are implemented by applying damping on the reverted function lDiffProfileFunction.invertAbscisses if (iParams[:AttackLookAhead] == false) lDiffProfileFunction.applyDamping(nil, -lReleaseSlope) elsif (iParams[:ReleaseLookAhead] == false) lDiffProfileFunction.applyDamping(lAttackSlope, nil) else lDiffProfileFunction.applyDamping(lAttackSlope, -lReleaseSlope) end lDiffProfileFunction.invertAbscisses end if (iParams[:AttackLookAhead] == true) if (iParams[:ReleaseLookAhead] == false) lDiffProfileFunction.applyDamping(lReleaseSlope, nil) end elsif (iParams[:ReleaseLookAhead] == true) if (iParams[:AttackLookAhead] == false) lDiffProfileFunction.applyDamping(nil, -lAttackSlope) end else lDiffProfileFunction.applyDamping(lReleaseSlope, -lAttackSlope) end #dumpDebugFct(iInputFileName, lDiffProfileFunction, 'DampedDiffProfileDB', lDBUnits, iTempDir) # Eliminate glitches in the function. # This is done by deleting intermediate abscisses that are too close to each other logInfo 'Smooth differing function ...' lDiffProfileFunction.removeNoiseAbscisses(Rational(readDuration(iParams[:MinChangeDuration], lHeader.SampleRate))) #dumpDebugFct(iInputFileName, lDiffProfileFunction, 'SmoothedDiffProfileDB', lDBUnits, iTempDir) # Save the volume transformation file lDiffProfileFunction.writeToFile(lTempVolTransformFile) end # Apply the volume transformation to the Wave file lStrUnitDB = 0 if (lDBUnits) lStrUnitDB = 1 end MusicMaster::wsk(iInputFileName, iOutputFileName, 'ApplyVolumeFct', "--function \"#{lTempVolTransformFile}\" --begin 0 --end -1 --unitdb #{lStrUnitDB}") end end |